| /* | 
 |  * tpm_tis_common.c - QEMU's TPM TIS interface emulator | 
 |  * device agnostic functions | 
 |  * | 
 |  * Copyright (C) 2006,2010-2013 IBM Corporation | 
 |  * | 
 |  * Authors: | 
 |  *  Stefan Berger <stefanb@us.ibm.com> | 
 |  *  David Safford <safford@us.ibm.com> | 
 |  * | 
 |  * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at> | 
 |  * | 
 |  * This work is licensed under the terms of the GNU GPL, version 2 or later. | 
 |  * See the COPYING file in the top-level directory. | 
 |  * | 
 |  * Implementation of the TIS interface according to specs found at | 
 |  * http://www.trustedcomputinggroup.org. This implementation currently | 
 |  * supports version 1.3, 21 March 2013 | 
 |  * In the developers menu choose the PC Client section then find the TIS | 
 |  * specification. | 
 |  * | 
 |  * TPM TIS for TPM 2 implementation following TCG PC Client Platform | 
 |  * TPM Profile (PTP) Specification, Family 2.0, Revision 00.43 | 
 |  */ | 
 | #include "qemu/osdep.h" | 
 | #include "hw/irq.h" | 
 | #include "hw/isa/isa.h" | 
 | #include "qapi/error.h" | 
 | #include "qemu/bswap.h" | 
 | #include "qemu/crc-ccitt.h" | 
 | #include "qemu/module.h" | 
 |  | 
 | #include "hw/acpi/tpm.h" | 
 | #include "hw/pci/pci_ids.h" | 
 | #include "hw/qdev-properties.h" | 
 | #include "migration/vmstate.h" | 
 | #include "sysemu/tpm_backend.h" | 
 | #include "sysemu/tpm_util.h" | 
 | #include "tpm_ppi.h" | 
 | #include "trace.h" | 
 |  | 
 | #include "tpm_tis.h" | 
 |  | 
 | #define DEBUG_TIS 0 | 
 |  | 
 | /* local prototypes */ | 
 |  | 
 | static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr, | 
 |                                   unsigned size); | 
 |  | 
 | /* utility functions */ | 
 |  | 
 | static uint8_t tpm_tis_locality_from_addr(hwaddr addr) | 
 | { | 
 |     uint8_t locty; | 
 |  | 
 |     locty = (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7); | 
 |     assert(TPM_TIS_IS_VALID_LOCTY(locty)); | 
 |  | 
 |     return locty; | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * Set the given flags in the STS register by clearing the register but | 
 |  * preserving the SELFTEST_DONE and TPM_FAMILY_MASK flags and then setting | 
 |  * the new flags. | 
 |  * | 
 |  * The SELFTEST_DONE flag is acquired from the backend that determines it by | 
 |  * peeking into TPM commands. | 
 |  * | 
 |  * A VM suspend/resume will preserve the flag by storing it into the VM | 
 |  * device state, but the backend will not remember it when QEMU is started | 
 |  * again. Therefore, we cache the flag here. Once set, it will not be unset | 
 |  * except by a reset. | 
 |  */ | 
 | static void tpm_tis_sts_set(TPMLocality *l, uint32_t flags) | 
 | { | 
 |     l->sts &= TPM_TIS_STS_SELFTEST_DONE | TPM_TIS_STS_TPM_FAMILY_MASK; | 
 |     l->sts |= flags; | 
 | } | 
 |  | 
 | /* | 
 |  * Send a request to the TPM. | 
 |  */ | 
 | static void tpm_tis_tpm_send(TPMState *s, uint8_t locty) | 
 | { | 
 |     tpm_util_show_buffer(s->buffer, s->be_buffer_size, "To TPM"); | 
 |  | 
 |     /* | 
 |      * rw_offset serves as length indicator for length of data; | 
 |      * it's reset when the response comes back | 
 |      */ | 
 |     s->loc[locty].state = TPM_TIS_STATE_EXECUTION; | 
 |  | 
 |     s->cmd = (TPMBackendCmd) { | 
 |         .locty = locty, | 
 |         .in = s->buffer, | 
 |         .in_len = s->rw_offset, | 
 |         .out = s->buffer, | 
 |         .out_len = s->be_buffer_size, | 
 |     }; | 
 |  | 
 |     tpm_backend_deliver_request(s->be_driver, &s->cmd); | 
 | } | 
 |  | 
 | /* raise an interrupt if allowed */ | 
 | static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask) | 
 | { | 
 |     if (!TPM_TIS_IS_VALID_LOCTY(locty)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     if ((s->loc[locty].inte & TPM_TIS_INT_ENABLED) && | 
 |         (s->loc[locty].inte & irqmask)) { | 
 |         trace_tpm_tis_raise_irq(irqmask); | 
 |         qemu_irq_raise(s->irq); | 
 |         s->loc[locty].ints |= irqmask; | 
 |     } | 
 | } | 
 |  | 
 | static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty) | 
 | { | 
 |     uint8_t l; | 
 |  | 
 |     for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) { | 
 |         if (l == locty) { | 
 |             continue; | 
 |         } | 
 |         if ((s->loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) { | 
 |             return 1; | 
 |         } | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty) | 
 | { | 
 |     bool change = (s->active_locty != new_active_locty); | 
 |     bool is_seize; | 
 |     uint8_t mask; | 
 |  | 
 |     if (change && TPM_TIS_IS_VALID_LOCTY(s->active_locty)) { | 
 |         is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) && | 
 |                    s->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE; | 
 |  | 
 |         if (is_seize) { | 
 |             mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY); | 
 |         } else { | 
 |             mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY| | 
 |                      TPM_TIS_ACCESS_REQUEST_USE); | 
 |         } | 
 |         /* reset flags on the old active locality */ | 
 |         s->loc[s->active_locty].access &= mask; | 
 |  | 
 |         if (is_seize) { | 
 |             s->loc[s->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED; | 
 |         } | 
 |     } | 
 |  | 
 |     s->active_locty = new_active_locty; | 
 |  | 
 |     trace_tpm_tis_new_active_locality(s->active_locty); | 
 |  | 
 |     if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) { | 
 |         /* set flags on the new active locality */ | 
 |         s->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY; | 
 |         s->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE | | 
 |                                                TPM_TIS_ACCESS_SEIZE); | 
 |     } | 
 |  | 
 |     if (change) { | 
 |         tpm_tis_raise_irq(s, s->active_locty, TPM_TIS_INT_LOCALITY_CHANGED); | 
 |     } | 
 | } | 
 |  | 
 | /* abort -- this function switches the locality */ | 
 | static void tpm_tis_abort(TPMState *s) | 
 | { | 
 |     s->rw_offset = 0; | 
 |  | 
 |     trace_tpm_tis_abort(s->next_locty); | 
 |  | 
 |     /* | 
 |      * Need to react differently depending on who's aborting now and | 
 |      * which locality will become active afterwards. | 
 |      */ | 
 |     if (s->aborting_locty == s->next_locty) { | 
 |         s->loc[s->aborting_locty].state = TPM_TIS_STATE_READY; | 
 |         tpm_tis_sts_set(&s->loc[s->aborting_locty], | 
 |                         TPM_TIS_STS_COMMAND_READY); | 
 |         tpm_tis_raise_irq(s, s->aborting_locty, TPM_TIS_INT_COMMAND_READY); | 
 |     } | 
 |  | 
 |     /* locality after abort is another one than the current one */ | 
 |     tpm_tis_new_active_locality(s, s->next_locty); | 
 |  | 
 |     s->next_locty = TPM_TIS_NO_LOCALITY; | 
 |     /* nobody's aborting a command anymore */ | 
 |     s->aborting_locty = TPM_TIS_NO_LOCALITY; | 
 | } | 
 |  | 
 | /* prepare aborting current command */ | 
 | static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty) | 
 | { | 
 |     uint8_t busy_locty; | 
 |  | 
 |     assert(TPM_TIS_IS_VALID_LOCTY(newlocty)); | 
 |  | 
 |     s->aborting_locty = locty; /* may also be TPM_TIS_NO_LOCALITY */ | 
 |     s->next_locty = newlocty;  /* locality after successful abort */ | 
 |  | 
 |     /* | 
 |      * only abort a command using an interrupt if currently executing | 
 |      * a command AND if there's a valid connection to the vTPM. | 
 |      */ | 
 |     for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) { | 
 |         if (s->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) { | 
 |             /* | 
 |              * request the backend to cancel. Some backends may not | 
 |              * support it | 
 |              */ | 
 |             tpm_backend_cancel_cmd(s->be_driver); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     tpm_tis_abort(s); | 
 | } | 
 |  | 
 | /* | 
 |  * Callback from the TPM to indicate that the response was received. | 
 |  */ | 
 | void tpm_tis_request_completed(TPMState *s, int ret) | 
 | { | 
 |     uint8_t locty = s->cmd.locty; | 
 |     uint8_t l; | 
 |  | 
 |     assert(TPM_TIS_IS_VALID_LOCTY(locty)); | 
 |  | 
 |     if (s->cmd.selftest_done) { | 
 |         for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) { | 
 |             s->loc[l].sts |= TPM_TIS_STS_SELFTEST_DONE; | 
 |         } | 
 |     } | 
 |  | 
 |     /* FIXME: report error if ret != 0 */ | 
 |     tpm_tis_sts_set(&s->loc[locty], | 
 |                     TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE); | 
 |     s->loc[locty].state = TPM_TIS_STATE_COMPLETION; | 
 |     s->rw_offset = 0; | 
 |  | 
 |     tpm_util_show_buffer(s->buffer, s->be_buffer_size, "From TPM"); | 
 |  | 
 |     if (TPM_TIS_IS_VALID_LOCTY(s->next_locty)) { | 
 |         tpm_tis_abort(s); | 
 |     } | 
 |  | 
 |     tpm_tis_raise_irq(s, locty, | 
 |                       TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID); | 
 | } | 
 |  | 
 | /* | 
 |  * Read a byte of response data | 
 |  */ | 
 | static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty) | 
 | { | 
 |     uint32_t ret = TPM_TIS_NO_DATA_BYTE; | 
 |     uint16_t len; | 
 |  | 
 |     if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) { | 
 |         len = MIN(tpm_cmd_get_size(&s->buffer), | 
 |                   s->be_buffer_size); | 
 |  | 
 |         ret = s->buffer[s->rw_offset++]; | 
 |         if (s->rw_offset >= len) { | 
 |             /* got last byte */ | 
 |             tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID); | 
 |             tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID); | 
 |         } | 
 |         trace_tpm_tis_data_read(ret, s->rw_offset - 1); | 
 |     } | 
 |  | 
 |     return ret; | 
 | } | 
 |  | 
 | #ifdef DEBUG_TIS | 
 | static void tpm_tis_dump_state(TPMState *s, hwaddr addr) | 
 | { | 
 |     static const unsigned regs[] = { | 
 |         TPM_TIS_REG_ACCESS, | 
 |         TPM_TIS_REG_INT_ENABLE, | 
 |         TPM_TIS_REG_INT_VECTOR, | 
 |         TPM_TIS_REG_INT_STATUS, | 
 |         TPM_TIS_REG_INTF_CAPABILITY, | 
 |         TPM_TIS_REG_STS, | 
 |         TPM_TIS_REG_DID_VID, | 
 |         TPM_TIS_REG_RID, | 
 |         0xfff}; | 
 |     int idx; | 
 |     uint8_t locty = tpm_tis_locality_from_addr(addr); | 
 |     hwaddr base = addr & ~0xfff; | 
 |  | 
 |     printf("tpm_tis: active locality      : %d\n" | 
 |            "tpm_tis: state of locality %d : %d\n" | 
 |            "tpm_tis: register dump:\n", | 
 |            s->active_locty, | 
 |            locty, s->loc[locty].state); | 
 |  | 
 |     for (idx = 0; regs[idx] != 0xfff; idx++) { | 
 |         printf("tpm_tis: 0x%04x : 0x%08x\n", regs[idx], | 
 |                (int)tpm_tis_mmio_read(s, base + regs[idx], 4)); | 
 |     } | 
 |  | 
 |     printf("tpm_tis: r/w offset    : %d\n" | 
 |            "tpm_tis: result buffer : ", | 
 |            s->rw_offset); | 
 |     for (idx = 0; | 
 |          idx < MIN(tpm_cmd_get_size(&s->buffer), s->be_buffer_size); | 
 |          idx++) { | 
 |         printf("%c%02x%s", | 
 |                s->rw_offset == idx ? '>' : ' ', | 
 |                s->buffer[idx], | 
 |                ((idx & 0xf) == 0xf) ? "\ntpm_tis:                 " : ""); | 
 |     } | 
 |     printf("\n"); | 
 | } | 
 | #endif | 
 |  | 
 | /* | 
 |  * Read a register of the TIS interface | 
 |  * See specs pages 33-63 for description of the registers | 
 |  */ | 
 | static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr, | 
 |                                   unsigned size) | 
 | { | 
 |     TPMState *s = opaque; | 
 |     uint16_t offset = addr & 0xffc; | 
 |     uint8_t shift = (addr & 0x3) * 8; | 
 |     uint32_t val = 0xffffffff; | 
 |     uint8_t locty = tpm_tis_locality_from_addr(addr); | 
 |     uint32_t avail; | 
 |     uint8_t v; | 
 |  | 
 |     if (tpm_backend_had_startup_error(s->be_driver)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     switch (offset) { | 
 |     case TPM_TIS_REG_ACCESS: | 
 |         /* never show the SEIZE flag even though we use it internally */ | 
 |         val = s->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE; | 
 |         /* the pending flag is always calculated */ | 
 |         if (tpm_tis_check_request_use_except(s, locty)) { | 
 |             val |= TPM_TIS_ACCESS_PENDING_REQUEST; | 
 |         } | 
 |         val |= !tpm_backend_get_tpm_established_flag(s->be_driver); | 
 |         break; | 
 |     case TPM_TIS_REG_INT_ENABLE: | 
 |         val = s->loc[locty].inte; | 
 |         break; | 
 |     case TPM_TIS_REG_INT_VECTOR: | 
 |         val = s->irq_num; | 
 |         break; | 
 |     case TPM_TIS_REG_INT_STATUS: | 
 |         val = s->loc[locty].ints; | 
 |         break; | 
 |     case TPM_TIS_REG_INTF_CAPABILITY: | 
 |         switch (s->be_tpm_version) { | 
 |         case TPM_VERSION_UNSPEC: | 
 |             val = 0; | 
 |             break; | 
 |         case TPM_VERSION_1_2: | 
 |             val = TPM_TIS_CAPABILITIES_SUPPORTED1_3; | 
 |             break; | 
 |         case TPM_VERSION_2_0: | 
 |             val = TPM_TIS_CAPABILITIES_SUPPORTED2_0; | 
 |             break; | 
 |         } | 
 |         break; | 
 |     case TPM_TIS_REG_STS: | 
 |         if (s->active_locty == locty) { | 
 |             if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) { | 
 |                 val = TPM_TIS_BURST_COUNT( | 
 |                        MIN(tpm_cmd_get_size(&s->buffer), | 
 |                            s->be_buffer_size) | 
 |                        - s->rw_offset) | s->loc[locty].sts; | 
 |             } else { | 
 |                 avail = s->be_buffer_size - s->rw_offset; | 
 |                 /* | 
 |                  * byte-sized reads should not return 0x00 for 0x100 | 
 |                  * available bytes. | 
 |                  */ | 
 |                 if (size == 1 && avail > 0xff) { | 
 |                     avail = 0xff; | 
 |                 } | 
 |                 val = TPM_TIS_BURST_COUNT(avail) | s->loc[locty].sts; | 
 |             } | 
 |         } | 
 |         break; | 
 |     case TPM_TIS_REG_DATA_FIFO: | 
 |     case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END: | 
 |         if (s->active_locty == locty) { | 
 |             if (size > 4 - (addr & 0x3)) { | 
 |                 /* prevent access beyond FIFO */ | 
 |                 size = 4 - (addr & 0x3); | 
 |             } | 
 |             val = 0; | 
 |             shift = 0; | 
 |             while (size > 0) { | 
 |                 switch (s->loc[locty].state) { | 
 |                 case TPM_TIS_STATE_COMPLETION: | 
 |                     v = tpm_tis_data_read(s, locty); | 
 |                     break; | 
 |                 default: | 
 |                     v = TPM_TIS_NO_DATA_BYTE; | 
 |                     break; | 
 |                 } | 
 |                 val |= (v << shift); | 
 |                 shift += 8; | 
 |                 size--; | 
 |             } | 
 |             shift = 0; /* no more adjustments */ | 
 |         } | 
 |         break; | 
 |     case TPM_TIS_REG_INTERFACE_ID: | 
 |         val = s->loc[locty].iface_id; | 
 |         break; | 
 |     case TPM_TIS_REG_DID_VID: | 
 |         val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID; | 
 |         break; | 
 |     case TPM_TIS_REG_RID: | 
 |         val = TPM_TIS_TPM_RID; | 
 |         break; | 
 | #ifdef DEBUG_TIS | 
 |     case TPM_TIS_REG_DEBUG: | 
 |         tpm_tis_dump_state(s, addr); | 
 |         break; | 
 | #endif | 
 |     } | 
 |  | 
 |     if (shift) { | 
 |         val >>= shift; | 
 |     } | 
 |  | 
 |     trace_tpm_tis_mmio_read(size, addr, val); | 
 |  | 
 |     return val; | 
 | } | 
 |  | 
 | /* | 
 |  * A wrapper read function so that it can be directly called without | 
 |  * mmio. | 
 |  */ | 
 | uint32_t tpm_tis_read_data(TPMState *s, hwaddr addr, unsigned size) | 
 | { | 
 |     return tpm_tis_mmio_read(s, addr, size); | 
 | } | 
 |  | 
 | /* | 
 |  * Calculate current data buffer checksum | 
 |  */ | 
 | uint16_t tpm_tis_get_checksum(TPMState *s) | 
 | { | 
 |     return bswap16(crc_ccitt(0, s->buffer, s->rw_offset)); | 
 | } | 
 |  | 
 | /* | 
 |  * Write a value to a register of the TIS interface | 
 |  * See specs pages 33-63 for description of the registers | 
 |  */ | 
 | static void tpm_tis_mmio_write(void *opaque, hwaddr addr, | 
 |                                uint64_t val, unsigned size) | 
 | { | 
 |     TPMState *s = opaque; | 
 |     uint16_t off = addr & 0xffc; | 
 |     uint8_t shift = (addr & 0x3) * 8; | 
 |     uint8_t locty = tpm_tis_locality_from_addr(addr); | 
 |     uint8_t active_locty, l; | 
 |     int c, set_new_locty = 1; | 
 |     uint16_t len; | 
 |     uint32_t mask = (size == 1) ? 0xff : ((size == 2) ? 0xffff : ~0); | 
 |  | 
 |     trace_tpm_tis_mmio_write(size, addr, val); | 
 |  | 
 |     if (locty == 4) { | 
 |         trace_tpm_tis_mmio_write_locty4(); | 
 |         return; | 
 |     } | 
 |  | 
 |     if (tpm_backend_had_startup_error(s->be_driver)) { | 
 |         return; | 
 |     } | 
 |  | 
 |     val &= mask; | 
 |  | 
 |     if (shift) { | 
 |         val <<= shift; | 
 |         mask <<= shift; | 
 |     } | 
 |  | 
 |     mask ^= 0xffffffff; | 
 |  | 
 |     switch (off) { | 
 |     case TPM_TIS_REG_ACCESS: | 
 |  | 
 |         if ((val & TPM_TIS_ACCESS_SEIZE)) { | 
 |             val &= ~(TPM_TIS_ACCESS_REQUEST_USE | | 
 |                      TPM_TIS_ACCESS_ACTIVE_LOCALITY); | 
 |         } | 
 |  | 
 |         active_locty = s->active_locty; | 
 |  | 
 |         if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) { | 
 |             /* give up locality if currently owned */ | 
 |             if (s->active_locty == locty) { | 
 |                 trace_tpm_tis_mmio_write_release_locty(locty); | 
 |  | 
 |                 uint8_t newlocty = TPM_TIS_NO_LOCALITY; | 
 |                 /* anybody wants the locality ? */ | 
 |                 for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) { | 
 |                     if ((s->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) { | 
 |                         trace_tpm_tis_mmio_write_locty_req_use(c); | 
 |                         newlocty = c; | 
 |                         break; | 
 |                     } | 
 |                 } | 
 |                 trace_tpm_tis_mmio_write_next_locty(newlocty); | 
 |  | 
 |                 if (TPM_TIS_IS_VALID_LOCTY(newlocty)) { | 
 |                     set_new_locty = 0; | 
 |                     tpm_tis_prep_abort(s, locty, newlocty); | 
 |                 } else { | 
 |                     active_locty = TPM_TIS_NO_LOCALITY; | 
 |                 } | 
 |             } else { | 
 |                 /* not currently the owner; clear a pending request */ | 
 |                 s->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE; | 
 |             } | 
 |         } | 
 |  | 
 |         if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) { | 
 |             s->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED; | 
 |         } | 
 |  | 
 |         if ((val & TPM_TIS_ACCESS_SEIZE)) { | 
 |             /* | 
 |              * allow seize if a locality is active and the requesting | 
 |              * locality is higher than the one that's active | 
 |              * OR | 
 |              * allow seize for requesting locality if no locality is | 
 |              * active | 
 |              */ | 
 |             while ((TPM_TIS_IS_VALID_LOCTY(s->active_locty) && | 
 |                     locty > s->active_locty) || | 
 |                     !TPM_TIS_IS_VALID_LOCTY(s->active_locty)) { | 
 |                 bool higher_seize = false; | 
 |  | 
 |                 /* already a pending SEIZE ? */ | 
 |                 if ((s->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) { | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 /* check for ongoing seize by a higher locality */ | 
 |                 for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) { | 
 |                     if ((s->loc[l].access & TPM_TIS_ACCESS_SEIZE)) { | 
 |                         higher_seize = true; | 
 |                         break; | 
 |                     } | 
 |                 } | 
 |  | 
 |                 if (higher_seize) { | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 /* cancel any seize by a lower locality */ | 
 |                 for (l = 0; l < locty; l++) { | 
 |                     s->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE; | 
 |                 } | 
 |  | 
 |                 s->loc[locty].access |= TPM_TIS_ACCESS_SEIZE; | 
 |  | 
 |                 trace_tpm_tis_mmio_write_locty_seized(locty, s->active_locty); | 
 |                 trace_tpm_tis_mmio_write_init_abort(); | 
 |  | 
 |                 set_new_locty = 0; | 
 |                 tpm_tis_prep_abort(s, s->active_locty, locty); | 
 |                 break; | 
 |             } | 
 |         } | 
 |  | 
 |         if ((val & TPM_TIS_ACCESS_REQUEST_USE)) { | 
 |             if (s->active_locty != locty) { | 
 |                 if (TPM_TIS_IS_VALID_LOCTY(s->active_locty)) { | 
 |                     s->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE; | 
 |                 } else { | 
 |                     /* no locality active -> make this one active now */ | 
 |                     active_locty = locty; | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         if (set_new_locty) { | 
 |             tpm_tis_new_active_locality(s, active_locty); | 
 |         } | 
 |  | 
 |         break; | 
 |     case TPM_TIS_REG_INT_ENABLE: | 
 |         s->loc[locty].inte &= mask; | 
 |         s->loc[locty].inte |= (val & (TPM_TIS_INT_ENABLED | | 
 |                                         TPM_TIS_INT_POLARITY_MASK | | 
 |                                         TPM_TIS_INTERRUPTS_SUPPORTED)); | 
 |         break; | 
 |     case TPM_TIS_REG_INT_VECTOR: | 
 |         /* hard wired -- ignore */ | 
 |         break; | 
 |     case TPM_TIS_REG_INT_STATUS: | 
 |         /* clearing of interrupt flags */ | 
 |         if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) && | 
 |             (s->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) { | 
 |             s->loc[locty].ints &= ~val; | 
 |             if (s->loc[locty].ints == 0) { | 
 |                 qemu_irq_lower(s->irq); | 
 |                 trace_tpm_tis_mmio_write_lowering_irq(); | 
 |             } | 
 |         } | 
 |         s->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED); | 
 |         break; | 
 |     case TPM_TIS_REG_STS: | 
 |         if (s->active_locty != locty) { | 
 |             break; | 
 |         } | 
 |  | 
 |         if (s->be_tpm_version == TPM_VERSION_2_0) { | 
 |             /* some flags that are only supported for TPM 2 */ | 
 |             if (val & TPM_TIS_STS_COMMAND_CANCEL) { | 
 |                 if (s->loc[locty].state == TPM_TIS_STATE_EXECUTION) { | 
 |                     /* | 
 |                      * request the backend to cancel. Some backends may not | 
 |                      * support it | 
 |                      */ | 
 |                     tpm_backend_cancel_cmd(s->be_driver); | 
 |                 } | 
 |             } | 
 |  | 
 |             if (val & TPM_TIS_STS_RESET_ESTABLISHMENT_BIT) { | 
 |                 if (locty == 3 || locty == 4) { | 
 |                     tpm_backend_reset_tpm_established_flag(s->be_driver, locty); | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO | | 
 |                 TPM_TIS_STS_RESPONSE_RETRY); | 
 |  | 
 |         if (val == TPM_TIS_STS_COMMAND_READY) { | 
 |             switch (s->loc[locty].state) { | 
 |  | 
 |             case TPM_TIS_STATE_READY: | 
 |                 s->rw_offset = 0; | 
 |             break; | 
 |  | 
 |             case TPM_TIS_STATE_IDLE: | 
 |                 tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_COMMAND_READY); | 
 |                 s->loc[locty].state = TPM_TIS_STATE_READY; | 
 |                 tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY); | 
 |             break; | 
 |  | 
 |             case TPM_TIS_STATE_EXECUTION: | 
 |             case TPM_TIS_STATE_RECEPTION: | 
 |                 /* abort currently running command */ | 
 |                 trace_tpm_tis_mmio_write_init_abort(); | 
 |                 tpm_tis_prep_abort(s, locty, locty); | 
 |             break; | 
 |  | 
 |             case TPM_TIS_STATE_COMPLETION: | 
 |                 s->rw_offset = 0; | 
 |                 /* shortcut to ready state with C/R set */ | 
 |                 s->loc[locty].state = TPM_TIS_STATE_READY; | 
 |                 if (!(s->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) { | 
 |                     tpm_tis_sts_set(&s->loc[locty], | 
 |                                     TPM_TIS_STS_COMMAND_READY); | 
 |                     tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY); | 
 |                 } | 
 |                 s->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE); | 
 |             break; | 
 |  | 
 |             } | 
 |         } else if (val == TPM_TIS_STS_TPM_GO) { | 
 |             switch (s->loc[locty].state) { | 
 |             case TPM_TIS_STATE_RECEPTION: | 
 |                 if ((s->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) { | 
 |                     tpm_tis_tpm_send(s, locty); | 
 |                 } | 
 |                 break; | 
 |             default: | 
 |                 /* ignore */ | 
 |                 break; | 
 |             } | 
 |         } else if (val == TPM_TIS_STS_RESPONSE_RETRY) { | 
 |             switch (s->loc[locty].state) { | 
 |             case TPM_TIS_STATE_COMPLETION: | 
 |                 s->rw_offset = 0; | 
 |                 tpm_tis_sts_set(&s->loc[locty], | 
 |                                 TPM_TIS_STS_VALID| | 
 |                                 TPM_TIS_STS_DATA_AVAILABLE); | 
 |                 break; | 
 |             default: | 
 |                 /* ignore */ | 
 |                 break; | 
 |             } | 
 |         } | 
 |         break; | 
 |     case TPM_TIS_REG_DATA_FIFO: | 
 |     case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END: | 
 |         /* data fifo */ | 
 |         if (s->active_locty != locty) { | 
 |             break; | 
 |         } | 
 |  | 
 |         if (s->loc[locty].state == TPM_TIS_STATE_IDLE || | 
 |             s->loc[locty].state == TPM_TIS_STATE_EXECUTION || | 
 |             s->loc[locty].state == TPM_TIS_STATE_COMPLETION) { | 
 |             /* drop the byte */ | 
 |         } else { | 
 |             trace_tpm_tis_mmio_write_data2send(val, size); | 
 |             if (s->loc[locty].state == TPM_TIS_STATE_READY) { | 
 |                 s->loc[locty].state = TPM_TIS_STATE_RECEPTION; | 
 |                 tpm_tis_sts_set(&s->loc[locty], | 
 |                                 TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID); | 
 |             } | 
 |  | 
 |             val >>= shift; | 
 |             if (size > 4 - (addr & 0x3)) { | 
 |                 /* prevent access beyond FIFO */ | 
 |                 size = 4 - (addr & 0x3); | 
 |             } | 
 |  | 
 |             while ((s->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) { | 
 |                 if (s->rw_offset < s->be_buffer_size) { | 
 |                     s->buffer[s->rw_offset++] = | 
 |                         (uint8_t)val; | 
 |                     val >>= 8; | 
 |                     size--; | 
 |                 } else { | 
 |                     tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID); | 
 |                 } | 
 |             } | 
 |  | 
 |             /* check for complete packet */ | 
 |             if (s->rw_offset > 5 && | 
 |                 (s->loc[locty].sts & TPM_TIS_STS_EXPECT)) { | 
 |                 /* we have a packet length - see if we have all of it */ | 
 |                 bool need_irq = !(s->loc[locty].sts & TPM_TIS_STS_VALID); | 
 |  | 
 |                 len = tpm_cmd_get_size(&s->buffer); | 
 |                 if (len > s->rw_offset) { | 
 |                     tpm_tis_sts_set(&s->loc[locty], | 
 |                                     TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID); | 
 |                 } else { | 
 |                     /* packet complete */ | 
 |                     tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID); | 
 |                 } | 
 |                 if (need_irq) { | 
 |                     tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID); | 
 |                 } | 
 |             } | 
 |         } | 
 |         break; | 
 |     case TPM_TIS_REG_INTERFACE_ID: | 
 |         if (val & TPM_TIS_IFACE_ID_INT_SEL_LOCK) { | 
 |             for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) { | 
 |                 s->loc[l].iface_id |= TPM_TIS_IFACE_ID_INT_SEL_LOCK; | 
 |             } | 
 |         } | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | /* | 
 |  * A wrapper write function so that it can be directly called without | 
 |  * mmio. | 
 |  */ | 
 | void tpm_tis_write_data(TPMState *s, hwaddr addr, uint64_t val, uint32_t size) | 
 | { | 
 |     tpm_tis_mmio_write(s, addr, val, size); | 
 | } | 
 |  | 
 | const MemoryRegionOps tpm_tis_memory_ops = { | 
 |     .read = tpm_tis_mmio_read, | 
 |     .write = tpm_tis_mmio_write, | 
 |     .endianness = DEVICE_LITTLE_ENDIAN, | 
 |     .valid = { | 
 |         .min_access_size = 1, | 
 |         .max_access_size = 4, | 
 |     }, | 
 | }; | 
 |  | 
 | /* | 
 |  * Get the TPMVersion of the backend device being used | 
 |  */ | 
 | enum TPMVersion tpm_tis_get_tpm_version(TPMState *s) | 
 | { | 
 |     if (tpm_backend_had_startup_error(s->be_driver)) { | 
 |         return TPM_VERSION_UNSPEC; | 
 |     } | 
 |  | 
 |     return tpm_backend_get_tpm_version(s->be_driver); | 
 | } | 
 |  | 
 | /* | 
 |  * This function is called when the machine starts, resets or due to | 
 |  * S3 resume. | 
 |  */ | 
 | void tpm_tis_reset(TPMState *s) | 
 | { | 
 |     int c; | 
 |  | 
 |     s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver); | 
 |     s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->be_driver), | 
 |                             TPM_TIS_BUFFER_MAX); | 
 |  | 
 |     if (s->ppi_enabled) { | 
 |         tpm_ppi_reset(&s->ppi); | 
 |     } | 
 |     tpm_backend_reset(s->be_driver); | 
 |  | 
 |     s->active_locty = TPM_TIS_NO_LOCALITY; | 
 |     s->next_locty = TPM_TIS_NO_LOCALITY; | 
 |     s->aborting_locty = TPM_TIS_NO_LOCALITY; | 
 |  | 
 |     for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) { | 
 |         s->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS; | 
 |         switch (s->be_tpm_version) { | 
 |         case TPM_VERSION_UNSPEC: | 
 |             break; | 
 |         case TPM_VERSION_1_2: | 
 |             s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY1_2; | 
 |             s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3; | 
 |             break; | 
 |         case TPM_VERSION_2_0: | 
 |             s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY2_0; | 
 |             s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0; | 
 |             break; | 
 |         } | 
 |         s->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL; | 
 |         s->loc[c].ints = 0; | 
 |         s->loc[c].state = TPM_TIS_STATE_IDLE; | 
 |  | 
 |         s->rw_offset = 0; | 
 |     } | 
 |  | 
 |     if (tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size) < 0) { | 
 |         exit(1); | 
 |     } | 
 | } | 
 |  | 
 | /* persistent state handling */ | 
 |  | 
 | int tpm_tis_pre_save(TPMState *s) | 
 | { | 
 |     uint8_t locty = s->active_locty; | 
 |  | 
 |     trace_tpm_tis_pre_save(locty, s->rw_offset); | 
 |  | 
 |     if (DEBUG_TIS) { | 
 |         tpm_tis_dump_state(s, 0); | 
 |     } | 
 |  | 
 |     /* | 
 |      * Synchronize with backend completion. | 
 |      */ | 
 |     tpm_backend_finish_sync(s->be_driver); | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | const VMStateDescription vmstate_locty = { | 
 |     .name = "tpm-tis/locty", | 
 |     .version_id = 0, | 
 |     .fields      = (VMStateField[]) { | 
 |         VMSTATE_UINT32(state, TPMLocality), | 
 |         VMSTATE_UINT32(inte, TPMLocality), | 
 |         VMSTATE_UINT32(ints, TPMLocality), | 
 |         VMSTATE_UINT8(access, TPMLocality), | 
 |         VMSTATE_UINT32(sts, TPMLocality), | 
 |         VMSTATE_UINT32(iface_id, TPMLocality), | 
 |         VMSTATE_END_OF_LIST(), | 
 |     } | 
 | }; | 
 |  |