]>
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 <marcandre.lureau@redhat.com> | |
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" | |
4ab6cb4c MAL |
22 | #include "hw/qdev-properties.h" |
23 | #include "hw/pci/pci_ids.h" | |
24 | #include "hw/acpi/tpm.h" | |
25 | #include "migration/vmstate.h" | |
26 | #include "sysemu/tpm_backend.h" | |
b8d44ab8 | 27 | #include "sysemu/reset.h" |
4ab6cb4c MAL |
28 | #include "tpm_int.h" |
29 | #include "tpm_util.h" | |
3b97c01e | 30 | #include "tpm_ppi.h" |
ec427498 | 31 | #include "trace.h" |
4ab6cb4c MAL |
32 | |
33 | typedef struct CRBState { | |
34 | DeviceState parent_obj; | |
35 | ||
36 | TPMBackend *tpmbe; | |
37 | TPMBackendCmd cmd; | |
38 | uint32_t regs[TPM_CRB_R_MAX]; | |
39 | MemoryRegion mmio; | |
40 | MemoryRegion cmdmem; | |
41 | ||
42 | size_t be_buffer_size; | |
b6148757 MAL |
43 | |
44 | bool ppi_enabled; | |
3b97c01e | 45 | TPMPPI ppi; |
4ab6cb4c MAL |
46 | } CRBState; |
47 | ||
48 | #define CRB(obj) OBJECT_CHECK(CRBState, (obj), TYPE_TPM_CRB) | |
49 | ||
4ab6cb4c MAL |
50 | #define CRB_INTF_TYPE_CRB_ACTIVE 0b1 |
51 | #define CRB_INTF_VERSION_CRB 0b1 | |
52 | #define CRB_INTF_CAP_LOCALITY_0_ONLY 0b0 | |
53 | #define CRB_INTF_CAP_IDLE_FAST 0b0 | |
54 | #define CRB_INTF_CAP_XFER_SIZE_64 0b11 | |
55 | #define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0 | |
56 | #define CRB_INTF_CAP_CRB_SUPPORTED 0b1 | |
57 | #define CRB_INTF_IF_SELECTOR_CRB 0b1 | |
58 | ||
59 | #define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER) | |
60 | ||
61 | enum crb_loc_ctrl { | |
62 | CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0), | |
63 | CRB_LOC_CTRL_RELINQUISH = BIT(1), | |
64 | CRB_LOC_CTRL_SEIZE = BIT(2), | |
65 | CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT = BIT(3), | |
66 | }; | |
67 | ||
68 | enum crb_ctrl_req { | |
69 | CRB_CTRL_REQ_CMD_READY = BIT(0), | |
70 | CRB_CTRL_REQ_GO_IDLE = BIT(1), | |
71 | }; | |
72 | ||
73 | enum crb_start { | |
74 | CRB_START_INVOKE = BIT(0), | |
75 | }; | |
76 | ||
77 | enum crb_cancel { | |
78 | CRB_CANCEL_INVOKE = BIT(0), | |
79 | }; | |
80 | ||
384cf1fc SB |
81 | #define TPM_CRB_NO_LOCALITY 0xff |
82 | ||
4ab6cb4c MAL |
83 | static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr, |
84 | unsigned size) | |
85 | { | |
86 | CRBState *s = CRB(opaque); | |
87 | void *regs = (void *)&s->regs + (addr & ~3); | |
88 | unsigned offset = addr & 3; | |
89 | uint32_t val = *(uint32_t *)regs >> (8 * offset); | |
90 | ||
ffbf24bd SB |
91 | switch (addr) { |
92 | case A_CRB_LOC_STATE: | |
93 | val |= !tpm_backend_get_tpm_established_flag(s->tpmbe); | |
94 | break; | |
95 | } | |
96 | ||
ec427498 SB |
97 | trace_tpm_crb_mmio_read(addr, size, val); |
98 | ||
4ab6cb4c MAL |
99 | return val; |
100 | } | |
101 | ||
384cf1fc SB |
102 | static uint8_t tpm_crb_get_active_locty(CRBState *s) |
103 | { | |
104 | if (!ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, locAssigned)) { | |
105 | return TPM_CRB_NO_LOCALITY; | |
106 | } | |
107 | return ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, activeLocality); | |
108 | } | |
109 | ||
4ab6cb4c MAL |
110 | static void tpm_crb_mmio_write(void *opaque, hwaddr addr, |
111 | uint64_t val, unsigned size) | |
112 | { | |
113 | CRBState *s = CRB(opaque); | |
384cf1fc | 114 | uint8_t locty = addr >> 12; |
ec427498 SB |
115 | |
116 | trace_tpm_crb_mmio_write(addr, size, val); | |
4ab6cb4c MAL |
117 | |
118 | switch (addr) { | |
119 | case A_CRB_CTRL_REQ: | |
120 | switch (val) { | |
121 | case CRB_CTRL_REQ_CMD_READY: | |
122 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
123 | tpmIdle, 0); | |
124 | break; | |
125 | case CRB_CTRL_REQ_GO_IDLE: | |
126 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
127 | tpmIdle, 1); | |
128 | break; | |
129 | } | |
130 | break; | |
131 | case A_CRB_CTRL_CANCEL: | |
132 | if (val == CRB_CANCEL_INVOKE && | |
133 | s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) { | |
134 | tpm_backend_cancel_cmd(s->tpmbe); | |
135 | } | |
136 | break; | |
137 | case A_CRB_CTRL_START: | |
138 | if (val == CRB_START_INVOKE && | |
384cf1fc SB |
139 | !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) && |
140 | tpm_crb_get_active_locty(s) == locty) { | |
4ab6cb4c MAL |
141 | void *mem = memory_region_get_ram_ptr(&s->cmdmem); |
142 | ||
143 | s->regs[R_CRB_CTRL_START] |= CRB_START_INVOKE; | |
144 | s->cmd = (TPMBackendCmd) { | |
145 | .in = mem, | |
146 | .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size), | |
147 | .out = mem, | |
148 | .out_len = s->be_buffer_size, | |
149 | }; | |
150 | ||
151 | tpm_backend_deliver_request(s->tpmbe, &s->cmd); | |
152 | } | |
153 | break; | |
154 | case A_CRB_LOC_CTRL: | |
155 | switch (val) { | |
156 | case CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: | |
157 | /* not loc 3 or 4 */ | |
158 | break; | |
159 | case CRB_LOC_CTRL_RELINQUISH: | |
de4a22d0 SB |
160 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, |
161 | locAssigned, 0); | |
025bc936 SB |
162 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, |
163 | Granted, 0); | |
4ab6cb4c MAL |
164 | break; |
165 | case CRB_LOC_CTRL_REQUEST_ACCESS: | |
166 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, | |
167 | Granted, 1); | |
168 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS, | |
169 | beenSeized, 0); | |
170 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, | |
171 | locAssigned, 1); | |
4ab6cb4c MAL |
172 | break; |
173 | } | |
174 | break; | |
175 | } | |
176 | } | |
177 | ||
178 | static const MemoryRegionOps tpm_crb_memory_ops = { | |
179 | .read = tpm_crb_mmio_read, | |
180 | .write = tpm_crb_mmio_write, | |
181 | .endianness = DEVICE_LITTLE_ENDIAN, | |
182 | .valid = { | |
183 | .min_access_size = 1, | |
184 | .max_access_size = 4, | |
185 | }, | |
186 | }; | |
187 | ||
188 | static void tpm_crb_request_completed(TPMIf *ti, int ret) | |
189 | { | |
190 | CRBState *s = CRB(ti); | |
191 | ||
192 | s->regs[R_CRB_CTRL_START] &= ~CRB_START_INVOKE; | |
193 | if (ret != 0) { | |
194 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, | |
195 | tpmSts, 1); /* fatal error */ | |
196 | } | |
197 | } | |
198 | ||
199 | static enum TPMVersion tpm_crb_get_version(TPMIf *ti) | |
200 | { | |
201 | CRBState *s = CRB(ti); | |
202 | ||
203 | return tpm_backend_get_tpm_version(s->tpmbe); | |
204 | } | |
205 | ||
206 | static int tpm_crb_pre_save(void *opaque) | |
207 | { | |
208 | CRBState *s = opaque; | |
209 | ||
210 | tpm_backend_finish_sync(s->tpmbe); | |
211 | ||
212 | return 0; | |
213 | } | |
214 | ||
215 | static const VMStateDescription vmstate_tpm_crb = { | |
216 | .name = "tpm-crb", | |
217 | .pre_save = tpm_crb_pre_save, | |
218 | .fields = (VMStateField[]) { | |
219 | VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX), | |
220 | VMSTATE_END_OF_LIST(), | |
221 | } | |
222 | }; | |
223 | ||
224 | static Property tpm_crb_properties[] = { | |
225 | DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe), | |
b6148757 | 226 | DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true), |
4ab6cb4c MAL |
227 | DEFINE_PROP_END_OF_LIST(), |
228 | }; | |
229 | ||
b8d44ab8 | 230 | static void tpm_crb_reset(void *dev) |
4ab6cb4c MAL |
231 | { |
232 | CRBState *s = CRB(dev); | |
233 | ||
ffab1be7 MAL |
234 | if (s->ppi_enabled) { |
235 | tpm_ppi_reset(&s->ppi); | |
236 | } | |
4ab6cb4c MAL |
237 | tpm_backend_reset(s->tpmbe); |
238 | ||
e1880ed8 SB |
239 | memset(s->regs, 0, sizeof(s->regs)); |
240 | ||
be052a3b SB |
241 | ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE, |
242 | tpmRegValidSts, 1); | |
3a3c8735 SB |
243 | ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, |
244 | tpmIdle, 1); | |
4ab6cb4c MAL |
245 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, |
246 | InterfaceType, CRB_INTF_TYPE_CRB_ACTIVE); | |
247 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
248 | InterfaceVersion, CRB_INTF_VERSION_CRB); | |
249 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
250 | CapLocality, CRB_INTF_CAP_LOCALITY_0_ONLY); | |
251 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
252 | CapCRBIdleBypass, CRB_INTF_CAP_IDLE_FAST); | |
253 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
254 | CapDataXferSizeSupport, CRB_INTF_CAP_XFER_SIZE_64); | |
255 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
256 | CapFIFO, CRB_INTF_CAP_FIFO_NOT_SUPPORTED); | |
257 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
258 | CapCRB, CRB_INTF_CAP_CRB_SUPPORTED); | |
259 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
260 | InterfaceSelector, CRB_INTF_IF_SELECTOR_CRB); | |
261 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID, | |
262 | RID, 0b0000); | |
263 | ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID2, | |
264 | VID, PCI_VENDOR_ID_IBM); | |
265 | ||
266 | s->regs[R_CRB_CTRL_CMD_SIZE] = CRB_CTRL_CMD_SIZE; | |
267 | s->regs[R_CRB_CTRL_CMD_LADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER; | |
268 | s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE; | |
269 | s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER; | |
270 | ||
271 | s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe), | |
272 | CRB_CTRL_CMD_SIZE); | |
273 | ||
bcfd16fe SB |
274 | if (tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size) < 0) { |
275 | exit(1); | |
276 | } | |
4ab6cb4c MAL |
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; | |
4f67d30b | 316 | device_class_set_props(dc, tpm_crb_properties); |
4ab6cb4c MAL |
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) |