#include <sys/cdefs.h>
#include "tpm20.h"
#define TPM_LOC_STATE 0x0
#define TPM_LOC_CTRL 0x8
#define TPM_LOC_STS 0xC
#define TPM_CRB_INTF_ID 0x30
#define TPM_CRB_CTRL_EXT 0x38
#define TPM_CRB_CTRL_REQ 0x40
#define TPM_CRB_CTRL_STS 0x44
#define TPM_CRB_CTRL_CANCEL 0x48
#define TPM_CRB_CTRL_START 0x4C
#define TPM_CRB_INT_ENABLE 0x50
#define TPM_CRB_INT_STS 0x54
#define TPM_CRB_CTRL_CMD_SIZE 0x58
#define TPM_CRB_CTRL_CMD_LADDR 0x5C
#define TPM_CRB_CTRL_CMD_HADDR 0x60
#define TPM_CRB_CTRL_RSP_SIZE 0x64
#define TPM_CRB_CTRL_RSP_ADDR 0x68
#define TPM_CRB_CTRL_RSP_HADDR 0x6c
#define TPM_CRB_DATA_BUFFER 0x80
#define TPM_LOC_STATE_ESTB BIT(0)
#define TPM_LOC_STATE_ASSIGNED BIT(1)
#define TPM_LOC_STATE_ACTIVE_MASK 0x9C
#define TPM_LOC_STATE_VALID BIT(7)
#define TPM_CRB_INTF_ID_TYPE_CRB 0x1
#define TPM_CRB_INTF_ID_TYPE 0x7
#define TPM_LOC_CTRL_REQUEST BIT(0)
#define TPM_LOC_CTRL_RELINQUISH BIT(1)
#define TPM_CRB_CTRL_REQ_GO_READY BIT(0)
#define TPM_CRB_CTRL_REQ_GO_IDLE BIT(1)
#define TPM_CRB_CTRL_STS_ERR_BIT BIT(0)
#define TPM_CRB_CTRL_STS_IDLE_BIT BIT(1)
#define TPM_CRB_CTRL_CANCEL_CMD 0x1
#define TPM_CRB_CTRL_CANCEL_CLEAR 0x0
#define TPM_CRB_CTRL_START_CMD BIT(0)
#define TPM_CRB_INT_ENABLE_BIT BIT(31)
struct tpmcrb_sc;
typedef bool (sm_attach_t)(struct tpmcrb_sc *, void *, size_t);
typedef bool (sm_statechange_t)(struct tpmcrb_sc *, int);
struct tpmcrb_sm_cfg {
sm_attach_t *sm_attach;
sm_statechange_t *sm_statechange;
sm_statechange_t *sm_cmdready;
};
static sm_attach_t pluton_attach;
static sm_statechange_t pluton_doorbell;
static const struct tpmcrb_sm_cfg_map {
int acpi_sm;
const char *desc;
const struct tpmcrb_sm_cfg sm_cfg;
} tpmcrb_sm_cfg_map[] = {
{
.acpi_sm = TPM2_START_METHOD_CRB,
.desc = "Trusted Platform Module 2.0, CRB mode",
.sm_cfg = { NULL },
},
{
.acpi_sm = ACPI_TPM2_COMMAND_BUFFER_WITH_PLUTON,
.desc = "Trusted Platform Module 2.0, CRB mode (Pluton)",
.sm_cfg = {
.sm_attach = &pluton_attach,
.sm_statechange = &pluton_doorbell,
.sm_cmdready = &pluton_doorbell,
},
},
};
struct tpmcrb_sc {
struct tpm_sc base;
const struct tpmcrb_sm_cfg *sm_cfg;
union {
struct {
uint64_t start_reg;
uint64_t reply_reg;
} pluton;
};
bus_size_t cmd_off;
bus_size_t rsp_off;
size_t cmd_buf_size;
size_t rsp_buf_size;
};
int tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t size);
static int tpmcrb_acpi_probe(device_t dev);
static int tpmcrb_attach(device_t dev);
static int tpmcrb_detach(device_t dev);
static ACPI_STATUS tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg);
static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off,
uint32_t mask, uint32_t val, int32_t timeout);
static bool tpmcrb_request_locality(struct tpm_sc *sc, int locality);
static void tpmcrb_relinquish_locality(struct tpm_sc *sc);
static bool tpmcrb_cancel_cmd(struct tpm_sc *sc);
char *tpmcrb_ids[] = {"MSFT0101", NULL};
static const struct tpmcrb_sm_cfg_map *
tpmcrb_acpi_startmethod_cfg(int method)
{
const struct tpmcrb_sm_cfg_map *entry;
for (size_t i = 0; i < nitems(tpmcrb_sm_cfg_map); i++) {
entry = &tpmcrb_sm_cfg_map[i];
if (method == entry->acpi_sm)
return (entry);
}
return (NULL);
}
static int
tpmcrb_acpi_probe(device_t dev)
{
int err, smethod;
const struct tpmcrb_sm_cfg_map *sm_cfg_map;
ACPI_TABLE_TPM23 *tbl;
ACPI_STATUS status;
err = ACPI_ID_PROBE(device_get_parent(dev), dev, tpmcrb_ids, NULL);
if (err > 0)
return (err);
status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **) &tbl);
if (ACPI_FAILURE(status))
return (ENXIO);
smethod = tbl->StartMethod;
AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
sm_cfg_map = tpmcrb_acpi_startmethod_cfg(smethod);
if (sm_cfg_map == NULL)
return (ENXIO);
device_set_desc(dev, sm_cfg_map->desc);
return (0);
}
static ACPI_STATUS
tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg)
{
struct tpmcrb_sc *crb_sc;
size_t length;
uint32_t base_addr;
crb_sc = (struct tpmcrb_sc *)arg;
if (res->Type != ACPI_RESOURCE_TYPE_FIXED_MEMORY32)
return (AE_OK);
base_addr = res->Data.FixedMemory32.Address;
length = res->Data.FixedMemory32.AddressLength;
if (crb_sc->cmd_off > base_addr && crb_sc->cmd_off < base_addr + length)
crb_sc->cmd_off -= base_addr;
if (crb_sc->rsp_off > base_addr && crb_sc->rsp_off < base_addr + length)
crb_sc->rsp_off -= base_addr;
return (AE_OK);
}
static bool
tpmcrb_attach_startmethod(struct tpmcrb_sc *crb_sc)
{
const struct tpmcrb_sm_cfg_map *sm_cfg_map;
const struct tpmcrb_sm_cfg *sm_cfg;
ACPI_TABLE_TPM23 *tbl;
void *smdata;
ACPI_STATUS status;
bool ret;
status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **)(void **)&tbl);
if (ACPI_FAILURE(status))
return (false);
sm_cfg_map = tpmcrb_acpi_startmethod_cfg(tbl->StartMethod);
MPASS(sm_cfg_map != NULL);
sm_cfg = &sm_cfg_map->sm_cfg;
crb_sc->sm_cfg = sm_cfg;
smdata = tbl + 1;
if (sm_cfg->sm_attach != NULL) {
ret = (*sm_cfg->sm_attach)(crb_sc, smdata,
tbl->Header.Length - sizeof(*tbl));
} else {
ret = true;
}
AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
return (ret);
}
static int
tpmcrb_attach(device_t dev)
{
struct tpmcrb_sc *crb_sc;
struct tpm_sc *sc;
ACPI_HANDLE handle;
ACPI_STATUS status;
int result;
crb_sc = device_get_softc(dev);
sc = &crb_sc->base;
handle = acpi_get_handle(dev);
sc->dev = dev;
sx_init(&sc->dev_lock, "TPM driver lock");
sc->mem_rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
RF_ACTIVE);
if (sc->mem_res == NULL) {
tpmcrb_detach(dev);
return (ENXIO);
}
if (!tpmcrb_attach_startmethod(crb_sc)) {
tpmcrb_detach(dev);
return (ENXIO);
}
if(!tpmcrb_request_locality(sc, 0)) {
tpmcrb_detach(dev);
return (ENXIO);
}
AND4(sc, TPM_CRB_INT_ENABLE, ~TPM_CRB_INT_ENABLE_BIT);
sc->interrupts = false;
#ifdef __amd64__
crb_sc->rsp_off = TPM_READ_8(sc->dev, TPM_CRB_CTRL_RSP_ADDR);
#else
crb_sc->rsp_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_ADDR);
crb_sc->rsp_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_HADDR) << 32);
#endif
crb_sc->cmd_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_LADDR);
crb_sc->cmd_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_HADDR) << 32);
crb_sc->cmd_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_SIZE);
crb_sc->rsp_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_SIZE);
tpmcrb_relinquish_locality(sc);
status = AcpiWalkResources(handle, "_CRS", tpmcrb_fix_buff_offsets,
(void *)crb_sc);
if (ACPI_FAILURE(status)) {
tpmcrb_detach(dev);
return (ENXIO);
}
if (crb_sc->rsp_off == crb_sc->cmd_off) {
if (crb_sc->cmd_buf_size != crb_sc->rsp_buf_size) {
device_printf(sc->dev,
"Overlapping Tx/Rx buffers have different sizes\n");
tpmcrb_detach(dev);
return (ENXIO);
}
}
result = tpm20_init(sc);
if (result != 0)
tpmcrb_detach(dev);
return (result);
}
static int
tpmcrb_detach(device_t dev)
{
struct tpm_sc *sc;
sc = device_get_softc(dev);
tpm20_release(sc);
if (sc->mem_res != NULL)
bus_release_resource(dev, SYS_RES_MEMORY,
sc->mem_rid, sc->mem_res);
return (0);
}
static bool
tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val,
int32_t timeout)
{
if ((TPM_READ_4(sc->dev, off) & mask) == val)
return (true);
while (timeout > 0) {
if ((TPM_READ_4(sc->dev, off) & mask) == val)
return (true);
pause("TPM in polling mode", 1);
timeout -= tick;
}
return (false);
}
static bool
tpmcrb_request_locality(struct tpm_sc *sc, int locality)
{
uint32_t mask;
if (locality != 0)
return (false);
mask = TPM_LOC_STATE_VALID | TPM_LOC_STATE_ASSIGNED;
OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_REQUEST);
if (!tpm_wait_for_u32(sc, TPM_LOC_STATE, mask, mask, TPM_TIMEOUT_C))
return (false);
return (true);
}
static void
tpmcrb_relinquish_locality(struct tpm_sc *sc)
{
OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_RELINQUISH);
}
static bool
tpmcrb_cancel_cmd(struct tpm_sc *sc)
{
uint32_t mask = ~0;
TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CMD);
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START,
mask, ~mask, TPM_TIMEOUT_B)) {
device_printf(sc->dev,
"Device failed to cancel command\n");
return (false);
}
TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
return (true);
}
static bool
tpmcrb_notify_cmdready(struct tpmcrb_sc *crb_sc, int timeout)
{
sm_statechange_t *cmdready_fn;
cmdready_fn = crb_sc->sm_cfg->sm_cmdready;
if (cmdready_fn == NULL)
return (true);
return ((*cmdready_fn)(crb_sc, timeout));
}
static bool
tpmcrb_notify_state_changing(struct tpmcrb_sc *crb_sc, int timeout)
{
sm_statechange_t *statechange_fn;
statechange_fn = crb_sc->sm_cfg->sm_statechange;
if (statechange_fn == NULL)
return (true);
return ((*statechange_fn)(crb_sc, timeout));
}
static bool
tpmcrb_state_idle(struct tpmcrb_sc *crb_sc, bool wait)
{
struct tpm_sc *sc;
int mask, timeout;
timeout = wait ? TPM_TIMEOUT_C : 0;
sc = &crb_sc->base;
OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE);
if (!tpmcrb_notify_state_changing(crb_sc, timeout))
return (false);
if (timeout > 0) {
mask = TPM_CRB_CTRL_STS_IDLE_BIT;
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, mask,
timeout))
return (false);
}
return (true);
}
static bool
tpmcrb_state_ready(struct tpmcrb_sc *crb_sc, bool wait)
{
struct tpm_sc *sc;
int mask, timeout;
timeout = wait ? TPM_TIMEOUT_C : 0;
sc = &crb_sc->base;
OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_READY);
if (!tpmcrb_notify_state_changing(crb_sc, timeout))
return (false);
if (timeout > 0) {
mask = TPM_CRB_CTRL_REQ_GO_READY;
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, !mask,
timeout))
return (false);
}
return (true);
}
int
tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
{
struct tpmcrb_sc *crb_sc;
struct tpm_sc *sc;
uint32_t mask, curr_cmd;
int timeout, bytes_available;
crb_sc = device_get_softc(dev);
sc = &crb_sc->base;
sx_assert(&sc->dev_lock, SA_XLOCKED);
if (length > crb_sc->cmd_buf_size) {
device_printf(dev,
"Requested transfer is bigger than buffer size\n");
return (E2BIG);
}
if (TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_ERR_BIT) {
device_printf(dev,
"Device has Error bit set\n");
return (EIO);
}
if (!tpmcrb_request_locality(sc, 0)) {
device_printf(dev,
"Failed to obtain locality\n");
return (EIO);
}
TPM_WRITE_4(dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
if (!(TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_IDLE_BIT)) {
if (!tpmcrb_state_idle(crb_sc, true)) {
device_printf(dev,
"Failed to transition to idle state\n");
return (EIO);
}
}
if (!tpmcrb_state_ready(crb_sc, true)) {
device_printf(dev,
"Failed to transition to ready state\n");
return (EIO);
}
curr_cmd = be32toh(*(uint32_t *) (&priv->buf[6]));
timeout = tpm20_get_timeout(curr_cmd);
bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off,
priv->buf, length);
TPM_WRITE_BARRIER(dev, crb_sc->cmd_off, length);
TPM_WRITE_4(dev, TPM_CRB_CTRL_START, TPM_CRB_CTRL_START_CMD);
TPM_WRITE_BARRIER(dev, TPM_CRB_CTRL_START, 4);
if (!tpmcrb_notify_cmdready(crb_sc, timeout)) {
device_printf(dev,
"Timeout while waiting for device to ready\n");
if (!tpmcrb_cancel_cmd(sc))
return (EIO);
}
mask = ~0;
if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, mask, ~mask, timeout)) {
device_printf(dev,
"Timeout while waiting for device to process cmd\n");
if (!tpmcrb_cancel_cmd(sc))
return (EIO);
}
bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off,
priv->buf, TPM_HEADER_SIZE);
bytes_available = be32toh(*(uint32_t *) (&priv->buf[2]));
if (bytes_available > TPM_BUFSIZE || bytes_available < TPM_HEADER_SIZE) {
device_printf(dev,
"Incorrect response size: %d\n",
bytes_available);
return (EIO);
}
bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE,
&priv->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE);
if (!tpmcrb_state_idle(crb_sc, false)) {
device_printf(dev,
"Failed to transition to idle state post-send\n");
return (EIO);
}
tpmcrb_relinquish_locality(sc);
priv->offset = 0;
priv->len = bytes_available;
return (0);
}
struct tpmcrb_startmethod_pluton {
uint64_t sm_startaddr;
uint64_t sm_replyaddr;
};
static bool
pluton_attach(struct tpmcrb_sc *crb_sc, void *smdataregion, size_t datasz)
{
struct tpmcrb_startmethod_pluton *smdata;
struct tpm_sc *sc;
rman_res_t base_addr, end_addr;
if (datasz < sizeof(*smdata))
return (false);
smdata = smdataregion;
sc = &crb_sc->base;
base_addr = rman_get_start(sc->mem_res);
end_addr = rman_get_end(sc->mem_res);
if (smdata->sm_startaddr < base_addr ||
smdata->sm_startaddr > end_addr ||
smdata->sm_replyaddr < base_addr ||
smdata->sm_replyaddr > end_addr)
return (false);
crb_sc->pluton.start_reg = smdata->sm_startaddr - base_addr;
crb_sc->pluton.reply_reg = smdata->sm_replyaddr - base_addr;
return (true);
}
static bool
pluton_doorbell(struct tpmcrb_sc *crb_sc, int timeout)
{
struct tpm_sc *sc;
device_t dev;
sc = &crb_sc->base;
dev = sc->dev;
TPM_WRITE_4(dev, crb_sc->pluton.start_reg, 1);
TPM_WRITE_BARRIER(dev, crb_sc->pluton.start_reg, 4);
if (timeout > 0) {
if (!tpm_wait_for_u32(sc, crb_sc->pluton.reply_reg, ~0U, 1,
timeout))
return (false);
}
return (true);
}
static device_method_t tpmcrb_methods[] = {
DEVMETHOD(device_probe, tpmcrb_acpi_probe),
DEVMETHOD(device_attach, tpmcrb_attach),
DEVMETHOD(device_detach, tpmcrb_detach),
DEVMETHOD(device_shutdown, tpm20_shutdown),
DEVMETHOD(device_suspend, tpm20_suspend),
DEVMETHOD(device_resume, tpm20_resume),
DEVMETHOD(tpm_transmit, tpmcrb_transmit),
{0, 0}
};
DEFINE_CLASS_1(tpmcrb, tpmcrb_driver, tpmcrb_methods, sizeof(struct tpmcrb_sc),
tpm_bus_driver);
DRIVER_MODULE(tpmcrb, acpi, tpmcrb_driver, 0, 0);