On Mon, Aug 03, 2015 at 04:41:28PM +0200, Andrew Jones wrote:
> Add enough RTAS support to support power-off, and apply it to
> exit().
> 
> Signed-off-by: Andrew Jones <drjo...@redhat.com>
> ---
>  lib/powerpc/asm/rtas.h  | 27 ++++++++++++++++
>  lib/powerpc/io.c        |  3 ++
>  lib/powerpc/rtas.c      | 84 
> +++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/ppc64/asm/rtas.h    |  1 +
>  powerpc/Makefile.common |  1 +
>  powerpc/cstart64.S      |  9 ++++++
>  6 files changed, 125 insertions(+)
>  create mode 100644 lib/powerpc/asm/rtas.h
>  create mode 100644 lib/powerpc/rtas.c
>  create mode 100644 lib/ppc64/asm/rtas.h
> 
> diff --git a/lib/powerpc/asm/rtas.h b/lib/powerpc/asm/rtas.h
> new file mode 100644
> index 0000000000000..53ce126c69a42
> --- /dev/null
> +++ b/lib/powerpc/asm/rtas.h
> @@ -0,0 +1,27 @@
> +#ifndef _ASMPOWERPC_RTAS_H_
> +#define _ASMPOWERPC_RTAS_H_
> +/*
> + * Copyright (C) 2015, Red Hat Inc, Andrew Jones <drjo...@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +
> +#define RTAS_UNKNOWN_SERVICE (-1)
> +
> +struct rtas_args {
> +     u32 token;
> +     u32 nargs;
> +     u32 nret;
> +     u32 args[16];
> +     u32 *rets;
> +};
> +
> +extern void rtas_init(void);
> +extern void enter_rtas(unsigned long args);
> +extern int rtas_token(const char *service);
> +extern int rtas_call(int token, int nargs, int nret, int *outputs, ...);
> +
> +extern void rtas_power_off(void);
> +
> +#endif /* _ASMPOWERPC_RTAS_H_ */
> diff --git a/lib/powerpc/io.c b/lib/powerpc/io.c
> index a7eaafeca9205..25cdd0ad961ee 100644
> --- a/lib/powerpc/io.c
> +++ b/lib/powerpc/io.c
> @@ -7,6 +7,7 @@
>   */
>  #include <libcflat.h>
>  #include <asm/spinlock.h>
> +#include <asm/rtas.h>
>  
>  extern void halt(int code);
>  extern void putchar(int c);
> @@ -15,6 +16,7 @@ static struct spinlock uart_lock;
>  
>  void io_init(void)
>  {
> +     rtas_init();
>  }
>  
>  void puts(const char *s)
> @@ -27,5 +29,6 @@ void puts(const char *s)
>  
>  void exit(int code)
>  {
> +     rtas_power_off();
>       halt(code);
>  }
> diff --git a/lib/powerpc/rtas.c b/lib/powerpc/rtas.c
> new file mode 100644
> index 0000000000000..39f7d4428ee77
> --- /dev/null
> +++ b/lib/powerpc/rtas.c
> @@ -0,0 +1,84 @@
> +/*
> + * powerpc RTAS
> + *
> + * Copyright (C) 2015, Red Hat Inc, Andrew Jones <drjo...@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.
> + */
> +#include <libcflat.h>
> +#include <libfdt/libfdt.h>
> +#include <devicetree.h>
> +#include <asm/spinlock.h>
> +#include <asm/io.h>
> +#include <asm/rtas.h>
> +
> +static struct spinlock lock;
> +struct rtas_args rtas_args;
> +static int rtas_node;
> +
> +void rtas_init(void)
> +{
> +     if (!dt_available()) {
> +             printf("%s: No device tree!\n", __func__);
> +             abort();
> +     }
> +
> +     rtas_node = fdt_path_offset(dt_fdt(), "/rtas");
> +     if (rtas_node < 0) {
> +             printf("%s: /rtas node: %s\n", __func__,
> +                     fdt_strerror(rtas_node));
> +             abort();
> +     }
> +}
> +
> +int rtas_token(const char *service)
> +{
> +     const struct fdt_property *prop;
> +     u32 *token;
> +
> +     prop = fdt_get_property(dt_fdt(), rtas_node, service, NULL);
> +     if (prop) {
> +             token = (u32 *)prop->data;
> +             return fdt32_to_cpu(*token);
> +     }
> +     return RTAS_UNKNOWN_SERVICE;
> +}
> +
> +int rtas_call(int token, int nargs, int nret, int *outputs, ...)
> +{
> +     va_list list;
> +     int ret, i;
> +
> +     spin_lock(&lock);
> +
> +     rtas_args.token = cpu_to_be32(token);
> +     rtas_args.nargs = cpu_to_be32(nargs);
> +     rtas_args.nret = cpu_to_be32(nret);
> +     rtas_args.rets = &rtas_args.args[nargs];
> +
> +     va_start(list, outputs);
> +     for (i = 0; i < nargs; ++i)
> +             rtas_args.args[i] = cpu_to_be32(va_arg(list, u32));
> +     va_end(list);
> +
> +     for (i = 0; i < nret; ++i)
> +             rtas_args.rets[i] = 0;
> +
> +     enter_rtas(__pa(&rtas_args));
> +
> +     if (nret > 1 && outputs != NULL)
> +             for (i = 0; i < nret - 1; ++i)
> +                     outputs[i] = be32_to_cpu(rtas_args.rets[i + 1]);
> +
> +     ret = nret > 0 ? be32_to_cpu(rtas_args.rets[0]) : 0;
> +
> +     spin_unlock(&lock);
> +     return ret;
> +}
> +
> +void rtas_power_off(void)
> +{
> +     printf("RTAS power-off returned %d\n",
> +             rtas_call(rtas_token("power-off"), 2, 1, NULL, -1, -1));
> +     assert(0);
> +}
> diff --git a/lib/ppc64/asm/rtas.h b/lib/ppc64/asm/rtas.h
> new file mode 100644
> index 0000000000000..fe77f635cd860
> --- /dev/null
> +++ b/lib/ppc64/asm/rtas.h
> @@ -0,0 +1 @@
> +#include "../../powerpc/asm/rtas.h"
> diff --git a/powerpc/Makefile.common b/powerpc/Makefile.common
> index b130342dee60e..f4eff7f8eccb4 100644
> --- a/powerpc/Makefile.common
> +++ b/powerpc/Makefile.common
> @@ -39,6 +39,7 @@ cflatobjs += lib/powerpc/io.o
>  cflatobjs += lib/powerpc/setup.o
>  cflatobjs += lib/powerpc/mmu.o
>  cflatobjs += lib/powerpc/smp.o
> +cflatobjs += lib/powerpc/rtas.o
>  
>  libgcc := $(shell $(CC) $(machine) --print-libgcc-file-name)
>  start_addr := $(shell printf "%x\n" $$(( $(phys_base) + $(kernel_offset) )))
> diff --git a/powerpc/cstart64.S b/powerpc/cstart64.S
> index 8edaaa6e251fc..1c48083efa33c 100644
> --- a/powerpc/cstart64.S
> +++ b/powerpc/cstart64.S
> @@ -8,6 +8,7 @@
>  
>  #define HVSC                 .long 0x44000022
>  #define H_PUT_TERM_CHAR              0x58
> +#define KVMPPC_H_RTAS                0xf000
>  
>  #define LOAD_REG_IMMEDIATE(reg,expr)         \
>       lis     reg,(expr)@highest;             \
> @@ -85,3 +86,11 @@ putchar:
>       li      r5, 1   /* sending just 1 byte */
>       HVSC
>       blr
> +
> +.globl enter_rtas
> +enter_rtas:
> +     mr      r4, r3
> +     lis     r3, KVMPPC_H_RTAS@h
> +     ori     r3, r3, KVMPPC_H_RTAS@l
> +     HVSC
> +     blr

So, strictly speaking this isn't how a client program is supposed to
access RTAS.  Instead, you're supposed to call into SLOF to
instantiate RTAS, then jump into the blob it gives you.  Or, if you're
replacing rather than using SLOF, then you're supposed to get the
existing RTAS blob address from the device tree and jump into that (or
copy it somewhere else yourself).

In practice that RTAS blob will have the same 5 instructions as your
enter_rtas, so it's probably ok to do this.

What concerns me slightly, though, is that if we ever messed up the
RTAS blob handling in qemu or SLOF, then bypassing it in the kvm tests
like this means they wouldn't catch that breakage.

-- 
David Gibson                    | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au  | minimalist, thank you.  NOT _the_ _other_
                                | _way_ _around_!
http://www.ozlabs.org/~dgibson

Attachment: pgp9_1ptY4tQk.pgp
Description: PGP signature

Reply via email to