]>
Commit | Line | Data |
---|---|---|
4549a8b7 SB |
1 | /* |
2 | * passthrough TPM driver | |
3 | * | |
4 | * Copyright (c) 2010 - 2013 IBM Corporation | |
5 | * Authors: | |
6 | * Stefan Berger <stefanb@us.ibm.com> | |
7 | * | |
8 | * Copyright (C) 2011 IAIK, Graz University of Technology | |
9 | * Author: Andreas Niederl | |
10 | * | |
11 | * This library is free software; you can redistribute it and/or | |
12 | * modify it under the terms of the GNU Lesser General Public | |
13 | * License as published by the Free Software Foundation; either | |
14 | * version 2 of the License, or (at your option) any later version. | |
15 | * | |
16 | * This library is distributed in the hope that it will be useful, | |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
19 | * Lesser General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU Lesser General Public | |
22 | * License along with this library; if not, see <http://www.gnu.org/licenses/> | |
23 | */ | |
24 | ||
0430891c | 25 | #include "qemu/osdep.h" |
4549a8b7 | 26 | #include "qemu-common.h" |
d49b6836 | 27 | #include "qemu/error-report.h" |
4549a8b7 | 28 | #include "qemu/sockets.h" |
dccfcd0e | 29 | #include "sysemu/tpm_backend.h" |
4549a8b7 SB |
30 | #include "tpm_int.h" |
31 | #include "hw/hw.h" | |
0d09e41a | 32 | #include "hw/i386/pc.h" |
f59864ba | 33 | #include "qapi/clone-visitor.h" |
56a3c24f | 34 | #include "tpm_util.h" |
4549a8b7 | 35 | |
4d1ba9c4 SB |
36 | #define DEBUG_TPM 0 |
37 | ||
38 | #define DPRINTF(fmt, ...) do { \ | |
39 | if (DEBUG_TPM) { \ | |
40 | fprintf(stderr, fmt, ## __VA_ARGS__); \ | |
41 | } \ | |
42 | } while (0); | |
4549a8b7 | 43 | |
8f0605cc SB |
44 | #define TYPE_TPM_PASSTHROUGH "tpm-passthrough" |
45 | #define TPM_PASSTHROUGH(obj) \ | |
46 | OBJECT_CHECK(TPMPassthruState, (obj), TYPE_TPM_PASSTHROUGH) | |
4549a8b7 | 47 | |
8f0605cc | 48 | /* data structures */ |
4549a8b7 | 49 | struct TPMPassthruState { |
8f0605cc SB |
50 | TPMBackend parent; |
51 | ||
f59864ba AV |
52 | TPMPassthroughOptions *options; |
53 | const char *tpm_dev; | |
4549a8b7 | 54 | int tpm_fd; |
92dcc234 SB |
55 | bool tpm_executing; |
56 | bool tpm_op_canceled; | |
57 | int cancel_fd; | |
56a3c24f SB |
58 | |
59 | TPMVersion tpm_version; | |
4549a8b7 SB |
60 | }; |
61 | ||
8f0605cc SB |
62 | typedef struct TPMPassthruState TPMPassthruState; |
63 | ||
4549a8b7 SB |
64 | #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" |
65 | ||
92dcc234 SB |
66 | /* functions */ |
67 | ||
68 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb); | |
69 | ||
4549a8b7 SB |
70 | static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) |
71 | { | |
46f296cd DB |
72 | int ret; |
73 | reread: | |
74 | ret = read(fd, buf, len); | |
75 | if (ret < 0) { | |
76 | if (errno != EINTR && errno != EAGAIN) { | |
77 | return -1; | |
78 | } | |
79 | goto reread; | |
80 | } | |
81 | return ret; | |
4549a8b7 | 82 | } |
92dcc234 | 83 | static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, |
4549a8b7 | 84 | const uint8_t *in, uint32_t in_len, |
fd859081 SB |
85 | uint8_t *out, uint32_t out_len, |
86 | bool *selftest_done) | |
4549a8b7 | 87 | { |
4a3d8098 | 88 | ssize_t ret; |
fd859081 SB |
89 | bool is_selftest; |
90 | const struct tpm_resp_hdr *hdr; | |
4549a8b7 | 91 | |
92dcc234 SB |
92 | tpm_pt->tpm_op_canceled = false; |
93 | tpm_pt->tpm_executing = true; | |
fd859081 SB |
94 | *selftest_done = false; |
95 | ||
4a3d8098 | 96 | is_selftest = tpm_util_is_selftest(in, in_len); |
92dcc234 | 97 | |
54aa36d5 | 98 | ret = qemu_write_full(tpm_pt->tpm_fd, in, in_len); |
4549a8b7 | 99 | if (ret != in_len) { |
5f333d79 | 100 | if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { |
92dcc234 | 101 | error_report("tpm_passthrough: error while transmitting data " |
27215a22 | 102 | "to TPM: %s (%i)", |
92dcc234 SB |
103 | strerror(errno), errno); |
104 | } | |
4549a8b7 SB |
105 | goto err_exit; |
106 | } | |
107 | ||
92dcc234 SB |
108 | tpm_pt->tpm_executing = false; |
109 | ||
110 | ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); | |
4549a8b7 | 111 | if (ret < 0) { |
5f333d79 | 112 | if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { |
92dcc234 | 113 | error_report("tpm_passthrough: error while reading data from " |
27215a22 | 114 | "TPM: %s (%i)", |
92dcc234 SB |
115 | strerror(errno), errno); |
116 | } | |
4549a8b7 | 117 | } else if (ret < sizeof(struct tpm_resp_hdr) || |
4a3d8098 | 118 | be32_to_cpu(((struct tpm_resp_hdr *)out)->len) != ret) { |
4549a8b7 SB |
119 | ret = -1; |
120 | error_report("tpm_passthrough: received invalid response " | |
27215a22 | 121 | "packet from TPM"); |
4549a8b7 SB |
122 | } |
123 | ||
fd859081 SB |
124 | if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) { |
125 | hdr = (struct tpm_resp_hdr *)out; | |
126 | *selftest_done = (be32_to_cpu(hdr->errcode) == 0); | |
127 | } | |
128 | ||
4549a8b7 SB |
129 | err_exit: |
130 | if (ret < 0) { | |
4a3d8098 | 131 | tpm_util_write_fatal_error_response(out, out_len); |
4549a8b7 SB |
132 | } |
133 | ||
92dcc234 SB |
134 | tpm_pt->tpm_executing = false; |
135 | ||
4549a8b7 SB |
136 | return ret; |
137 | } | |
138 | ||
0e43b7e6 | 139 | static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd) |
4549a8b7 | 140 | { |
b19a5eea | 141 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
4549a8b7 | 142 | |
0e43b7e6 | 143 | DPRINTF("tpm_passthrough: processing command %p\n", cmd); |
905e78ba | 144 | |
0e43b7e6 MAL |
145 | tpm_passthrough_unix_tx_bufs(tpm_pt, cmd->in, cmd->in_len, |
146 | cmd->out, cmd->out_len, &cmd->selftest_done); | |
4549a8b7 SB |
147 | } |
148 | ||
4549a8b7 SB |
149 | static void tpm_passthrough_reset(TPMBackend *tb) |
150 | { | |
4549a8b7 SB |
151 | DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n"); |
152 | ||
92dcc234 | 153 | tpm_passthrough_cancel_cmd(tb); |
4549a8b7 SB |
154 | } |
155 | ||
156 | static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) | |
157 | { | |
158 | return false; | |
159 | } | |
160 | ||
116694c3 SB |
161 | static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, |
162 | uint8_t locty) | |
163 | { | |
164 | /* only a TPM 2.0 will support this */ | |
165 | return 0; | |
166 | } | |
167 | ||
4549a8b7 SB |
168 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb) |
169 | { | |
8f0605cc | 170 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
92dcc234 SB |
171 | int n; |
172 | ||
173 | /* | |
174 | * As of Linux 3.7 the tpm_tis driver does not properly cancel | |
175 | * commands on all TPM manufacturers' TPMs. | |
176 | * Only cancel if we're busy so we don't cancel someone else's | |
177 | * command, e.g., a command executed on the host. | |
178 | */ | |
179 | if (tpm_pt->tpm_executing) { | |
180 | if (tpm_pt->cancel_fd >= 0) { | |
181 | n = write(tpm_pt->cancel_fd, "-", 1); | |
182 | if (n != 1) { | |
27215a22 | 183 | error_report("Canceling TPM command failed: %s", |
92dcc234 SB |
184 | strerror(errno)); |
185 | } else { | |
186 | tpm_pt->tpm_op_canceled = true; | |
187 | } | |
188 | } else { | |
189 | error_report("Cannot cancel TPM command due to missing " | |
190 | "TPM sysfs cancel entry"); | |
191 | } | |
192 | } | |
4549a8b7 SB |
193 | } |
194 | ||
116694c3 SB |
195 | static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb) |
196 | { | |
56a3c24f | 197 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
4549a8b7 | 198 | |
56a3c24f | 199 | return tpm_pt->tpm_version; |
4549a8b7 SB |
200 | } |
201 | ||
92dcc234 SB |
202 | /* |
203 | * Unless path or file descriptor set has been provided by user, | |
204 | * determine the sysfs cancel file following kernel documentation | |
205 | * in Documentation/ABI/stable/sysfs-class-tpm. | |
8e36d6ca | 206 | * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel |
92dcc234 | 207 | */ |
f59864ba | 208 | static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt) |
92dcc234 SB |
209 | { |
210 | int fd = -1; | |
8e36d6ca | 211 | char *dev; |
92dcc234 | 212 | char path[PATH_MAX]; |
92dcc234 | 213 | |
f59864ba AV |
214 | if (tpm_pt->options->cancel_path) { |
215 | fd = qemu_open(tpm_pt->options->cancel_path, O_WRONLY); | |
92dcc234 SB |
216 | if (fd < 0) { |
217 | error_report("Could not open TPM cancel path : %s", | |
218 | strerror(errno)); | |
219 | } | |
220 | return fd; | |
221 | } | |
222 | ||
8e36d6ca SB |
223 | dev = strrchr(tpm_pt->tpm_dev, '/'); |
224 | if (dev) { | |
225 | dev++; | |
226 | if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", | |
227 | dev) < sizeof(path)) { | |
92dcc234 | 228 | fd = qemu_open(path, O_WRONLY); |
69c07db0 | 229 | if (fd < 0) { |
8e36d6ca SB |
230 | error_report("tpm_passthrough: Could not open TPM cancel " |
231 | "path %s : %s", path, strerror(errno)); | |
232 | } | |
92dcc234 | 233 | } |
8e36d6ca SB |
234 | } else { |
235 | error_report("tpm_passthrough: Bad TPM device path %s", | |
236 | tpm_pt->tpm_dev); | |
92dcc234 SB |
237 | } |
238 | ||
239 | return fd; | |
240 | } | |
241 | ||
803de211 MAL |
242 | static int |
243 | tpm_passthrough_handle_device_opts(TPMPassthruState *tpm_pt, QemuOpts *opts) | |
4549a8b7 SB |
244 | { |
245 | const char *value; | |
246 | ||
92dcc234 | 247 | value = qemu_opt_get(opts, "cancel-path"); |
f59864ba AV |
248 | if (value) { |
249 | tpm_pt->options->cancel_path = g_strdup(value); | |
250 | tpm_pt->options->has_cancel_path = true; | |
251 | } | |
92dcc234 | 252 | |
4549a8b7 | 253 | value = qemu_opt_get(opts, "path"); |
f59864ba AV |
254 | if (value) { |
255 | tpm_pt->options->has_path = true; | |
256 | tpm_pt->options->path = g_strdup(value); | |
4549a8b7 SB |
257 | } |
258 | ||
f59864ba | 259 | tpm_pt->tpm_dev = value ? value : TPM_PASSTHROUGH_DEFAULT_DEVICE; |
8f0605cc SB |
260 | tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR); |
261 | if (tpm_pt->tpm_fd < 0) { | |
27215a22 | 262 | error_report("Cannot access TPM device using '%s': %s", |
8f0605cc | 263 | tpm_pt->tpm_dev, strerror(errno)); |
4549a8b7 SB |
264 | goto err_free_parameters; |
265 | } | |
266 | ||
56a3c24f | 267 | if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) { |
27215a22 | 268 | error_report("'%s' is not a TPM device.", |
8f0605cc | 269 | tpm_pt->tpm_dev); |
4549a8b7 SB |
270 | goto err_close_tpmdev; |
271 | } | |
272 | ||
273 | return 0; | |
274 | ||
275 | err_close_tpmdev: | |
8f0605cc SB |
276 | qemu_close(tpm_pt->tpm_fd); |
277 | tpm_pt->tpm_fd = -1; | |
4549a8b7 SB |
278 | |
279 | err_free_parameters: | |
f59864ba AV |
280 | qapi_free_TPMPassthroughOptions(tpm_pt->options); |
281 | tpm_pt->options = NULL; | |
8f0605cc | 282 | tpm_pt->tpm_dev = NULL; |
4549a8b7 SB |
283 | |
284 | return 1; | |
285 | } | |
286 | ||
287 | static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) | |
288 | { | |
8f0605cc SB |
289 | Object *obj = object_new(TYPE_TPM_PASSTHROUGH); |
290 | TPMBackend *tb = TPM_BACKEND(obj); | |
291 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
4549a8b7 | 292 | |
4549a8b7 | 293 | tb->id = g_strdup(id); |
4549a8b7 | 294 | |
803de211 | 295 | if (tpm_passthrough_handle_device_opts(tpm_pt, opts)) { |
4549a8b7 SB |
296 | goto err_exit; |
297 | } | |
298 | ||
f59864ba | 299 | tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tpm_pt); |
8f0605cc | 300 | if (tpm_pt->cancel_fd < 0) { |
92dcc234 SB |
301 | goto err_exit; |
302 | } | |
303 | ||
4549a8b7 SB |
304 | return tb; |
305 | ||
306 | err_exit: | |
f35fe5cb | 307 | object_unref(obj); |
4549a8b7 SB |
308 | |
309 | return NULL; | |
310 | } | |
311 | ||
f59864ba AV |
312 | static TpmTypeOptions *tpm_passthrough_get_tpm_options(TPMBackend *tb) |
313 | { | |
314 | TpmTypeOptions *options = g_new0(TpmTypeOptions, 1); | |
315 | ||
316 | options->type = TPM_TYPE_OPTIONS_KIND_PASSTHROUGH; | |
317 | options->u.passthrough.data = QAPI_CLONE(TPMPassthroughOptions, | |
318 | TPM_PASSTHROUGH(tb)->options); | |
319 | ||
320 | return options; | |
321 | } | |
322 | ||
bb716238 SB |
323 | static const QemuOptDesc tpm_passthrough_cmdline_opts[] = { |
324 | TPM_STANDARD_CMDLINE_OPTS, | |
325 | { | |
326 | .name = "cancel-path", | |
327 | .type = QEMU_OPT_STRING, | |
328 | .help = "Sysfs file entry for canceling TPM commands", | |
329 | }, | |
330 | { | |
331 | .name = "path", | |
332 | .type = QEMU_OPT_STRING, | |
333 | .help = "Path to TPM device on the host", | |
334 | }, | |
335 | { /* end of list */ }, | |
336 | }; | |
337 | ||
8f0605cc SB |
338 | static void tpm_passthrough_inst_init(Object *obj) |
339 | { | |
f35fe5cb AV |
340 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); |
341 | ||
f59864ba | 342 | tpm_pt->options = g_new0(TPMPassthroughOptions, 1); |
f35fe5cb AV |
343 | tpm_pt->tpm_fd = -1; |
344 | tpm_pt->cancel_fd = -1; | |
8f0605cc SB |
345 | } |
346 | ||
347 | static void tpm_passthrough_inst_finalize(Object *obj) | |
348 | { | |
f35fe5cb AV |
349 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); |
350 | ||
351 | tpm_passthrough_cancel_cmd(TPM_BACKEND(obj)); | |
352 | ||
353 | qemu_close(tpm_pt->tpm_fd); | |
354 | qemu_close(tpm_pt->cancel_fd); | |
f59864ba | 355 | qapi_free_TPMPassthroughOptions(tpm_pt->options); |
8f0605cc SB |
356 | } |
357 | ||
358 | static void tpm_passthrough_class_init(ObjectClass *klass, void *data) | |
359 | { | |
360 | TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass); | |
361 | ||
d31076ba MAL |
362 | tbc->type = TPM_TYPE_PASSTHROUGH; |
363 | tbc->opts = tpm_passthrough_cmdline_opts; | |
364 | tbc->desc = "Passthrough TPM backend driver"; | |
365 | tbc->create = tpm_passthrough_create; | |
366 | tbc->reset = tpm_passthrough_reset; | |
367 | tbc->cancel_cmd = tpm_passthrough_cancel_cmd; | |
368 | tbc->get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag; | |
369 | tbc->reset_tpm_established_flag = | |
370 | tpm_passthrough_reset_tpm_established_flag; | |
371 | tbc->get_tpm_version = tpm_passthrough_get_tpm_version; | |
372 | tbc->get_tpm_options = tpm_passthrough_get_tpm_options; | |
b19a5eea | 373 | tbc->handle_request = tpm_passthrough_handle_request; |
8f0605cc SB |
374 | } |
375 | ||
376 | static const TypeInfo tpm_passthrough_info = { | |
377 | .name = TYPE_TPM_PASSTHROUGH, | |
378 | .parent = TYPE_TPM_BACKEND, | |
379 | .instance_size = sizeof(TPMPassthruState), | |
380 | .class_init = tpm_passthrough_class_init, | |
381 | .instance_init = tpm_passthrough_inst_init, | |
382 | .instance_finalize = tpm_passthrough_inst_finalize, | |
383 | }; | |
384 | ||
4549a8b7 SB |
385 | static void tpm_passthrough_register(void) |
386 | { | |
8f0605cc | 387 | type_register_static(&tpm_passthrough_info); |
4549a8b7 SB |
388 | } |
389 | ||
390 | type_init(tpm_passthrough_register) |