diff mbox

[v2,4/4] acpi: build TPM Physical Presence interface

Message ID 1516117900-11382-5-git-send-email-stefanb@linux.vnet.ibm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Stefan Berger Jan. 16, 2018, 3:51 p.m. UTC
The TPM Physical Presence interface consists of an ACPI part, a shared
memory part, and code in the firmware. Users can send messages to the
firmware by writing a code into the shared memory through invoking the
ACPI code. When a reboot happens, the firmware looks for the code and
acts on it by sending sequences of commands to the TPM.

This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
assume that SMIs are necessary to use. It uses a similar datastructure for
the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
of it. I extended the shared memory data structure with an array of 256
bytes, one for each code that could be implemented. The array contains
flags describing the individual codes. This decouples the ACPI implementation
from the firmware implementation.

The underlying TCG specification is accessible from the following page.

https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/

This patch implements version 1.30.

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 v1->v2:
   - get rid of FAIL variable; function 5 was using it and always
     returns 0; the value is related to the ACPI function call not
     a possible failure of the TPM function call.
   - extend shared memory data structure with per-opcode entries
     holding flags and use those flags to determine what to return
     to caller
   - implement interface version 1.3
---
 hw/i386/acpi-build.c        | 273 ++++++++++++++++++++++++++++++++++++++++++++
 include/hw/acpi/acpi-defs.h |   2 +
 include/hw/acpi/tpm.h       |  31 +++++
 3 files changed, 306 insertions(+)

Comments

Stefan Berger Feb. 9, 2018, 8:19 p.m. UTC | #1
On 01/16/2018 10:51 AM, Stefan Berger wrote:
> The TPM Physical Presence interface consists of an ACPI part, a shared
> memory part, and code in the firmware. Users can send messages to the
> firmware by writing a code into the shared memory through invoking the
> ACPI code. When a reboot happens, the firmware looks for the code and
> acts on it by sending sequences of commands to the TPM.
>
> This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
> assume that SMIs are necessary to use. It uses a similar datastructure for
> the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
> of it. I extended the shared memory data structure with an array of 256
> bytes, one for each code that could be implemented. The array contains
> flags describing the individual codes. This decouples the ACPI implementation
> from the firmware implementation.
>
> The underlying TCG specification is accessible from the following page.
>
> https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
>
> This patch implements version 1.30.

I have played around with this patch and some modifications to EDK2. 
Though for EDK2 the question is whether to try to circumvent their 
current implementation that uses SMM or use SMM. With this patch so far 
I circumvent it, which is maybe not a good idea.

The facts for EDK2's PPI:

- from within the OS a PPI code is submitted to ACPI and ACPI enters SMM 
via an SMI and the PPI code is written into a UEFI variable. For this 
ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS 
(via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at 
that address. Once the machine is rebooted, UEFI reads the variable and 
finds the PPI code and reacts to it.


The facts for SeaBIOS:
- we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to 
this address. So we would have to use the PPI memory device's memory 
region, which is currently at 0xFED4 5000. SeaBIOS doesn't have 
persistent memory like NVRAM where it stores varaibles. So to pass the 
PPI code here the OS would invoke ACPI, which in turn would write the 
PPI code into the PPI memory directly. Upon reboot SeaBIOS would find 
the PPI code in the PPI memory device and react to it.

The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes 
are used by the OperationRegion() in this patch series. The rest was 
thought of for future extensions.

To allow both firmwares to use PPI, we would need to be able to have the 
OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and 
0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve 
this would be to have the firmwares choose their operation region base 
address by writing into the PPI memory device at offset 0x200 (for 
example). A '1' there would mean that we want the OperationRegion() at 
0xFFFF 0000, and a '2' would mean at the base address of the PPI device 
(0xFED4 5000). This could be achieved by declaring a 2nd 
OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we 
declare that 1st OperationRegion()'s address based on findings from 
there. Further, a flags variable at offset 0x201 could indicate whether 
an SMI is needed to enter SMM or not. Obviously, the ACPI code would 
become more complex to be able to support both firmwares at the same time.

This is a lot of details but I rather post this description before 
posting more patches. To make these changes and get it to work with at 
least SeaBIOS is probably fairly easy. But is this acceptable?

    Stefan

>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
>
> ---
>   v1->v2:
>     - get rid of FAIL variable; function 5 was using it and always
>       returns 0; the value is related to the ACPI function call not
>       a possible failure of the TPM function call.
>     - extend shared memory data structure with per-opcode entries
>       holding flags and use those flags to determine what to return
>       to caller
>     - implement interface version 1.3
> ---
>   hw/i386/acpi-build.c        | 273 ++++++++++++++++++++++++++++++++++++++++++++
>   include/hw/acpi/acpi-defs.h |   2 +
>   include/hw/acpi/tpm.h       |  31 +++++
>   3 files changed, 306 insertions(+)
>
> diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
> index 522d6d2..f8c2d01 100644
> --- a/hw/i386/acpi-build.c
> +++ b/hw/i386/acpi-build.c
> @@ -42,6 +42,7 @@
>   #include "hw/acpi/memory_hotplug.h"
>   #include "sysemu/tpm.h"
>   #include "hw/acpi/tpm.h"
> +#include "hw/tpm/tpm_ppi.h"
>   #include "hw/acpi/vmgenid.h"
>   #include "sysemu/tpm_backend.h"
>   #include "hw/timer/mc146818rtc_regs.h"
> @@ -1860,6 +1861,276 @@ static Aml *build_q35_osc_method(void)
>   }
>
>   static void
> +build_tpm_ppi(Aml *dev, TPMVersion tpm_version)
> +{
> +    Aml *method, *field, *ifctx, *ifctx2, *ifctx3, *pak;
> +
> +    aml_append(dev,
> +               aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
> +                                    aml_int(TPM_PPI_ADDR_BASE),
> +                                    TPM_PPI_STRUCT_SIZE));
> +
> +    field = aml_field("TPPI", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
> +    aml_append(field, aml_named_field("PPIN",
> +               sizeof(uint8_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_named_field("PPIP",
> +               sizeof(uint32_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_named_field("PPRP",
> +               sizeof(uint32_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_named_field("PPRQ",
> +               sizeof(uint32_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_named_field("PPRM",
> +               sizeof(uint32_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_named_field("LPPR",
> +               sizeof(uint32_t) * BITS_PER_BYTE));
> +    aml_append(field, aml_reserved_field(
> +               sizeof(uint32_t) * BITS_PER_BYTE /* FRET */ +
> +               sizeof(uint8_t) * BITS_PER_BYTE /* MCIN */ +
> +               sizeof(uint32_t) * BITS_PER_BYTE * 4 /* MCIP .. UCRQ */ +
> +               sizeof(uint8_t) * BITS_PER_BYTE * 214));
> +    aml_append(field, aml_named_field("FUNC",
> +               sizeof(uint8_t) * BITS_PER_BYTE * 256));
> +    aml_append(dev, field);
> +
> +    method = aml_method("_DSM", 4, AML_SERIALIZED);
> +    {
> +        uint8_t zerobyte[1] = { 0 };
> +
> +        ifctx = aml_if(
> +                  aml_equal(aml_arg(0),
> +                            aml_touuid("3DDDFAA6-361B-4EB4-A424-8D10089D1653"))
> +                );
> +        {
> +            aml_append(ifctx,
> +                       aml_store(aml_to_integer(aml_arg(2)), aml_local(0)));
> +
> +            /* standard DSM query function */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(0)));
> +            {
> +                uint8_t byte_list[2] = { 0xff, 0x01 };
> +                aml_append(ifctx2, aml_return(aml_buffer(2, byte_list)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* interface version: 1.3 */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(1)));
> +            {
> +                aml_append(ifctx2, aml_return(aml_string("1.3")));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* submit TPM operation */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(2)));
> +            {
> +                /* get opcode */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> +                                                           aml_int(0))),
> +                                     aml_local(0)));
> +                /* get opcode flags */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> +                                                           aml_local(0))),
> +                                     aml_local(1)));
> +                ifctx3 = aml_if(
> +                              aml_equal(
> +                                  aml_and(aml_local(1),
> +                                          aml_int(TPM_PPI_FUNC_IMPLEMENTED),
> +                                          NULL),
> +                                  aml_int(0)
> +                              )
> +                         );
> +                {
> +                    /* 1: not implemented */
> +                    aml_append(ifctx3, aml_return(aml_int(1)));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +                aml_append(ifctx2, aml_store(aml_local(0), aml_name("PPRQ")));
> +                aml_append(ifctx2, aml_store(aml_int(0), aml_name("PPRM")));
> +                /* 0: success */
> +                aml_append(ifctx2, aml_return(aml_int(0)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* get pending TPM operation */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(3)));
> +            {
> +                /* revision to integer */
> +                aml_append(ifctx2,
> +                           aml_store(
> +                             aml_to_integer(aml_arg(1)),
> +                             aml_local(1)));
> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
> +                {
> +                    pak = aml_package(2);
> +                    aml_append(pak, aml_int(0));
> +                    aml_append(pak, aml_name("PPRQ"));
> +                    aml_append(ifctx3, aml_return(pak));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +
> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
> +                {
> +                    pak = aml_package(3);
> +                    aml_append(pak, aml_int(0));
> +                    aml_append(pak, aml_name("PPRQ"));
> +                    aml_append(pak, aml_name("PPRM"));
> +                    aml_append(ifctx3, aml_return(pak));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* get platform-specific action to transition to pre-OS env. */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(4)));
> +            {
> +                /* get opcode */
> +                aml_append(ifctx2,
> +                           aml_store(aml_name("PPRQ"),
> +                                     aml_local(0)));
> +                /* get opcode flags */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> +                                                           aml_local(0))),
> +                                     aml_local(1)));
> +                /* return action flags */
> +                aml_append(ifctx2,
> +                           aml_return(
> +                               aml_shiftright(
> +                                   aml_and(aml_local(1),
> +                                           aml_int(TPM_PPI_FUNC_ACTION_MASK),
> +                                           NULL),
> +                                   aml_int(1),
> +                                   NULL)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* get TPM operation response */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(5)));
> +            {
> +                pak = aml_package(3);
> +
> +                aml_append(pak, aml_int(0));
> +                aml_append(pak, aml_name("LPPR"));
> +                aml_append(pak, aml_name("PPRP"));
> +
> +                aml_append(ifctx2, aml_return(pak));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* submit preferred user language */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(6)));
> +            {
> +                /* 3 = not implemented */
> +                aml_append(ifctx2, aml_return(aml_int(3)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* submit TPM operation v2 */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(7)));
> +            {
> +                /* get opcode */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> +                                                           aml_int(0))),
> +                                     aml_local(0)));
> +                /* get opcode flags */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> +                                                           aml_local(0))),
> +                                     aml_local(1)));
> +                ifctx3 = aml_if(
> +                              aml_equal(
> +                                  aml_and(aml_local(1),
> +                                          aml_int(TPM_PPI_FUNC_MASK),
> +                                          NULL),
> +                                  aml_int(TPM_PPI_FUNC_NOT_IMPLEMENTED)
> +                              )
> +                         );
> +                {
> +                    /* 1: not implemented */
> +                    aml_append(ifctx3, aml_return(aml_int(1)));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +
> +                ifctx3 = aml_if(
> +                              aml_equal(
> +                                  aml_and(
> +                                      aml_local(1),
> +                                      aml_int(TPM_PPI_FUNC_MASK),
> +                                      NULL),
> +                                  aml_int(TPM_PPI_FUNC_BLOCKED)
> +                              )
> +                         );
> +                {
> +                    /* 3: blocked by firmware */
> +                    aml_append(ifctx3, aml_return(aml_int(3)));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +
> +                /* revision to integer */
> +                aml_append(ifctx2,
> +                           aml_store(
> +                             aml_to_integer(aml_arg(1)),
> +                             aml_local(1)));
> +
> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
> +                {
> +                    /* revision 1 */
> +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
> +                    aml_append(ifctx3, aml_store(aml_int(0), aml_name("PPRM")));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +
> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
> +                {
> +                    /* revision 2 */
> +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
> +                    aml_append(ifctx3, aml_store(
> +                                         aml_derefof(
> +                                           aml_index(aml_arg(3),
> +                                                     aml_int(1))),
> +                                         aml_name("PPRM")));
> +                }
> +                aml_append(ifctx2, ifctx3);
> +                /* 0: success */
> +                aml_append(ifctx2, aml_return(aml_int(0)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            /* get user confirmation status for operation */
> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(8)));
> +            {
> +                /* get opcode */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> +                                                           aml_int(0))),
> +                                     aml_local(0)));
> +                /* get opcode flags */
> +                aml_append(ifctx2,
> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> +                                                           aml_local(0))),
> +                                     aml_local(1)));
> +                /* return confirmation status code */
> +                aml_append(ifctx2,
> +                           aml_return(
> +                               aml_shiftright(
> +                                   aml_and(aml_local(1),
> +                                           aml_int(TPM_PPI_FUNC_MASK),
> +                                           NULL),
> +                                   aml_int(3),
> +                                   NULL)));
> +            }
> +            aml_append(ifctx, ifctx2);
> +
> +            aml_append(ifctx, aml_return(aml_buffer(1, zerobyte)));
> +        }
> +        aml_append(method, ifctx);
> +    }
> +    aml_append(dev, method);
> +}
> +
> +static void
>   build_dsdt(GArray *table_data, BIOSLinker *linker,
>              AcpiPmInfo *pm, AcpiMiscInfo *misc,
>              Range *pci_hole, Range *pci_hole64, MachineState *machine)
> @@ -2218,6 +2489,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
>                    */
>                   /* aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ)); */
>                   aml_append(dev, aml_name_decl("_CRS", crs));
> +                build_tpm_ppi(dev, misc->tpm_version);
>                   aml_append(scope, dev);
>               }
>
> @@ -2636,6 +2908,7 @@ static void build_qemu(GArray *table_data, BIOSLinker *linker,
>       if (tpm_version != TPM_VERSION_UNSPEC) {
>           qemu->tpmppi_addr = TPM_PPI_ADDR_BASE;
>           qemu->tpm_version = tpm_version;
> +        qemu->tpmppi_version = TPM_PPI_VERSION_1_30;
>       }
>
>       build_header(linker, table_data,
> diff --git a/include/hw/acpi/acpi-defs.h b/include/hw/acpi/acpi-defs.h
> index 98764c1..a182194 100644
> --- a/include/hw/acpi/acpi-defs.h
> +++ b/include/hw/acpi/acpi-defs.h
> @@ -578,6 +578,8 @@ struct AcpiTableQemu {
>       ACPI_TABLE_HEADER_DEF
>       uint32_t tpmppi_addr;
>       uint8_t tpm_version; /* 1 = 1.2, 2 = 2 */
> +    uint8_t tpmppi_version;
> +#define TPM_PPI_VERSION_1_30  1
>   };
>   typedef struct AcpiTableQemu AcpiTableQemu;
>
> diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
> index 16227bc..130a039 100644
> --- a/include/hw/acpi/tpm.h
> +++ b/include/hw/acpi/tpm.h
> @@ -37,4 +37,35 @@
>   #define TPM_PPI_ADDR_SIZE           0x400
>   #define TPM_PPI_ADDR_BASE           0xfffef000
>
> +struct tpm_ppi {
> +    uint8_t ppin;            /*  0: set by BIOS */
> +    uint32_t ppip;           /*  1: set by ACPI; not used */
> +    uint32_t pprp;           /*  5: response from TPM; set by BIOS */
> +    uint32_t pprq;           /*  9: opcode; set by ACPI */
> +    uint32_t pprm;           /* 13: parameter for opcode; set by ACPI */
> +    uint32_t lppr;           /* 17: last opcode; set by BIOS */
> +    uint32_t fret;           /* 21: set by ACPI; not used */
> +    uint8_t res1;            /* 25: reserved */
> +    uint32_t res2[4];        /* 26: reserved */
> +    uint8_t  res3[214];      /* 42: reserved */
> +    uint8_t  func[256];      /* 256: per TPM function implementation flags;
> +                                     set by BIOS */
> +/* indication whether function is implemented; bit 0 */
> +#define TPM_PPI_FUNC_IMPLEMENTED       (1 << 0)
> +/* actions OS should take to transition to the pre-OS env.; bits 1, 2 */
> +#define TPM_PPI_FUNC_ACTION_SHUTDOWN   (1 << 1)
> +#define TPM_PPI_FUNC_ACTION_REBOOT     (2 << 1)
> +#define TPM_PPI_FUNC_ACTION_VENDOR     (3 << 1)
> +#define TPM_PPI_FUNC_ACTION_MASK       (3 << 1)
> +/* whether function is blocked by BIOS settings; bits 3,4,5 */
> +#define TPM_PPI_FUNC_NOT_IMPLEMENTED     (0 << 3)
> +#define TPM_PPI_FUNC_BIOS_ONLY           (1 << 3)
> +#define TPM_PPI_FUNC_BLOCKED             (2 << 3)
> +#define TPM_PPI_FUNC_ALLOWED_USR_REQ     (3 << 3)
> +#define TPM_PPI_FUNC_ALLOWED_USR_NOT_REQ (4 << 3)
> +#define TPM_PPI_FUNC_MASK                (7 << 3)
> +} QEMU_PACKED;
> +
> +#define TPM_PPI_STRUCT_SIZE  sizeof(struct tpm_ppi)
> +
>   #endif /* HW_ACPI_TPM_H */
Igor Mammedov Feb. 12, 2018, 2:27 p.m. UTC | #2
On Fri, 9 Feb 2018 15:19:31 -0500
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:

> On 01/16/2018 10:51 AM, Stefan Berger wrote:
> > The TPM Physical Presence interface consists of an ACPI part, a shared
> > memory part, and code in the firmware. Users can send messages to the
> > firmware by writing a code into the shared memory through invoking the
> > ACPI code. When a reboot happens, the firmware looks for the code and
> > acts on it by sending sequences of commands to the TPM.
> >
> > This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
> > assume that SMIs are necessary to use. It uses a similar datastructure for
> > the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
> > of it. I extended the shared memory data structure with an array of 256
> > bytes, one for each code that could be implemented. The array contains
> > flags describing the individual codes. This decouples the ACPI implementation
> > from the firmware implementation.
> >
> > The underlying TCG specification is accessible from the following page.
> >
> > https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
> >
> > This patch implements version 1.30.  
> 
> I have played around with this patch and some modifications to EDK2. 
> Though for EDK2 the question is whether to try to circumvent their 
> current implementation that uses SMM or use SMM. With this patch so far 
> I circumvent it, which is maybe not a good idea.
> 
> The facts for EDK2's PPI:
> 
> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM 
> via an SMI and the PPI code is written into a UEFI variable. For this 
> ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS 
> (via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at 
> that address. Once the machine is rebooted, UEFI reads the variable and 
> finds the PPI code and reacts to it.
> 
> 
> The facts for SeaBIOS:
> - we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to 
> this address. So we would have to use the PPI memory device's memory 
> region, which is currently at 0xFED4 5000. SeaBIOS doesn't have 
> persistent memory like NVRAM where it stores varaibles. So to pass the 
> PPI code here the OS would invoke ACPI, which in turn would write the 
> PPI code into the PPI memory directly. Upon reboot SeaBIOS would find 
> the PPI code in the PPI memory device and react to it.
> 
> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes 
> are used by the OperationRegion() in this patch series. The rest was 
> thought of for future extensions.
> 
> To allow both firmwares to use PPI, we would need to be able to have the 
> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and 
> 0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve 
> this would be to have the firmwares choose their operation region base 
> address by writing into the PPI memory device at offset 0x200 (for 
> example). A '1' there would mean that we want the OperationRegion() at 
> 0xFFFF 0000, and a '2' would mean at the base address of the PPI device 
> (0xFED4 5000). This could be achieved by declaring a 2nd 
> OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we 
> declare that 1st OperationRegion()'s address based on findings from 
> there. Further, a flags variable at offset 0x201 could indicate whether 
> an SMI is needed to enter SMM or not. Obviously, the ACPI code would 
> become more complex to be able to support both firmwares at the same time.
> This is a lot of details but I rather post this description before 
> posting more patches. To make these changes and get it to work with at 
> least SeaBIOS is probably fairly easy. But is this acceptable?

You could use hw/acpi/vmgenid.c as example,

use following command to instruct firmware to write address back to QEMU:

    bios_linker_loader_write_pointer(linker,                                     
        TMP_PPI_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),                           
        TPM_PPI_MEM_FW_CFG_FILE, TPM_PPI_MEM_OFFSET); 

then when address is written into fwcfg, a callback in QEMU would be called due to

    /* Create a read-write fw_cfg file for Address */                            
    fw_cfg_add_file_callback(s, TPM_PPI_ADDR_FW_CFG_FILE, ...);  

when CB is called you could map persistent overlay memory region
(PPI memory device) at address written by firmware.

As for OperationRegion, you can instruct firmware to patch address
in AML as well, using bios_linker_loader_add_pointer() linker command.
what I'd suggest is to use dedicated TPM SSDT table and
at its start put a DWORD/QWORD variable where address would be patched in
and then use that variable as argument to OperationRegion

 ssdt = init_aml_allocator();
 ...
 addr_offset = table_data->len + build_append_named_dword(ssdt->buf, "PPIA");
 ...
 ... aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
                      aml_name("PPIA"), TPM_PPI_STRUCT_SIZE)
 ...
 bios_linker_loader_add_pointer(linker,                                       
       ACPI_BUILD_TABLE_FILE, addr_offset, sizeof(uint32_t),                    
       TPM_PPI_MEM_FW_CFG_FILE, 0);

This way both UEFI and Seabios would use the same implementation and
work the same way.

aml_operation_region("TPPI",..., aml_name("PPIA"), ...)
might work but it's not per spec, so it would be better to add
an API similar to build_append_named_dword() but only for
OperationRegion() which would return its offset within the table.
And skip on intermediate dword variable.


Wrt migration, you'd need to migrate address where PPI memory device
is mapped and its memory region.

As for giving up control to from OS (APCI) to firmware that would be
target depended, one could use writing to SMM port to trigger SMI
for example and something else in case of ARM or x86 SMM-less design.

>     Stefan
> 
> >
> > Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> >
> > ---
> >   v1->v2:
> >     - get rid of FAIL variable; function 5 was using it and always
> >       returns 0; the value is related to the ACPI function call not
> >       a possible failure of the TPM function call.
> >     - extend shared memory data structure with per-opcode entries
> >       holding flags and use those flags to determine what to return
> >       to caller
> >     - implement interface version 1.3
> > ---
> >   hw/i386/acpi-build.c        | 273 ++++++++++++++++++++++++++++++++++++++++++++
> >   include/hw/acpi/acpi-defs.h |   2 +
> >   include/hw/acpi/tpm.h       |  31 +++++
> >   3 files changed, 306 insertions(+)
> >
> > diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
> > index 522d6d2..f8c2d01 100644
> > --- a/hw/i386/acpi-build.c
> > +++ b/hw/i386/acpi-build.c
> > @@ -42,6 +42,7 @@
> >   #include "hw/acpi/memory_hotplug.h"
> >   #include "sysemu/tpm.h"
> >   #include "hw/acpi/tpm.h"
> > +#include "hw/tpm/tpm_ppi.h"
> >   #include "hw/acpi/vmgenid.h"
> >   #include "sysemu/tpm_backend.h"
> >   #include "hw/timer/mc146818rtc_regs.h"
> > @@ -1860,6 +1861,276 @@ static Aml *build_q35_osc_method(void)
> >   }
> >
> >   static void
> > +build_tpm_ppi(Aml *dev, TPMVersion tpm_version)
> > +{
> > +    Aml *method, *field, *ifctx, *ifctx2, *ifctx3, *pak;
> > +
> > +    aml_append(dev,
> > +               aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
> > +                                    aml_int(TPM_PPI_ADDR_BASE),
> > +                                    TPM_PPI_STRUCT_SIZE));
> > +
> > +    field = aml_field("TPPI", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
> > +    aml_append(field, aml_named_field("PPIN",
> > +               sizeof(uint8_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_named_field("PPIP",
> > +               sizeof(uint32_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_named_field("PPRP",
> > +               sizeof(uint32_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_named_field("PPRQ",
> > +               sizeof(uint32_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_named_field("PPRM",
> > +               sizeof(uint32_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_named_field("LPPR",
> > +               sizeof(uint32_t) * BITS_PER_BYTE));
> > +    aml_append(field, aml_reserved_field(
> > +               sizeof(uint32_t) * BITS_PER_BYTE /* FRET */ +
> > +               sizeof(uint8_t) * BITS_PER_BYTE /* MCIN */ +
> > +               sizeof(uint32_t) * BITS_PER_BYTE * 4 /* MCIP .. UCRQ */ +
> > +               sizeof(uint8_t) * BITS_PER_BYTE * 214));
> > +    aml_append(field, aml_named_field("FUNC",
> > +               sizeof(uint8_t) * BITS_PER_BYTE * 256));
> > +    aml_append(dev, field);
> > +
> > +    method = aml_method("_DSM", 4, AML_SERIALIZED);
> > +    {
> > +        uint8_t zerobyte[1] = { 0 };
> > +
> > +        ifctx = aml_if(
> > +                  aml_equal(aml_arg(0),
> > +                            aml_touuid("3DDDFAA6-361B-4EB4-A424-8D10089D1653"))
> > +                );
> > +        {
> > +            aml_append(ifctx,
> > +                       aml_store(aml_to_integer(aml_arg(2)), aml_local(0)));
> > +
> > +            /* standard DSM query function */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(0)));
> > +            {
> > +                uint8_t byte_list[2] = { 0xff, 0x01 };
> > +                aml_append(ifctx2, aml_return(aml_buffer(2, byte_list)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* interface version: 1.3 */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(1)));
> > +            {
> > +                aml_append(ifctx2, aml_return(aml_string("1.3")));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* submit TPM operation */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(2)));
> > +            {
> > +                /* get opcode */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> > +                                                           aml_int(0))),
> > +                                     aml_local(0)));
> > +                /* get opcode flags */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> > +                                                           aml_local(0))),
> > +                                     aml_local(1)));
> > +                ifctx3 = aml_if(
> > +                              aml_equal(
> > +                                  aml_and(aml_local(1),
> > +                                          aml_int(TPM_PPI_FUNC_IMPLEMENTED),
> > +                                          NULL),
> > +                                  aml_int(0)
> > +                              )
> > +                         );
> > +                {
> > +                    /* 1: not implemented */
> > +                    aml_append(ifctx3, aml_return(aml_int(1)));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +                aml_append(ifctx2, aml_store(aml_local(0), aml_name("PPRQ")));
> > +                aml_append(ifctx2, aml_store(aml_int(0), aml_name("PPRM")));
> > +                /* 0: success */
> > +                aml_append(ifctx2, aml_return(aml_int(0)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* get pending TPM operation */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(3)));
> > +            {
> > +                /* revision to integer */
> > +                aml_append(ifctx2,
> > +                           aml_store(
> > +                             aml_to_integer(aml_arg(1)),
> > +                             aml_local(1)));
> > +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
> > +                {
> > +                    pak = aml_package(2);
> > +                    aml_append(pak, aml_int(0));
> > +                    aml_append(pak, aml_name("PPRQ"));
> > +                    aml_append(ifctx3, aml_return(pak));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +
> > +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
> > +                {
> > +                    pak = aml_package(3);
> > +                    aml_append(pak, aml_int(0));
> > +                    aml_append(pak, aml_name("PPRQ"));
> > +                    aml_append(pak, aml_name("PPRM"));
> > +                    aml_append(ifctx3, aml_return(pak));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* get platform-specific action to transition to pre-OS env. */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(4)));
> > +            {
> > +                /* get opcode */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_name("PPRQ"),
> > +                                     aml_local(0)));
> > +                /* get opcode flags */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> > +                                                           aml_local(0))),
> > +                                     aml_local(1)));
> > +                /* return action flags */
> > +                aml_append(ifctx2,
> > +                           aml_return(
> > +                               aml_shiftright(
> > +                                   aml_and(aml_local(1),
> > +                                           aml_int(TPM_PPI_FUNC_ACTION_MASK),
> > +                                           NULL),
> > +                                   aml_int(1),
> > +                                   NULL)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* get TPM operation response */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(5)));
> > +            {
> > +                pak = aml_package(3);
> > +
> > +                aml_append(pak, aml_int(0));
> > +                aml_append(pak, aml_name("LPPR"));
> > +                aml_append(pak, aml_name("PPRP"));
> > +
> > +                aml_append(ifctx2, aml_return(pak));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* submit preferred user language */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(6)));
> > +            {
> > +                /* 3 = not implemented */
> > +                aml_append(ifctx2, aml_return(aml_int(3)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* submit TPM operation v2 */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(7)));
> > +            {
> > +                /* get opcode */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> > +                                                           aml_int(0))),
> > +                                     aml_local(0)));
> > +                /* get opcode flags */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> > +                                                           aml_local(0))),
> > +                                     aml_local(1)));
> > +                ifctx3 = aml_if(
> > +                              aml_equal(
> > +                                  aml_and(aml_local(1),
> > +                                          aml_int(TPM_PPI_FUNC_MASK),
> > +                                          NULL),
> > +                                  aml_int(TPM_PPI_FUNC_NOT_IMPLEMENTED)
> > +                              )
> > +                         );
> > +                {
> > +                    /* 1: not implemented */
> > +                    aml_append(ifctx3, aml_return(aml_int(1)));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +
> > +                ifctx3 = aml_if(
> > +                              aml_equal(
> > +                                  aml_and(
> > +                                      aml_local(1),
> > +                                      aml_int(TPM_PPI_FUNC_MASK),
> > +                                      NULL),
> > +                                  aml_int(TPM_PPI_FUNC_BLOCKED)
> > +                              )
> > +                         );
> > +                {
> > +                    /* 3: blocked by firmware */
> > +                    aml_append(ifctx3, aml_return(aml_int(3)));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +
> > +                /* revision to integer */
> > +                aml_append(ifctx2,
> > +                           aml_store(
> > +                             aml_to_integer(aml_arg(1)),
> > +                             aml_local(1)));
> > +
> > +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
> > +                {
> > +                    /* revision 1 */
> > +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
> > +                    aml_append(ifctx3, aml_store(aml_int(0), aml_name("PPRM")));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +
> > +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
> > +                {
> > +                    /* revision 2 */
> > +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
> > +                    aml_append(ifctx3, aml_store(
> > +                                         aml_derefof(
> > +                                           aml_index(aml_arg(3),
> > +                                                     aml_int(1))),
> > +                                         aml_name("PPRM")));
> > +                }
> > +                aml_append(ifctx2, ifctx3);
> > +                /* 0: success */
> > +                aml_append(ifctx2, aml_return(aml_int(0)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            /* get user confirmation status for operation */
> > +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(8)));
> > +            {
> > +                /* get opcode */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_arg(3),
> > +                                                           aml_int(0))),
> > +                                     aml_local(0)));
> > +                /* get opcode flags */
> > +                aml_append(ifctx2,
> > +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
> > +                                                           aml_local(0))),
> > +                                     aml_local(1)));
> > +                /* return confirmation status code */
> > +                aml_append(ifctx2,
> > +                           aml_return(
> > +                               aml_shiftright(
> > +                                   aml_and(aml_local(1),
> > +                                           aml_int(TPM_PPI_FUNC_MASK),
> > +                                           NULL),
> > +                                   aml_int(3),
> > +                                   NULL)));
> > +            }
> > +            aml_append(ifctx, ifctx2);
> > +
> > +            aml_append(ifctx, aml_return(aml_buffer(1, zerobyte)));
> > +        }
> > +        aml_append(method, ifctx);
> > +    }
> > +    aml_append(dev, method);
> > +}
> > +
> > +static void
> >   build_dsdt(GArray *table_data, BIOSLinker *linker,
> >              AcpiPmInfo *pm, AcpiMiscInfo *misc,
> >              Range *pci_hole, Range *pci_hole64, MachineState *machine)
> > @@ -2218,6 +2489,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
> >                    */
> >                   /* aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ)); */
> >                   aml_append(dev, aml_name_decl("_CRS", crs));
> > +                build_tpm_ppi(dev, misc->tpm_version);
> >                   aml_append(scope, dev);
> >               }
> >
> > @@ -2636,6 +2908,7 @@ static void build_qemu(GArray *table_data, BIOSLinker *linker,
> >       if (tpm_version != TPM_VERSION_UNSPEC) {
> >           qemu->tpmppi_addr = TPM_PPI_ADDR_BASE;
> >           qemu->tpm_version = tpm_version;
> > +        qemu->tpmppi_version = TPM_PPI_VERSION_1_30;
> >       }
> >
> >       build_header(linker, table_data,
> > diff --git a/include/hw/acpi/acpi-defs.h b/include/hw/acpi/acpi-defs.h
> > index 98764c1..a182194 100644
> > --- a/include/hw/acpi/acpi-defs.h
> > +++ b/include/hw/acpi/acpi-defs.h
> > @@ -578,6 +578,8 @@ struct AcpiTableQemu {
> >       ACPI_TABLE_HEADER_DEF
> >       uint32_t tpmppi_addr;
> >       uint8_t tpm_version; /* 1 = 1.2, 2 = 2 */
> > +    uint8_t tpmppi_version;
> > +#define TPM_PPI_VERSION_1_30  1
> >   };
> >   typedef struct AcpiTableQemu AcpiTableQemu;
> >
> > diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
> > index 16227bc..130a039 100644
> > --- a/include/hw/acpi/tpm.h
> > +++ b/include/hw/acpi/tpm.h
> > @@ -37,4 +37,35 @@
> >   #define TPM_PPI_ADDR_SIZE           0x400
> >   #define TPM_PPI_ADDR_BASE           0xfffef000
> >
> > +struct tpm_ppi {
> > +    uint8_t ppin;            /*  0: set by BIOS */
> > +    uint32_t ppip;           /*  1: set by ACPI; not used */
> > +    uint32_t pprp;           /*  5: response from TPM; set by BIOS */
> > +    uint32_t pprq;           /*  9: opcode; set by ACPI */
> > +    uint32_t pprm;           /* 13: parameter for opcode; set by ACPI */
> > +    uint32_t lppr;           /* 17: last opcode; set by BIOS */
> > +    uint32_t fret;           /* 21: set by ACPI; not used */
> > +    uint8_t res1;            /* 25: reserved */
> > +    uint32_t res2[4];        /* 26: reserved */
> > +    uint8_t  res3[214];      /* 42: reserved */
> > +    uint8_t  func[256];      /* 256: per TPM function implementation flags;
> > +                                     set by BIOS */
> > +/* indication whether function is implemented; bit 0 */
> > +#define TPM_PPI_FUNC_IMPLEMENTED       (1 << 0)
> > +/* actions OS should take to transition to the pre-OS env.; bits 1, 2 */
> > +#define TPM_PPI_FUNC_ACTION_SHUTDOWN   (1 << 1)
> > +#define TPM_PPI_FUNC_ACTION_REBOOT     (2 << 1)
> > +#define TPM_PPI_FUNC_ACTION_VENDOR     (3 << 1)
> > +#define TPM_PPI_FUNC_ACTION_MASK       (3 << 1)
> > +/* whether function is blocked by BIOS settings; bits 3,4,5 */
> > +#define TPM_PPI_FUNC_NOT_IMPLEMENTED     (0 << 3)
> > +#define TPM_PPI_FUNC_BIOS_ONLY           (1 << 3)
> > +#define TPM_PPI_FUNC_BLOCKED             (2 << 3)
> > +#define TPM_PPI_FUNC_ALLOWED_USR_REQ     (3 << 3)
> > +#define TPM_PPI_FUNC_ALLOWED_USR_NOT_REQ (4 << 3)
> > +#define TPM_PPI_FUNC_MASK                (7 << 3)
> > +} QEMU_PACKED;
> > +
> > +#define TPM_PPI_STRUCT_SIZE  sizeof(struct tpm_ppi)
> > +
> >   #endif /* HW_ACPI_TPM_H */  
> 
> 
>
Stefan Berger Feb. 12, 2018, 4:44 p.m. UTC | #3
On 02/12/2018 09:27 AM, Igor Mammedov wrote:
> On Fri, 9 Feb 2018 15:19:31 -0500
> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
>
>> On 01/16/2018 10:51 AM, Stefan Berger wrote:
>>> The TPM Physical Presence interface consists of an ACPI part, a shared
>>> memory part, and code in the firmware. Users can send messages to the
>>> firmware by writing a code into the shared memory through invoking the
>>> ACPI code. When a reboot happens, the firmware looks for the code and
>>> acts on it by sending sequences of commands to the TPM.
>>>
>>> This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
>>> assume that SMIs are necessary to use. It uses a similar datastructure for
>>> the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
>>> of it. I extended the shared memory data structure with an array of 256
>>> bytes, one for each code that could be implemented. The array contains
>>> flags describing the individual codes. This decouples the ACPI implementation
>>> from the firmware implementation.
>>>
>>> The underlying TCG specification is accessible from the following page.
>>>
>>> https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
>>>
>>> This patch implements version 1.30.
>> I have played around with this patch and some modifications to EDK2.
>> Though for EDK2 the question is whether to try to circumvent their
>> current implementation that uses SMM or use SMM. With this patch so far
>> I circumvent it, which is maybe not a good idea.
>>
>> The facts for EDK2's PPI:
>>
>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM
>> via an SMI and the PPI code is written into a UEFI variable. For this
>> ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS
>> (via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at
>> that address. Once the machine is rebooted, UEFI reads the variable and
>> finds the PPI code and reacts to it.
>>
>>
>> The facts for SeaBIOS:
>> - we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to
>> this address. So we would have to use the PPI memory device's memory
>> region, which is currently at 0xFED4 5000. SeaBIOS doesn't have
>> persistent memory like NVRAM where it stores varaibles. So to pass the
>> PPI code here the OS would invoke ACPI, which in turn would write the
>> PPI code into the PPI memory directly. Upon reboot SeaBIOS would find
>> the PPI code in the PPI memory device and react to it.
>>
>> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes
>> are used by the OperationRegion() in this patch series. The rest was
>> thought of for future extensions.
>>
>> To allow both firmwares to use PPI, we would need to be able to have the
>> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and
>> 0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve
>> this would be to have the firmwares choose their operation region base
>> address by writing into the PPI memory device at offset 0x200 (for
>> example). A '1' there would mean that we want the OperationRegion() at
>> 0xFFFF 0000, and a '2' would mean at the base address of the PPI device
>> (0xFED4 5000). This could be achieved by declaring a 2nd
>> OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we
>> declare that 1st OperationRegion()'s address based on findings from
>> there. Further, a flags variable at offset 0x201 could indicate whether
>> an SMI is needed to enter SMM or not. Obviously, the ACPI code would
>> become more complex to be able to support both firmwares at the same time.
>> This is a lot of details but I rather post this description before
>> posting more patches. To make these changes and get it to work with at
>> least SeaBIOS is probably fairly easy. But is this acceptable?
> You could use hw/acpi/vmgenid.c as example,
>
> use following command to instruct firmware to write address back to QEMU:
>
>      bios_linker_loader_write_pointer(linker,
>          TMP_PPI_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
>          TPM_PPI_MEM_FW_CFG_FILE, TPM_PPI_MEM_OFFSET);
>
> then when address is written into fwcfg, a callback in QEMU would be called due to
>
>      /* Create a read-write fw_cfg file for Address */
>      fw_cfg_add_file_callback(s, TPM_PPI_ADDR_FW_CFG_FILE, ...);
>
> when CB is called you could map persistent overlay memory region
> (PPI memory device) at address written by firmware.


> As for OperationRegion, you can instruct firmware to patch address
> in AML as well, using bios_linker_loader_add_pointer() linker command.
> what I'd suggest is to use dedicated TPM SSDT table and
> at its start put a DWORD/QWORD variable where address would be patched in
> and then use that variable as argument to OperationRegion
>
>   ssdt = init_aml_allocator();
>   ...
>   addr_offset = table_data->len + build_append_named_dword(ssdt->buf, "PPIA");
>   ...
>   ... aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
>                        aml_name("PPIA"), TPM_PPI_STRUCT_SIZE)
>   ...
>   bios_linker_loader_add_pointer(linker,
>         ACPI_BUILD_TABLE_FILE, addr_offset, sizeof(uint32_t),
>         TPM_PPI_MEM_FW_CFG_FILE, 0);
>
> This way both UEFI and Seabios would use the same implementation and
> work the same way.

Thanks for this sample code. Though it only applies to how they write 
the base address for the OperationRegion() and not the behaviour of the 
code when it comes to SMI versus non-SMI.

>
> aml_operation_region("TPPI",..., aml_name("PPIA"), ...)
> might work but it's not per spec, so it would be better to add
> an API similar to build_append_named_dword() but only for
> OperationRegion() which would return its offset within the table.
> And skip on intermediate dword variable.
>
>
> Wrt migration, you'd need to migrate address where PPI memory device
> is mapped and its memory region.

So today the PPI memory device is located at some address A. If we 
decided that we need to move it to address B due to a collision with 
another device, then are we going to be able to update the ACPI 
OperationRegion base address post-migration to move it from address A to 
address B? Or should we refuse the migration ? Keeping it at address A 
seems wrong due to the collision.

>
> As for giving up control to from OS (APCI) to firmware that would be
> target depended, one could use writing to SMM port to trigger SMI
> for example and something else in case of ARM or x86 SMM-less design.

Though to support these different cases, we either need to be able to 
generate different ACPI code or make it configurable via some sort of 
register. If we cannot get rid of these flag to let the firmware for 
example choose between non-SMI and SMI, then do we need to use the above 
shown callback mechanisms to avoid a 2nd field that lets one choose the 
base address of the PPI memory device?

>
>>      Stefan
>>
>>> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
>>>
>>> ---
>>>    v1->v2:
>>>      - get rid of FAIL variable; function 5 was using it and always
>>>        returns 0; the value is related to the ACPI function call not
>>>        a possible failure of the TPM function call.
>>>      - extend shared memory data structure with per-opcode entries
>>>        holding flags and use those flags to determine what to return
>>>        to caller
>>>      - implement interface version 1.3
>>> ---
>>>    hw/i386/acpi-build.c        | 273 ++++++++++++++++++++++++++++++++++++++++++++
>>>    include/hw/acpi/acpi-defs.h |   2 +
>>>    include/hw/acpi/tpm.h       |  31 +++++
>>>    3 files changed, 306 insertions(+)
>>>
>>> diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
>>> index 522d6d2..f8c2d01 100644
>>> --- a/hw/i386/acpi-build.c
>>> +++ b/hw/i386/acpi-build.c
>>> @@ -42,6 +42,7 @@
>>>    #include "hw/acpi/memory_hotplug.h"
>>>    #include "sysemu/tpm.h"
>>>    #include "hw/acpi/tpm.h"
>>> +#include "hw/tpm/tpm_ppi.h"
>>>    #include "hw/acpi/vmgenid.h"
>>>    #include "sysemu/tpm_backend.h"
>>>    #include "hw/timer/mc146818rtc_regs.h"
>>> @@ -1860,6 +1861,276 @@ static Aml *build_q35_osc_method(void)
>>>    }
>>>
>>>    static void
>>> +build_tpm_ppi(Aml *dev, TPMVersion tpm_version)
>>> +{
>>> +    Aml *method, *field, *ifctx, *ifctx2, *ifctx3, *pak;
>>> +
>>> +    aml_append(dev,
>>> +               aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
>>> +                                    aml_int(TPM_PPI_ADDR_BASE),
>>> +                                    TPM_PPI_STRUCT_SIZE));
>>> +
>>> +    field = aml_field("TPPI", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
>>> +    aml_append(field, aml_named_field("PPIN",
>>> +               sizeof(uint8_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_named_field("PPIP",
>>> +               sizeof(uint32_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_named_field("PPRP",
>>> +               sizeof(uint32_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_named_field("PPRQ",
>>> +               sizeof(uint32_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_named_field("PPRM",
>>> +               sizeof(uint32_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_named_field("LPPR",
>>> +               sizeof(uint32_t) * BITS_PER_BYTE));
>>> +    aml_append(field, aml_reserved_field(
>>> +               sizeof(uint32_t) * BITS_PER_BYTE /* FRET */ +
>>> +               sizeof(uint8_t) * BITS_PER_BYTE /* MCIN */ +
>>> +               sizeof(uint32_t) * BITS_PER_BYTE * 4 /* MCIP .. UCRQ */ +
>>> +               sizeof(uint8_t) * BITS_PER_BYTE * 214));
>>> +    aml_append(field, aml_named_field("FUNC",
>>> +               sizeof(uint8_t) * BITS_PER_BYTE * 256));
>>> +    aml_append(dev, field);
>>> +
>>> +    method = aml_method("_DSM", 4, AML_SERIALIZED);
>>> +    {
>>> +        uint8_t zerobyte[1] = { 0 };
>>> +
>>> +        ifctx = aml_if(
>>> +                  aml_equal(aml_arg(0),
>>> +                            aml_touuid("3DDDFAA6-361B-4EB4-A424-8D10089D1653"))
>>> +                );
>>> +        {
>>> +            aml_append(ifctx,
>>> +                       aml_store(aml_to_integer(aml_arg(2)), aml_local(0)));
>>> +
>>> +            /* standard DSM query function */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(0)));
>>> +            {
>>> +                uint8_t byte_list[2] = { 0xff, 0x01 };
>>> +                aml_append(ifctx2, aml_return(aml_buffer(2, byte_list)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* interface version: 1.3 */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(1)));
>>> +            {
>>> +                aml_append(ifctx2, aml_return(aml_string("1.3")));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* submit TPM operation */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(2)));
>>> +            {
>>> +                /* get opcode */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
>>> +                                                           aml_int(0))),
>>> +                                     aml_local(0)));
>>> +                /* get opcode flags */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
>>> +                                                           aml_local(0))),
>>> +                                     aml_local(1)));
>>> +                ifctx3 = aml_if(
>>> +                              aml_equal(
>>> +                                  aml_and(aml_local(1),
>>> +                                          aml_int(TPM_PPI_FUNC_IMPLEMENTED),
>>> +                                          NULL),
>>> +                                  aml_int(0)
>>> +                              )
>>> +                         );
>>> +                {
>>> +                    /* 1: not implemented */
>>> +                    aml_append(ifctx3, aml_return(aml_int(1)));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +                aml_append(ifctx2, aml_store(aml_local(0), aml_name("PPRQ")));
>>> +                aml_append(ifctx2, aml_store(aml_int(0), aml_name("PPRM")));
>>> +                /* 0: success */
>>> +                aml_append(ifctx2, aml_return(aml_int(0)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* get pending TPM operation */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(3)));
>>> +            {
>>> +                /* revision to integer */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(
>>> +                             aml_to_integer(aml_arg(1)),
>>> +                             aml_local(1)));
>>> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
>>> +                {
>>> +                    pak = aml_package(2);
>>> +                    aml_append(pak, aml_int(0));
>>> +                    aml_append(pak, aml_name("PPRQ"));
>>> +                    aml_append(ifctx3, aml_return(pak));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +
>>> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
>>> +                {
>>> +                    pak = aml_package(3);
>>> +                    aml_append(pak, aml_int(0));
>>> +                    aml_append(pak, aml_name("PPRQ"));
>>> +                    aml_append(pak, aml_name("PPRM"));
>>> +                    aml_append(ifctx3, aml_return(pak));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* get platform-specific action to transition to pre-OS env. */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(4)));
>>> +            {
>>> +                /* get opcode */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_name("PPRQ"),
>>> +                                     aml_local(0)));
>>> +                /* get opcode flags */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
>>> +                                                           aml_local(0))),
>>> +                                     aml_local(1)));
>>> +                /* return action flags */
>>> +                aml_append(ifctx2,
>>> +                           aml_return(
>>> +                               aml_shiftright(
>>> +                                   aml_and(aml_local(1),
>>> +                                           aml_int(TPM_PPI_FUNC_ACTION_MASK),
>>> +                                           NULL),
>>> +                                   aml_int(1),
>>> +                                   NULL)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* get TPM operation response */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(5)));
>>> +            {
>>> +                pak = aml_package(3);
>>> +
>>> +                aml_append(pak, aml_int(0));
>>> +                aml_append(pak, aml_name("LPPR"));
>>> +                aml_append(pak, aml_name("PPRP"));
>>> +
>>> +                aml_append(ifctx2, aml_return(pak));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* submit preferred user language */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(6)));
>>> +            {
>>> +                /* 3 = not implemented */
>>> +                aml_append(ifctx2, aml_return(aml_int(3)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* submit TPM operation v2 */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(7)));
>>> +            {
>>> +                /* get opcode */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
>>> +                                                           aml_int(0))),
>>> +                                     aml_local(0)));
>>> +                /* get opcode flags */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
>>> +                                                           aml_local(0))),
>>> +                                     aml_local(1)));
>>> +                ifctx3 = aml_if(
>>> +                              aml_equal(
>>> +                                  aml_and(aml_local(1),
>>> +                                          aml_int(TPM_PPI_FUNC_MASK),
>>> +                                          NULL),
>>> +                                  aml_int(TPM_PPI_FUNC_NOT_IMPLEMENTED)
>>> +                              )
>>> +                         );
>>> +                {
>>> +                    /* 1: not implemented */
>>> +                    aml_append(ifctx3, aml_return(aml_int(1)));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +
>>> +                ifctx3 = aml_if(
>>> +                              aml_equal(
>>> +                                  aml_and(
>>> +                                      aml_local(1),
>>> +                                      aml_int(TPM_PPI_FUNC_MASK),
>>> +                                      NULL),
>>> +                                  aml_int(TPM_PPI_FUNC_BLOCKED)
>>> +                              )
>>> +                         );
>>> +                {
>>> +                    /* 3: blocked by firmware */
>>> +                    aml_append(ifctx3, aml_return(aml_int(3)));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +
>>> +                /* revision to integer */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(
>>> +                             aml_to_integer(aml_arg(1)),
>>> +                             aml_local(1)));
>>> +
>>> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
>>> +                {
>>> +                    /* revision 1 */
>>> +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
>>> +                    aml_append(ifctx3, aml_store(aml_int(0), aml_name("PPRM")));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +
>>> +                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
>>> +                {
>>> +                    /* revision 2 */
>>> +                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
>>> +                    aml_append(ifctx3, aml_store(
>>> +                                         aml_derefof(
>>> +                                           aml_index(aml_arg(3),
>>> +                                                     aml_int(1))),
>>> +                                         aml_name("PPRM")));
>>> +                }
>>> +                aml_append(ifctx2, ifctx3);
>>> +                /* 0: success */
>>> +                aml_append(ifctx2, aml_return(aml_int(0)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            /* get user confirmation status for operation */
>>> +            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(8)));
>>> +            {
>>> +                /* get opcode */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_arg(3),
>>> +                                                           aml_int(0))),
>>> +                                     aml_local(0)));
>>> +                /* get opcode flags */
>>> +                aml_append(ifctx2,
>>> +                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
>>> +                                                           aml_local(0))),
>>> +                                     aml_local(1)));
>>> +                /* return confirmation status code */
>>> +                aml_append(ifctx2,
>>> +                           aml_return(
>>> +                               aml_shiftright(
>>> +                                   aml_and(aml_local(1),
>>> +                                           aml_int(TPM_PPI_FUNC_MASK),
>>> +                                           NULL),
>>> +                                   aml_int(3),
>>> +                                   NULL)));
>>> +            }
>>> +            aml_append(ifctx, ifctx2);
>>> +
>>> +            aml_append(ifctx, aml_return(aml_buffer(1, zerobyte)));
>>> +        }
>>> +        aml_append(method, ifctx);
>>> +    }
>>> +    aml_append(dev, method);
>>> +}
>>> +
>>> +static void
>>>    build_dsdt(GArray *table_data, BIOSLinker *linker,
>>>               AcpiPmInfo *pm, AcpiMiscInfo *misc,
>>>               Range *pci_hole, Range *pci_hole64, MachineState *machine)
>>> @@ -2218,6 +2489,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
>>>                     */
>>>                    /* aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ)); */
>>>                    aml_append(dev, aml_name_decl("_CRS", crs));
>>> +                build_tpm_ppi(dev, misc->tpm_version);
>>>                    aml_append(scope, dev);
>>>                }
>>>
>>> @@ -2636,6 +2908,7 @@ static void build_qemu(GArray *table_data, BIOSLinker *linker,
>>>        if (tpm_version != TPM_VERSION_UNSPEC) {
>>>            qemu->tpmppi_addr = TPM_PPI_ADDR_BASE;
>>>            qemu->tpm_version = tpm_version;
>>> +        qemu->tpmppi_version = TPM_PPI_VERSION_1_30;
>>>        }
>>>
>>>        build_header(linker, table_data,
>>> diff --git a/include/hw/acpi/acpi-defs.h b/include/hw/acpi/acpi-defs.h
>>> index 98764c1..a182194 100644
>>> --- a/include/hw/acpi/acpi-defs.h
>>> +++ b/include/hw/acpi/acpi-defs.h
>>> @@ -578,6 +578,8 @@ struct AcpiTableQemu {
>>>        ACPI_TABLE_HEADER_DEF
>>>        uint32_t tpmppi_addr;
>>>        uint8_t tpm_version; /* 1 = 1.2, 2 = 2 */
>>> +    uint8_t tpmppi_version;
>>> +#define TPM_PPI_VERSION_1_30  1
>>>    };
>>>    typedef struct AcpiTableQemu AcpiTableQemu;
>>>
>>> diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
>>> index 16227bc..130a039 100644
>>> --- a/include/hw/acpi/tpm.h
>>> +++ b/include/hw/acpi/tpm.h
>>> @@ -37,4 +37,35 @@
>>>    #define TPM_PPI_ADDR_SIZE           0x400
>>>    #define TPM_PPI_ADDR_BASE           0xfffef000
>>>
>>> +struct tpm_ppi {
>>> +    uint8_t ppin;            /*  0: set by BIOS */
>>> +    uint32_t ppip;           /*  1: set by ACPI; not used */
>>> +    uint32_t pprp;           /*  5: response from TPM; set by BIOS */
>>> +    uint32_t pprq;           /*  9: opcode; set by ACPI */
>>> +    uint32_t pprm;           /* 13: parameter for opcode; set by ACPI */
>>> +    uint32_t lppr;           /* 17: last opcode; set by BIOS */
>>> +    uint32_t fret;           /* 21: set by ACPI; not used */
>>> +    uint8_t res1;            /* 25: reserved */
>>> +    uint32_t res2[4];        /* 26: reserved */
>>> +    uint8_t  res3[214];      /* 42: reserved */
>>> +    uint8_t  func[256];      /* 256: per TPM function implementation flags;
>>> +                                     set by BIOS */
>>> +/* indication whether function is implemented; bit 0 */
>>> +#define TPM_PPI_FUNC_IMPLEMENTED       (1 << 0)
>>> +/* actions OS should take to transition to the pre-OS env.; bits 1, 2 */
>>> +#define TPM_PPI_FUNC_ACTION_SHUTDOWN   (1 << 1)
>>> +#define TPM_PPI_FUNC_ACTION_REBOOT     (2 << 1)
>>> +#define TPM_PPI_FUNC_ACTION_VENDOR     (3 << 1)
>>> +#define TPM_PPI_FUNC_ACTION_MASK       (3 << 1)
>>> +/* whether function is blocked by BIOS settings; bits 3,4,5 */
>>> +#define TPM_PPI_FUNC_NOT_IMPLEMENTED     (0 << 3)
>>> +#define TPM_PPI_FUNC_BIOS_ONLY           (1 << 3)
>>> +#define TPM_PPI_FUNC_BLOCKED             (2 << 3)
>>> +#define TPM_PPI_FUNC_ALLOWED_USR_REQ     (3 << 3)
>>> +#define TPM_PPI_FUNC_ALLOWED_USR_NOT_REQ (4 << 3)
>>> +#define TPM_PPI_FUNC_MASK                (7 << 3)
>>> +} QEMU_PACKED;
>>> +
>>> +#define TPM_PPI_STRUCT_SIZE  sizeof(struct tpm_ppi)
>>> +
>>>    #endif /* HW_ACPI_TPM_H */
>>
>>
Igor Mammedov Feb. 12, 2018, 5:52 p.m. UTC | #4
On Mon, 12 Feb 2018 11:44:16 -0500
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:

> On 02/12/2018 09:27 AM, Igor Mammedov wrote:
> > On Fri, 9 Feb 2018 15:19:31 -0500
> > Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> >  
> >> On 01/16/2018 10:51 AM, Stefan Berger wrote:  
> >>> The TPM Physical Presence interface consists of an ACPI part, a shared
> >>> memory part, and code in the firmware. Users can send messages to the
> >>> firmware by writing a code into the shared memory through invoking the
> >>> ACPI code. When a reboot happens, the firmware looks for the code and
> >>> acts on it by sending sequences of commands to the TPM.
> >>>
> >>> This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
> >>> assume that SMIs are necessary to use. It uses a similar datastructure for
> >>> the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
> >>> of it. I extended the shared memory data structure with an array of 256
> >>> bytes, one for each code that could be implemented. The array contains
> >>> flags describing the individual codes. This decouples the ACPI implementation
> >>> from the firmware implementation.
> >>>
> >>> The underlying TCG specification is accessible from the following page.
> >>>
> >>> https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
> >>>
> >>> This patch implements version 1.30.  
> >> I have played around with this patch and some modifications to EDK2.
> >> Though for EDK2 the question is whether to try to circumvent their
> >> current implementation that uses SMM or use SMM. With this patch so far
> >> I circumvent it, which is maybe not a good idea.
> >>
> >> The facts for EDK2's PPI:
> >>
> >> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM
> >> via an SMI and the PPI code is written into a UEFI variable. For this
> >> ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS
> >> (via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at
> >> that address. Once the machine is rebooted, UEFI reads the variable and
> >> finds the PPI code and reacts to it.
> >>
> >>
> >> The facts for SeaBIOS:
> >> - we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to
> >> this address. So we would have to use the PPI memory device's memory
> >> region, which is currently at 0xFED4 5000. SeaBIOS doesn't have
> >> persistent memory like NVRAM where it stores varaibles. So to pass the
> >> PPI code here the OS would invoke ACPI, which in turn would write the
> >> PPI code into the PPI memory directly. Upon reboot SeaBIOS would find
> >> the PPI code in the PPI memory device and react to it.
> >>
> >> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes
> >> are used by the OperationRegion() in this patch series. The rest was
> >> thought of for future extensions.
> >>
> >> To allow both firmwares to use PPI, we would need to be able to have the
> >> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and
> >> 0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve
> >> this would be to have the firmwares choose their operation region base
> >> address by writing into the PPI memory device at offset 0x200 (for
> >> example). A '1' there would mean that we want the OperationRegion() at
> >> 0xFFFF 0000, and a '2' would mean at the base address of the PPI device
> >> (0xFED4 5000). This could be achieved by declaring a 2nd
> >> OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we
> >> declare that 1st OperationRegion()'s address based on findings from
> >> there. Further, a flags variable at offset 0x201 could indicate whether
> >> an SMI is needed to enter SMM or not. Obviously, the ACPI code would
> >> become more complex to be able to support both firmwares at the same time.
> >> This is a lot of details but I rather post this description before
> >> posting more patches. To make these changes and get it to work with at
> >> least SeaBIOS is probably fairly easy. But is this acceptable?  
> > You could use hw/acpi/vmgenid.c as example,
> >
> > use following command to instruct firmware to write address back to QEMU:
> >
> >      bios_linker_loader_write_pointer(linker,
> >          TMP_PPI_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
> >          TPM_PPI_MEM_FW_CFG_FILE, TPM_PPI_MEM_OFFSET);
> >
> > then when address is written into fwcfg, a callback in QEMU would be called due to
> >
> >      /* Create a read-write fw_cfg file for Address */
> >      fw_cfg_add_file_callback(s, TPM_PPI_ADDR_FW_CFG_FILE, ...);
> >
> > when CB is called you could map persistent overlay memory region
> > (PPI memory device) at address written by firmware.  
> 
> 
> > As for OperationRegion, you can instruct firmware to patch address
> > in AML as well, using bios_linker_loader_add_pointer() linker command.
> > what I'd suggest is to use dedicated TPM SSDT table and
> > at its start put a DWORD/QWORD variable where address would be patched in
> > and then use that variable as argument to OperationRegion
> >
> >   ssdt = init_aml_allocator();
> >   ...
> >   addr_offset = table_data->len + build_append_named_dword(ssdt->buf, "PPIA");
> >   ...
> >   ... aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
> >                        aml_name("PPIA"), TPM_PPI_STRUCT_SIZE)
> >   ...
> >   bios_linker_loader_add_pointer(linker,
> >         ACPI_BUILD_TABLE_FILE, addr_offset, sizeof(uint32_t),
> >         TPM_PPI_MEM_FW_CFG_FILE, 0);
> >
> > This way both UEFI and Seabios would use the same implementation and
> > work the same way.  
> 
> Thanks for this sample code. Though it only applies to how they write 
> the base address for the OperationRegion() and not the behaviour of the 
> code when it comes to SMI versus non-SMI.
From what I've gathered from discussions around the topic
is that untrusted guest code writes request into PPI memory
and then FW on reboot should act on it somehow (i.e. ask
user a terminal id changes are ok).

So I'd say, SMI versus non-SMI is a separate issue and we shouldn't
mix it with how firmware allocates PPI memory.

> > aml_operation_region("TPPI",..., aml_name("PPIA"), ...)
> > might work but it's not per spec, so it would be better to add
> > an API similar to build_append_named_dword() but only for
> > OperationRegion() which would return its offset within the table.
> > And skip on intermediate dword variable.
> >
> >
> > Wrt migration, you'd need to migrate address where PPI memory device
> > is mapped and its memory region.  
> 
> So today the PPI memory device is located at some address A. If we 
> decided that we need to move it to address B due to a collision with 
> another device, then are we going to be able to update the ACPI 
> OperationRegion base address post-migration to move it from address A to 
> address B? Or should we refuse the migration ? Keeping it at address A 
> seems wrong due to the collision.
Rule of the thumb is that HW on src and dst must be the same,
which implies the same address.
Also note that firmware on src is in RAM and it's also
migrated including address it's allocated/reported to QEMU on src.
So above mismatch nor collision isn't possible.

Though, more important point is that QEMU doesn't have to
give a damn (besides migrating this values from src to dst and
remapping overlay PPI memory region at that address (PCI code does
similar thing for migrated BARs)) about where in RAM this address
is allocated (it's upto firmware and should eliminate cross
version QEMU migration issues).
On new boot dst could get a new firmware which might allocate
region somewhere else and it would still work if it's migrated
back to the old host.

> > As for giving up control to from OS (APCI) to firmware that would be
> > target depended, one could use writing to SMM port to trigger SMI
> > for example and something else in case of ARM or x86 SMM-less design.  
> 
> Though to support these different cases, we either need to be able to 
> generate different ACPI code or make it configurable via some sort of 
> register. If we cannot get rid of these flag to let the firmware for 
> example choose between non-SMI and SMI, then do we need to use the above 
> shown callback mechanisms to avoid a 2nd field that lets one choose the 
> base address of the PPI memory device?
I'm sorry this is long discussion across several threads and
I've lost track of SMI vs SMI-less issue.
Could you point it out to me or describe here again what's the
problem and why we would need one vs another.

> >>      Stefan
[...]
Stefan Berger Feb. 12, 2018, 6:45 p.m. UTC | #5
On 02/12/2018 12:52 PM, Igor Mammedov wrote:
> On Mon, 12 Feb 2018 11:44:16 -0500
> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
>
>> On 02/12/2018 09:27 AM, Igor Mammedov wrote:
>>> On Fri, 9 Feb 2018 15:19:31 -0500
>>> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
>>>   
>>>> On 01/16/2018 10:51 AM, Stefan Berger wrote:
>>>>> The TPM Physical Presence interface consists of an ACPI part, a shared
>>>>> memory part, and code in the firmware. Users can send messages to the
>>>>> firmware by writing a code into the shared memory through invoking the
>>>>> ACPI code. When a reboot happens, the firmware looks for the code and
>>>>> acts on it by sending sequences of commands to the TPM.
>>>>>
>>>>> This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
>>>>> assume that SMIs are necessary to use. It uses a similar datastructure for
>>>>> the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
>>>>> of it. I extended the shared memory data structure with an array of 256
>>>>> bytes, one for each code that could be implemented. The array contains
>>>>> flags describing the individual codes. This decouples the ACPI implementation
>>>>> from the firmware implementation.
>>>>>
>>>>> The underlying TCG specification is accessible from the following page.
>>>>>
>>>>> https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
>>>>>
>>>>> This patch implements version 1.30.
>>>> I have played around with this patch and some modifications to EDK2.
>>>> Though for EDK2 the question is whether to try to circumvent their
>>>> current implementation that uses SMM or use SMM. With this patch so far
>>>> I circumvent it, which is maybe not a good idea.
>>>>
>>>> The facts for EDK2's PPI:
>>>>
>>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM
>>>> via an SMI and the PPI code is written into a UEFI variable. For this
>>>> ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS
>>>> (via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at
>>>> that address. Once the machine is rebooted, UEFI reads the variable and
>>>> finds the PPI code and reacts to it.
>>>>
>>>>
>>>> The facts for SeaBIOS:
>>>> - we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to
>>>> this address. So we would have to use the PPI memory device's memory
>>>> region, which is currently at 0xFED4 5000. SeaBIOS doesn't have
>>>> persistent memory like NVRAM where it stores varaibles. So to pass the
>>>> PPI code here the OS would invoke ACPI, which in turn would write the
>>>> PPI code into the PPI memory directly. Upon reboot SeaBIOS would find
>>>> the PPI code in the PPI memory device and react to it.
>>>>
>>>> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes
>>>> are used by the OperationRegion() in this patch series. The rest was
>>>> thought of for future extensions.
>>>>
>>>> To allow both firmwares to use PPI, we would need to be able to have the
>>>> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and
>>>> 0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve
>>>> this would be to have the firmwares choose their operation region base
>>>> address by writing into the PPI memory device at offset 0x200 (for
>>>> example). A '1' there would mean that we want the OperationRegion() at
>>>> 0xFFFF 0000, and a '2' would mean at the base address of the PPI device
>>>> (0xFED4 5000). This could be achieved by declaring a 2nd
>>>> OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we
>>>> declare that 1st OperationRegion()'s address based on findings from
>>>> there. Further, a flags variable at offset 0x201 could indicate whether
>>>> an SMI is needed to enter SMM or not. Obviously, the ACPI code would
>>>> become more complex to be able to support both firmwares at the same time.
>>>> This is a lot of details but I rather post this description before
>>>> posting more patches. To make these changes and get it to work with at
>>>> least SeaBIOS is probably fairly easy. But is this acceptable?
>>> You could use hw/acpi/vmgenid.c as example,
>>>
>>> use following command to instruct firmware to write address back to QEMU:
>>>
>>>       bios_linker_loader_write_pointer(linker,
>>>           TMP_PPI_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
>>>           TPM_PPI_MEM_FW_CFG_FILE, TPM_PPI_MEM_OFFSET);
>>>
>>> then when address is written into fwcfg, a callback in QEMU would be called due to
>>>
>>>       /* Create a read-write fw_cfg file for Address */
>>>       fw_cfg_add_file_callback(s, TPM_PPI_ADDR_FW_CFG_FILE, ...);
>>>
>>> when CB is called you could map persistent overlay memory region
>>> (PPI memory device) at address written by firmware.
>>
>>> As for OperationRegion, you can instruct firmware to patch address
>>> in AML as well, using bios_linker_loader_add_pointer() linker command.
>>> what I'd suggest is to use dedicated TPM SSDT table and
>>> at its start put a DWORD/QWORD variable where address would be patched in
>>> and then use that variable as argument to OperationRegion
>>>
>>>    ssdt = init_aml_allocator();
>>>    ...
>>>    addr_offset = table_data->len + build_append_named_dword(ssdt->buf, "PPIA");
>>>    ...
>>>    ... aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
>>>                         aml_name("PPIA"), TPM_PPI_STRUCT_SIZE)
>>>    ...
>>>    bios_linker_loader_add_pointer(linker,
>>>          ACPI_BUILD_TABLE_FILE, addr_offset, sizeof(uint32_t),
>>>          TPM_PPI_MEM_FW_CFG_FILE, 0);
>>>
>>> This way both UEFI and Seabios would use the same implementation and
>>> work the same way.
>> Thanks for this sample code. Though it only applies to how they write
>> the base address for the OperationRegion() and not the behaviour of the
>> code when it comes to SMI versus non-SMI.
>  From what I've gathered from discussions around the topic
> is that untrusted guest code writes request into PPI memory
> and then FW on reboot should act on it somehow (i.e. ask
> user a terminal id changes are ok).
>
> So I'd say, SMI versus non-SMI is a separate issue and we shouldn't
> mix it with how firmware allocates PPI memory.
>
>>> aml_operation_region("TPPI",..., aml_name("PPIA"), ...)
>>> might work but it's not per spec, so it would be better to add
>>> an API similar to build_append_named_dword() but only for
>>> OperationRegion() which would return its offset within the table.
>>> And skip on intermediate dword variable.
>>>
>>>
>>> Wrt migration, you'd need to migrate address where PPI memory device
>>> is mapped and its memory region.
>> So today the PPI memory device is located at some address A. If we
>> decided that we need to move it to address B due to a collision with
>> another device, then are we going to be able to update the ACPI
>> OperationRegion base address post-migration to move it from address A to
>> address B? Or should we refuse the migration ? Keeping it at address A
>> seems wrong due to the collision.
> Rule of the thumb is that HW on src and dst must be the same,
> which implies the same address.

Following this we don't need to migrate the address, do we ? My current 
implementation doesn't and uses a constant to set the subregion.

+void tpm_ppi_init_io(TPMPPI *tpmppi, struct MemoryRegion *m, Object *obj)
+{
+    memory_region_init_io(&tpmppi->mmio, obj, &tpm_ppi_memory_ops,
+                          tpmppi, "tpm-ppi-mmio",
+                          TPM_PPI_ADDR_SIZE);
+
+    memory_region_add_subregion(m, TPM_PPI_ADDR_BASE, &tpmppi->mmio);
+}


Here TPM_PPI_ADDR_BASE is set to 0xfed4 5000. Is that wrong and would 
have to be dynamic?I must admit I haven't spent much thought on EDK2's 
0xffff 0000 area and how that would be handled. It may very well have to 
be yet another region because in the current PPI memory is more data 
stored than what EDK2 uses in the 0xffff 0000 region.

> Also note that firmware on src is in RAM and it's also
> migrated including address it's allocated/reported to QEMU on src.
> So above mismatch nor collision isn't possible.

> Though, more important point is that QEMU doesn't have to
> give a damn (besides migrating this values from src to dst and
> remapping overlay PPI memory region at that address (PCI code does
> similar thing for migrated BARs)) about where in RAM this address
> is allocated (it's upto firmware and should eliminate cross
> version QEMU migration issues).
> On new boot dst could get a new firmware which might allocate
> region somewhere else and it would still work if it's migrated
> back to the old host.

That it can get a region somewhere else would depend on 'what'? Would 
the firmware decide the address of the PPI device? So the call 
'memory_region_add_subregion(m, TPM_PPI_ADDR_BASE, &tpmppi->mmio)' above 
would have to be called by that callback ?

I don't understand the part why 'it would still work if it's migrated 
back to the old host.'  Maybe there's more flexibility with moving 
addresses between migrations than what I currently know how to 
implement. My understanding is that the base address of the PPI device 
cannot change and both source and destination QEMU need to have it at 
the same address. As I point out above, I am working with a hard coded 
address that may have to be changed (=edited in the .h file) if we ever 
were to detect a collision with another device.

>
>>> As for giving up control to from OS (APCI) to firmware that would be
>>> target depended, one could use writing to SMM port to trigger SMI
>>> for example and something else in case of ARM or x86 SMM-less design.
>> Though to support these different cases, we either need to be able to
>> generate different ACPI code or make it configurable via some sort of
>> register. If we cannot get rid of these flag to let the firmware for
>> example choose between non-SMI and SMI, then do we need to use the above
>> shown callback mechanisms to avoid a 2nd field that lets one choose the
>> base address of the PPI memory device?
> I'm sorry this is long discussion across several threads and
> I've lost track of SMI vs SMI-less issue.
> Could you point it out to me or describe here again what's the
> problem and why we would need one vs another.

See the explanation above. With SeaBIOS we wouldn't need SMM since we 
wouldn't write the data in anything else than PPI. EDK2 uses SMM to 
write the data into UEFI variables and ACPI would transition to it via 
that interrupt.
Kevin O'Connor Feb. 12, 2018, 7:45 p.m. UTC | #6
On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:
> I have played around with this patch and some modifications to EDK2. Though
> for EDK2 the question is whether to try to circumvent their current
> implementation that uses SMM or use SMM. With this patch so far I circumvent
> it, which is maybe not a good idea.
> 
> The facts for EDK2's PPI:
> 
> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
> SMM. This is declared in ACPI with an OperationRegion() at that address.
> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
> and reacts to it.

I'm a bit confused by this.  The top 1M of the first 4G of ram is
generally mapped to the flash device on real machines.  Indeed, this
is part of the mechanism used to boot an X86 machine - it starts
execution from flash at 0xfffffff0.  This is true even on modern
machines.

So, it seems strange that UEFI is pushing a code through a memory
device at 0xffff0000.  I can't see how that would be portable.  Are
you sure the memory write to 0xffff0000 is not just a trigger to
invoke the SMI?

It sounds as if the ultimate TPM interface that must be supported is
the ACPI DSDT methods.  Since you're crafting the DSDT table yourself,
why does there need to be two different backends - why can't the same
mechanism be used for both SeaBIOS and UEFI?  Is this because you are
looking to reuse TPM code already in UEFI that requires a specific
interface?

-Kevin
Stefan Berger Feb. 12, 2018, 8:17 p.m. UTC | #7
On 02/12/2018 02:45 PM, Kevin O'Connor wrote:
> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:
>> I have played around with this patch and some modifications to EDK2. Though
>> for EDK2 the question is whether to try to circumvent their current
>> implementation that uses SMM or use SMM. With this patch so far I circumvent
>> it, which is maybe not a good idea.
>>
>> The facts for EDK2's PPI:
>>
>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
>> SMM. This is declared in ACPI with an OperationRegion() at that address.
>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
>> and reacts to it.
> I'm a bit confused by this.  The top 1M of the first 4G of ram is
> generally mapped to the flash device on real machines.  Indeed, this
> is part of the mechanism used to boot an X86 machine - it starts
> execution from flash at 0xfffffff0.  This is true even on modern
> machines.
>
> So, it seems strange that UEFI is pushing a code through a memory
> device at 0xffff0000.  I can't see how that would be portable.  Are
> you sure the memory write to 0xffff0000 is not just a trigger to
> invoke the SMI?

I base this on the code here:

https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81

OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)

>
> It sounds as if the ultimate TPM interface that must be supported is
> the ACPI DSDT methods.  Since you're crafting the DSDT table yourself,
> why does there need to be two different backends - why can't the same
> mechanism be used for both SeaBIOS and UEFI?  Is this because you are
> looking to reuse TPM code already in UEFI that requires a specific
> interface?

UEFI uses SMM to write the PPI code into a UEFI variable that it then 
presumably stores back to NVRAM. With SeaBIOS I would go the path of 
having the ACPI code write the code in the OperationRegion() and leave 
it at that, so not invoke SMM. EDK2 also reads the result of the 
operation from a register that SMM uses to pass a return value through. 
We wouldn't need that for SeaBIOS.

    Stefan

>
> -Kevin
>
Kevin O'Connor Feb. 12, 2018, 8:46 p.m. UTC | #8
On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:
> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes are
> used by the OperationRegion() in this patch series. The rest was thought of
> for future extensions.
>
> To allow both firmwares to use PPI, we would need to be able to have the
> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and 0xFED4
> 5000 for SeaBIOS, per choice of the firmware. One way to achieve this would
> be to have the firmwares choose their operation region base address by
> writing into the PPI memory device at offset 0x200 (for example). A '1'
> there would mean that we want the OperationRegion() at 0xFFFF 0000, and a
> '2' would mean at the base address of the PPI device (0xFED4 5000). This
> could be achieved by declaring a 2nd OperationRegion() in the ACPI code that
> is located at 0xFED4 5200 and we declare that 1st OperationRegion()'s
> address based on findings from there. Further, a flags variable at offset
> 0x201 could indicate whether an SMI is needed to enter SMM or not.
> Obviously, the ACPI code would become more complex to be able to support
> both firmwares at the same time.
>
> This is a lot of details but I rather post this description before posting
> more patches. To make these changes and get it to work with at least SeaBIOS
> is probably fairly easy. But is this acceptable?

I'm not sure I fully understand the goals of the PPI interface.
Here's what I understand so far:

The TPM specs define some actions that are considered privileged.  An
example of this would be disabling the TPM itself.  In order to
prevent an attacker from performing these actions without
authorization, the TPM specs define a mechanism to assert "physical
presence" before the privileged action can be done.  They do this by
having the firmware present a menu during early boot that permits
these privileged operations, and then the firmware locks the TPM chip
so the actions can no longer be done by any software that runs after
the firmware.  Thus "physical presence" is asserted by demonstrating
one has console access to the machine during early boot.

The PPI spec implements a work around for this - presumably some found
the enforcement mechanism too onerous.  It allows the OS to provide a
request code to the firmware, and on the next boot the firmware will
take the requested action before it locks the chip.  Thus allowing the
OS to indirectly perform the privileged action even after the chip has
been locked.  Thus, the PPI system seems to be an "elaborate hack" to
allow users to circumvent the physical presence mechanism (if they
choose to).

Here's what I understand the proposed implementation involves:

1 - in addition to emulating the TPM device itself, QEMU will also
    introduce a virtual memory device with 0x400 bytes.

2 - on first boot the firmware (seabios and uefi) will populate the
    memory region created in step 1.  In particular it will fill an
    array with the list of request codes it supports.  (Each request
    is an 8bit value, the array has 256 entries.)

3 - QEMU will produce AML code implementing the standard PPI ACPI
    interface.  This AML code will take the request, find the table
    produced in step 1, compare it to the list of accepted requests
    produced in step 2, and then place the 8bit request in another
    qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).

4 - the OS will signal a reboot, qemu will do its normal reboot logic,
    and the firmware will be run again.

5 - the firmware will extract the code written in stage 3, and if the
    tpm device has been configured to accept PPI codes from the OS, it
    will invoke the requested action.

Did I understand the above correctly?

Separately, is there someone clamoring for PPI support today?  That
is, is the goal to implement PPI to make QEMU more spec compliant, or
is there someone struggling to perform a particular task today that
this support would improve?

Thanks,
-Kevin
Stefan Berger Feb. 12, 2018, 8:49 p.m. UTC | #9
On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:
>> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes are
>> used by the OperationRegion() in this patch series. The rest was thought of
>> for future extensions.
>>
>> To allow both firmwares to use PPI, we would need to be able to have the
>> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and 0xFED4
>> 5000 for SeaBIOS, per choice of the firmware. One way to achieve this would
>> be to have the firmwares choose their operation region base address by
>> writing into the PPI memory device at offset 0x200 (for example). A '1'
>> there would mean that we want the OperationRegion() at 0xFFFF 0000, and a
>> '2' would mean at the base address of the PPI device (0xFED4 5000). This
>> could be achieved by declaring a 2nd OperationRegion() in the ACPI code that
>> is located at 0xFED4 5200 and we declare that 1st OperationRegion()'s
>> address based on findings from there. Further, a flags variable at offset
>> 0x201 could indicate whether an SMI is needed to enter SMM or not.
>> Obviously, the ACPI code would become more complex to be able to support
>> both firmwares at the same time.
>>
>> This is a lot of details but I rather post this description before posting
>> more patches. To make these changes and get it to work with at least SeaBIOS
>> is probably fairly easy. But is this acceptable?
> I'm not sure I fully understand the goals of the PPI interface.
> Here's what I understand so far:
>
> The TPM specs define some actions that are considered privileged.  An
> example of this would be disabling the TPM itself.  In order to
> prevent an attacker from performing these actions without
> authorization, the TPM specs define a mechanism to assert "physical
> presence" before the privileged action can be done.  They do this by
> having the firmware present a menu during early boot that permits
> these privileged operations, and then the firmware locks the TPM chip
> so the actions can no longer be done by any software that runs after
> the firmware.  Thus "physical presence" is asserted by demonstrating
> one has console access to the machine during early boot.
>
> The PPI spec implements a work around for this - presumably some found
> the enforcement mechanism too onerous.  It allows the OS to provide a
> request code to the firmware, and on the next boot the firmware will
> take the requested action before it locks the chip.  Thus allowing the
> OS to indirectly perform the privileged action even after the chip has
> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
> allow users to circumvent the physical presence mechanism (if they
> choose to).

Correct.
>
> Here's what I understand the proposed implementation involves:
>
> 1 - in addition to emulating the TPM device itself, QEMU will also
>      introduce a virtual memory device with 0x400 bytes.
Correct.
>
> 2 - on first boot the firmware (seabios and uefi) will populate the
>      memory region created in step 1.  In particular it will fill an
>      array with the list of request codes it supports.  (Each request
>      is an 8bit value, the array has 256 entries.)
Correct. Each firmware would fill out the 256 byte array depending on 
what it supports. The 8 bit values are basically flags and so on.
> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>      interface.  This AML code will take the request, find the table
>      produced in step 1, compare it to the list of accepted requests
>      produced in step 2, and then place the 8bit request in another
>      qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).

Correct.

Now EDK2 wants to store the code in a UEFI variable in NVRAM. We 
therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to 
do this.

> 4 - the OS will signal a reboot, qemu will do its normal reboot logic,
>      and the firmware will be run again.
>
> 5 - the firmware will extract the code written in stage 3, and if the
>      tpm device has been configured to accept PPI codes from the OS, it
>      will invoke the requested action.

SeaBIOS would look into memory to find the code. EDK2 will read the code 
from a UEFI variable.

> Did I understand the above correctly?
I think so. With the fine differences between SeaBIOS and EDK2 pointed out.

> Separately, is there someone clamoring for PPI support today?  That
> is, is the goal to implement PPI to make QEMU more spec compliant, or
> is there someone struggling to perform a particular task today that
> this support would improve?

We could defer the implementation of this. My main goal was to to 
support TPM migration in QEMU and the PPI device also needs to be 
migrated as part of TPM migration. So that's why I am looking at PPI now.

     Stefan
> Thanks,
> -Kevin
>
Igor Mammedov Feb. 13, 2018, 12:50 p.m. UTC | #10
On Mon, 12 Feb 2018 13:45:17 -0500
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:

> On 02/12/2018 12:52 PM, Igor Mammedov wrote:
> > On Mon, 12 Feb 2018 11:44:16 -0500
> > Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> >  
> >> On 02/12/2018 09:27 AM, Igor Mammedov wrote:  
> >>> On Fri, 9 Feb 2018 15:19:31 -0500
> >>> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> >>>     
> >>>> On 01/16/2018 10:51 AM, Stefan Berger wrote:  
> >>>>> The TPM Physical Presence interface consists of an ACPI part, a shared
> >>>>> memory part, and code in the firmware. Users can send messages to the
> >>>>> firmware by writing a code into the shared memory through invoking the
> >>>>> ACPI code. When a reboot happens, the firmware looks for the code and
> >>>>> acts on it by sending sequences of commands to the TPM.
> >>>>>
> >>>>> This patch adds the ACPI code. It is similar to the one in EDK2 but doesn't
> >>>>> assume that SMIs are necessary to use. It uses a similar datastructure for
> >>>>> the shared memory as EDK2 does so that EDK2 and SeaBIOS could both make use
> >>>>> of it. I extended the shared memory data structure with an array of 256
> >>>>> bytes, one for each code that could be implemented. The array contains
> >>>>> flags describing the individual codes. This decouples the ACPI implementation
> >>>>> from the firmware implementation.
> >>>>>
> >>>>> The underlying TCG specification is accessible from the following page.
> >>>>>
> >>>>> https://trustedcomputinggroup.org/tcg-physical-presence-interface-specification/
> >>>>>
> >>>>> This patch implements version 1.30.  
> >>>> I have played around with this patch and some modifications to EDK2.
> >>>> Though for EDK2 the question is whether to try to circumvent their
> >>>> current implementation that uses SMM or use SMM. With this patch so far
> >>>> I circumvent it, which is maybe not a good idea.
> >>>>
> >>>> The facts for EDK2's PPI:
> >>>>
> >>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM
> >>>> via an SMI and the PPI code is written into a UEFI variable. For this
> >>>> ACPI uses the memory are at 0xFFFF 0000 to pass parameters from the OS
> >>>> (via ACPI) to SMM. This is declared in ACPI with an OperationRegion() at
> >>>> that address. Once the machine is rebooted, UEFI reads the variable and
> >>>> finds the PPI code and reacts to it.
> >>>>
> >>>>
> >>>> The facts for SeaBIOS:
> >>>> - we cannot use the area at 0xFFFF 0000 since SeaBIOS is also mapped to
> >>>> this address. So we would have to use the PPI memory device's memory
> >>>> region, which is currently at 0xFED4 5000. SeaBIOS doesn't have
> >>>> persistent memory like NVRAM where it stores varaibles. So to pass the
> >>>> PPI code here the OS would invoke ACPI, which in turn would write the
> >>>> PPI code into the PPI memory directly. Upon reboot SeaBIOS would find
> >>>> the PPI code in the PPI memory device and react to it.
> >>>>
> >>>> The PPI device in this patch series allocates 0x400 bytes. 0x200 bytes
> >>>> are used by the OperationRegion() in this patch series. The rest was
> >>>> thought of for future extensions.
> >>>>
> >>>> To allow both firmwares to use PPI, we would need to be able to have the
> >>>> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and
> >>>> 0xFED4 5000 for SeaBIOS, per choice of the firmware. One way to achieve
> >>>> this would be to have the firmwares choose their operation region base
> >>>> address by writing into the PPI memory device at offset 0x200 (for
> >>>> example). A '1' there would mean that we want the OperationRegion() at
> >>>> 0xFFFF 0000, and a '2' would mean at the base address of the PPI device
> >>>> (0xFED4 5000). This could be achieved by declaring a 2nd
> >>>> OperationRegion() in the ACPI code that is located at 0xFED4 5200 and we
> >>>> declare that 1st OperationRegion()'s address based on findings from
> >>>> there. Further, a flags variable at offset 0x201 could indicate whether
> >>>> an SMI is needed to enter SMM or not. Obviously, the ACPI code would
> >>>> become more complex to be able to support both firmwares at the same time.
> >>>> This is a lot of details but I rather post this description before
> >>>> posting more patches. To make these changes and get it to work with at
> >>>> least SeaBIOS is probably fairly easy. But is this acceptable?  
> >>> You could use hw/acpi/vmgenid.c as example,
> >>>
> >>> use following command to instruct firmware to write address back to QEMU:
> >>>
> >>>       bios_linker_loader_write_pointer(linker,
> >>>           TMP_PPI_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
> >>>           TPM_PPI_MEM_FW_CFG_FILE, TPM_PPI_MEM_OFFSET);
> >>>
> >>> then when address is written into fwcfg, a callback in QEMU would be called due to
> >>>
> >>>       /* Create a read-write fw_cfg file for Address */
> >>>       fw_cfg_add_file_callback(s, TPM_PPI_ADDR_FW_CFG_FILE, ...);
> >>>
> >>> when CB is called you could map persistent overlay memory region
> >>> (PPI memory device) at address written by firmware.  
> >>  
> >>> As for OperationRegion, you can instruct firmware to patch address
> >>> in AML as well, using bios_linker_loader_add_pointer() linker command.
> >>> what I'd suggest is to use dedicated TPM SSDT table and
> >>> at its start put a DWORD/QWORD variable where address would be patched in
> >>> and then use that variable as argument to OperationRegion
> >>>
> >>>    ssdt = init_aml_allocator();
> >>>    ...
> >>>    addr_offset = table_data->len + build_append_named_dword(ssdt->buf, "PPIA");
> >>>    ...
> >>>    ... aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
> >>>                         aml_name("PPIA"), TPM_PPI_STRUCT_SIZE)
> >>>    ...
> >>>    bios_linker_loader_add_pointer(linker,
> >>>          ACPI_BUILD_TABLE_FILE, addr_offset, sizeof(uint32_t),
> >>>          TPM_PPI_MEM_FW_CFG_FILE, 0);
> >>>
> >>> This way both UEFI and Seabios would use the same implementation and
> >>> work the same way.  
> >> Thanks for this sample code. Though it only applies to how they write
> >> the base address for the OperationRegion() and not the behaviour of the
> >> code when it comes to SMI versus non-SMI.  
> >  From what I've gathered from discussions around the topic
> > is that untrusted guest code writes request into PPI memory
> > and then FW on reboot should act on it somehow (i.e. ask
> > user a terminal id changes are ok).
> >
> > So I'd say, SMI versus non-SMI is a separate issue and we shouldn't
> > mix it with how firmware allocates PPI memory.
> >  
> >>> aml_operation_region("TPPI",..., aml_name("PPIA"), ...)
> >>> might work but it's not per spec, so it would be better to add
> >>> an API similar to build_append_named_dword() but only for
> >>> OperationRegion() which would return its offset within the table.
> >>> And skip on intermediate dword variable.
> >>>
> >>>
> >>> Wrt migration, you'd need to migrate address where PPI memory device
> >>> is mapped and its memory region.  
> >> So today the PPI memory device is located at some address A. If we
> >> decided that we need to move it to address B due to a collision with
> >> another device, then are we going to be able to update the ACPI
> >> OperationRegion base address post-migration to move it from address A to
> >> address B? Or should we refuse the migration ? Keeping it at address A
> >> seems wrong due to the collision.  
> > Rule of the thumb is that HW on src and dst must be the same,
> > which implies the same address.  
> 
> Following this we don't need to migrate the address, do we ? My current 
> implementation doesn't and uses a constant to set the subregion.
see below why migrating address might be necessary
 
> +void tpm_ppi_init_io(TPMPPI *tpmppi, struct MemoryRegion *m, Object *obj)
> +{
> +    memory_region_init_io(&tpmppi->mmio, obj, &tpm_ppi_memory_ops,
> +                          tpmppi, "tpm-ppi-mmio",
> +                          TPM_PPI_ADDR_SIZE);
> +
> +    memory_region_add_subregion(m, TPM_PPI_ADDR_BASE, &tpmppi->mmio);
In QEMU typically it's not acceptable for device to map itself at some
fixed address. (but there are exceptions). Usually mapping in
common address space is done by board that uses device.

> +}
> 
> 
> Here TPM_PPI_ADDR_BASE is set to 0xfed4 5000. Is that wrong and would 
> have to be dynamic?I must admit I haven't spent much thought on EDK2's 
> 0xffff 0000 area and how that would be handled. It may very well have to 
> be yet another region because in the current PPI memory is more data 
> stored than what EDK2 uses in the 0xffff 0000 region.
If you in advance take in account that address might be different
depending on target/board/FW kind or even version. Starting
with flexible allocation makes more sense, since one won't have
to redo implementation once something else beside SeaBIOS
needed to be supported. And there won't be need to introduce
negotiation mechanism on top to allow FW specify which of fixed
addresses to use. With all this building up, a simplistic
fixed address impl. becomes rather complex.

> > Also note that firmware on src is in RAM and it's also
> > migrated including address it's allocated/reported to QEMU on src.
> > So above mismatch nor collision isn't possible.  
> 
> > Though, more important point is that QEMU doesn't have to
> > give a damn (besides migrating this values from src to dst and
> > remapping overlay PPI memory region at that address (PCI code does
> > similar thing for migrated BARs)) about where in RAM this address
> > is allocated (it's upto firmware and should eliminate cross
> > version QEMU migration issues).
> > On new boot dst could get a new firmware which might allocate
> > region somewhere else and it would still work if it's migrated
> > back to the old host.  
> 
> That it can get a region somewhere else would depend on 'what'? Would 
> the firmware decide the address of the PPI device?
Yes, firmware would decide where to put it
For example EDK could write 0xffff0000 into fwcgf file
while SeaBIOS could write there something else.

If bios_linker API is used to allocate region, then that address
is taken from RAM address space that firmware reserves for ACPI
tables (i.e. it doesn't conflict with anything and won't be used
by guest OS as it's reported as reserved in E802 table).

> So the call 
> 'memory_region_add_subregion(m, TPM_PPI_ADDR_BASE, &tpmppi->mmio)' above 
> would have to be called by that callback ?
the callback, attached to file, will map PPI device memory
at written address:
  memory_region_add_subregion(m, fw_reported_ppi_addr, &tpmppi->mmio)

> I don't understand the part why 'it would still work if it's migrated 
> back to the old host.'  Maybe there's more flexibility with moving 
> addresses between migrations than what I currently know how to 
> implement. My understanding is that the base address of the PPI device 
> cannot change and both source and destination QEMU need to have it at 
> the same address. As I point out above, I am working with a hard coded 
> address that may have to be changed (=edited in the .h file) if we ever 
> were to detect a collision with another device.
And that changing 'fixed' address is the problem in case of
cross version migrations, as fixed address implies lockstep
update in both QEMU and firmware.
In case firmware with OLD constant is migrated to newer QEMU
with another address and vice verse, things will break.
Then to overcome issue you would have to migrate address as
device state anyway (but that would work only one way since
older QEMU with simple fixed address didn't have a clue about
it, plus other surrounding compat glue for supporting
old/new/other firmware).

If you go dynamic approach from the begging, you'll have
address as migrated state from the starters and QEMU won't
have any fixed address built in so there won't be any
dependency between QEMU and firmware. It won't matter
whether it's old or new QEMU is used, as firmware decides
how to program TMP/PPI device (allocates address from its
free address pool).

Considering you already have 2 different addresses depending
on firmware and there might be 3rd when ARM comes in
picture, I'm trying to persuade you to use dynamic approach
from the beginning to avoid compatibility/migration issues
in future.

Maybe instead of rewriting PPI series again, you could
just start with a simple prototype that would implement
this allocation logic in QEMU/SeaBIOS/EDK. And once that
is settled, fill it in with TPM specific logic.

PS:
I can guide you wrt usage of bios_liker API if hw/acpi/vmgenid.c
isn't enough of example.

> >>> As for giving up control to from OS (APCI) to firmware that would be
> >>> target depended, one could use writing to SMM port to trigger SMI
> >>> for example and something else in case of ARM or x86 SMM-less design.  
> >> Though to support these different cases, we either need to be able to
> >> generate different ACPI code or make it configurable via some sort of
> >> register. If we cannot get rid of these flag to let the firmware for
> >> example choose between non-SMI and SMI, then do we need to use the above
> >> shown callback mechanisms to avoid a 2nd field that lets one choose the
> >> base address of the PPI memory device?  
> > I'm sorry this is long discussion across several threads and
> > I've lost track of SMI vs SMI-less issue.
> > Could you point it out to me or describe here again what's the
> > problem and why we would need one vs another.  
> 
> See the explanation above. With SeaBIOS we wouldn't need SMM since we 
> wouldn't write the data in anything else than PPI. EDK2 uses SMM to 
> write the data into UEFI variables and ACPI would transition to it via 
> that interrupt.
I think Kevin did nice sum up of design.
Igor Mammedov Feb. 13, 2018, 12:57 p.m. UTC | #11
On Mon, 12 Feb 2018 15:17:21 -0500
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:

> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:
> > On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:  
> >> I have played around with this patch and some modifications to EDK2. Though
> >> for EDK2 the question is whether to try to circumvent their current
> >> implementation that uses SMM or use SMM. With this patch so far I circumvent
> >> it, which is maybe not a good idea.
> >>
> >> The facts for EDK2's PPI:
> >>
> >> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
> >> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
> >> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
> >> SMM. This is declared in ACPI with an OperationRegion() at that address.
> >> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
> >> and reacts to it.  
> > I'm a bit confused by this.  The top 1M of the first 4G of ram is
> > generally mapped to the flash device on real machines.  Indeed, this
> > is part of the mechanism used to boot an X86 machine - it starts
> > execution from flash at 0xfffffff0.  This is true even on modern
> > machines.
> >
> > So, it seems strange that UEFI is pushing a code through a memory
> > device at 0xffff0000.  I can't see how that would be portable.  Are
> > you sure the memory write to 0xffff0000 is not just a trigger to
> > invoke the SMI?  
> 
> I base this on the code here:
> 
> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
> 
> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)
Is the goal to reuse EDK PPI impl. including ASL?

If it's so, then perhaps we only need to write address into QEMU
and let OVMF to discard PPI SSDT from QEMU.

>     Stefan
> 
> >
> > -Kevin
> >  
> 
>
Laszlo Ersek Feb. 13, 2018, 1:31 p.m. UTC | #12
On 02/13/18 13:57, Igor Mammedov wrote:
> On Mon, 12 Feb 2018 15:17:21 -0500
> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> 
>> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:
>>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:  
>>>> I have played around with this patch and some modifications to EDK2. Though
>>>> for EDK2 the question is whether to try to circumvent their current
>>>> implementation that uses SMM or use SMM. With this patch so far I circumvent
>>>> it, which is maybe not a good idea.
>>>>
>>>> The facts for EDK2's PPI:
>>>>
>>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
>>>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
>>>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
>>>> SMM. This is declared in ACPI with an OperationRegion() at that address.
>>>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
>>>> and reacts to it.  
>>> I'm a bit confused by this.  The top 1M of the first 4G of ram is
>>> generally mapped to the flash device on real machines.  Indeed, this
>>> is part of the mechanism used to boot an X86 machine - it starts
>>> execution from flash at 0xfffffff0.  This is true even on modern
>>> machines.
>>>
>>> So, it seems strange that UEFI is pushing a code through a memory
>>> device at 0xffff0000.  I can't see how that would be portable.  Are
>>> you sure the memory write to 0xffff0000 is not just a trigger to
>>> invoke the SMI?  
>>
>> I base this on the code here:
>>
>> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
>>
>> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)
> Is the goal to reuse EDK PPI impl. including ASL?

Right, that is one important question. Some code in edk2 is meant only
as example code (so actual firmware platforms are encouraged to copy &
customize the code, or use similar or dissimilar alternatives). Some
modules are meant for inclusion "as-is" in the platform description
files (~ makefiles). There are so many edk2 modules related to TPM
(several versions of the specs even) that it's hard to say what is meant
for what usage type. (By my earlier count, there are 19 modules.)

> If it's so, then perhaps we only need to write address into QEMU
> and let OVMF to discard PPI SSDT from QEMU.

That's something I would not like. When running on QEMU, it's OK for
some edk2 modules to install their own ACPI tables, but only if they are
"software" tables, such as the IBFT for iSCSI booting, BGRT (boot
graphics table) for the boot logo / splash screen, etc. For ACPI tables
that describe hardware (data tables) or carry out actions related to
hardware (DefinitionBlocks / AML), I much prefer QEMU to generate all
the stuff.

If there is a conflict between hardware-related tables that QEMU
generates and similar tables that pre-exist in edk2, I prefer working
with the edk2 maintainers to customize their subsystems, so that a
concrete firmware platform, such as OvmfPkg and ArmVirtPkg, can
conditionalize / exclude those preexistent ACPI tables, while benefiting
from the rest of the code. Then the ACPI linker/loader client used in
both OvmfPkg and ArmVirtPkg can remain dumb and process & expose
whatever tables QEMU generates.

We could control the AML payload for TPM and/or TPM PPI -- e.g. whether
it should raise an SMI -- via "-device tpm2,smi=on", for example. (Just
making up the syntax, but you know what I mean.)

We could go one step further, "-device tpm2,acpi=[on|off]". acpi=on
would make QEMU generate the TPM related tables (perhaps targeting only
SeaBIOS, if that makes sense), acpi=off would leave the related tables
to the firmware.

This is just speculation on my part; the point is I'd like to avoid any
more "intelligent filtering" regarding ACPI tables in OVMF. What we have
there is terrible enough already.

Thanks
Laszlo
Igor Mammedov Feb. 13, 2018, 2:17 p.m. UTC | #13
On Tue, 13 Feb 2018 14:31:41 +0100
Laszlo Ersek <lersek@redhat.com> wrote:

> On 02/13/18 13:57, Igor Mammedov wrote:
> > On Mon, 12 Feb 2018 15:17:21 -0500
> > Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> >   
> >> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:  
> >>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:    
> >>>> I have played around with this patch and some modifications to EDK2. Though
> >>>> for EDK2 the question is whether to try to circumvent their current
> >>>> implementation that uses SMM or use SMM. With this patch so far I circumvent
> >>>> it, which is maybe not a good idea.
> >>>>
> >>>> The facts for EDK2's PPI:
> >>>>
> >>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
> >>>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
> >>>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
> >>>> SMM. This is declared in ACPI with an OperationRegion() at that address.
> >>>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
> >>>> and reacts to it.    
> >>> I'm a bit confused by this.  The top 1M of the first 4G of ram is
> >>> generally mapped to the flash device on real machines.  Indeed, this
> >>> is part of the mechanism used to boot an X86 machine - it starts
> >>> execution from flash at 0xfffffff0.  This is true even on modern
> >>> machines.
> >>>
> >>> So, it seems strange that UEFI is pushing a code through a memory
> >>> device at 0xffff0000.  I can't see how that would be portable.  Are
> >>> you sure the memory write to 0xffff0000 is not just a trigger to
> >>> invoke the SMI?    
> >>
> >> I base this on the code here:
> >>
> >> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
> >>
> >> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)  
> > Is the goal to reuse EDK PPI impl. including ASL?  
> 
> Right, that is one important question. Some code in edk2 is meant only
> as example code (so actual firmware platforms are encouraged to copy &
> customize the code, or use similar or dissimilar alternatives). Some
> modules are meant for inclusion "as-is" in the platform description
> files (~ makefiles). There are so many edk2 modules related to TPM
> (several versions of the specs even) that it's hard to say what is meant
> for what usage type. (By my earlier count, there are 19 modules.)
> 
> > If it's so, then perhaps we only need to write address into QEMU
> > and let OVMF to discard PPI SSDT from QEMU.  
> 
> That's something I would not like. When running on QEMU, it's OK for
> some edk2 modules to install their own ACPI tables, but only if they are
> "software" tables, such as the IBFT for iSCSI booting, BGRT (boot
> graphics table) for the boot logo / splash screen, etc. For ACPI tables
> that describe hardware (data tables) or carry out actions related to
> hardware (DefinitionBlocks / AML), I much prefer QEMU to generate all
> the stuff.
> 
> If there is a conflict between hardware-related tables that QEMU
> generates and similar tables that pre-exist in edk2, I prefer working
> with the edk2 maintainers to customize their subsystems, so that a
> concrete firmware platform, such as OvmfPkg and ArmVirtPkg, can
> conditionalize / exclude those preexistent ACPI tables, while benefiting
> from the rest of the code. Then the ACPI linker/loader client used in
> both OvmfPkg and ArmVirtPkg can remain dumb and process & expose
> whatever tables QEMU generates.
> 
> We could control the AML payload for TPM and/or TPM PPI -- e.g. whether
> it should raise an SMI -- via "-device tpm2,smi=on", for example. (Just
> making up the syntax, but you know what I mean.)
> 
> We could go one step further, "-device tpm2,acpi=[on|off]". acpi=on
> would make QEMU generate the TPM related tables (perhaps targeting only
> SeaBIOS, if that makes sense), acpi=off would leave the related tables
> to the firmware.
> 
> This is just speculation on my part; the point is I'd like to avoid any
> more "intelligent filtering" regarding ACPI tables in OVMF. What we have
> there is terrible enough already.
If we could discard EDK's generated tables, it's fine as well,
but we would need to model TPM device model to match EDK's one
(risk here is that implementation goes of sync, if it's not spec
dictated). Then SeaBIOS side could 're-implement' it using
the same set of tables from QEMU.

I wonder if we could somehow detect firmware flavor we are
running on from ASL /wrt [no]using SMI/?
 * I don't really like idea but we can probably detect
   in QEMU if firmware is OVMF or not and generate extra SMI hunk
   based on that.
 * or we could always generate SMI code and probe for some
   EDK specific SSDT code to see in AML to enable it.
   Maybe OVMF could inject such table.

> 
> Thanks
> Laszlo
Laszlo Ersek Feb. 13, 2018, 3:39 p.m. UTC | #14
On 02/13/18 15:17, Igor Mammedov wrote:
> On Tue, 13 Feb 2018 14:31:41 +0100
> Laszlo Ersek <lersek@redhat.com> wrote:
> 
>> On 02/13/18 13:57, Igor Mammedov wrote:
>>> On Mon, 12 Feb 2018 15:17:21 -0500
>>> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
>>>   
>>>> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:  
>>>>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:    
>>>>>> I have played around with this patch and some modifications to EDK2. Though
>>>>>> for EDK2 the question is whether to try to circumvent their current
>>>>>> implementation that uses SMM or use SMM. With this patch so far I circumvent
>>>>>> it, which is maybe not a good idea.
>>>>>>
>>>>>> The facts for EDK2's PPI:
>>>>>>
>>>>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
>>>>>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
>>>>>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
>>>>>> SMM. This is declared in ACPI with an OperationRegion() at that address.
>>>>>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
>>>>>> and reacts to it.    
>>>>> I'm a bit confused by this.  The top 1M of the first 4G of ram is
>>>>> generally mapped to the flash device on real machines.  Indeed, this
>>>>> is part of the mechanism used to boot an X86 machine - it starts
>>>>> execution from flash at 0xfffffff0.  This is true even on modern
>>>>> machines.
>>>>>
>>>>> So, it seems strange that UEFI is pushing a code through a memory
>>>>> device at 0xffff0000.  I can't see how that would be portable.  Are
>>>>> you sure the memory write to 0xffff0000 is not just a trigger to
>>>>> invoke the SMI?    
>>>>
>>>> I base this on the code here:
>>>>
>>>> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
>>>>
>>>> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)  
>>> Is the goal to reuse EDK PPI impl. including ASL?  
>>
>> Right, that is one important question. Some code in edk2 is meant only
>> as example code (so actual firmware platforms are encouraged to copy &
>> customize the code, or use similar or dissimilar alternatives). Some
>> modules are meant for inclusion "as-is" in the platform description
>> files (~ makefiles). There are so many edk2 modules related to TPM
>> (several versions of the specs even) that it's hard to say what is meant
>> for what usage type. (By my earlier count, there are 19 modules.)
>>
>>> If it's so, then perhaps we only need to write address into QEMU
>>> and let OVMF to discard PPI SSDT from QEMU.  
>>
>> That's something I would not like. When running on QEMU, it's OK for
>> some edk2 modules to install their own ACPI tables, but only if they are
>> "software" tables, such as the IBFT for iSCSI booting, BGRT (boot
>> graphics table) for the boot logo / splash screen, etc. For ACPI tables
>> that describe hardware (data tables) or carry out actions related to
>> hardware (DefinitionBlocks / AML), I much prefer QEMU to generate all
>> the stuff.
>>
>> If there is a conflict between hardware-related tables that QEMU
>> generates and similar tables that pre-exist in edk2, I prefer working
>> with the edk2 maintainers to customize their subsystems, so that a
>> concrete firmware platform, such as OvmfPkg and ArmVirtPkg, can
>> conditionalize / exclude those preexistent ACPI tables, while benefiting
>> from the rest of the code. Then the ACPI linker/loader client used in
>> both OvmfPkg and ArmVirtPkg can remain dumb and process & expose
>> whatever tables QEMU generates.
>>
>> We could control the AML payload for TPM and/or TPM PPI -- e.g. whether
>> it should raise an SMI -- via "-device tpm2,smi=on", for example. (Just
>> making up the syntax, but you know what I mean.)
>>
>> We could go one step further, "-device tpm2,acpi=[on|off]". acpi=on
>> would make QEMU generate the TPM related tables (perhaps targeting only
>> SeaBIOS, if that makes sense), acpi=off would leave the related tables
>> to the firmware.
>>
>> This is just speculation on my part; the point is I'd like to avoid any
>> more "intelligent filtering" regarding ACPI tables in OVMF. What we have
>> there is terrible enough already.
> If we could discard EDK's generated tables, it's fine as well,
> but we would need to model TPM device model to match EDK's one
> (risk here is that implementation goes of sync, if it's not spec
> dictated). Then SeaBIOS side could 're-implement' it using
> the same set of tables from QEMU.
> 
> I wonder if we could somehow detect firmware flavor we are
> running on from ASL /wrt [no]using SMI/?
>  * I don't really like idea but we can probably detect
>    in QEMU if firmware is OVMF or not and generate extra SMI hunk
>    based on that.
>  * or we could always generate SMI code and probe for some
>    EDK specific SSDT code to see in AML to enable it.
>    Maybe OVMF could inject such table.

There are already several QEMU artifacts that are specific to OVMF, but
none of them is a full match for this purpose:

* "-machine smm=on" means that SMM emulation is enabled. However, this
is automatically done for Q35, and it happens regardless of firmware. So
basing the decision on this is no good.

* One (or much more frequently, two) pflash devices usually indicate
OVMF (as opposed to SeaBIOS, which is selected with -L, or with -bios,
or by default). However, the same pflash setup is appropriate for i440fx
machine types too, which lack SMM emulation.

* The "-global driver=cfi.pflash01,property=secure,value=on" flag means
that QEMU should restrict pflash write access to guest code that runs in
SMM. This is required for making the UEFI variable store actually
secure, but a user might have any reason for *not* specifying this flag
(while keeping SMM emulation generally enabled, with "-machine smm=on").
This looks like the closest match, but still not a 100% match.

* OVMF generally negotiates "SMI broadcast", but the user can prevent
that too on the QEMU command line.

... I really think SMM is a red herring here. We've established several
times that the PPI opcodes need no protection. SMM is an implementation
detail in edk2, and it is used *only* because there is no other way for
AML code to enter (invoke) the firmware. In other words, this is simply
a "firmware calling convention" for AML code -- prepare a request buffer
and raise an SMI.

A valid alternative would be if we provided native OS drivers for a new
ACPI device (both for Windows and Linux), and used Notify() in the
TPM-related AML. Then the ACPI code could call back into the native OS
driver, and *that* driver could then use the regular interface to the
UEFI variable services (for example), without having to care about SMM
explicitly. (Note that I'm not actually suggesting this; I'd just like
to show that SMM is accidental here, not inherent.)

Thanks
Laszlo
Laszlo Ersek Feb. 13, 2018, 4:16 p.m. UTC | #15
On 02/12/18 21:49, Stefan Berger wrote:
> On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:
>>> The PPI device in this patch series allocates 0x400 bytes. 0x200
>>> bytes are
>>> used by the OperationRegion() in this patch series. The rest was
>>> thought of
>>> for future extensions.
>>>
>>> To allow both firmwares to use PPI, we would need to be able to have the
>>> OperationRegion() be flexible and located at 0xFFFF 0000 for EDK2 and
>>> 0xFED4
>>> 5000 for SeaBIOS, per choice of the firmware. One way to achieve this
>>> would
>>> be to have the firmwares choose their operation region base address by
>>> writing into the PPI memory device at offset 0x200 (for example). A '1'
>>> there would mean that we want the OperationRegion() at 0xFFFF 0000,
>>> and a
>>> '2' would mean at the base address of the PPI device (0xFED4 5000). This
>>> could be achieved by declaring a 2nd OperationRegion() in the ACPI
>>> code that
>>> is located at 0xFED4 5200 and we declare that 1st OperationRegion()'s
>>> address based on findings from there. Further, a flags variable at
>>> offset
>>> 0x201 could indicate whether an SMI is needed to enter SMM or not.
>>> Obviously, the ACPI code would become more complex to be able to support
>>> both firmwares at the same time.
>>>
>>> This is a lot of details but I rather post this description before
>>> posting
>>> more patches. To make these changes and get it to work with at least
>>> SeaBIOS
>>> is probably fairly easy. But is this acceptable?
>> I'm not sure I fully understand the goals of the PPI interface.
>> Here's what I understand so far:
>>
>> The TPM specs define some actions that are considered privileged.  An
>> example of this would be disabling the TPM itself.  In order to
>> prevent an attacker from performing these actions without
>> authorization, the TPM specs define a mechanism to assert "physical
>> presence" before the privileged action can be done.  They do this by
>> having the firmware present a menu during early boot that permits
>> these privileged operations, and then the firmware locks the TPM chip
>> so the actions can no longer be done by any software that runs after
>> the firmware.  Thus "physical presence" is asserted by demonstrating
>> one has console access to the machine during early boot.
>>
>> The PPI spec implements a work around for this - presumably some found
>> the enforcement mechanism too onerous.  It allows the OS to provide a
>> request code to the firmware, and on the next boot the firmware will
>> take the requested action before it locks the chip.  Thus allowing the
>> OS to indirectly perform the privileged action even after the chip has
>> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
>> allow users to circumvent the physical presence mechanism (if they
>> choose to).
> 
> Correct.
>>
>> Here's what I understand the proposed implementation involves:
>>
>> 1 - in addition to emulating the TPM device itself, QEMU will also
>>      introduce a virtual memory device with 0x400 bytes.
> Correct.
>>
>> 2 - on first boot the firmware (seabios and uefi) will populate the
>>      memory region created in step 1.  In particular it will fill an
>>      array with the list of request codes it supports.  (Each request
>>      is an 8bit value, the array has 256 entries.)
> Correct. Each firmware would fill out the 256 byte array depending on
> what it supports. The 8 bit values are basically flags and so on.
>> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>>      interface.  This AML code will take the request, find the table
>>      produced in step 1, compare it to the list of accepted requests
>>      produced in step 2, and then place the 8bit request in another
>>      qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
> 
> Correct.
> 
> Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
> therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
> do this.
> 
>> 4 - the OS will signal a reboot, qemu will do its normal reboot logic,
>>      and the firmware will be run again.
>>
>> 5 - the firmware will extract the code written in stage 3, and if the
>>      tpm device has been configured to accept PPI codes from the OS, it
>>      will invoke the requested action.
> 
> SeaBIOS would look into memory to find the code. EDK2 will read the code
> from a UEFI variable.
> 
>> Did I understand the above correctly?
> I think so. With the fine differences between SeaBIOS and EDK2 pointed out.

Here's what I suggest:

Please everyone continue working on this, according to Kevin's &
Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
for now. Generate as much as possible AML in QEMU, using the
linker/loader. If you generate data tables, please don't forget about
the "OVMF SDT header probe suppressor" padding (see vmgenid for
example). Focus on basic TPM enablement (measurements, PCRs) and PPI.
Please ignore the "memory clear request" TPM feature for now.

Also, please focus on TPM2 only.

It's obvious we are unable to target both firmwares at once. So finalize
a *sensible* hardware device model, and we'll drive it one way or
another from OVMF as a second step. "Sensible" means that it should be
dynamically detectable to the firmware, in some way; either via hardware
(MMIO) registers or fw_cfg.

Once everything is working with SeaBIOS and QEMU, and the Windows guest
is happy, I'll do my best to set time aside for the OVMF enablement. If
needed, I'll carve chunks out of the existing SecurityPkg modules and
implement the needed drivers and libraries under OvmfPkg. I fully intend
to remove both (a) the non-volatile UEFI variable storage for the PPI
opcodes, and (b) SMM, from the UEFI picture. There's no reason basic
TPM, and TPM PPI, shouldn't work under OVMF with i440fx, or (later)
under ArmVirtQemu with aarch64/"virt".

I've tried to avoid such major surgery under edk2, but I clearly see now
that there's no other way. Thus far I've lacked both commitment and time
for working on this; from now on I'm only lacking time. Please polish
*and document* the device model and get it 100% functional with SeaBIOS
and Windows; I'll do my best to take care of OVMF. Again, please focus
on measurements / PCRs and PPI, and ignore the "memory clear request"
for now. (I very much hope that Windows does not insist on the latter.)

It's possible that I'll scream for additional hw enlightement under OVMF
later; so I suggest designing in some kind of feature negotiation up-front.

Importantly, please make sure that the TPM *backend* works without using
the TPM hardware of the virtualization host; the backend should be a
full software emulator. Otherwise I won't be able to experiment with OVMF.

I don't want to ignore or reject work that's already been done for edk2
/ OVMF, but I'm still not seeing patches or discussion on edk2-devel.
Plus, as I mentioned above, I fully intend to kill the complicated
*accidental* artifacts, such as non-volatile UEFI variables for PPI
opcode storage, and the consequent SMI requirement for the AML->fw
calling convention. No such calls should be necessary in the first place.

Thanks
Laszlo

>> Separately, is there someone clamoring for PPI support today?  That
>> is, is the goal to implement PPI to make QEMU more spec compliant, or
>> is there someone struggling to perform a particular task today that
>> this support would improve?
> 
> We could defer the implementation of this. My main goal was to to
> support TPM migration in QEMU and the PPI device also needs to be
> migrated as part of TPM migration. So that's why I am looking at PPI now.
> 
>     Stefan
>> Thanks,
>> -Kevin
>>
> 
>
Igor Mammedov Feb. 13, 2018, 4:19 p.m. UTC | #16
On Tue, 13 Feb 2018 16:39:01 +0100
Laszlo Ersek <lersek@redhat.com> wrote:

> On 02/13/18 15:17, Igor Mammedov wrote:
> > On Tue, 13 Feb 2018 14:31:41 +0100
> > Laszlo Ersek <lersek@redhat.com> wrote:
> >   
> >> On 02/13/18 13:57, Igor Mammedov wrote:  
> >>> On Mon, 12 Feb 2018 15:17:21 -0500
> >>> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
> >>>     
> >>>> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:    
> >>>>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:      
> >>>>>> I have played around with this patch and some modifications to EDK2. Though
> >>>>>> for EDK2 the question is whether to try to circumvent their current
> >>>>>> implementation that uses SMM or use SMM. With this patch so far I circumvent
> >>>>>> it, which is maybe not a good idea.
> >>>>>>
> >>>>>> The facts for EDK2's PPI:
> >>>>>>
> >>>>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
> >>>>>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
> >>>>>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
> >>>>>> SMM. This is declared in ACPI with an OperationRegion() at that address.
> >>>>>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
> >>>>>> and reacts to it.      
> >>>>> I'm a bit confused by this.  The top 1M of the first 4G of ram is
> >>>>> generally mapped to the flash device on real machines.  Indeed, this
> >>>>> is part of the mechanism used to boot an X86 machine - it starts
> >>>>> execution from flash at 0xfffffff0.  This is true even on modern
> >>>>> machines.
> >>>>>
> >>>>> So, it seems strange that UEFI is pushing a code through a memory
> >>>>> device at 0xffff0000.  I can't see how that would be portable.  Are
> >>>>> you sure the memory write to 0xffff0000 is not just a trigger to
> >>>>> invoke the SMI?      
> >>>>
> >>>> I base this on the code here:
> >>>>
> >>>> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
> >>>>
> >>>> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)    
> >>> Is the goal to reuse EDK PPI impl. including ASL?    
> >>
> >> Right, that is one important question. Some code in edk2 is meant only
> >> as example code (so actual firmware platforms are encouraged to copy &
> >> customize the code, or use similar or dissimilar alternatives). Some
> >> modules are meant for inclusion "as-is" in the platform description
> >> files (~ makefiles). There are so many edk2 modules related to TPM
> >> (several versions of the specs even) that it's hard to say what is meant
> >> for what usage type. (By my earlier count, there are 19 modules.)
> >>  
> >>> If it's so, then perhaps we only need to write address into QEMU
> >>> and let OVMF to discard PPI SSDT from QEMU.    
> >>
> >> That's something I would not like. When running on QEMU, it's OK for
> >> some edk2 modules to install their own ACPI tables, but only if they are
> >> "software" tables, such as the IBFT for iSCSI booting, BGRT (boot
> >> graphics table) for the boot logo / splash screen, etc. For ACPI tables
> >> that describe hardware (data tables) or carry out actions related to
> >> hardware (DefinitionBlocks / AML), I much prefer QEMU to generate all
> >> the stuff.
> >>
> >> If there is a conflict between hardware-related tables that QEMU
> >> generates and similar tables that pre-exist in edk2, I prefer working
> >> with the edk2 maintainers to customize their subsystems, so that a
> >> concrete firmware platform, such as OvmfPkg and ArmVirtPkg, can
> >> conditionalize / exclude those preexistent ACPI tables, while benefiting
> >> from the rest of the code. Then the ACPI linker/loader client used in
> >> both OvmfPkg and ArmVirtPkg can remain dumb and process & expose
> >> whatever tables QEMU generates.
> >>
> >> We could control the AML payload for TPM and/or TPM PPI -- e.g. whether
> >> it should raise an SMI -- via "-device tpm2,smi=on", for example. (Just
> >> making up the syntax, but you know what I mean.)
> >>
> >> We could go one step further, "-device tpm2,acpi=[on|off]". acpi=on
> >> would make QEMU generate the TPM related tables (perhaps targeting only
> >> SeaBIOS, if that makes sense), acpi=off would leave the related tables
> >> to the firmware.
> >>
> >> This is just speculation on my part; the point is I'd like to avoid any
> >> more "intelligent filtering" regarding ACPI tables in OVMF. What we have
> >> there is terrible enough already.  
> > If we could discard EDK's generated tables, it's fine as well,
> > but we would need to model TPM device model to match EDK's one
> > (risk here is that implementation goes of sync, if it's not spec
> > dictated). Then SeaBIOS side could 're-implement' it using
> > the same set of tables from QEMU.
> > 
> > I wonder if we could somehow detect firmware flavor we are
> > running on from ASL /wrt [no]using SMI/?
> >  * I don't really like idea but we can probably detect
> >    in QEMU if firmware is OVMF or not and generate extra SMI hunk
> >    based on that.
> >  * or we could always generate SMI code and probe for some
> >    EDK specific SSDT code to see in AML to enable it.
> >    Maybe OVMF could inject such table.  
> 
> There are already several QEMU artifacts that are specific to OVMF, but
> none of them is a full match for this purpose:
> 
> * "-machine smm=on" means that SMM emulation is enabled. However, this
> is automatically done for Q35, and it happens regardless of firmware. So
> basing the decision on this is no good.
> 
> * One (or much more frequently, two) pflash devices usually indicate
> OVMF (as opposed to SeaBIOS, which is selected with -L, or with -bios,
> or by default). However, the same pflash setup is appropriate for i440fx
> machine types too, which lack SMM emulation.
> 
> * The "-global driver=cfi.pflash01,property=secure,value=on" flag means
> that QEMU should restrict pflash write access to guest code that runs in
> SMM. This is required for making the UEFI variable store actually
> secure, but a user might have any reason for *not* specifying this flag
> (while keeping SMM emulation generally enabled, with "-machine smm=on").
> This looks like the closest match, but still not a 100% match.
> 
> * OVMF generally negotiates "SMI broadcast", but the user can prevent
> that too on the QEMU command line.
> 
> ... I really think SMM is a red herring here. We've established several
> times that the PPI opcodes need no protection. SMM is an implementation
> detail in edk2, and it is used *only* because there is no other way for
> AML code to enter (invoke) the firmware. In other words, this is simply
> a "firmware calling convention" for AML code -- prepare a request buffer
> and raise an SMI.
> 
> A valid alternative would be if we provided native OS drivers for a new
> ACPI device (both for Windows and Linux), and used Notify() in the
> TPM-related AML. Then the ACPI code could call back into the native OS
> driver, and *that* driver could then use the regular interface to the
> UEFI variable services (for example), without having to care about SMM
> explicitly. (Note that I'm not actually suggesting this; I'd just like
> to show that SMM is accidental here, not inherent.)
Yep, relying on detecting SMM or pflash or ... is not really reliable.

What about my proposal for PPI enabled OVMF to inject an additional
SSDT table with a variable ex:
  firmware_ovmf = 1

then in QEMU generated tables we can probe for it in runtime
when OSPM executes method and trigger SMI if variable exists.


> 
> Thanks
> Laszlo
Laszlo Ersek Feb. 13, 2018, 4:34 p.m. UTC | #17
On 02/13/18 17:19, Igor Mammedov wrote:
> On Tue, 13 Feb 2018 16:39:01 +0100
> Laszlo Ersek <lersek@redhat.com> wrote:
> 
>> On 02/13/18 15:17, Igor Mammedov wrote:
>>> On Tue, 13 Feb 2018 14:31:41 +0100
>>> Laszlo Ersek <lersek@redhat.com> wrote:
>>>   
>>>> On 02/13/18 13:57, Igor Mammedov wrote:  
>>>>> On Mon, 12 Feb 2018 15:17:21 -0500
>>>>> Stefan Berger <stefanb@linux.vnet.ibm.com> wrote:
>>>>>     
>>>>>> On 02/12/2018 02:45 PM, Kevin O'Connor wrote:    
>>>>>>> On Fri, Feb 09, 2018 at 03:19:31PM -0500, Stefan Berger wrote:      
>>>>>>>> I have played around with this patch and some modifications to EDK2. Though
>>>>>>>> for EDK2 the question is whether to try to circumvent their current
>>>>>>>> implementation that uses SMM or use SMM. With this patch so far I circumvent
>>>>>>>> it, which is maybe not a good idea.
>>>>>>>>
>>>>>>>> The facts for EDK2's PPI:
>>>>>>>>
>>>>>>>> - from within the OS a PPI code is submitted to ACPI and ACPI enters SMM via
>>>>>>>> an SMI and the PPI code is written into a UEFI variable. For this ACPI uses
>>>>>>>> the memory are at 0xFFFF 0000 to pass parameters from the OS (via ACPI) to
>>>>>>>> SMM. This is declared in ACPI with an OperationRegion() at that address.
>>>>>>>> Once the machine is rebooted, UEFI reads the variable and finds the PPI code
>>>>>>>> and reacts to it.      
>>>>>>> I'm a bit confused by this.  The top 1M of the first 4G of ram is
>>>>>>> generally mapped to the flash device on real machines.  Indeed, this
>>>>>>> is part of the mechanism used to boot an X86 machine - it starts
>>>>>>> execution from flash at 0xfffffff0.  This is true even on modern
>>>>>>> machines.
>>>>>>>
>>>>>>> So, it seems strange that UEFI is pushing a code through a memory
>>>>>>> device at 0xffff0000.  I can't see how that would be portable.  Are
>>>>>>> you sure the memory write to 0xffff0000 is not just a trigger to
>>>>>>> invoke the SMI?      
>>>>>>
>>>>>> I base this on the code here:
>>>>>>
>>>>>> https://github.com/tianocore/edk2/blob/master/SecurityPkg/Tcg/Tcg2Smm/Tpm.asl#L81
>>>>>>
>>>>>> OperationRegion (TNVS, SystemMemory, 0xFFFF0000, 0xF0)    
>>>>> Is the goal to reuse EDK PPI impl. including ASL?    
>>>>
>>>> Right, that is one important question. Some code in edk2 is meant only
>>>> as example code (so actual firmware platforms are encouraged to copy &
>>>> customize the code, or use similar or dissimilar alternatives). Some
>>>> modules are meant for inclusion "as-is" in the platform description
>>>> files (~ makefiles). There are so many edk2 modules related to TPM
>>>> (several versions of the specs even) that it's hard to say what is meant
>>>> for what usage type. (By my earlier count, there are 19 modules.)
>>>>  
>>>>> If it's so, then perhaps we only need to write address into QEMU
>>>>> and let OVMF to discard PPI SSDT from QEMU.    
>>>>
>>>> That's something I would not like. When running on QEMU, it's OK for
>>>> some edk2 modules to install their own ACPI tables, but only if they are
>>>> "software" tables, such as the IBFT for iSCSI booting, BGRT (boot
>>>> graphics table) for the boot logo / splash screen, etc. For ACPI tables
>>>> that describe hardware (data tables) or carry out actions related to
>>>> hardware (DefinitionBlocks / AML), I much prefer QEMU to generate all
>>>> the stuff.
>>>>
>>>> If there is a conflict between hardware-related tables that QEMU
>>>> generates and similar tables that pre-exist in edk2, I prefer working
>>>> with the edk2 maintainers to customize their subsystems, so that a
>>>> concrete firmware platform, such as OvmfPkg and ArmVirtPkg, can
>>>> conditionalize / exclude those preexistent ACPI tables, while benefiting
>>>> from the rest of the code. Then the ACPI linker/loader client used in
>>>> both OvmfPkg and ArmVirtPkg can remain dumb and process & expose
>>>> whatever tables QEMU generates.
>>>>
>>>> We could control the AML payload for TPM and/or TPM PPI -- e.g. whether
>>>> it should raise an SMI -- via "-device tpm2,smi=on", for example. (Just
>>>> making up the syntax, but you know what I mean.)
>>>>
>>>> We could go one step further, "-device tpm2,acpi=[on|off]". acpi=on
>>>> would make QEMU generate the TPM related tables (perhaps targeting only
>>>> SeaBIOS, if that makes sense), acpi=off would leave the related tables
>>>> to the firmware.
>>>>
>>>> This is just speculation on my part; the point is I'd like to avoid any
>>>> more "intelligent filtering" regarding ACPI tables in OVMF. What we have
>>>> there is terrible enough already.  
>>> If we could discard EDK's generated tables, it's fine as well,
>>> but we would need to model TPM device model to match EDK's one
>>> (risk here is that implementation goes of sync, if it's not spec
>>> dictated). Then SeaBIOS side could 're-implement' it using
>>> the same set of tables from QEMU.
>>>
>>> I wonder if we could somehow detect firmware flavor we are
>>> running on from ASL /wrt [no]using SMI/?
>>>  * I don't really like idea but we can probably detect
>>>    in QEMU if firmware is OVMF or not and generate extra SMI hunk
>>>    based on that.
>>>  * or we could always generate SMI code and probe for some
>>>    EDK specific SSDT code to see in AML to enable it.
>>>    Maybe OVMF could inject such table.  
>>
>> There are already several QEMU artifacts that are specific to OVMF, but
>> none of them is a full match for this purpose:
>>
>> * "-machine smm=on" means that SMM emulation is enabled. However, this
>> is automatically done for Q35, and it happens regardless of firmware. So
>> basing the decision on this is no good.
>>
>> * One (or much more frequently, two) pflash devices usually indicate
>> OVMF (as opposed to SeaBIOS, which is selected with -L, or with -bios,
>> or by default). However, the same pflash setup is appropriate for i440fx
>> machine types too, which lack SMM emulation.
>>
>> * The "-global driver=cfi.pflash01,property=secure,value=on" flag means
>> that QEMU should restrict pflash write access to guest code that runs in
>> SMM. This is required for making the UEFI variable store actually
>> secure, but a user might have any reason for *not* specifying this flag
>> (while keeping SMM emulation generally enabled, with "-machine smm=on").
>> This looks like the closest match, but still not a 100% match.
>>
>> * OVMF generally negotiates "SMI broadcast", but the user can prevent
>> that too on the QEMU command line.
>>
>> ... I really think SMM is a red herring here. We've established several
>> times that the PPI opcodes need no protection. SMM is an implementation
>> detail in edk2, and it is used *only* because there is no other way for
>> AML code to enter (invoke) the firmware. In other words, this is simply
>> a "firmware calling convention" for AML code -- prepare a request buffer
>> and raise an SMI.
>>
>> A valid alternative would be if we provided native OS drivers for a new
>> ACPI device (both for Windows and Linux), and used Notify() in the
>> TPM-related AML. Then the ACPI code could call back into the native OS
>> driver, and *that* driver could then use the regular interface to the
>> UEFI variable services (for example), without having to care about SMM
>> explicitly. (Note that I'm not actually suggesting this; I'd just like
>> to show that SMM is accidental here, not inherent.)
> Yep, relying on detecting SMM or pflash or ... is not really reliable.
> 
> What about my proposal for PPI enabled OVMF to inject an additional
> SSDT table with a variable ex:
>   firmware_ovmf = 1
> 
> then in QEMU generated tables we can probe for it in runtime
> when OSPM executes method and trigger SMI if variable exists.

According to my last email, I don't consider this necessary any longer,
for this concrete case.

Speaking generally (independently of TPM), the above seems simple enough
(for advertising OVMF characteristics to QEMU-generated AML), but it
also looks like a slippery slope. It establishes a new feature
advertisement method, and soon enough, OVMF would have to expose further
variables (maybe even methods!) that QEMU-generated AML code would
interrogate. Let's not go there just yet; let's keep the generated AML
firmware-agnostic for as long as we can.

Thanks,
Laszlo
Igor Mammedov Feb. 13, 2018, 4:34 p.m. UTC | #18
On Tue, 13 Feb 2018 17:16:49 +0100
Laszlo Ersek <lersek@redhat.com> wrote:

[...]
> 
> It's possible that I'll scream for additional hw enlightement under OVMF
> later; so I suggest designing in some kind of feature negotiation up-front.
We can add an extra fwcfg file for that later, when/if it's actually needed.

[...]
Laszlo Ersek Feb. 13, 2018, 5:01 p.m. UTC | #19
On 02/13/18 17:34, Igor Mammedov wrote:
> On Tue, 13 Feb 2018 17:16:49 +0100
> Laszlo Ersek <lersek@redhat.com> wrote:
> 
> [...]
>>
>> It's possible that I'll scream for additional hw enlightement under OVMF
>> later; so I suggest designing in some kind of feature negotiation up-front.
> We can add an extra fwcfg file for that later, when/if it's actually needed.

Works for me.

Thanks
Laszlo
Kevin O'Connor Feb. 13, 2018, 7:37 p.m. UTC | #20
On Tue, Feb 13, 2018 at 05:16:49PM +0100, Laszlo Ersek wrote:
> On 02/12/18 21:49, Stefan Berger wrote:
> > On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
> >> I'm not sure I fully understand the goals of the PPI interface.
> >> Here's what I understand so far:
> >>
> >> The TPM specs define some actions that are considered privileged.  An
> >> example of this would be disabling the TPM itself.  In order to
> >> prevent an attacker from performing these actions without
> >> authorization, the TPM specs define a mechanism to assert "physical
> >> presence" before the privileged action can be done.  They do this by
> >> having the firmware present a menu during early boot that permits
> >> these privileged operations, and then the firmware locks the TPM chip
> >> so the actions can no longer be done by any software that runs after
> >> the firmware.  Thus "physical presence" is asserted by demonstrating
> >> one has console access to the machine during early boot.
> >>
> >> The PPI spec implements a work around for this - presumably some found
> >> the enforcement mechanism too onerous.  It allows the OS to provide a
> >> request code to the firmware, and on the next boot the firmware will
> >> take the requested action before it locks the chip.  Thus allowing the
> >> OS to indirectly perform the privileged action even after the chip has
> >> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
> >> allow users to circumvent the physical presence mechanism (if they
> >> choose to).
> > 
> > Correct.
> >>
> >> Here's what I understand the proposed implementation involves:
> >>
> >> 1 - in addition to emulating the TPM device itself, QEMU will also
> >>      introduce a virtual memory device with 0x400 bytes.
> > Correct.
> >>
> >> 2 - on first boot the firmware (seabios and uefi) will populate the
> >>      memory region created in step 1.  In particular it will fill an
> >>      array with the list of request codes it supports.  (Each request
> >>      is an 8bit value, the array has 256 entries.)
> > Correct. Each firmware would fill out the 256 byte array depending on
> > what it supports. The 8 bit values are basically flags and so on.
> >> 3 - QEMU will produce AML code implementing the standard PPI ACPI
> >>      interface.  This AML code will take the request, find the table
> >>      produced in step 1, compare it to the list of accepted requests
> >>      produced in step 2, and then place the 8bit request in another
> >>      qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
> > 
> > Correct.
> > 
> > Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
> > therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
> > do this.
> > 
> >> 4 - the OS will signal a reboot, qemu will do its normal reboot logic,
> >>      and the firmware will be run again.
> >>
> >> 5 - the firmware will extract the code written in stage 3, and if the
> >>      tpm device has been configured to accept PPI codes from the OS, it
> >>      will invoke the requested action.
> > 
> > SeaBIOS would look into memory to find the code. EDK2 will read the code
> > from a UEFI variable.
> > 
> >> Did I understand the above correctly?
> > I think so. With the fine differences between SeaBIOS and EDK2 pointed out.
> 
> Here's what I suggest:
> 
> Please everyone continue working on this, according to Kevin's &
> Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
> for now.

If this were targetted at SeaBIOS, I'd look for a simpler
QEMU/firmware interface.  Something like:

A - QEMU produces AML code implementing the standard PPI ACPI
    interface that generates a request code and stores it in the
    device memory of an existing device (eg, writable fw_cfg or an
    extension field in the existing emulated TPM device).

B - after a reboot the firmware extracts the PPI request code
    (produced in step A) and performs the requested action (if the TPM
    is configured to accept OS generated codes).

That is, skip steps 1 and 2 from the original proposal.

-Kevin
Laszlo Ersek Feb. 13, 2018, 7:59 p.m. UTC | #21
On 02/13/18 20:37, Kevin O'Connor wrote:
> On Tue, Feb 13, 2018 at 05:16:49PM +0100, Laszlo Ersek wrote:
>> On 02/12/18 21:49, Stefan Berger wrote:
>>> On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
>>>> I'm not sure I fully understand the goals of the PPI interface.
>>>> Here's what I understand so far:
>>>>
>>>> The TPM specs define some actions that are considered privileged.  An
>>>> example of this would be disabling the TPM itself.  In order to
>>>> prevent an attacker from performing these actions without
>>>> authorization, the TPM specs define a mechanism to assert "physical
>>>> presence" before the privileged action can be done.  They do this by
>>>> having the firmware present a menu during early boot that permits
>>>> these privileged operations, and then the firmware locks the TPM chip
>>>> so the actions can no longer be done by any software that runs after
>>>> the firmware.  Thus "physical presence" is asserted by demonstrating
>>>> one has console access to the machine during early boot.
>>>>
>>>> The PPI spec implements a work around for this - presumably some found
>>>> the enforcement mechanism too onerous.  It allows the OS to provide a
>>>> request code to the firmware, and on the next boot the firmware will
>>>> take the requested action before it locks the chip.  Thus allowing the
>>>> OS to indirectly perform the privileged action even after the chip has
>>>> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
>>>> allow users to circumvent the physical presence mechanism (if they
>>>> choose to).
>>>
>>> Correct.
>>>>
>>>> Here's what I understand the proposed implementation involves:
>>>>
>>>> 1 - in addition to emulating the TPM device itself, QEMU will also
>>>>      introduce a virtual memory device with 0x400 bytes.
>>> Correct.
>>>>
>>>> 2 - on first boot the firmware (seabios and uefi) will populate the
>>>>      memory region created in step 1.  In particular it will fill an
>>>>      array with the list of request codes it supports.  (Each request
>>>>      is an 8bit value, the array has 256 entries.)
>>> Correct. Each firmware would fill out the 256 byte array depending on
>>> what it supports. The 8 bit values are basically flags and so on.
>>>> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>>>>      interface.  This AML code will take the request, find the table
>>>>      produced in step 1, compare it to the list of accepted requests
>>>>      produced in step 2, and then place the 8bit request in another
>>>>      qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
>>>
>>> Correct.
>>>
>>> Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
>>> therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
>>> do this.
>>>
>>>> 4 - the OS will signal a reboot, qemu will do its normal reboot logic,
>>>>      and the firmware will be run again.
>>>>
>>>> 5 - the firmware will extract the code written in stage 3, and if the
>>>>      tpm device has been configured to accept PPI codes from the OS, it
>>>>      will invoke the requested action.
>>>
>>> SeaBIOS would look into memory to find the code. EDK2 will read the code
>>> from a UEFI variable.
>>>
>>>> Did I understand the above correctly?
>>> I think so. With the fine differences between SeaBIOS and EDK2 pointed out.
>>
>> Here's what I suggest:
>>
>> Please everyone continue working on this, according to Kevin's &
>> Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
>> for now.
> 
> If this were targetted at SeaBIOS, I'd look for a simpler
> QEMU/firmware interface.  Something like:
> 
> A - QEMU produces AML code implementing the standard PPI ACPI
>     interface that generates a request code and stores it in the
>     device memory of an existing device (eg, writable fw_cfg or an
>     extension field in the existing emulated TPM device).
> 
> B - after a reboot the firmware extracts the PPI request code
>     (produced in step A) and performs the requested action (if the TPM
>     is configured to accept OS generated codes).
> 
> That is, skip steps 1 and 2 from the original proposal.

I think A/B can work fine, as long as
- the firmware can somehow dynamically recognize the device / "register
  block" that the request codes have to be pulled from, and
- QEMU is free to move the device or register block around, from release
  to release, without disturbing migration.

Thanks!
Laszlo
Stefan Berger Feb. 13, 2018, 8:29 p.m. UTC | #22
On 02/13/2018 02:59 PM, Laszlo Ersek wrote:
> On 02/13/18 20:37, Kevin O'Connor wrote:
>> On Tue, Feb 13, 2018 at 05:16:49PM +0100, Laszlo Ersek wrote:
>>> On 02/12/18 21:49, Stefan Berger wrote:
>>>> On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
>>>>> I'm not sure I fully understand the goals of the PPI interface.
>>>>> Here's what I understand so far:
>>>>>
>>>>> The TPM specs define some actions that are considered privileged.  An
>>>>> example of this would be disabling the TPM itself.  In order to
>>>>> prevent an attacker from performing these actions without
>>>>> authorization, the TPM specs define a mechanism to assert "physical
>>>>> presence" before the privileged action can be done.  They do this by
>>>>> having the firmware present a menu during early boot that permits
>>>>> these privileged operations, and then the firmware locks the TPM chip
>>>>> so the actions can no longer be done by any software that runs after
>>>>> the firmware.  Thus "physical presence" is asserted by demonstrating
>>>>> one has console access to the machine during early boot.
>>>>>
>>>>> The PPI spec implements a work around for this - presumably some found
>>>>> the enforcement mechanism too onerous.  It allows the OS to provide a
>>>>> request code to the firmware, and on the next boot the firmware will
>>>>> take the requested action before it locks the chip.  Thus allowing the
>>>>> OS to indirectly perform the privileged action even after the chip has
>>>>> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
>>>>> allow users to circumvent the physical presence mechanism (if they
>>>>> choose to).
>>>> Correct.
>>>>> Here's what I understand the proposed implementation involves:
>>>>>
>>>>> 1 - in addition to emulating the TPM device itself, QEMU will also
>>>>>       introduce a virtual memory device with 0x400 bytes.
>>>> Correct.
>>>>> 2 - on first boot the firmware (seabios and uefi) will populate the
>>>>>       memory region created in step 1.  In particular it will fill an
>>>>>       array with the list of request codes it supports.  (Each request
>>>>>       is an 8bit value, the array has 256 entries.)
>>>> Correct. Each firmware would fill out the 256 byte array depending on
>>>> what it supports. The 8 bit values are basically flags and so on.
>>>>> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>>>>>       interface.  This AML code will take the request, find the table
>>>>>       produced in step 1, compare it to the list of accepted requests
>>>>>       produced in step 2, and then place the 8bit request in another
>>>>>       qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
>>>> Correct.
>>>>
>>>> Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
>>>> therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
>>>> do this.
>>>>
>>>>> 4 - the OS will signal a reboot, qemu will do its normal reboot logic,
>>>>>       and the firmware will be run again.
>>>>>
>>>>> 5 - the firmware will extract the code written in stage 3, and if the
>>>>>       tpm device has been configured to accept PPI codes from the OS, it
>>>>>       will invoke the requested action.
>>>> SeaBIOS would look into memory to find the code. EDK2 will read the code
>>>> from a UEFI variable.
>>>>
>>>>> Did I understand the above correctly?
>>>> I think so. With the fine differences between SeaBIOS and EDK2 pointed out.
>>> Here's what I suggest:
>>>
>>> Please everyone continue working on this, according to Kevin's &
>>> Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
>>> for now.
>> If this were targetted at SeaBIOS, I'd look for a simpler
>> QEMU/firmware interface.  Something like:
>>
>> A - QEMU produces AML code implementing the standard PPI ACPI
>>      interface that generates a request code and stores it in the
>>      device memory of an existing device (eg, writable fw_cfg or an
>>      extension field in the existing emulated TPM device).

ACPI code writing into fw_cfg sounds difficult.
I initially had PPI SeaBIOS code write into the TPM TIS device's memory 
into some custom addresses. I'd consider this a hack. Now we have that 
virtual memory device with those 0x400 bytes...

In these 0x400 bytes we have 256 bytes that are used for configuration 
flags describing the supported opcode as you previously described. This 
array allows us to decouple the firmware implementation from the ACPI 
code and we need not hard code what is supported in the firmware inside 
the ACPI code (which would be difficult to do anyway since in QEMU we 
would not what firmware will be started and what PPI opcodes are 
support) and the ppi sysfs entries in Linux for example show exactly 
those PPI opcodes that are supported. The firmware needs to set those 
flags and the firmware knows what it supports.

I hope we can settle that this device is the right path.

>>
>> B - after a reboot the firmware extracts the PPI request code
>>      (produced in step A) and performs the requested action (if the TPM
>>      is configured to accept OS generated codes).
>>
>> That is, skip steps 1 and 2 from the original proposal.
> I think A/B can work fine, as long as
> - the firmware can somehow dynamically recognize the device / "register
>    block" that the request codes have to be pulled from, and

I experimented with what Igor had suggested with the fw_cfg file 
callback and so on.

> - QEMU is free to move the device or register block around, from release
>    to release, without disturbing migration.

I think we should basically limit the firmware to writing two addresses 
into this fw_cfg file:
- SeaBIOS: write back the same address that QEMU suggested in the file 
(currently 0xfed4 5000)
- EDK2: write back 0xffff 0000

No other address would be accepted by QEMU since presumably QEMU knows 
where otherwise address collisions can occur.

>
> Thanks!
> Laszlo
>
Laszlo Ersek Feb. 13, 2018, 9:04 p.m. UTC | #23
On 02/13/18 21:29, Stefan Berger wrote:
> On 02/13/2018 02:59 PM, Laszlo Ersek wrote:
>> On 02/13/18 20:37, Kevin O'Connor wrote:
>>> On Tue, Feb 13, 2018 at 05:16:49PM +0100, Laszlo Ersek wrote:
>>>> On 02/12/18 21:49, Stefan Berger wrote:
>>>>> On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
>>>>>> I'm not sure I fully understand the goals of the PPI interface.
>>>>>> Here's what I understand so far:
>>>>>>
>>>>>> The TPM specs define some actions that are considered privileged.  An
>>>>>> example of this would be disabling the TPM itself.  In order to
>>>>>> prevent an attacker from performing these actions without
>>>>>> authorization, the TPM specs define a mechanism to assert "physical
>>>>>> presence" before the privileged action can be done.  They do this by
>>>>>> having the firmware present a menu during early boot that permits
>>>>>> these privileged operations, and then the firmware locks the TPM chip
>>>>>> so the actions can no longer be done by any software that runs after
>>>>>> the firmware.  Thus "physical presence" is asserted by demonstrating
>>>>>> one has console access to the machine during early boot.
>>>>>>
>>>>>> The PPI spec implements a work around for this - presumably some
>>>>>> found
>>>>>> the enforcement mechanism too onerous.  It allows the OS to provide a
>>>>>> request code to the firmware, and on the next boot the firmware will
>>>>>> take the requested action before it locks the chip.  Thus allowing
>>>>>> the
>>>>>> OS to indirectly perform the privileged action even after the chip
>>>>>> has
>>>>>> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
>>>>>> allow users to circumvent the physical presence mechanism (if they
>>>>>> choose to).
>>>>> Correct.
>>>>>> Here's what I understand the proposed implementation involves:
>>>>>>
>>>>>> 1 - in addition to emulating the TPM device itself, QEMU will also
>>>>>>       introduce a virtual memory device with 0x400 bytes.
>>>>> Correct.
>>>>>> 2 - on first boot the firmware (seabios and uefi) will populate the
>>>>>>       memory region created in step 1.  In particular it will fill an
>>>>>>       array with the list of request codes it supports.  (Each
>>>>>> request
>>>>>>       is an 8bit value, the array has 256 entries.)
>>>>> Correct. Each firmware would fill out the 256 byte array depending on
>>>>> what it supports. The 8 bit values are basically flags and so on.
>>>>>> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>>>>>>       interface.  This AML code will take the request, find the table
>>>>>>       produced in step 1, compare it to the list of accepted requests
>>>>>>       produced in step 2, and then place the 8bit request in another
>>>>>>       qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
>>>>> Correct.
>>>>>
>>>>> Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
>>>>> therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
>>>>> do this.
>>>>>
>>>>>> 4 - the OS will signal a reboot, qemu will do its normal reboot
>>>>>> logic,
>>>>>>       and the firmware will be run again.
>>>>>>
>>>>>> 5 - the firmware will extract the code written in stage 3, and if the
>>>>>>       tpm device has been configured to accept PPI codes from the
>>>>>> OS, it
>>>>>>       will invoke the requested action.
>>>>> SeaBIOS would look into memory to find the code. EDK2 will read the
>>>>> code
>>>>> from a UEFI variable.
>>>>>
>>>>>> Did I understand the above correctly?
>>>>> I think so. With the fine differences between SeaBIOS and EDK2
>>>>> pointed out.
>>>> Here's what I suggest:
>>>>
>>>> Please everyone continue working on this, according to Kevin's &
>>>> Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
>>>> for now.
>>> If this were targetted at SeaBIOS, I'd look for a simpler
>>> QEMU/firmware interface.  Something like:
>>>
>>> A - QEMU produces AML code implementing the standard PPI ACPI
>>>      interface that generates a request code and stores it in the
>>>      device memory of an existing device (eg, writable fw_cfg or an
>>>      extension field in the existing emulated TPM device).
> 
> ACPI code writing into fw_cfg sounds difficult.
> I initially had PPI SeaBIOS code write into the TPM TIS device's memory
> into some custom addresses. I'd consider this a hack. Now we have that
> virtual memory device with those 0x400 bytes...
> 
> In these 0x400 bytes we have 256 bytes that are used for configuration
> flags describing the supported opcode as you previously described. This
> array allows us to decouple the firmware implementation from the ACPI
> code and we need not hard code what is supported in the firmware inside
> the ACPI code (which would be difficult to do anyway since in QEMU we
> would not what firmware will be started and what PPI opcodes are
> support) and the ppi sysfs entries in Linux for example show exactly
> those PPI opcodes that are supported. The firmware needs to set those
> flags and the firmware knows what it supports.
> 
> I hope we can settle that this device is the right path.
> 
>>>
>>> B - after a reboot the firmware extracts the PPI request code
>>>      (produced in step A) and performs the requested action (if the TPM
>>>      is configured to accept OS generated codes).
>>>
>>> That is, skip steps 1 and 2 from the original proposal.
>> I think A/B can work fine, as long as
>> - the firmware can somehow dynamically recognize the device / "register
>>    block" that the request codes have to be pulled from, and
> 
> I experimented with what Igor had suggested with the fw_cfg file
> callback and so on.
> 
>> - QEMU is free to move the device or register block around, from release
>>    to release, without disturbing migration.
> 
> I think we should basically limit the firmware to writing two addresses
> into this fw_cfg file:
> - SeaBIOS: write back the same address that QEMU suggested in the file
> (currently 0xfed4 5000)
> - EDK2: write back 0xffff 0000
Given that I intend to rip the SecurityPkg ASL code out of OVMF's
solution for this, I don't think that the address 0xffff_0000 has any
relevance for OVMF. If the QEMU x86 MMIO space generally has a suitable
gap at 0xfed4_5000 (and I do think it has, even considering pflash &
LAPIC), then just put the register block there.

As long as Igor agrees. :)

Another question just occurred to me. If the guest OS queues some PPI
operations, and then the VM is powered down fully (instead of a reboot),
then we're at liberty to forget the queued PPI ops, right?

Thanks
Laszlo

> 
> No other address would be accepted by QEMU since presumably QEMU knows
> where otherwise address collisions can occur.
Stefan Berger Feb. 13, 2018, 9:32 p.m. UTC | #24
On 02/13/2018 04:04 PM, Laszlo Ersek wrote:
> On 02/13/18 21:29, Stefan Berger wrote:
>> On 02/13/2018 02:59 PM, Laszlo Ersek wrote:
>>> On 02/13/18 20:37, Kevin O'Connor wrote:
>>>> On Tue, Feb 13, 2018 at 05:16:49PM +0100, Laszlo Ersek wrote:
>>>>> On 02/12/18 21:49, Stefan Berger wrote:
>>>>>> On 02/12/2018 03:46 PM, Kevin O'Connor wrote:
>>>>>>> I'm not sure I fully understand the goals of the PPI interface.
>>>>>>> Here's what I understand so far:
>>>>>>>
>>>>>>> The TPM specs define some actions that are considered privileged.  An
>>>>>>> example of this would be disabling the TPM itself.  In order to
>>>>>>> prevent an attacker from performing these actions without
>>>>>>> authorization, the TPM specs define a mechanism to assert "physical
>>>>>>> presence" before the privileged action can be done.  They do this by
>>>>>>> having the firmware present a menu during early boot that permits
>>>>>>> these privileged operations, and then the firmware locks the TPM chip
>>>>>>> so the actions can no longer be done by any software that runs after
>>>>>>> the firmware.  Thus "physical presence" is asserted by demonstrating
>>>>>>> one has console access to the machine during early boot.
>>>>>>>
>>>>>>> The PPI spec implements a work around for this - presumably some
>>>>>>> found
>>>>>>> the enforcement mechanism too onerous.  It allows the OS to provide a
>>>>>>> request code to the firmware, and on the next boot the firmware will
>>>>>>> take the requested action before it locks the chip.  Thus allowing
>>>>>>> the
>>>>>>> OS to indirectly perform the privileged action even after the chip
>>>>>>> has
>>>>>>> been locked.  Thus, the PPI system seems to be an "elaborate hack" to
>>>>>>> allow users to circumvent the physical presence mechanism (if they
>>>>>>> choose to).
>>>>>> Correct.
>>>>>>> Here's what I understand the proposed implementation involves:
>>>>>>>
>>>>>>> 1 - in addition to emulating the TPM device itself, QEMU will also
>>>>>>>        introduce a virtual memory device with 0x400 bytes.
>>>>>> Correct.
>>>>>>> 2 - on first boot the firmware (seabios and uefi) will populate the
>>>>>>>        memory region created in step 1.  In particular it will fill an
>>>>>>>        array with the list of request codes it supports.  (Each
>>>>>>> request
>>>>>>>        is an 8bit value, the array has 256 entries.)
>>>>>> Correct. Each firmware would fill out the 256 byte array depending on
>>>>>> what it supports. The 8 bit values are basically flags and so on.
>>>>>>> 3 - QEMU will produce AML code implementing the standard PPI ACPI
>>>>>>>        interface.  This AML code will take the request, find the table
>>>>>>>        produced in step 1, compare it to the list of accepted requests
>>>>>>>        produced in step 2, and then place the 8bit request in another
>>>>>>>        qemu virtual memory device (at 0xFFFF0000 or 0xFED45000).
>>>>>> Correct.
>>>>>>
>>>>>> Now EDK2 wants to store the code in a UEFI variable in NVRAM. We
>>>>>> therefore would need to trigger an SMI. In SeaBIOS we wouldn't have to
>>>>>> do this.
>>>>>>
>>>>>>> 4 - the OS will signal a reboot, qemu will do its normal reboot
>>>>>>> logic,
>>>>>>>        and the firmware will be run again.
>>>>>>>
>>>>>>> 5 - the firmware will extract the code written in stage 3, and if the
>>>>>>>        tpm device has been configured to accept PPI codes from the
>>>>>>> OS, it
>>>>>>>        will invoke the requested action.
>>>>>> SeaBIOS would look into memory to find the code. EDK2 will read the
>>>>>> code
>>>>>> from a UEFI variable.
>>>>>>
>>>>>>> Did I understand the above correctly?
>>>>>> I think so. With the fine differences between SeaBIOS and EDK2
>>>>>> pointed out.
>>>>> Here's what I suggest:
>>>>>
>>>>> Please everyone continue working on this, according to Kevin's &
>>>>> Stefan's description, but focus on QEMU and SeaBIOS *only*. Ignore edk2
>>>>> for now.
>>>> If this were targetted at SeaBIOS, I'd look for a simpler
>>>> QEMU/firmware interface.  Something like:
>>>>
>>>> A - QEMU produces AML code implementing the standard PPI ACPI
>>>>       interface that generates a request code and stores it in the
>>>>       device memory of an existing device (eg, writable fw_cfg or an
>>>>       extension field in the existing emulated TPM device).
>> ACPI code writing into fw_cfg sounds difficult.
>> I initially had PPI SeaBIOS code write into the TPM TIS device's memory
>> into some custom addresses. I'd consider this a hack. Now we have that
>> virtual memory device with those 0x400 bytes...
>>
>> In these 0x400 bytes we have 256 bytes that are used for configuration
>> flags describing the supported opcode as you previously described. This
>> array allows us to decouple the firmware implementation from the ACPI
>> code and we need not hard code what is supported in the firmware inside
>> the ACPI code (which would be difficult to do anyway since in QEMU we
>> would not what firmware will be started and what PPI opcodes are
>> support) and the ppi sysfs entries in Linux for example show exactly
>> those PPI opcodes that are supported. The firmware needs to set those
>> flags and the firmware knows what it supports.
>>
>> I hope we can settle that this device is the right path.
>>
>>>> B - after a reboot the firmware extracts the PPI request code
>>>>       (produced in step A) and performs the requested action (if the TPM
>>>>       is configured to accept OS generated codes).
>>>>
>>>> That is, skip steps 1 and 2 from the original proposal.
>>> I think A/B can work fine, as long as
>>> - the firmware can somehow dynamically recognize the device / "register
>>>     block" that the request codes have to be pulled from, and
>> I experimented with what Igor had suggested with the fw_cfg file
>> callback and so on.
>>
>>> - QEMU is free to move the device or register block around, from release
>>>     to release, without disturbing migration.
>> I think we should basically limit the firmware to writing two addresses
>> into this fw_cfg file:
>> - SeaBIOS: write back the same address that QEMU suggested in the file
>> (currently 0xfed4 5000)
>> - EDK2: write back 0xffff 0000
> Given that I intend to rip the SecurityPkg ASL code out of OVMF's
> solution for this, I don't think that the address 0xffff_0000 has any
> relevance for OVMF. If the QEMU x86 MMIO space generally has a suitable
> gap at 0xfed4_5000 (and I do think it has, even considering pflash &
> LAPIC), then just put the register block there.
>
> As long as Igor agrees. :)
>
> Another question just occurred to me. If the guest OS queues some PPI
> operations, and then the VM is powered down fully (instead of a reboot),
> then we're at liberty to forget the queued PPI ops, right?

I would say that is implementation-dependent. UEFI wouldn't forget due 
to these UEFI Variables in NVRAM...

     Stefan

>
> Thanks
> Laszlo
>
>> No other address would be accepted by QEMU since presumably QEMU knows
>> where otherwise address collisions can occur.
Kevin O'Connor Feb. 14, 2018, 6:39 p.m. UTC | #25
On Tue, Feb 13, 2018 at 03:29:20PM -0500, Stefan Berger wrote:
[...]
> In these 0x400 bytes we have 256 bytes that are used for configuration flags
> describing the supported opcode as you previously described. This array
> allows us to decouple the firmware implementation from the ACPI code and we
> need not hard code what is supported in the firmware inside the ACPI code
> (which would be difficult to do anyway since in QEMU we would not what
> firmware will be started and what PPI opcodes are support) and the ppi sysfs
> entries in Linux for example show exactly those PPI opcodes that are
> supported. The firmware needs to set those flags and the firmware knows what
> it supports.
> 
> I hope we can settle that this device is the right path.

It seems that the primary purpose of the 0x400 virtual device is to
pass information from firmware to QEMU (specifically to pass the list
of supported PPI opcodes to the QEMU generated AML code).  Passing
information between firmware and QEMU is not new territory, and fw_cfg
was specifically built to do this.  I'd prefer to use fw_cfg if we
need to pass information between firmware and QEMU.

That said, I don't see why this list is needed - why not just
implement the same opcodes in both UEFI and SeaBIOS and be done with
it?  The spec defines 22 actions, and most of these are permutations
of 4 basic features (Enable, Activate, Clear, SetOwnerInstall).

[...]
> I initially had PPI SeaBIOS code write into the TPM TIS device's memory into
> some custom addresses. I'd consider this a hack.

Well, sure, it could be considered a hack.  But, it seems to me the
whole PPI spec is a bit of a hack.  If elegance isn't an option,
settle for simplicity?

-Kevin
Stefan Berger Feb. 15, 2018, 11:52 a.m. UTC | #26
On 02/14/2018 01:39 PM, Kevin O'Connor wrote:
> On Tue, Feb 13, 2018 at 03:29:20PM -0500, Stefan Berger wrote:
> [...]
>> In these 0x400 bytes we have 256 bytes that are used for configuration flags
>> describing the supported opcode as you previously described. This array
>> allows us to decouple the firmware implementation from the ACPI code and we
>> need not hard code what is supported in the firmware inside the ACPI code
>> (which would be difficult to do anyway since in QEMU we would not what
>> firmware will be started and what PPI opcodes are support) and the ppi sysfs
>> entries in Linux for example show exactly those PPI opcodes that are
>> supported. The firmware needs to set those flags and the firmware knows what
>> it supports.
>>
>> I hope we can settle that this device is the right path.
> It seems that the primary purpose of the 0x400 virtual device is to
> pass information from firmware to QEMU (specifically to pass the list
> of supported PPI opcodes to the QEMU generated AML code).  Passing
> information between firmware and QEMU is not new territory, and fw_cfg
> was specifically built to do this.  I'd prefer to use fw_cfg if we
> need to pass information between firmware and QEMU.
>
> That said, I don't see why this list is needed - why not just
> implement the same opcodes in both UEFI and SeaBIOS and be done with
> it?  The spec defines 22 actions, and most of these are permutations
> of 4 basic features (Enable, Activate, Clear, SetOwnerInstall).

... which may be a substantial amount of work to implement. There are 
another 23 or so defined for TPM 2, some of which are optional.

>
> [...]
>> I initially had PPI SeaBIOS code write into the TPM TIS device's memory into
>> some custom addresses. I'd consider this a hack.
> Well, sure, it could be considered a hack.  But, it seems to me the
> whole PPI spec is a bit of a hack.  If elegance isn't an option,
> settle for simplicity?
>
> -Kevin
>
diff mbox

Patch

diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 522d6d2..f8c2d01 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -42,6 +42,7 @@ 
 #include "hw/acpi/memory_hotplug.h"
 #include "sysemu/tpm.h"
 #include "hw/acpi/tpm.h"
+#include "hw/tpm/tpm_ppi.h"
 #include "hw/acpi/vmgenid.h"
 #include "sysemu/tpm_backend.h"
 #include "hw/timer/mc146818rtc_regs.h"
@@ -1860,6 +1861,276 @@  static Aml *build_q35_osc_method(void)
 }
 
 static void
+build_tpm_ppi(Aml *dev, TPMVersion tpm_version)
+{
+    Aml *method, *field, *ifctx, *ifctx2, *ifctx3, *pak;
+
+    aml_append(dev,
+               aml_operation_region("TPPI", AML_SYSTEM_MEMORY,
+                                    aml_int(TPM_PPI_ADDR_BASE),
+                                    TPM_PPI_STRUCT_SIZE));
+
+    field = aml_field("TPPI", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
+    aml_append(field, aml_named_field("PPIN",
+               sizeof(uint8_t) * BITS_PER_BYTE));
+    aml_append(field, aml_named_field("PPIP",
+               sizeof(uint32_t) * BITS_PER_BYTE));
+    aml_append(field, aml_named_field("PPRP",
+               sizeof(uint32_t) * BITS_PER_BYTE));
+    aml_append(field, aml_named_field("PPRQ",
+               sizeof(uint32_t) * BITS_PER_BYTE));
+    aml_append(field, aml_named_field("PPRM",
+               sizeof(uint32_t) * BITS_PER_BYTE));
+    aml_append(field, aml_named_field("LPPR",
+               sizeof(uint32_t) * BITS_PER_BYTE));
+    aml_append(field, aml_reserved_field(
+               sizeof(uint32_t) * BITS_PER_BYTE /* FRET */ +
+               sizeof(uint8_t) * BITS_PER_BYTE /* MCIN */ +
+               sizeof(uint32_t) * BITS_PER_BYTE * 4 /* MCIP .. UCRQ */ +
+               sizeof(uint8_t) * BITS_PER_BYTE * 214));
+    aml_append(field, aml_named_field("FUNC",
+               sizeof(uint8_t) * BITS_PER_BYTE * 256));
+    aml_append(dev, field);
+
+    method = aml_method("_DSM", 4, AML_SERIALIZED);
+    {
+        uint8_t zerobyte[1] = { 0 };
+
+        ifctx = aml_if(
+                  aml_equal(aml_arg(0),
+                            aml_touuid("3DDDFAA6-361B-4EB4-A424-8D10089D1653"))
+                );
+        {
+            aml_append(ifctx,
+                       aml_store(aml_to_integer(aml_arg(2)), aml_local(0)));
+
+            /* standard DSM query function */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(0)));
+            {
+                uint8_t byte_list[2] = { 0xff, 0x01 };
+                aml_append(ifctx2, aml_return(aml_buffer(2, byte_list)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* interface version: 1.3 */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(1)));
+            {
+                aml_append(ifctx2, aml_return(aml_string("1.3")));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* submit TPM operation */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(2)));
+            {
+                /* get opcode */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_arg(3),
+                                                           aml_int(0))),
+                                     aml_local(0)));
+                /* get opcode flags */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
+                                                           aml_local(0))),
+                                     aml_local(1)));
+                ifctx3 = aml_if(
+                              aml_equal(
+                                  aml_and(aml_local(1),
+                                          aml_int(TPM_PPI_FUNC_IMPLEMENTED),
+                                          NULL),
+                                  aml_int(0)
+                              )
+                         );
+                {
+                    /* 1: not implemented */
+                    aml_append(ifctx3, aml_return(aml_int(1)));
+                }
+                aml_append(ifctx2, ifctx3);
+                aml_append(ifctx2, aml_store(aml_local(0), aml_name("PPRQ")));
+                aml_append(ifctx2, aml_store(aml_int(0), aml_name("PPRM")));
+                /* 0: success */
+                aml_append(ifctx2, aml_return(aml_int(0)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* get pending TPM operation */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(3)));
+            {
+                /* revision to integer */
+                aml_append(ifctx2,
+                           aml_store(
+                             aml_to_integer(aml_arg(1)),
+                             aml_local(1)));
+                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
+                {
+                    pak = aml_package(2);
+                    aml_append(pak, aml_int(0));
+                    aml_append(pak, aml_name("PPRQ"));
+                    aml_append(ifctx3, aml_return(pak));
+                }
+                aml_append(ifctx2, ifctx3);
+
+                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
+                {
+                    pak = aml_package(3);
+                    aml_append(pak, aml_int(0));
+                    aml_append(pak, aml_name("PPRQ"));
+                    aml_append(pak, aml_name("PPRM"));
+                    aml_append(ifctx3, aml_return(pak));
+                }
+                aml_append(ifctx2, ifctx3);
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* get platform-specific action to transition to pre-OS env. */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(4)));
+            {
+                /* get opcode */
+                aml_append(ifctx2,
+                           aml_store(aml_name("PPRQ"),
+                                     aml_local(0)));
+                /* get opcode flags */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
+                                                           aml_local(0))),
+                                     aml_local(1)));
+                /* return action flags */
+                aml_append(ifctx2,
+                           aml_return(
+                               aml_shiftright(
+                                   aml_and(aml_local(1),
+                                           aml_int(TPM_PPI_FUNC_ACTION_MASK),
+                                           NULL),
+                                   aml_int(1),
+                                   NULL)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* get TPM operation response */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(5)));
+            {
+                pak = aml_package(3);
+
+                aml_append(pak, aml_int(0));
+                aml_append(pak, aml_name("LPPR"));
+                aml_append(pak, aml_name("PPRP"));
+
+                aml_append(ifctx2, aml_return(pak));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* submit preferred user language */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(6)));
+            {
+                /* 3 = not implemented */
+                aml_append(ifctx2, aml_return(aml_int(3)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* submit TPM operation v2 */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(7)));
+            {
+                /* get opcode */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_arg(3),
+                                                           aml_int(0))),
+                                     aml_local(0)));
+                /* get opcode flags */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
+                                                           aml_local(0))),
+                                     aml_local(1)));
+                ifctx3 = aml_if(
+                              aml_equal(
+                                  aml_and(aml_local(1),
+                                          aml_int(TPM_PPI_FUNC_MASK),
+                                          NULL),
+                                  aml_int(TPM_PPI_FUNC_NOT_IMPLEMENTED)
+                              )
+                         );
+                {
+                    /* 1: not implemented */
+                    aml_append(ifctx3, aml_return(aml_int(1)));
+                }
+                aml_append(ifctx2, ifctx3);
+
+                ifctx3 = aml_if(
+                              aml_equal(
+                                  aml_and(
+                                      aml_local(1),
+                                      aml_int(TPM_PPI_FUNC_MASK),
+                                      NULL),
+                                  aml_int(TPM_PPI_FUNC_BLOCKED)
+                              )
+                         );
+                {
+                    /* 3: blocked by firmware */
+                    aml_append(ifctx3, aml_return(aml_int(3)));
+                }
+                aml_append(ifctx2, ifctx3);
+
+                /* revision to integer */
+                aml_append(ifctx2,
+                           aml_store(
+                             aml_to_integer(aml_arg(1)),
+                             aml_local(1)));
+
+                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(1)));
+                {
+                    /* revision 1 */
+                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
+                    aml_append(ifctx3, aml_store(aml_int(0), aml_name("PPRM")));
+                }
+                aml_append(ifctx2, ifctx3);
+
+                ifctx3 = aml_if(aml_equal(aml_local(1), aml_int(2)));
+                {
+                    /* revision 2 */
+                    aml_append(ifctx3, aml_store(aml_local(0), aml_name("PPRQ")));
+                    aml_append(ifctx3, aml_store(
+                                         aml_derefof(
+                                           aml_index(aml_arg(3),
+                                                     aml_int(1))),
+                                         aml_name("PPRM")));
+                }
+                aml_append(ifctx2, ifctx3);
+                /* 0: success */
+                aml_append(ifctx2, aml_return(aml_int(0)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            /* get user confirmation status for operation */
+            ifctx2 = aml_if(aml_equal(aml_local(0), aml_int(8)));
+            {
+                /* get opcode */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_arg(3),
+                                                           aml_int(0))),
+                                     aml_local(0)));
+                /* get opcode flags */
+                aml_append(ifctx2,
+                           aml_store(aml_derefof(aml_index(aml_name("FUNC"),
+                                                           aml_local(0))),
+                                     aml_local(1)));
+                /* return confirmation status code */
+                aml_append(ifctx2,
+                           aml_return(
+                               aml_shiftright(
+                                   aml_and(aml_local(1),
+                                           aml_int(TPM_PPI_FUNC_MASK),
+                                           NULL),
+                                   aml_int(3),
+                                   NULL)));
+            }
+            aml_append(ifctx, ifctx2);
+
+            aml_append(ifctx, aml_return(aml_buffer(1, zerobyte)));
+        }
+        aml_append(method, ifctx);
+    }
+    aml_append(dev, method);
+}
+
+static void
 build_dsdt(GArray *table_data, BIOSLinker *linker,
            AcpiPmInfo *pm, AcpiMiscInfo *misc,
            Range *pci_hole, Range *pci_hole64, MachineState *machine)
@@ -2218,6 +2489,7 @@  build_dsdt(GArray *table_data, BIOSLinker *linker,
                  */
                 /* aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ)); */
                 aml_append(dev, aml_name_decl("_CRS", crs));
+                build_tpm_ppi(dev, misc->tpm_version);
                 aml_append(scope, dev);
             }
 
@@ -2636,6 +2908,7 @@  static void build_qemu(GArray *table_data, BIOSLinker *linker,
     if (tpm_version != TPM_VERSION_UNSPEC) {
         qemu->tpmppi_addr = TPM_PPI_ADDR_BASE;
         qemu->tpm_version = tpm_version;
+        qemu->tpmppi_version = TPM_PPI_VERSION_1_30;
     }
 
     build_header(linker, table_data,
diff --git a/include/hw/acpi/acpi-defs.h b/include/hw/acpi/acpi-defs.h
index 98764c1..a182194 100644
--- a/include/hw/acpi/acpi-defs.h
+++ b/include/hw/acpi/acpi-defs.h
@@ -578,6 +578,8 @@  struct AcpiTableQemu {
     ACPI_TABLE_HEADER_DEF
     uint32_t tpmppi_addr;
     uint8_t tpm_version; /* 1 = 1.2, 2 = 2 */
+    uint8_t tpmppi_version;
+#define TPM_PPI_VERSION_1_30  1
 };
 typedef struct AcpiTableQemu AcpiTableQemu;
 
diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
index 16227bc..130a039 100644
--- a/include/hw/acpi/tpm.h
+++ b/include/hw/acpi/tpm.h
@@ -37,4 +37,35 @@ 
 #define TPM_PPI_ADDR_SIZE           0x400
 #define TPM_PPI_ADDR_BASE           0xfffef000
 
+struct tpm_ppi {
+    uint8_t ppin;            /*  0: set by BIOS */
+    uint32_t ppip;           /*  1: set by ACPI; not used */
+    uint32_t pprp;           /*  5: response from TPM; set by BIOS */
+    uint32_t pprq;           /*  9: opcode; set by ACPI */
+    uint32_t pprm;           /* 13: parameter for opcode; set by ACPI */
+    uint32_t lppr;           /* 17: last opcode; set by BIOS */
+    uint32_t fret;           /* 21: set by ACPI; not used */
+    uint8_t res1;            /* 25: reserved */
+    uint32_t res2[4];        /* 26: reserved */
+    uint8_t  res3[214];      /* 42: reserved */
+    uint8_t  func[256];      /* 256: per TPM function implementation flags;
+                                     set by BIOS */
+/* indication whether function is implemented; bit 0 */
+#define TPM_PPI_FUNC_IMPLEMENTED       (1 << 0)
+/* actions OS should take to transition to the pre-OS env.; bits 1, 2 */
+#define TPM_PPI_FUNC_ACTION_SHUTDOWN   (1 << 1)
+#define TPM_PPI_FUNC_ACTION_REBOOT     (2 << 1)
+#define TPM_PPI_FUNC_ACTION_VENDOR     (3 << 1)
+#define TPM_PPI_FUNC_ACTION_MASK       (3 << 1)
+/* whether function is blocked by BIOS settings; bits 3,4,5 */
+#define TPM_PPI_FUNC_NOT_IMPLEMENTED     (0 << 3)
+#define TPM_PPI_FUNC_BIOS_ONLY           (1 << 3)
+#define TPM_PPI_FUNC_BLOCKED             (2 << 3)
+#define TPM_PPI_FUNC_ALLOWED_USR_REQ     (3 << 3)
+#define TPM_PPI_FUNC_ALLOWED_USR_NOT_REQ (4 << 3)
+#define TPM_PPI_FUNC_MASK                (7 << 3)
+} QEMU_PACKED;
+
+#define TPM_PPI_STRUCT_SIZE  sizeof(struct tpm_ppi)
+
 #endif /* HW_ACPI_TPM_H */