static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty) { TPMTISEmuState *tis = &s->s.tis; bool change = (s->s.tis.active_locty != new_active_locty); bool is_seize; uint8_t mask; if (change && TPM_TIS_IS_VALID_LOCTY(s->s.tis.active_locty)) { is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) && tis->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 */ tis->loc[s->s.tis.active_locty].access &= mask; if (is_seize) { tis->loc[tis->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED; } } tis->active_locty = new_active_locty; DPRINTF("tpm_tis: Active locality is now %d\n", s->s.tis.active_locty); if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) { /* set flags on the new active locality */ tis->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY; tis->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE | TPM_TIS_ACCESS_SEIZE); } if (change) { tpm_tis_raise_irq(s, tis->active_locty, TPM_TIS_INT_LOCALITY_CHANGED); } }
/* raise an interrupt if allowed */ static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask) { TPMTISEmuState *tis = &s->s.tis; if (!TPM_TIS_IS_VALID_LOCTY(locty)) { return; } if ((tis->loc[locty].inte & TPM_TIS_INT_ENABLED) && (tis->loc[locty].inte & irqmask)) { DPRINTF("tpm_tis: Raising IRQ for flag %08x\n", irqmask); qemu_irq_raise(s->s.tis.irq); tis->loc[locty].ints |= irqmask; } }
static void tpm_tis_receive_bh(void *opaque) { TPMState *s = opaque; TPMTISEmuState *tis = &s->s.tis; uint8_t locty = s->locty_number; tis->loc[locty].sts = TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE; tis->loc[locty].state = TPM_TIS_STATE_COMPLETION; tis->loc[locty].r_offset = 0; tis->loc[locty].w_offset = 0; if (TPM_TIS_IS_VALID_LOCTY(tis->next_locty)) { tpm_tis_abort(s, locty); } #ifndef RAISE_STS_IRQ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE); #else tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID); #endif }
/* * 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_intern(void *opaque, hwaddr addr, uint64_t val, unsigned size, bool hw_access) { TPMState *s = opaque; TPMTISEmuState *tis = &s->s.tis; 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); DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (int)val); if (locty == 4 && !hw_access) { DPRINTF("tpm_tis: Access to locality 4 only allowed from hardware\n"); 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 = tis->active_locty; if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) { /* give up locality if currently owned */ if (tis->active_locty == locty) { DPRINTF("tpm_tis: Releasing locality %d\n", locty); uint8_t newlocty = TPM_TIS_NO_LOCALITY; /* anybody wants the locality ? */ for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) { if ((tis->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) { DPRINTF("tpm_tis: Locality %d requests use.\n", c); newlocty = c; break; } } DPRINTF("tpm_tis: TPM_TIS_ACCESS_ACTIVE_LOCALITY: " "Next active locality: %d\n", 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 */ tis->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE; } } if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) { tis->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(tis->active_locty) && locty > tis->active_locty) || !TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) { bool higher_seize = FALSE; /* already a pending SEIZE ? */ if ((tis->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 ((tis->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 - 1; l++) { tis->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE; } tis->loc[locty].access |= TPM_TIS_ACCESS_SEIZE; DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: " "Locality %d seized from locality %d\n", locty, tis->active_locty); DPRINTF("tpm_tis: TPM_TIS_ACCESS_SEIZE: Initiating abort.\n"); set_new_locty = 0; tpm_tis_prep_abort(s, tis->active_locty, locty); break; } } if ((val & TPM_TIS_ACCESS_REQUEST_USE)) { if (tis->active_locty != locty) { if (TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) { tis->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: if (tis->active_locty != locty) { break; } tis->loc[locty].inte &= mask; tis->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: if (tis->active_locty != locty) { break; } /* clearing of interrupt flags */ if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) && (tis->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) { tis->loc[locty].ints &= ~val; if (tis->loc[locty].ints == 0) { qemu_irq_lower(tis->irq); DPRINTF("tpm_tis: Lowering IRQ\n"); } } tis->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED); break; case TPM_TIS_REG_STS: if (tis->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 (tis->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 (tis->loc[locty].state) { case TPM_TIS_STATE_READY: tis->loc[locty].w_offset = 0; tis->loc[locty].r_offset = 0; break; case TPM_TIS_STATE_IDLE: tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_COMMAND_READY); tis->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 */ DPRINTF("tpm_tis: %s: Initiating abort.\n", __func__); tpm_tis_prep_abort(s, locty, locty); break; case TPM_TIS_STATE_COMPLETION: tis->loc[locty].w_offset = 0; tis->loc[locty].r_offset = 0; /* shortcut to ready state with C/R set */ tis->loc[locty].state = TPM_TIS_STATE_READY; if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) { tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_COMMAND_READY); tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY); } tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE); break; } } else if (val == TPM_TIS_STS_TPM_GO) { switch (tis->loc[locty].state) { case TPM_TIS_STATE_RECEPTION: if ((tis->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 (tis->loc[locty].state) { case TPM_TIS_STATE_COMPLETION: tis->loc[locty].r_offset = 0; tpm_tis_sts_set(&tis->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 (tis->active_locty != locty) { break; } if (tis->loc[locty].state == TPM_TIS_STATE_IDLE || tis->loc[locty].state == TPM_TIS_STATE_EXECUTION || tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) { /* drop the byte */ } else { DPRINTF("tpm_tis: Data to send to TPM: %08x (size=%d)\n", (int)val, size); if (tis->loc[locty].state == TPM_TIS_STATE_READY) { tis->loc[locty].state = TPM_TIS_STATE_RECEPTION; tpm_tis_sts_set(&tis->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 ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) { if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) { tis->loc[locty].w_buffer. buffer[tis->loc[locty].w_offset++] = (uint8_t)val; val >>= 8; size--; } else { tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID); } } /* check for complete packet */ if (tis->loc[locty].w_offset > 5 && (tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) { /* we have a packet length - see if we have all of it */ #ifdef RAISE_STS_IRQ bool need_irq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID); #endif len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer); if (len > tis->loc[locty].w_offset) { tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID); } else { /* packet complete */ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID); } #ifdef RAISE_STS_IRQ if (need_irq) { tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID); } #endif } }