]>
Commit | Line | Data |
---|---|---|
4ab6cb4c MAL |
1 | /* |
2 | * tpm_crb.c - QEMU's TPM CRB interface emulator | |
3 | * | |
4 | * Copyright (c) 2018 Red Hat, Inc. | |
5 | * | |
6 | * Authors: | |
7 | * Marc-André Lureau <[email protected]> | |
8 | * | |
9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
10 | * See the COPYING file in the top-level directory. | |
11 | * | |
12 | * tpm_crb is a device for TPM 2.0 Command Response Buffer (CRB) Interface | |
13 | * as defined in TCG PC Client Platform TPM Profile (PTP) Specification | |
14 | * Family “2.0” Level 00 Revision 01.03 v22 | |
15 | */ | |
16 | ||
17 | #include "qemu/osdep.h" | |
18 | ||
0b8fa32f | 19 | #include "qemu/module.h" |
4ab6cb4c MAL |
20 | #include "qapi/error.h" |
21 | #include "exec/address-spaces.h" | |
22 | ||
23 | #include "hw/qdev-core.h" | |
24 | #include "hw/qdev-properties.h" | |
25 | #include "hw/pci/pci_ids.h" | |
26 | #include "hw/acpi/tpm.h" | |
27 | #include "migration/vmstate.h" | |
28 | #include "sysemu/tpm_backend.h" | |
b8d44ab8 | 29 | #include "sysemu/reset.h" |
4ab6cb4c MAL |
30 | #include "tpm_int.h" |
31 | #include "tpm_util.h" | |
3b97c01e | 32 | #include "tpm_ppi.h" |
ec427498 | 33 | #include "trace.h" |
4ab6cb4c MAL |
34 | |
35 | typedef struct CRBState { | |
36 | DeviceState parent_obj; | |
37 | ||
38 | TPMBackend *tpmbe; | |
39 | TPMBackendCmd cmd; | |
40 | uint32_t regs[TPM_CRB_R_MAX]; | |
41 | MemoryRegion mmio; | |
42 | MemoryRegion cmdmem; | |
43 | ||
44 | size_t be_buffer_size; | |
b6148757 MAL |
45 | |
46 | bool ppi_enabled; | |
3b97c01e | 47 | TPMPPI ppi; |
4ab6cb4c MAL |
48 | } CRBState; |
49 | ||
50 | #define CRB(obj) OBJECT_CHECK(CRBState, (obj), TYPE_TPM_CRB) | |
51 | ||
4ab6cb4c MAL |
52 | #define CRB_INTF_TYPE_CRB_ACTIVE 0b1 |
53 | #define CRB_INTF_VERSION_CRB 0b1 | |
54 | #define CRB_INTF_CAP_LOCALITY_0_ONLY 0b0 | |
55 | #define CRB_INTF_CAP_IDLE_FAST 0b0 | |
56 | #define CRB_INTF_CAP_XFER_SIZE_64 0b11 | |
57 | #define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0 | |
58 | #define CRB_INTF_CAP_CRB_SUPPORTED 0b1 | |
59 | #define CRB_INTF_IF_SELECTOR_CRB 0b1 | |
60 | ||
61 | #define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER) | |
62 | ||
63 | enum crb_loc_ctrl { | |
64 | CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0), | |
65 | CRB_LOC_CTRL_RELINQUISH = BIT(1), | |
66 | CRB_LOC_CTRL_SEIZE = BIT(2), | |
67 | CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT = BIT(3), | |
68 | }; | |
69 | ||
70 | enum crb_ctrl_req { | |
71 | CRB_CTRL_REQ_CMD_READY = BIT(0), | |
72 | CRB_CTRL_REQ_GO_IDLE = BIT(1), | |
73 | }; | |
74 | ||
75 | enum crb_start { | |
76 | CRB_START_INVOKE = BIT(0), | |
77 | }; | |
78 | ||
79 | enum crb_cancel { | |
80 | CRB_CANCEL_INVOKE = BIT(0), | |
81 | }; | |
82 | ||
384cf1fc SB |
83 | #define TPM_CRB_NO_LOCALITY 0xff |
84 | ||
4ab6cb4c MAL |
85 | static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr, |
86 | unsigned size) | |
87 | { | |
88 | CRBState *s = CRB(opaque); | |
89 | void *regs = (void *)&s->regs + (addr & ~3); | |
90 | unsigned offset = addr & 3; | |
91 | uint32_t val = *(uint32_t *)regs >> (8 * offset); | |
92 | ||
ffbf24bd SB |
93 | switch (addr) { |
94 | case A_CRB_LOC_STATE: | |
95 | val |= !tpm_backend_get_tpm_established_flag(s->tpmbe); | |
96 | break; | |
97 | } | |
98 | ||
ec427498 SB |
99 | trace_tpm_crb_mmio_read(addr, size, val); |
100 | ||
4ab6cb4c MAL |
101 | return val; |
102 | } | |
103 | ||
384cf1fc SB |
104 | static uint8_t tpm_crb_get_active_locty(CRBState *s) |
105 | { | |
106 | if (!ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, locAssigned)) { | |
107 | return TPM_CRB_NO_LOCALITY; | |
108 | } | |
109 | return ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, activeLocality); | |
110 | } | |
111 | ||
4ab6cb4c MAL |
112 | static void tpm_crb_mmio_write(void *opaque, hwaddr addr, |
113 | uint64_t val, unsigned size) | |
114 | { | |
115 | CRBState *s = CRB(opaque); | |
384cf1fc | 116 | uint8_t locty = addr >> 12; |
ec427498 SB |
117 | |
118 | trace_tpm_crb_mmio_write(addr, size, val); | |
4ab6cb4c MAL |
119 | |
120 | switch (addr) { | |
121 | case A_CRB_CTRL_REQ: | |
122 | switch (val) { | |
123 | case CRB_CTRL_REQ_CMD_READY: | |
124 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
125 | tpmIdle, 0); | |
126 | break; | |
127 | case CRB_CTRL_REQ_GO_IDLE: | |
128 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
129 | tpmIdle, 1); | |
130 | break; | |
131 | } | |
132 | break; | |
133 | case A_CRB_CTRL_CANCEL: | |
134 | if (val == CRB_CANCEL_INVOKE && | |
135 | s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) { | |
136 | tpm_backend_cancel_cmd(s->tpmbe); | |
137 | } | |
138 | break; | |
139 | case A_CRB_CTRL_START: | |
140 | if (val == CRB_START_INVOKE && | |
384cf1fc SB |
141 | !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) && |
142 | tpm_crb_get_active_locty(s) == locty) { | |
4ab6cb4c MAL |
143 | void *mem = memory_region_get_ram_ptr(&s->cmdmem); |
144 | ||
145 | s->regs[R_CRB_CTRL_START] |= CRB_START_INVOKE; | |
146 | s->cmd = (TPMBackendCmd) { | |
147 | .in = mem, | |
148 | .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size), | |
149 | .out = mem, | |
150 | .out_len = s->be_buffer_size, | |
151 | }; | |
152 | ||
153 | tpm_backend_deliver_request(s->tpmbe, &s->cmd); | |
154 | } | |
155 | break; | |
156 | case A_CRB_LOC_CTRL: | |
157 | switch (val) { | |
158 | case CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: | |
159 | /* not loc 3 or 4 */ | |
160 | break; | |
161 | case CRB_LOC_CTRL_RELINQUISH: | |
de4a22d0 SB |
162 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, |
163 | locAssigned, 0); | |
025bc936 SB |
164 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, |
165 | Granted, 0); | |
4ab6cb4c MAL |
166 | break; |
167 | case CRB_LOC_CTRL_REQUEST_ACCESS: | |
168 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, | |
169 | Granted, 1); | |
170 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, | |
171 | beenSeized, 0); | |
172 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, | |
173 | locAssigned, 1); | |
4ab6cb4c MAL |
174 | break; |
175 | } | |
176 | break; | |
177 | } | |
178 | } | |
179 | ||
180 | static const MemoryRegionOps tpm_crb_memory_ops = { | |
181 | .read = tpm_crb_mmio_read, | |
182 | .write = tpm_crb_mmio_write, | |
183 | .endianness = DEVICE_LITTLE_ENDIAN, | |
184 | .valid = { | |
185 | .min_access_size = 1, | |
186 | .max_access_size = 4, | |
187 | }, | |
188 | }; | |
189 | ||
190 | static void tpm_crb_request_completed(TPMIf *ti, int ret) | |
191 | { | |
192 | CRBState *s = CRB(ti); | |
193 | ||
194 | s->regs[R_CRB_CTRL_START] &= ~CRB_START_INVOKE; | |
195 | if (ret != 0) { | |
196 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
197 | tpmSts, 1); /* fatal error */ | |
198 | } | |
199 | } | |
200 | ||
201 | static enum TPMVersion tpm_crb_get_version(TPMIf *ti) | |
202 | { | |
203 | CRBState *s = CRB(ti); | |
204 | ||
205 | return tpm_backend_get_tpm_version(s->tpmbe); | |
206 | } | |
207 | ||
208 | static int tpm_crb_pre_save(void *opaque) | |
209 | { | |
210 | CRBState *s = opaque; | |
211 | ||
212 | tpm_backend_finish_sync(s->tpmbe); | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | static const VMStateDescription vmstate_tpm_crb = { | |
218 | .name = "tpm-crb", | |
219 | .pre_save = tpm_crb_pre_save, | |
220 | .fields = (VMStateField[]) { | |
221 | VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX), | |
222 | VMSTATE_END_OF_LIST(), | |
223 | } | |
224 | }; | |
225 | ||
226 | static Property tpm_crb_properties[] = { | |
227 | DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe), | |
b6148757 | 228 | DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true), |
4ab6cb4c MAL |
229 | DEFINE_PROP_END_OF_LIST(), |
230 | }; | |
231 | ||
b8d44ab8 | 232 | static void tpm_crb_reset(void *dev) |
4ab6cb4c MAL |
233 | { |
234 | CRBState *s = CRB(dev); | |
235 | ||
ffab1be7 MAL |
236 | if (s->ppi_enabled) { |
237 | tpm_ppi_reset(&s->ppi); | |
238 | } | |
4ab6cb4c MAL |
239 | tpm_backend_reset(s->tpmbe); |
240 | ||
e1880ed8 SB |
241 | memset(s->regs, 0, sizeof(s->regs)); |
242 | ||
be052a3b SB |
243 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, |
244 | tpmRegValidSts, 1); | |
3a3c8735 SB |
245 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, |
246 | tpmIdle, 1); | |
4ab6cb4c MAL |
247 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, |
248 | InterfaceType, CRB_INTF_TYPE_CRB_ACTIVE); | |
249 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
250 | InterfaceVersion, CRB_INTF_VERSION_CRB); | |
251 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
252 | CapLocality, CRB_INTF_CAP_LOCALITY_0_ONLY); | |
253 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
254 | CapCRBIdleBypass, CRB_INTF_CAP_IDLE_FAST); | |
255 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
256 | CapDataXferSizeSupport, CRB_INTF_CAP_XFER_SIZE_64); | |
257 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
258 | CapFIFO, CRB_INTF_CAP_FIFO_NOT_SUPPORTED); | |
259 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
260 | CapCRB, CRB_INTF_CAP_CRB_SUPPORTED); | |
261 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
262 | InterfaceSelector, CRB_INTF_IF_SELECTOR_CRB); | |
263 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
264 | RID, 0b0000); | |
265 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID2, | |
266 | VID, PCI_VENDOR_ID_IBM); | |
267 | ||
268 | s->regs[R_CRB_CTRL_CMD_SIZE] = CRB_CTRL_CMD_SIZE; | |
269 | s->regs[R_CRB_CTRL_CMD_LADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER; | |
270 | s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE; | |
271 | s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER; | |
272 | ||
273 | s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe), | |
274 | CRB_CTRL_CMD_SIZE); | |
275 | ||
276 | tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size); | |
277 | } | |
278 | ||
b8d44ab8 SB |
279 | static void tpm_crb_realize(DeviceState *dev, Error **errp) |
280 | { | |
281 | CRBState *s = CRB(dev); | |
282 | ||
283 | if (!tpm_find()) { | |
284 | error_setg(errp, "at most one TPM device is permitted"); | |
285 | return; | |
286 | } | |
287 | if (!s->tpmbe) { | |
288 | error_setg(errp, "'tpmdev' property is required"); | |
289 | return; | |
290 | } | |
291 | ||
292 | memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s, | |
293 | "tpm-crb-mmio", sizeof(s->regs)); | |
294 | memory_region_init_ram(&s->cmdmem, OBJECT(s), | |
295 | "tpm-crb-cmd", CRB_CTRL_CMD_SIZE, errp); | |
296 | ||
297 | memory_region_add_subregion(get_system_memory(), | |
298 | TPM_CRB_ADDR_BASE, &s->mmio); | |
299 | memory_region_add_subregion(get_system_memory(), | |
300 | TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem); | |
301 | ||
3b97c01e SB |
302 | if (s->ppi_enabled) { |
303 | tpm_ppi_init(&s->ppi, get_system_memory(), | |
304 | TPM_PPI_ADDR_BASE, OBJECT(s)); | |
305 | } | |
306 | ||
b8d44ab8 SB |
307 | qemu_register_reset(tpm_crb_reset, dev); |
308 | } | |
309 | ||
4ab6cb4c MAL |
310 | static void tpm_crb_class_init(ObjectClass *klass, void *data) |
311 | { | |
312 | DeviceClass *dc = DEVICE_CLASS(klass); | |
313 | TPMIfClass *tc = TPM_IF_CLASS(klass); | |
314 | ||
315 | dc->realize = tpm_crb_realize; | |
316 | dc->props = tpm_crb_properties; | |
317 | dc->vmsd = &vmstate_tpm_crb; | |
318 | dc->user_creatable = true; | |
319 | tc->model = TPM_MODEL_TPM_CRB; | |
320 | tc->get_version = tpm_crb_get_version; | |
321 | tc->request_completed = tpm_crb_request_completed; | |
322 | ||
323 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); | |
324 | } | |
325 | ||
326 | static const TypeInfo tpm_crb_info = { | |
327 | .name = TYPE_TPM_CRB, | |
328 | /* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */ | |
329 | .parent = TYPE_DEVICE, | |
330 | .instance_size = sizeof(CRBState), | |
331 | .class_init = tpm_crb_class_init, | |
332 | .interfaces = (InterfaceInfo[]) { | |
333 | { TYPE_TPM_IF }, | |
334 | { } | |
335 | } | |
336 | }; | |
337 | ||
338 | static void tpm_crb_register(void) | |
339 | { | |
340 | type_register_static(&tpm_crb_info); | |
341 | } | |
342 | ||
343 | type_init(tpm_crb_register) |