diff options
38 files changed, 3399 insertions, 70 deletions
diff --git a/Makefile.objs b/Makefile.objs index 8c90b92d01..f99841ce54 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -74,6 +74,7 @@ common-obj-y += bt-host.o bt-vhci.o common-obj-y += dma-helpers.o common-obj-y += vl.o +common-obj-y += tpm/ common-obj-$(CONFIG_SLIRP) += slirp/ @@ -228,6 +228,7 @@ glusterfs="" virtio_blk_data_plane="" gtk="" gtkabi="2.0" +tpm="no" # parse CC options first for opt do @@ -905,6 +906,8 @@ for opt do ;; --with-gtkabi=*) gtkabi="$optarg" ;; + --enable-tpm) tpm="yes" + ;; *) echo "ERROR: unknown option $opt"; show_help="yes" ;; esac @@ -1160,6 +1163,7 @@ echo " --enable-glusterfs enable GlusterFS backend" echo " --disable-glusterfs disable GlusterFS backend" echo " --enable-gcov enable test coverage analysis with gcov" echo " --gcov=GCOV use specified gcov [$gcov_tool]" +echo " --enable-tpm enable TPM support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2760,6 +2764,20 @@ if compile_prog "" "" ; then epoll_pwait=yes fi +# check for sendfile support +sendfile=no +cat > $TMPC << EOF +#include <sys/sendfile.h> + +int main(void) +{ + return sendfile(0, 0, 0, 0); +} +EOF +if compile_prog "" "" ; then + sendfile=yes +fi + # Check if tools are available to build documentation. if test "$docs" != "no" ; then if has makeinfo && has pod2man; then @@ -3420,6 +3438,7 @@ echo "GlusterFS support $glusterfs" echo "virtio-blk-data-plane $virtio_blk_data_plane" echo "gcov $gcov_tool" echo "gcov enabled $gcov" +echo "TPM support $tpm" if test "$sdl_too_old" = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -3628,6 +3647,9 @@ fi if test "$epoll_pwait" = "yes" ; then echo "CONFIG_EPOLL_PWAIT=y" >> $config_host_mak fi +if test "$sendfile" = "yes" ; then + echo "CONFIG_SENDFILE=y" >> $config_host_mak +fi if test "$inotify" = "yes" ; then echo "CONFIG_INOTIFY=y" >> $config_host_mak fi @@ -4329,6 +4351,12 @@ if test "$gprof" = "yes" ; then fi fi +if test "$tpm" = "yes"; then + if test "$target_softmmu" = "yes" ; then + echo "CONFIG_TPM=y" >> $config_host_mak + fi +fi + if test "$ARCH" = "tci"; then linker_script="" else diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak index 1b23025a98..df9e126c1f 100644 --- a/default-configs/i386-softmmu.mak +++ b/default-configs/i386-softmmu.mak @@ -26,3 +26,5 @@ CONFIG_HPET=y CONFIG_APPLESMC=y CONFIG_I8259=y CONFIG_PFLASH_CFI01=y +CONFIG_TPM_TIS=y +CONFIG_TPM_PASSTHROUGH=y diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak index 3392f5abd6..ab3cd5fc35 100644 --- a/default-configs/x86_64-softmmu.mak +++ b/default-configs/x86_64-softmmu.mak @@ -26,3 +26,5 @@ CONFIG_HPET=y CONFIG_APPLESMC=y CONFIG_I8259=y CONFIG_PFLASH_CFI01=y +CONFIG_TPM_TIS=y +CONFIG_TPM_PASSTHROUGH=y @@ -846,6 +846,8 @@ static void *file_ram_alloc(RAMBlock *block, const char *path) { char *filename; + char *sanitized_name; + char *c; void *area; int fd; #ifdef MAP_POPULATE @@ -867,7 +869,16 @@ static void *file_ram_alloc(RAMBlock *block, return NULL; } - filename = g_strdup_printf("%s/qemu_back_mem.XXXXXX", path); + /* Make name safe to use with mkstemp by replacing '/' with '_'. */ + sanitized_name = g_strdup(block->mr->name); + for (c = sanitized_name; *c != '\0'; c++) { + if (*c == '/') + *c = '_'; + } + + filename = g_strdup_printf("%s/qemu_back_mem.%s.XXXXXX", path, + sanitized_name); + g_free(sanitized_name); fd = mkstemp(filename); if (fd < 0) { diff --git a/hmp-commands.hx b/hmp-commands.hx index 69c707d332..4bda3fea0e 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1642,6 +1642,8 @@ show device tree show qdev device model list @item info roms show roms +@item info tpm +show the TPM device @end table ETEXI @@ -607,6 +607,50 @@ void hmp_info_block_jobs(Monitor *mon, const QDict *qdict) } } +void hmp_info_tpm(Monitor *mon, const QDict *qdict) +{ + TPMInfoList *info_list, *info; + Error *err = NULL; + unsigned int c = 0; + TPMPassthroughOptions *tpo; + + info_list = qmp_query_tpm(&err); + if (err) { + monitor_printf(mon, "TPM device not supported\n"); + error_free(err); + return; + } + + if (info_list) { + monitor_printf(mon, "TPM device:\n"); + } + + for (info = info_list; info; info = info->next) { + TPMInfo *ti = info->value; + monitor_printf(mon, " tpm%d: model=%s\n", + c, TpmModel_lookup[ti->model]); + + monitor_printf(mon, " \\ %s: type=%s", + ti->id, TpmType_lookup[ti->type]); + + switch (ti->tpm_options->kind) { + case TPM_TYPE_OPTIONS_KIND_TPM_PASSTHROUGH_OPTIONS: + tpo = ti->tpm_options->tpm_passthrough_options; + monitor_printf(mon, "%s%s%s%s", + tpo->has_path ? ",path=" : "", + tpo->has_path ? tpo->path : "", + tpo->has_cancel_path ? ",cancel-path=" : "", + tpo->has_cancel_path ? tpo->cancel_path : ""); + break; + case TPM_TYPE_OPTIONS_KIND_MAX: + break; + } + monitor_printf(mon, "\n"); + c++; + } + qapi_free_TPMInfoList(info_list); +} + void hmp_quit(Monitor *mon, const QDict *qdict) { monitor_suspend(mon); @@ -36,6 +36,7 @@ void hmp_info_spice(Monitor *mon, const QDict *qdict); void hmp_info_balloon(Monitor *mon, const QDict *qdict); void hmp_info_pci(Monitor *mon, const QDict *qdict); void hmp_info_block_jobs(Monitor *mon, const QDict *qdict); +void hmp_info_tpm(Monitor *mon, const QDict *qdict); void hmp_quit(Monitor *mon, const QDict *qdict); void hmp_stop(Monitor *mon, const QDict *qdict); void hmp_system_reset(Monitor *mon, const QDict *qdict); diff --git a/include/qemu/sockets.h b/include/qemu/sockets.h index 6125bf7bdf..c5cee4bf2f 100644 --- a/include/qemu/sockets.h +++ b/include/qemu/sockets.h @@ -38,6 +38,7 @@ int socket_set_nodelay(int fd); void socket_set_block(int fd); void socket_set_nonblock(int fd); int send_all(int fd, const void *buf, int len1); +int recv_all(int fd, void *buf, int len1, bool single_read); /* callback function for nonblocking connect * valid fd on success, negative error code on failure diff --git a/include/tpm/tpm.h b/include/tpm/tpm.h new file mode 100644 index 0000000000..cc8f20e69e --- /dev/null +++ b/include/tpm/tpm.h @@ -0,0 +1,21 @@ +/* + * Public TPM functions + * + * Copyright (C) 2011-2013 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#ifndef QEMU_TPM_H +#define QEMU_TPM_H + +#include "qemu/option.h" + +int tpm_config_parse(QemuOptsList *opts_list, const char *optarg); +int tpm_init(void); +void tpm_cleanup(void); + +#endif /* QEMU_TPM_H */ diff --git a/linux-user/main.c b/linux-user/main.c index d8b0cd65fa..4e92a0b4c5 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -3406,27 +3406,35 @@ static void usage(void) "Options and associated environment variables:\n" "\n"); - maxarglen = maxenvlen = 0; + /* Calculate column widths. We must always have at least enough space + * for the column header. + */ + maxarglen = strlen("Argument"); + maxenvlen = strlen("Env-variable"); for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { + int arglen = strlen(arginfo->argv); + if (arginfo->has_arg) { + arglen += strlen(arginfo->example) + 1; + } if (strlen(arginfo->env) > maxenvlen) { maxenvlen = strlen(arginfo->env); } - if (strlen(arginfo->argv) > maxarglen) { - maxarglen = strlen(arginfo->argv); + if (arglen > maxarglen) { + maxarglen = arglen; } } - printf("%-*s%-*sDescription\n", maxarglen+3, "Argument", - maxenvlen+1, "Env-variable"); + printf("%-*s %-*s Description\n", maxarglen+1, "Argument", + maxenvlen, "Env-variable"); for (arginfo = arg_table; arginfo->handle_opt != NULL; arginfo++) { if (arginfo->has_arg) { printf("-%s %-*s %-*s %s\n", arginfo->argv, - (int)(maxarglen-strlen(arginfo->argv)), arginfo->example, - maxenvlen, arginfo->env, arginfo->help); + (int)(maxarglen - strlen(arginfo->argv) - 1), + arginfo->example, maxenvlen, arginfo->env, arginfo->help); } else { - printf("-%-*s %-*s %s\n", maxarglen+1, arginfo->argv, + printf("-%-*s %-*s %s\n", maxarglen, arginfo->argv, maxenvlen, arginfo->env, arginfo->help); } diff --git a/linux-user/socket.h b/linux-user/socket.h index 93d47823d8..339cae5a16 100644 --- a/linux-user/socket.h +++ b/linux-user/socket.h @@ -87,6 +87,75 @@ #define TARGET_SOCK_MAX (SOCK_PACKET + 1) +#elif defined(TARGET_ALPHA) + + /* For setsockopt(2) */ + #define TARGET_SOL_SOCKET 0xffff + + #define TARGET_SO_DEBUG 0x0001 + #define TARGET_SO_REUSEADDR 0x0004 + #define TARGET_SO_KEEPALIVE 0x0008 + #define TARGET_SO_DONTROUTE 0x0010 + #define TARGET_SO_BROADCAST 0x0020 + #define TARGET_SO_LINGER 0x0080 + #define TARGET_SO_OOBINLINE 0x0100 + /* To add :#define TARGET_SO_REUSEPORT 0x0200 */ + + #define TARGET_SO_TYPE 0x1008 + #define TARGET_SO_ERROR 0x1007 + #define TARGET_SO_SNDBUF 0x1001 + #define TARGET_SO_RCVBUF 0x1002 + #define TARGET_SO_SNDBUFFORCE 0x100a + #define TARGET_SO_RCVBUFFORCE 0x100b + #define TARGET_SO_RCVLOWAT 0x1010 + #define TARGET_SO_SNDLOWAT 0x1011 + #define TARGET_SO_RCVTIMEO 0x1012 + #define TARGET_SO_SNDTIMEO 0x1013 + #define TARGET_SO_ACCEPTCONN 0x1014 + #define TARGET_SO_PROTOCOL 0x1028 + #define TARGET_SO_DOMAIN 0x1029 + + /* linux-specific, might as well be the same as on i386 */ + #define TARGET_SO_NO_CHECK 11 + #define TARGET_SO_PRIORITY 12 + #define TARGET_SO_BSDCOMPAT 14 + + #define TARGET_SO_PASSCRED 17 + #define TARGET_SO_PEERCRED 18 + #define TARGET_SO_BINDTODEVICE 25 + + /* Socket filtering */ + #define TARGET_SO_ATTACH_FILTER 26 + #define TARGET_SO_DETACH_FILTER 27 + + #define TARGET_SO_PEERNAME 28 + #define TARGET_SO_TIMESTAMP 29 + #define TARGET_SCM_TIMESTAMP TARGET_SO_TIMESTAMP + + #define TARGET_SO_PEERSEC 30 + #define TARGET_SO_PASSSEC 34 + #define TARGET_SO_TIMESTAMPNS 35 + #define TARGET_SCM_TIMESTAMPNS TARGET_SO_TIMESTAMPNS + + /* Security levels - as per NRL IPv6 - don't actually do anything */ + #define TARGET_SO_SECURITY_AUTHENTICATION 19 + #define TARGET_SO_SECURITY_ENCRYPTION_TRANSPORT 20 + #define TARGET_SO_SECURITY_ENCRYPTION_NETWORK 21 + + #define TARGET_SO_MARK 36 + + #define TARGET_SO_TIMESTAMPING 37 + #define TARGET_SCM_TIMESTAMPING TARGET_SO_TIMESTAMPING + + #define TARGET_SO_RXQ_OVFL 40 + + #define TARGET_SO_WIFI_STATUS 41 + #define TARGET_SCM_WIFI_STATUS TARGET_SO_WIFI_STATUS + #define TARGET_SO_PEEK_OFF 42 + + /* Instruct lower device to use last 4-bytes of skb data as FCS */ + #define TARGET_SO_NOFCS 43 + #else /* For setsockopt(2) */ diff --git a/linux-user/sparc/syscall_nr.h b/linux-user/sparc/syscall_nr.h index 061711cc03..534e6e9963 100644 --- a/linux-user/sparc/syscall_nr.h +++ b/linux-user/sparc/syscall_nr.h @@ -200,6 +200,8 @@ #define TARGET_NR__newselect 230 /* Linux Specific */ #define TARGET_NR_time 231 /* Linux Specific */ #define TARGET_NR_stime 233 /* Linux Specific */ +#define TARGET_NR_statfs64 234 /* Linux Specific */ +#define TARGET_NR_fstatfs64 235 /* Linux Specific */ #define TARGET_NR__llseek 236 /* Linux Specific */ #define TARGET_NR_mlock 237 #define TARGET_NR_munlock 238 diff --git a/linux-user/strace.c b/linux-user/strace.c index 4e91a6eb9c..0fbae3c2f6 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -462,18 +462,6 @@ UNUSED static struct flags mmap_flags[] = { FLAG_END, }; -UNUSED static struct flags fcntl_flags[] = { - FLAG_TARGET(F_DUPFD), - FLAG_TARGET(F_GETFD), - FLAG_TARGET(F_SETFD), - FLAG_TARGET(F_GETFL), - FLAG_TARGET(F_SETFL), - FLAG_TARGET(F_GETLK), - FLAG_TARGET(F_SETLK), - FLAG_TARGET(F_SETLKW), - FLAG_END, -}; - UNUSED static struct flags clone_flags[] = { FLAG_GENERIC(CLONE_VM), FLAG_GENERIC(CLONE_FS), @@ -867,12 +855,85 @@ print_fcntl(const struct syscallname *name, { print_syscall_prologue(name); print_raw_param("%d", arg0, 0); - print_flags(fcntl_flags, arg1, 0); - /* - * TODO: check flags and print following argument only - * when needed. - */ - print_pointer(arg2, 1); + switch(arg1) { + case TARGET_F_DUPFD: + gemu_log("F_DUPFD,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); + break; + case TARGET_F_GETFD: + gemu_log("F_GETFD"); + break; + case TARGET_F_SETFD: + gemu_log("F_SETFD,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); + break; + case TARGET_F_GETFL: + gemu_log("F_GETFL"); + break; + case TARGET_F_SETFL: + gemu_log("F_SETFL,"); + print_open_flags(arg2, 1); + break; + case TARGET_F_GETLK: + gemu_log("F_GETLK,"); + print_pointer(arg2, 1); + break; + case TARGET_F_SETLK: + gemu_log("F_SETLK,"); + print_pointer(arg2, 1); + break; + case TARGET_F_SETLKW: + gemu_log("F_SETLKW,"); + print_pointer(arg2, 1); + break; + case TARGET_F_GETOWN: + gemu_log("F_GETOWN"); + break; + case TARGET_F_SETOWN: + gemu_log("F_SETOWN,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + break; + case TARGET_F_GETSIG: + gemu_log("F_GETSIG"); + break; + case TARGET_F_SETSIG: + gemu_log("F_SETSIG,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + break; +#if TARGET_ABI_BITS == 32 + case TARGET_F_GETLK64: + gemu_log("F_GETLK64,"); + print_pointer(arg2, 1); + break; + case TARGET_F_SETLK64: + gemu_log("F_SETLK64,"); + print_pointer(arg2, 1); + break; + case TARGET_F_SETLKW64: + gemu_log("F_SETLKW64,"); + print_pointer(arg2, 1); + break; +#endif + case TARGET_F_SETLEASE: + gemu_log("F_SETLEASE,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + break; + case TARGET_F_GETLEASE: + gemu_log("F_GETLEASE"); + break; + case TARGET_F_DUPFD_CLOEXEC: + gemu_log("F_DUPFD_CLOEXEC,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 1); + break; + case TARGET_F_NOTIFY: + gemu_log("F_NOTIFY,"); + print_raw_param(TARGET_ABI_FMT_ld, arg2, 0); + break; + default: + print_raw_param(TARGET_ABI_FMT_ld, arg1, 0); + print_pointer(arg2, 1); + break; + } print_syscall_epilogue(name); } #define print_fcntl64 print_fcntl @@ -1437,6 +1498,12 @@ if( cmd == val ) { \ cmd &= ~FUTEX_PRIVATE_FLAG; } #endif +#ifdef FUTEX_CLOCK_REALTIME + if (cmd & FUTEX_CLOCK_REALTIME) { + gemu_log("FUTEX_CLOCK_REALTIME|"); + cmd &= ~FUTEX_CLOCK_REALTIME; + } +#endif print_op(FUTEX_WAIT) print_op(FUTEX_WAKE) print_op(FUTEX_FD) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 19630eaf20..ee82a2da4e 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -78,6 +78,9 @@ int __clone2(int (*fn)(void *), void *child_stack_base, #ifdef CONFIG_ATTR #include "qemu/xattr.h" #endif +#ifdef CONFIG_SENDFILE +#include <sys/sendfile.h> +#endif #define termios host_termios #define winsize host_winsize @@ -1776,7 +1779,7 @@ static struct iovec *lock_iovec(int type, abi_ulong target_addr, errno = 0; return NULL; } - if (count > IOV_MAX) { + if (count < 0 || count > IOV_MAX) { errno = EINVAL; return NULL; } @@ -2001,16 +2004,30 @@ out2: return ret; } -/* do_accept() Must return target values and target errnos. */ -static abi_long do_accept(int fd, abi_ulong target_addr, - abi_ulong target_addrlen_addr) +/* If we don't have a system accept4() then just call accept. + * The callsites to do_accept4() will ensure that they don't + * pass a non-zero flags argument in this config. + */ +#ifndef CONFIG_ACCEPT4 +static inline int accept4(int sockfd, struct sockaddr *addr, + socklen_t *addrlen, int flags) +{ + assert(flags == 0); + return accept(sockfd, addr, addrlen); +} +#endif + +/* do_accept4() Must return target values and target errnos. */ +static abi_long do_accept4(int fd, abi_ulong target_addr, + abi_ulong target_addrlen_addr, int flags) { socklen_t addrlen; void *addr; abi_long ret; - if (target_addr == 0) - return get_errno(accept(fd, NULL, NULL)); + if (target_addr == 0) { + return get_errno(accept4(fd, NULL, NULL, flags)); + } /* linux returns EINVAL if addrlen pointer is invalid */ if (get_user_u32(addrlen, target_addrlen_addr)) @@ -2025,7 +2042,7 @@ static abi_long do_accept(int fd, abi_ulong target_addr, addr = alloca(addrlen); - ret = get_errno(accept(fd, addr, &addrlen)); + ret = get_errno(accept4(fd, addr, &addrlen, flags)); if (!is_error(ret)) { host_to_target_sockaddr(target_addr, addr, addrlen); if (put_user_u32(addrlen, target_addrlen_addr)) @@ -2251,7 +2268,7 @@ static abi_long do_socketcall(int num, abi_ulong vptr) || get_user_ual(target_addrlen, vptr + 2 * n)) return -TARGET_EFAULT; - ret = do_accept(sockfd, target_addr, target_addrlen); + ret = do_accept4(sockfd, target_addr, target_addrlen, 0); } break; case SOCKOP_getsockname: @@ -4922,6 +4939,7 @@ static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, #endif switch (base_op) { case FUTEX_WAIT: + case FUTEX_WAIT_BITSET: if (timeout) { pts = &ts; target_to_host_timespec(pts, timeout); @@ -4929,7 +4947,7 @@ static int do_futex(target_ulong uaddr, int op, int val, target_ulong timeout, pts = NULL; } return get_errno(sys_futex(g2h(uaddr), op, tswap32(val), - pts, NULL, 0)); + pts, NULL, val3)); case FUTEX_WAKE: return get_errno(sys_futex(g2h(uaddr), op, val, NULL, NULL, 0)); case FUTEX_FD: @@ -6673,7 +6691,16 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, #endif #ifdef TARGET_NR_accept case TARGET_NR_accept: - ret = do_accept(arg1, arg2, arg3); + ret = do_accept4(arg1, arg2, arg3, 0); + break; +#endif +#ifdef TARGET_NR_accept4 + case TARGET_NR_accept4: +#ifdef CONFIG_ACCEPT4 + ret = do_accept4(arg1, arg2, arg3, arg4); +#else + goto unimplemented; +#endif break; #endif #ifdef TARGET_NR_bind @@ -7530,8 +7557,58 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, #else goto unimplemented; #endif + +#ifdef CONFIG_SENDFILE + case TARGET_NR_sendfile: + { + off_t *offp = NULL; + off_t off; + if (arg3) { + ret = get_user_sal(off, arg3); + if (is_error(ret)) { + break; + } + offp = &off; + } + ret = get_errno(sendfile(arg1, arg2, offp, arg4)); + if (!is_error(ret) && arg3) { + abi_long ret2 = put_user_sal(off, arg3); + if (is_error(ret2)) { + ret = ret2; + } + } + break; + } +#ifdef TARGET_NR_sendfile64 + case TARGET_NR_sendfile64: + { + off_t *offp = NULL; + off_t off; + if (arg3) { + ret = get_user_s64(off, arg3); + if (is_error(ret)) { + break; + } + offp = &off; + } + ret = get_errno(sendfile(arg1, arg2, offp, arg4)); + if (!is_error(ret) && arg3) { + abi_long ret2 = put_user_s64(off, arg3); + if (is_error(ret2)) { + ret = ret2; + } + } + break; + } +#endif +#else case TARGET_NR_sendfile: +#ifdef TARGET_NR_sendfile64: + case TARGET_NR_sendfile64: +#endif goto unimplemented; +#endif + #ifdef TARGET_NR_getpmsg case TARGET_NR_getpmsg: goto unimplemented; @@ -7679,18 +7756,20 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, { int gidsetsize = arg1; target_id *target_grouplist; - gid_t *grouplist; + gid_t *grouplist = NULL; int i; - - grouplist = alloca(gidsetsize * sizeof(gid_t)); - target_grouplist = lock_user(VERIFY_READ, arg2, gidsetsize * 2, 1); - if (!target_grouplist) { - ret = -TARGET_EFAULT; - goto fail; + if (gidsetsize) { + grouplist = alloca(gidsetsize * sizeof(gid_t)); + target_grouplist = lock_user(VERIFY_READ, arg2, gidsetsize * 2, 1); + if (!target_grouplist) { + ret = -TARGET_EFAULT; + goto fail; + } + for (i = 0; i < gidsetsize; i++) { + grouplist[i] = low2highgid(tswapid(target_grouplist[i])); + } + unlock_user(target_grouplist, arg2, 0); } - for(i = 0;i < gidsetsize; i++) - grouplist[i] = low2highgid(tswapid(target_grouplist[i])); - unlock_user(target_grouplist, arg2, 0); ret = get_errno(setgroups(gidsetsize, grouplist)); } break; @@ -8552,7 +8631,20 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1, #ifdef TARGET_NR_set_robust_list case TARGET_NR_set_robust_list: - goto unimplemented_nowarn; + case TARGET_NR_get_robust_list: + /* The ABI for supporting robust futexes has userspace pass + * the kernel a pointer to a linked list which is updated by + * userspace after the syscall; the list is walked by the kernel + * when the thread exits. Since the linked list in QEMU guest + * memory isn't a valid linked list for the host and we have + * no way to reliably intercept the thread-death event, we can't + * support these. Silently return ENOSYS so that guest userspace + * falls back to a non-robust futex implementation (which should + * be OK except in the corner case of the guest crashing while + * holding a mutex that is shared with another process via + * shared memory). + */ + goto unimplemented_nowarn; #endif #if defined(TARGET_NR_utimensat) && defined(__NR_utimensat) @@ -47,6 +47,7 @@ #include "migration/migration.h" #include "sysemu/kvm.h" #include "qemu/acl.h" +#include "tpm/tpm.h" #include "qapi/qmp/qint.h" #include "qapi/qmp/qfloat.h" #include "qapi/qmp/qlist.h" @@ -2719,6 +2720,13 @@ static mon_cmd_t info_cmds[] = { .mhandler.cmd = do_trace_print_events, }, { + .name = "tpm", + .args_type = "", + .params = "", + .help = "show the TPM device", + .mhandler.cmd = hmp_info_tpm, + }, + { .name = NULL, }, }; diff --git a/qapi-schema.json b/qapi-schema.json index 28b070f16b..4494e53693 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3240,3 +3240,107 @@ # Since: 1.4 ## { 'command': 'chardev-remove', 'data': {'id': 'str'} } + +## +# @TpmModel: +# +# An enumeration of TPM models +# +# @tpm-tis: TPM TIS model +# +# Since: 1.5 +## +{ 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] } + +## +# @query-tpm-models: +# +# Return a list of supported TPM models +# +# Returns: a list of TpmModel +# +# Since: 1.5 +## +{ 'command': 'query-tpm-models', 'returns': ['TpmModel'] } + +## +# @TpmType: +# +# An enumeration of TPM types +# +# @passthrough: TPM passthrough type +# +# Since: 1.5 +## +{ 'enum': 'TpmType', 'data': [ 'passthrough' ] } + +## +# @query-tpm-types: +# +# Return a list of supported TPM types +# +# Returns: a list of TpmType +# +# Since: 1.5 +## +{ 'command': 'query-tpm-types', 'returns': ['TpmType'] } + +## +# @TPMPassthroughOptions: +# +# Information about the TPM passthrough type +# +# @path: #optional string describing the path used for accessing the TPM device +# +# @cancel-path: #optional string showing the TPM's sysfs cancel file +# for cancellation of TPM commands while they are executing +# +# Since: 1.5 +## +{ 'type': 'TPMPassthroughOptions', 'data': { '*path' : 'str', + '*cancel-path' : 'str'} } + +## +# @TpmTypeOptions: +# +# A union referencing different TPM backend types' configuration options +# +# @tpm-passthough-options: TPMPassthroughOptions describing the TPM +# passthrough configuration options +# +# Since: 1.5 +## +{ 'union': 'TpmTypeOptions', + 'data': { 'tpm-passthrough-options' : 'TPMPassthroughOptions' } } + +## +# @TpmInfo: +# +# Information about the TPM +# +# @id: The Id of the TPM +# +# @model: The TPM frontend model +# +# @type: The TPM (backend) type being used +# +# @tpm-options: The TPM (backend) type configuration options +# +# Since: 1.5 +## +{ 'type': 'TPMInfo', + 'data': {'id': 'str', + 'model': 'TpmModel', + 'type': 'TpmType', + 'tpm-options': 'TpmTypeOptions' } } + +## +# @query-tpm: +# +# Return information about the TPM device +# +# Returns: @TPMInfo on success +# +# Since: 1.5 +## +{ 'command': 'query-tpm', 'returns': ['TPMInfo'] } diff --git a/qemu-char.c b/qemu-char.c index 04aa589c7e..83787c74c4 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -533,6 +533,30 @@ int send_all(int fd, const void *_buf, int len1) } return len1 - len; } + +int recv_all(int fd, void *_buf, int len1, bool single_read) +{ + int ret, len; + uint8_t *buf = _buf; + + len = len1; + while ((len > 0) && (ret = read(fd, buf, len)) != 0) { + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) { + return -1; + } + continue; + } else { + if (single_read) { + return ret; + } + buf += ret; + len -= ret; + } + } + return len1 - len; +} + #endif /* !_WIN32 */ typedef struct IOWatchPoll diff --git a/qemu-options.hx b/qemu-options.hx index cd76f2a00c..30fb85d619 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2217,6 +2217,80 @@ STEXI ETEXI DEFHEADING() +#ifdef CONFIG_TPM +DEFHEADING(TPM device options:) + +DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \ + "-tpmdev passthrough,id=id[,path=path][,cancel-path=path]\n" + " use path to provide path to a character device; default is /dev/tpm0\n" + " use cancel-path to provide path to TPM's cancel sysfs entry; if\n" + " not provided it will be searched for in /sys/class/misc/tpm?/device\n", + QEMU_ARCH_ALL) +STEXI + +The general form of a TPM device option is: +@table @option + +@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}] +@findex -tpmdev +Backend type must be: +@option{passthrough}. + +The specific backend type will determine the applicable options. +The @code{-tpmdev} option requires a @code{-device} option. + +Options to each backend are described below. + +Use 'help' to print all available TPM backend types. +@example +qemu -tpmdev help +@end example + +@item -tpmdev passthrough, id=@var{id}, path=@var{path}, cancel-path=@var{cancel-path} + +(Linux-host only) Enable access to the host's TPM using the passthrough +driver. + +@option{path} specifies the path to the host's TPM device, i.e., on +a Linux host this would be @code{/dev/tpm0}. +@option{path} is optional and by default @code{/dev/tpm0} is used. + +@option{cancel-path} specifies the path to the host TPM device's sysfs +entry allowing for cancellation of an ongoing TPM command. +@option{cancel-path} is optional and by default QEMU will search for the +sysfs entry to use. + +Some notes about using the host's TPM with the passthrough driver: + +The TPM device accessed by the passthrough driver must not be +used by any other application on the host. + +Since the host's firmware (BIOS/UEFI) has already initialized the TPM, +the VM's firmware (BIOS/UEFI) will not be able to initialize the +TPM again and may therefore not show a TPM-specific menu that would +otherwise allow the user to configure the TPM, e.g., allow the user to +enable/disable or activate/deactivate the TPM. +Further, if TPM ownership is released from within a VM then the host's TPM +will get disabled and deactivated. To enable and activate the +TPM again afterwards, the host has to be rebooted and the user is +required to enter the firmware's menu to enable and activate the TPM. +If the TPM is left disabled and/or deactivated most TPM commands will fail. + +To create a passthrough TPM use the following two options: +@example +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0 +@end example +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by +@code{tpmdev=tpm0} in the device option. + +@end table + +ETEXI + +DEFHEADING() + +#endif + DEFHEADING(Linux/Multiboot boot specific:) STEXI diff --git a/qga/channel-win32.c b/qga/channel-win32.c index 16bf44a376..7ed98d72fb 100644 --- a/qga/channel-win32.c +++ b/qga/channel-win32.c @@ -287,7 +287,7 @@ GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size) static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method, const gchar *path) { - if (!method == GA_CHANNEL_VIRTIO_SERIAL) { + if (method != GA_CHANNEL_VIRTIO_SERIAL) { g_critical("unsupported communication method"); return false; } diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 7a0202eb2a..d7da850615 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -15,6 +15,10 @@ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/wait.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" #include "qapi/qmp/qerror.h" @@ -119,6 +123,77 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) /* succeded */ } +int64_t qmp_guest_get_time(Error **errp) +{ + int ret; + qemu_timeval tq; + int64_t time_ns; + + ret = qemu_gettimeofday(&tq); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to get time"); + return -1; + } + + time_ns = tq.tv_sec * 1000000000LL + tq.tv_usec * 1000; + return time_ns; +} + +void qmp_guest_set_time(int64_t time_ns, Error **errp) +{ + int ret; + int status; + pid_t pid; + Error *local_err = NULL; + struct timeval tv; + + /* year-2038 will overflow in case time_t is 32bit */ + if (time_ns / 1000000000 != (time_t)(time_ns / 1000000000)) { + error_setg(errp, "Time %" PRId64 " is too large", time_ns); + return; + } + + tv.tv_sec = time_ns / 1000000000; + tv.tv_usec = (time_ns % 1000000000) / 1000; + + ret = settimeofday(&tv, NULL); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to set time to guest"); + return; + } + + /* Set the Hardware Clock to the current System Time. */ + pid = fork(); + if (pid == 0) { + setsid(); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + execle("/sbin/hwclock", "hwclock", "-w", NULL, environ); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + return; + } + + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + return; + } + + if (!WIFEXITED(status)) { + error_setg(errp, "child process has terminated abnormally"); + return; + } + + if (WEXITSTATUS(status)) { + error_setg(errp, "hwclock failed to set hardware clock to system time"); + return; + } +} + typedef struct GuestFileHandle { uint64_t id; FILE *fh; @@ -129,14 +204,22 @@ static struct { QTAILQ_HEAD(, GuestFileHandle) filehandles; } guest_file_state; -static void guest_file_handle_add(FILE *fh) +static int64_t guest_file_handle_add(FILE *fh, Error **errp) { GuestFileHandle *gfh; + int64_t handle; + + handle = ga_get_fd_handle(ga_state, errp); + if (error_is_set(errp)) { + return 0; + } gfh = g_malloc0(sizeof(GuestFileHandle)); - gfh->id = fileno(fh); + gfh->id = handle; gfh->fh = fh; QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); + + return handle; } static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err) @@ -158,7 +241,7 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E { FILE *fh; int fd; - int64_t ret = -1; + int64_t ret = -1, handle; if (!has_mode) { mode = "r"; @@ -184,9 +267,14 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E return -1; } - guest_file_handle_add(fh); - slog("guest-file-open, handle: %d", fd); - return fd; + handle = guest_file_handle_add(fh, err); + if (error_is_set(err)) { + fclose(fh); + return -1; + } + + slog("guest-file-open, handle: %d", handle); + return handle; } void qmp_guest_file_close(int64_t handle, Error **err) @@ -1027,6 +1115,162 @@ error: return NULL; } +#define SYSCONF_EXACT(name, err) sysconf_exact((name), #name, (err)) + +static long sysconf_exact(int name, const char *name_str, Error **err) +{ + long ret; + + errno = 0; + ret = sysconf(name); + if (ret == -1) { + if (errno == 0) { + error_setg(err, "sysconf(%s): value indefinite", name_str); + } else { + error_setg_errno(err, errno, "sysconf(%s)", name_str); + } + } + return ret; +} + +/* Transfer online/offline status between @vcpu and the guest system. + * + * On input either @errp or *@errp must be NULL. + * + * In system-to-@vcpu direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - W: vcpu->online + * - W: vcpu->can_offline + * + * In @vcpu-to-system direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - R: vcpu->online + * + * Written members remain unmodified on error. + */ +static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu, + Error **errp) +{ + char *dirpath; + int dirfd; + + dirpath = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", + vcpu->logical_id); + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + error_setg_errno(errp, errno, "open(\"%s\")", dirpath); + } else { + static const char fn[] = "online"; + int fd; + int res; + + fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); + if (fd == -1) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); + } else if (sys2vcpu) { + vcpu->online = true; + vcpu->can_offline = false; + } else if (!vcpu->online) { + error_setg(errp, "logical processor #%" PRId64 " can't be " + "offlined", vcpu->logical_id); + } /* otherwise pretend successful re-onlining */ + } else { + unsigned char status; + + res = pread(fd, &status, 1, 0); + if (res == -1) { + error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); + } else if (res == 0) { + error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, + fn); + } else if (sys2vcpu) { + vcpu->online = (status != '0'); + vcpu->can_offline = true; + } else if (vcpu->online != (status != '0')) { + status = '0' + vcpu->online; + if (pwrite(fd, &status, 1, 0) == -1) { + error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, + fn); + } + } /* otherwise pretend successful re-(on|off)-lining */ + + res = close(fd); + g_assert(res == 0); + } + + res = close(dirfd); + g_assert(res == 0); + } + + g_free(dirpath); +} + +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + int64_t current; + GuestLogicalProcessorList *head, **link; + long sc_max; + Error *local_err = NULL; + + current = 0; + head = NULL; + link = &head; + sc_max = SYSCONF_EXACT(_SC_NPROCESSORS_CONF, &local_err); + + while (local_err == NULL && current < sc_max) { + GuestLogicalProcessor *vcpu; + GuestLogicalProcessorList *entry; + + vcpu = g_malloc0(sizeof *vcpu); + vcpu->logical_id = current++; + vcpu->has_can_offline = true; /* lolspeak ftw */ + transfer_vcpu(vcpu, true, &local_err); + + entry = g_malloc0(sizeof *entry); + entry->value = vcpu; + + *link = entry; + link = &entry->next; + } + + if (local_err == NULL) { + /* there's no guest with zero VCPUs */ + g_assert(head != NULL); + return head; + } + + qapi_free_GuestLogicalProcessorList(head); + error_propagate(errp, local_err); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + int64_t processed; + Error *local_err = NULL; + + processed = 0; + while (vcpus != NULL) { + transfer_vcpu(vcpus->value, false, &local_err); + if (local_err != NULL) { + break; + } + ++processed; + vcpus = vcpus->next; + } + + if (local_err != NULL) { + if (processed == 0) { + error_propagate(errp, local_err); + } else { + error_free(local_err); + } + } + + return processed; +} + #else /* defined(__linux__) */ void qmp_guest_suspend_disk(Error **err) @@ -1050,6 +1294,18 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) return NULL; } +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + #endif #if !defined(CONFIG_FSFREEZE) diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 7e8ecb3b40..b19be9db48 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -278,6 +278,29 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **err) return NULL; } +int64_t qmp_guest_get_time(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + +void qmp_guest_set_time(int64_t time_ns, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); +} + +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return -1; +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h index 3354598362..624a559d94 100644 --- a/qga/guest-agent-core.h +++ b/qga/guest-agent-core.h @@ -35,6 +35,7 @@ bool ga_is_frozen(GAState *s); void ga_set_frozen(GAState *s); void ga_unset_frozen(GAState *s); const char *ga_fsfreeze_hook(GAState *s); +int64_t ga_get_fd_handle(GAState *s, Error **errp); #ifndef _WIN32 void reopen_fd_to_null(int fd); diff --git a/qga/main.c b/qga/main.c index db281a508b..99346e15aa 100644 --- a/qga/main.c +++ b/qga/main.c @@ -15,6 +15,7 @@ #include <stdbool.h> #include <glib.h> #include <getopt.h> +#include <glib/gstdio.h> #ifndef _WIN32 #include <syslog.h> #include <sys/wait.h> @@ -30,6 +31,7 @@ #include "qapi/qmp/qerror.h" #include "qapi/qmp/dispatch.h" #include "qga/channel.h" +#include "qemu/bswap.h" #ifdef _WIN32 #include "qga/service-win32.h" #include <windows.h> @@ -53,6 +55,11 @@ #endif #define QGA_SENTINEL_BYTE 0xFF +typedef struct GAPersistentState { +#define QGA_PSTATE_DEFAULT_FD_COUNTER 1000 + int64_t fd_counter; +} GAPersistentState; + struct GAState { JSONMessageParser parser; GMainLoop *main_loop; @@ -76,6 +83,8 @@ struct GAState { #ifdef CONFIG_FSFREEZE const char *fsfreeze_hook; #endif + const gchar *pstate_filepath; + GAPersistentState pstate; }; struct GAState *ga_state; @@ -85,6 +94,7 @@ static const char *ga_freeze_whitelist[] = { "guest-ping", "guest-info", "guest-sync", + "guest-sync-delimited", "guest-fsfreeze-status", "guest-fsfreeze-thaw", NULL @@ -724,6 +734,171 @@ VOID WINAPI service_main(DWORD argc, TCHAR *argv[]) } #endif +static void set_persistent_state_defaults(GAPersistentState *pstate) +{ + g_assert(pstate); + pstate->fd_counter = QGA_PSTATE_DEFAULT_FD_COUNTER; +} + +static void persistent_state_from_keyfile(GAPersistentState *pstate, + GKeyFile *keyfile) +{ + g_assert(pstate); + g_assert(keyfile); + /* if any fields are missing, either because the file was tampered with + * by agents of chaos, or because the field wasn't present at the time the + * file was created, the best we can ever do is start over with the default + * values. so load them now, and ignore any errors in accessing key-value + * pairs + */ + set_persistent_state_defaults(pstate); + + if (g_key_file_has_key(keyfile, "global", "fd_counter", NULL)) { + pstate->fd_counter = + g_key_file_get_int64(keyfile, "global", "fd_counter", NULL); + } +} + +static void persistent_state_to_keyfile(const GAPersistentState *pstate, + GKeyFile *keyfile) +{ + g_assert(pstate); + g_assert(keyfile); + + g_key_file_set_int64(keyfile, "global", "fd_counter", pstate->fd_counter); +} + +static gboolean write_persistent_state(const GAPersistentState *pstate, + const gchar *path) +{ + GKeyFile *keyfile = g_key_file_new(); + GError *gerr = NULL; + gboolean ret = true; + gchar *data = NULL; + gsize data_len; + + g_assert(pstate); + + persistent_state_to_keyfile(pstate, keyfile); + data = g_key_file_to_data(keyfile, &data_len, &gerr); + if (gerr) { + g_critical("failed to convert persistent state to string: %s", + gerr->message); + ret = false; + goto out; + } + + g_file_set_contents(path, data, data_len, &gerr); + if (gerr) { + g_critical("failed to write persistent state to %s: %s", + path, gerr->message); + ret = false; + goto out; + } + +out: + if (gerr) { + g_error_free(gerr); + } + if (keyfile) { + g_key_file_free(keyfile); + } + g_free(data); + return ret; +} + +static gboolean read_persistent_state(GAPersistentState *pstate, + const gchar *path, gboolean frozen) +{ + GKeyFile *keyfile = NULL; + GError *gerr = NULL; + struct stat st; + gboolean ret = true; + + g_assert(pstate); + + if (stat(path, &st) == -1) { + /* it's okay if state file doesn't exist, but any other error + * indicates a permissions issue or some other misconfiguration + * that we likely won't be able to recover from. + */ + if (errno != ENOENT) { + g_critical("unable to access state file at path %s: %s", + path, strerror(errno)); + ret = false; + goto out; + } + + /* file doesn't exist. initialize state to default values and + * attempt to save now. (we could wait till later when we have + * modified state we need to commit, but if there's a problem, + * such as a missing parent directory, we want to catch it now) + * + * there is a potential scenario where someone either managed to + * update the agent from a version that didn't use a key store + * while qemu-ga thought the filesystem was frozen, or + * deleted the key store prior to issuing a fsfreeze, prior + * to restarting the agent. in this case we go ahead and defer + * initial creation till we actually have modified state to + * write, otherwise fail to recover from freeze. + */ + set_persistent_state_defaults(pstate); + if (!frozen) { + ret = write_persistent_state(pstate, path); + if (!ret) { + g_critical("unable to create state file at path %s", path); + ret = false; + goto out; + } + } + ret = true; + goto out; + } + + keyfile = g_key_file_new(); + g_key_file_load_from_file(keyfile, path, 0, &gerr); + if (gerr) { + g_critical("error loading persistent state from path: %s, %s", + path, gerr->message); + ret = false; + goto out; + } + + persistent_state_from_keyfile(pstate, keyfile); + +out: + if (keyfile) { + g_key_file_free(keyfile); + } + if (gerr) { + g_error_free(gerr); + } + + return ret; +} + +int64_t ga_get_fd_handle(GAState *s, Error **errp) +{ + int64_t handle; + + g_assert(s->pstate_filepath); + /* we blacklist commands and avoid operations that potentially require + * writing to disk when we're in a frozen state. this includes opening + * new files, so we should never get here in that situation + */ + g_assert(!ga_is_frozen(s)); + + handle = s->pstate.fd_counter++; + if (s->pstate.fd_counter < 0) { + s->pstate.fd_counter = 0; + } + if (!write_persistent_state(&s->pstate, s->pstate_filepath)) { + error_setg(errp, "failed to commit persistent state to disk"); + } + + return handle; +} + int main(int argc, char **argv) { const char *sopt = "hVvdm:p:l:f:F::b:s:t:"; @@ -853,7 +1028,9 @@ int main(int argc, char **argv) ga_enable_logging(s); s->state_filepath_isfrozen = g_strdup_printf("%s/qga.state.isfrozen", state_dir); + s->pstate_filepath = g_strdup_printf("%s/qga.state", state_dir); s->frozen = false; + #ifndef _WIN32 /* check if a previous instance of qemu-ga exited with filesystems' state * marked as frozen. this could be a stale value (a non-qemu-ga process @@ -910,6 +1087,14 @@ int main(int argc, char **argv) } } + /* load persistent state from disk */ + if (!read_persistent_state(&s->pstate, + s->pstate_filepath, + ga_is_frozen(s))) { + g_critical("failed to load persistent state"); + goto out_bad; + } + if (blacklist) { s->blacklist = blacklist; do { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index d91d903256..dac4e6f95f 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -83,6 +83,45 @@ { 'command': 'guest-ping' } ## +# @guest-get-time: +# +# Get the information about guest time relative to the Epoch +# of 1970-01-01 in UTC. +# +# Returns: Time in nanoseconds. +# +# Since 1.5 +## +{ 'command': 'guest-get-time', + 'returns': 'int' } + +## +# @guest-set-time: +# +# Set guest time. +# +# When a guest is paused or migrated to a file then loaded +# from that file, the guest OS has no idea that there +# was a big gap in the time. Depending on how long the +# gap was, NTP might not be able to resynchronize the +# guest. +# +# This command tries to set guest time to the given value, +# then sets the Hardware Clock to the current System Time. +# This will make it easier for a guest to resynchronize +# without waiting for NTP. +# +# @time: time of nanoseconds, relative to the Epoch of +# 1970-01-01 in UTC. +# +# Returns: Nothing on success. +# +# Since: 1.5 +## +{ 'command': 'guest-set-time', + 'data': { 'time': 'int' } } + +## # @GuestAgentCommandInfo: # # Information about guest agent commands. @@ -515,3 +554,75 @@ ## { 'command': 'guest-network-get-interfaces', 'returns': ['GuestNetworkInterface'] } + +## +# @GuestLogicalProcessor: +# +# @logical-id: Arbitrary guest-specific unique identifier of the VCPU. +# +# @online: Whether the VCPU is enabled. +# +# @can-offline: Whether offlining the VCPU is possible. This member is always +# filled in by the guest agent when the structure is returned, +# and always ignored on input (hence it can be omitted then). +# +# Since: 1.5 +## +{ 'type': 'GuestLogicalProcessor', + 'data': {'logical-id': 'int', + 'online': 'bool', + '*can-offline': 'bool'} } + +## +# @guest-get-vcpus: +# +# Retrieve the list of the guest's logical processors. +# +# This is a read-only operation. +# +# Returns: The list of all VCPUs the guest knows about. Each VCPU is put on the +# list exactly once, but their order is unspecified. +# +# Since: 1.5 +## +{ 'command': 'guest-get-vcpus', + 'returns': ['GuestLogicalProcessor'] } + +## +# @guest-set-vcpus: +# +# Attempt to reconfigure (currently: enable/disable) logical processors inside +# the guest. +# +# The input list is processed node by node in order. In each node @logical-id +# is used to look up the guest VCPU, for which @online specifies the requested +# state. The set of distinct @logical-id's is only required to be a subset of +# the guest-supported identifiers. There's no restriction on list length or on +# repeating the same @logical-id (with possibly different @online field). +# Preferably the input list should describe a modified subset of +# @guest-get-vcpus' return value. +# +# Returns: The length of the initial sublist that has been successfully +# processed. The guest agent maximizes this value. Possible cases: +# +# 0: if the @vcpus list was empty on input. Guest state +# has not been changed. Otherwise, +# +# Error: processing the first node of @vcpus failed for the +# reason returned. Guest state has not been changed. +# Otherwise, +# +# < length(@vcpus): more than zero initial nodes have been processed, +# but not the entire @vcpus list. Guest state has +# changed accordingly. To retrieve the error +# (assuming it persists), repeat the call with the +# successfully processed initial sublist removed. +# Otherwise, +# +# length(@vcpus): call successful. +# +# Since: 1.5 +## +{ 'command': 'guest-set-vcpus', + 'data': {'vcpus': ['GuestLogicalProcessor'] }, + 'returns': 'int' } diff --git a/qga/service-win32.c b/qga/service-win32.c index 09054565d3..843398a6c6 100644 --- a/qga/service-win32.c +++ b/qga/service-win32.c @@ -29,7 +29,7 @@ static int printf_win_error(const char *text) MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char *)&message, 0, NULL); - n = printf("%s. (Error: %d) %s", text, err, message); + n = printf("%s. (Error: %d) %s", text, (int)err, message); LocalFree(message); return n; diff --git a/qmp-commands.hx b/qmp-commands.hx index 95022e259f..b370060848 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -2716,6 +2716,24 @@ EQMP }, { + .name = "query-tpm", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_tpm, + }, + + { + .name = "query-tpm-models", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_tpm_models, + }, + + { + .name = "query-tpm-types", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_tpm_types, + }, + + { .name = "chardev-add", .args_type = "id:s,backend:q", .mhandler.cmd_new = qmp_marshal_input_chardev_add, diff --git a/tpm/Makefile.objs b/tpm/Makefile.objs new file mode 100644 index 0000000000..86768244e4 --- /dev/null +++ b/tpm/Makefile.objs @@ -0,0 +1,6 @@ +common-obj-y = tpm.o +ifeq ($(CONFIG_TPM),y) +common-obj-y += tpm_backend.o +common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o +common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o +endif diff --git a/tpm/tpm.c b/tpm/tpm.c new file mode 100644 index 0000000000..ffd24956d7 --- /dev/null +++ b/tpm/tpm.c @@ -0,0 +1,357 @@ +/* + * TPM configuration + * + * Copyright (C) 2011-2013 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * Based on net.c + */ +#include "config-host.h" + +#include "monitor/monitor.h" +#include "qapi/qmp/qerror.h" +#include "tpm_int.h" +#include "tpm/tpm.h" +#include "qemu/config-file.h" +#include "qmp-commands.h" + +static QLIST_HEAD(, TPMBackend) tpm_backends = + QLIST_HEAD_INITIALIZER(tpm_backends); + + +#define TPM_MAX_MODELS 1 +#define TPM_MAX_DRIVERS 1 + +static TPMDriverOps const *be_drivers[TPM_MAX_DRIVERS] = { + NULL, +}; + +static enum TpmModel tpm_models[TPM_MAX_MODELS] = { + -1, +}; + +int tpm_register_model(enum TpmModel model) +{ + int i; + + for (i = 0; i < TPM_MAX_MODELS; i++) { + if (tpm_models[i] == -1) { + tpm_models[i] = model; + return 0; + } + } + error_report("Could not register TPM model"); + return 1; +} + +static bool tpm_model_is_registered(enum TpmModel model) +{ + int i; + + for (i = 0; i < TPM_MAX_MODELS; i++) { + if (tpm_models[i] == model) { + return true; + } + } + return false; +} + +/* + * Write an error message in the given output buffer. + */ +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len) +{ + if (out_len >= sizeof(struct tpm_resp_hdr)) { + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out; + + resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND); + resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr)); + resp->errcode = cpu_to_be32(TPM_FAIL); + } +} + +const TPMDriverOps *tpm_get_backend_driver(const char *type) +{ + int i; + + for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { + if (!strcmp(TpmType_lookup[be_drivers[i]->type], type)) { + return be_drivers[i]; + } + } + + return NULL; +} + +#ifdef CONFIG_TPM + +int tpm_register_driver(const TPMDriverOps *tdo) +{ + int i; + + for (i = 0; i < TPM_MAX_DRIVERS; i++) { + if (!be_drivers[i]) { + be_drivers[i] = tdo; + return 0; + } + } + error_report("Could not register TPM driver"); + return 1; +} + +/* + * Walk the list of available TPM backend drivers and display them on the + * screen. + */ +void tpm_display_backend_drivers(void) +{ + int i; + + fprintf(stderr, "Supported TPM types (choose only one):\n"); + + for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { + fprintf(stderr, "%12s %s\n", + TpmType_lookup[be_drivers[i]->type], be_drivers[i]->desc()); + } + fprintf(stderr, "\n"); +} + +/* + * Find the TPM with the given Id + */ +TPMBackend *qemu_find_tpm(const char *id) +{ + TPMBackend *drv; + + if (id) { + QLIST_FOREACH(drv, &tpm_backends, list) { + if (!strcmp(drv->id, id)) { + return drv; + } + } + } + + return NULL; +} + +static int configure_tpm(QemuOpts *opts) +{ + const char *value; + const char *id; + const TPMDriverOps *be; + TPMBackend *drv; + + if (!QLIST_EMPTY(&tpm_backends)) { + error_report("Only one TPM is allowed.\n"); + return 1; + } + + id = qemu_opts_id(opts); + if (id == NULL) { + qerror_report(QERR_MISSING_PARAMETER, "id"); + return 1; + } + + value = qemu_opt_get(opts, "type"); + if (!value) { + qerror_report(QERR_MISSING_PARAMETER, "type"); + tpm_display_backend_drivers(); + return 1; + } + + be = tpm_get_backend_driver(value); + if (be == NULL) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "type", + "a TPM backend type"); + tpm_display_backend_drivers(); + return 1; + } + + drv = be->create(opts, id); + if (!drv) { + return 1; + } + + QLIST_INSERT_HEAD(&tpm_backends, drv, list); + + return 0; +} + +static int tpm_init_tpmdev(QemuOpts *opts, void *dummy) +{ + return configure_tpm(opts); +} + +/* + * Walk the list of TPM backend drivers that are in use and call their + * destroy function to have them cleaned up. + */ +void tpm_cleanup(void) +{ + TPMBackend *drv, *next; + + QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) { + QLIST_REMOVE(drv, list); + drv->ops->destroy(drv); + } +} + +/* + * Initialize the TPM. Process the tpmdev command line options describing the + * TPM backend. + */ +int tpm_init(void) +{ + if (qemu_opts_foreach(qemu_find_opts("tpmdev"), + tpm_init_tpmdev, NULL, 1) != 0) { + return -1; + } + + atexit(tpm_cleanup); + + return 0; +} + +/* + * Parse the TPM configuration options. + * To display all available TPM backends the user may use '-tpmdev help' + */ +int tpm_config_parse(QemuOptsList *opts_list, const char *optarg) +{ + QemuOpts *opts; + + if (!strcmp(optarg, "help")) { + tpm_display_backend_drivers(); + return -1; + } + opts = qemu_opts_parse(opts_list, optarg, 1); + if (!opts) { + return -1; + } + return 0; +} + +#endif /* CONFIG_TPM */ + +static const TPMDriverOps *tpm_driver_find_by_type(enum TpmType type) +{ + int i; + + for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { + if (be_drivers[i]->type == type) { + return be_drivers[i]; + } + } + return NULL; +} + +static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv) +{ + TPMInfo *res = g_new0(TPMInfo, 1); + TPMPassthroughOptions *tpo; + + res->id = g_strdup(drv->id); + res->model = drv->fe_model; + res->type = drv->ops->type; + res->tpm_options = g_new0(TpmTypeOptions, 1); + + switch (res->type) { + case TPM_TYPE_PASSTHROUGH: + res->tpm_options->kind = TPM_TYPE_OPTIONS_KIND_TPM_PASSTHROUGH_OPTIONS; + tpo = g_new0(TPMPassthroughOptions, 1); + res->tpm_options->tpm_passthrough_options = tpo; + if (drv->path) { + tpo->path = g_strdup(drv->path); + tpo->has_path = true; + } + if (drv->cancel_path) { + tpo->cancel_path = g_strdup(drv->cancel_path); + tpo->has_cancel_path = true; + } + break; + case TPM_TYPE_MAX: + break; + } + + return res; +} + +/* + * Walk the list of active TPM backends and collect information about them + * following the schema description in qapi-schema.json. + */ +TPMInfoList *qmp_query_tpm(Error **errp) +{ + TPMBackend *drv; + TPMInfoList *info, *head = NULL, *cur_item = NULL; + + QLIST_FOREACH(drv, &tpm_backends, list) { + if (!tpm_model_is_registered(drv->fe_model)) { + continue; + } + info = g_new0(TPMInfoList, 1); + info->value = qmp_query_tpm_inst(drv); + + if (!cur_item) { + head = cur_item = info; + } else { + cur_item->next = info; + cur_item = info; + } + } + + return head; +} + +TpmTypeList *qmp_query_tpm_types(Error **errp) +{ + unsigned int i = 0; + TpmTypeList *head = NULL, *prev = NULL, *cur_item; + + for (i = 0; i < TPM_TYPE_MAX; i++) { + if (!tpm_driver_find_by_type(i)) { + continue; + } + cur_item = g_new0(TpmTypeList, 1); + cur_item->value = i; + + if (prev) { + prev->next = cur_item; + } + if (!head) { + head = cur_item; + } + prev = cur_item; + } + + return head; +} + +TpmModelList *qmp_query_tpm_models(Error **errp) +{ + unsigned int i = 0; + TpmModelList *head = NULL, *prev = NULL, *cur_item; + + for (i = 0; i < TPM_MODEL_MAX; i++) { + if (!tpm_model_is_registered(i)) { + continue; + } + cur_item = g_new0(TpmModelList, 1); + cur_item->value = i; + + if (prev) { + prev->next = cur_item; + } + if (!head) { + head = cur_item; + } + prev = cur_item; + } + + return head; +} diff --git a/tpm/tpm_backend.c b/tpm/tpm_backend.c new file mode 100644 index 0000000000..4144ef7d76 --- /dev/null +++ b/tpm/tpm_backend.c @@ -0,0 +1,58 @@ +/* + * common TPM backend driver functions + * + * Copyright (c) 2012-2013 IBM Corporation + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#include "tpm/tpm.h" +#include "qemu/thread.h" +#include "tpm_backend.h" + +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt) +{ + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_PROCESS_CMD, NULL); +} + +void tpm_backend_thread_create(TPMBackendThread *tbt, + GFunc func, gpointer user_data) +{ + if (!tbt->pool) { + tbt->pool = g_thread_pool_new(func, user_data, 1, TRUE, NULL); + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_INIT, NULL); + } +} + +void tpm_backend_thread_end(TPMBackendThread *tbt) +{ + if (tbt->pool) { + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_END, NULL); + g_thread_pool_free(tbt->pool, FALSE, TRUE); + tbt->pool = NULL; + } +} + +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt, + GFunc func, gpointer user_data) +{ + if (!tbt->pool) { + tpm_backend_thread_create(tbt, func, user_data); + } else { + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET, + NULL); + } +} diff --git a/tpm/tpm_backend.h b/tpm/tpm_backend.h new file mode 100644 index 0000000000..05d94d0f5b --- /dev/null +++ b/tpm/tpm_backend.h @@ -0,0 +1,45 @@ +/* + * common TPM backend driver functions + * + * Copyright (c) 2012-2013 IBM Corporation + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#ifndef TPM_TPM_BACKEND_H +#define TPM_TPM_BACKEND_H + +#include <glib.h> + +typedef struct TPMBackendThread { + GThreadPool *pool; +} TPMBackendThread; + +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt); +void tpm_backend_thread_create(TPMBackendThread *tbt, + GFunc func, gpointer user_data); +void tpm_backend_thread_end(TPMBackendThread *tbt); +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt, + GFunc func, gpointer user_data); + +typedef enum TPMBackendCmd { + TPM_BACKEND_CMD_INIT = 1, + TPM_BACKEND_CMD_PROCESS_CMD, + TPM_BACKEND_CMD_END, + TPM_BACKEND_CMD_TPM_RESET, +} TPMBackendCmd; + +#endif /* TPM_TPM_BACKEND_H */ diff --git a/tpm/tpm_int.h b/tpm/tpm_int.h new file mode 100644 index 0000000000..f7056436cc --- /dev/null +++ b/tpm/tpm_int.h @@ -0,0 +1,116 @@ +/* + * TPM configuration + * + * Copyright (C) 2011-2013 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#ifndef TPM_TPM_INT_H +#define TPM_TPM_INT_H + +#include "exec/memory.h" +#include "tpm/tpm_tis.h" + +struct TPMDriverOps; +typedef struct TPMDriverOps TPMDriverOps; + +typedef struct TPMPassthruState TPMPassthruState; + +typedef struct TPMBackend { + char *id; + enum TpmModel fe_model; + char *path; + char *cancel_path; + const TPMDriverOps *ops; + + union { + TPMPassthruState *tpm_pt; + } s; + + QLIST_ENTRY(TPMBackend) list; +} TPMBackend; + +/* overall state of the TPM interface */ +typedef struct TPMState { + ISADevice busdev; + MemoryRegion mmio; + + union { + TPMTISEmuState tis; + } s; + + uint8_t locty_number; + TPMLocality *locty_data; + + char *backend; + TPMBackend *be_driver; +} TPMState; + +#define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS) + +typedef void (TPMRecvDataCB)(TPMState *, uint8_t locty); + +struct TPMDriverOps { + enum TpmType type; + /* get a descriptive text of the backend to display to the user */ + const char *(*desc)(void); + + TPMBackend *(*create)(QemuOpts *opts, const char *id); + void (*destroy)(TPMBackend *t); + + /* initialize the backend */ + int (*init)(TPMBackend *t, TPMState *s, TPMRecvDataCB *datacb); + /* start up the TPM on the backend */ + int (*startup_tpm)(TPMBackend *t); + /* returns true if nothing will ever answer TPM requests */ + bool (*had_startup_error)(TPMBackend *t); + + size_t (*realloc_buffer)(TPMSizedBuffer *sb); + + void (*deliver_request)(TPMBackend *t); + + void (*reset)(TPMBackend *t); + + void (*cancel_cmd)(TPMBackend *t); + + bool (*get_tpm_established_flag)(TPMBackend *t); +}; + +struct tpm_req_hdr { + uint16_t tag; + uint32_t len; + uint32_t ordinal; +} QEMU_PACKED; + +struct tpm_resp_hdr { + uint16_t tag; + uint32_t len; + uint32_t errcode; +} QEMU_PACKED; + +#define TPM_TAG_RQU_COMMAND 0xc1 +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2 +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3 + +#define TPM_TAG_RSP_COMMAND 0xc4 +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5 +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6 + +#define TPM_FAIL 9 + +#define TPM_ORD_GetTicks 0xf1 + +TPMBackend *qemu_find_tpm(const char *id); +int tpm_register_model(enum TpmModel model); +int tpm_register_driver(const TPMDriverOps *tdo); +void tpm_display_backend_drivers(void); +const TPMDriverOps *tpm_get_backend_driver(const char *type); +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len); + +extern const TPMDriverOps tpm_passthrough_driver; + +#endif /* TPM_TPM_INT_H */ diff --git a/tpm/tpm_passthrough.c b/tpm/tpm_passthrough.c new file mode 100644 index 0000000000..24aff4d69c --- /dev/null +++ b/tpm/tpm_passthrough.c @@ -0,0 +1,530 @@ +/* + * passthrough TPM driver + * + * Copyright (c) 2010 - 2013 IBM Corporation + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * + * Copyright (C) 2011 IAIK, Graz University of Technology + * Author: Andreas Niederl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ + +#include <dirent.h> + +#include "qemu-common.h" +#include "qapi/error.h" +#include "qemu/sockets.h" +#include "tpm_int.h" +#include "hw/hw.h" +#include "hw/pc.h" +#include "tpm_tis.h" +#include "tpm_backend.h" + +/* #define DEBUG_TPM */ + +#ifdef DEBUG_TPM +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +/* data structures */ + +typedef struct TPMPassthruThreadParams { + TPMState *tpm_state; + + TPMRecvDataCB *recv_data_callback; + TPMBackend *tb; +} TPMPassthruThreadParams; + +struct TPMPassthruState { + TPMBackendThread tbt; + + TPMPassthruThreadParams tpm_thread_params; + + char *tpm_dev; + int tpm_fd; + bool tpm_executing; + bool tpm_op_canceled; + int cancel_fd; + bool had_startup_error; +}; + +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" + +/* functions */ + +static void tpm_passthrough_cancel_cmd(TPMBackend *tb); + +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len) +{ + return send_all(fd, buf, len); +} + +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) +{ + return recv_all(fd, buf, len, true); +} + +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf) +{ + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf; + + return be32_to_cpu(resp->len); +} + +static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, + const uint8_t *in, uint32_t in_len, + uint8_t *out, uint32_t out_len) +{ + int ret; + + tpm_pt->tpm_op_canceled = false; + tpm_pt->tpm_executing = true; + + ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len); + if (ret != in_len) { + if (!tpm_pt->tpm_op_canceled || + (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { + error_report("tpm_passthrough: error while transmitting data " + "to TPM: %s (%i)\n", + strerror(errno), errno); + } + goto err_exit; + } + + tpm_pt->tpm_executing = false; + + ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); + if (ret < 0) { + if (!tpm_pt->tpm_op_canceled || + (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { + error_report("tpm_passthrough: error while reading data from " + "TPM: %s (%i)\n", + strerror(errno), errno); + } + } else if (ret < sizeof(struct tpm_resp_hdr) || + tpm_passthrough_get_size_from_buffer(out) != ret) { + ret = -1; + error_report("tpm_passthrough: received invalid response " + "packet from TPM\n"); + } + +err_exit: + if (ret < 0) { + tpm_write_fatal_error_response(out, out_len); + } + + tpm_pt->tpm_executing = false; + + return ret; +} + +static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt, + const TPMLocality *locty_data) +{ + return tpm_passthrough_unix_tx_bufs(tpm_pt, + locty_data->w_buffer.buffer, + locty_data->w_offset, + locty_data->r_buffer.buffer, + locty_data->r_buffer.size); +} + +static void tpm_passthrough_worker_thread(gpointer data, + gpointer user_data) +{ + TPMPassthruThreadParams *thr_parms = user_data; + TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt; + TPMBackendCmd cmd = (TPMBackendCmd)data; + + DPRINTF("tpm_passthrough: processing command type %d\n", cmd); + + switch (cmd) { + case TPM_BACKEND_CMD_PROCESS_CMD: + tpm_passthrough_unix_transfer(tpm_pt, + thr_parms->tpm_state->locty_data); + + thr_parms->recv_data_callback(thr_parms->tpm_state, + thr_parms->tpm_state->locty_number); + break; + case TPM_BACKEND_CMD_INIT: + case TPM_BACKEND_CMD_END: + case TPM_BACKEND_CMD_TPM_RESET: + /* nothing to do */ + break; + } +} + +/* + * Start the TPM (thread). If it had been started before, then terminate + * and start it again. + */ +static int tpm_passthrough_startup_tpm(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + /* terminate a running TPM */ + tpm_backend_thread_end(&tpm_pt->tbt); + + tpm_backend_thread_create(&tpm_pt->tbt, + tpm_passthrough_worker_thread, + &tb->s.tpm_pt->tpm_thread_params); + + return 0; +} + +static void tpm_passthrough_reset(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n"); + + tpm_passthrough_cancel_cmd(tb); + + tpm_backend_thread_end(&tpm_pt->tbt); + + tpm_pt->had_startup_error = false; +} + +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, + TPMRecvDataCB *recv_data_cb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + tpm_pt->tpm_thread_params.tpm_state = s; + tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb; + tpm_pt->tpm_thread_params.tb = tb; + + return 0; +} + +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) +{ + return false; +} + +static bool tpm_passthrough_get_startup_error(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + return tpm_pt->had_startup_error; +} + +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb) +{ + size_t wanted_size = 4096; /* Linux tpm.c buffer size */ + + if (sb->size != wanted_size) { + sb->buffer = g_realloc(sb->buffer, wanted_size); + sb->size = wanted_size; + } + return sb->size; +} + +static void tpm_passthrough_deliver_request(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + tpm_backend_thread_deliver_request(&tpm_pt->tbt); +} + +static void tpm_passthrough_cancel_cmd(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + int n; + + /* + * As of Linux 3.7 the tpm_tis driver does not properly cancel + * commands on all TPM manufacturers' TPMs. + * Only cancel if we're busy so we don't cancel someone else's + * command, e.g., a command executed on the host. + */ + if (tpm_pt->tpm_executing) { + if (tpm_pt->cancel_fd >= 0) { + n = write(tpm_pt->cancel_fd, "-", 1); + if (n != 1) { + error_report("Canceling TPM command failed: %s\n", + strerror(errno)); + } else { + tpm_pt->tpm_op_canceled = true; + } + } else { + error_report("Cannot cancel TPM command due to missing " + "TPM sysfs cancel entry"); + } + } +} + +static const char *tpm_passthrough_create_desc(void) +{ + return "Passthrough TPM backend driver"; +} + +/* + * A basic test of a TPM device. We expect a well formatted response header + * (error response is fine) within one second. + */ +static int tpm_passthrough_test_tpmdev(int fd) +{ + struct tpm_req_hdr req = { + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), + .len = cpu_to_be32(sizeof(req)), + .ordinal = cpu_to_be32(TPM_ORD_GetTicks), + }; + struct tpm_resp_hdr *resp; + fd_set readfds; + int n; + struct timeval tv = { + .tv_sec = 1, + .tv_usec = 0, + }; + unsigned char buf[1024]; + + n = write(fd, &req, sizeof(req)); + if (n < 0) { + return errno; + } + if (n != sizeof(req)) { + return EFAULT; + } + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + /* wait for a second */ + n = select(fd + 1, &readfds, NULL, NULL, &tv); + if (n != 1) { + return errno; + } + + n = read(fd, &buf, sizeof(buf)); + if (n < sizeof(struct tpm_resp_hdr)) { + return EFAULT; + } + + resp = (struct tpm_resp_hdr *)buf; + /* check the header */ + if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND || + be32_to_cpu(resp->len) != n) { + return EBADMSG; + } + + return 0; +} + +/* + * Check whether the given base path, e.g., /sys/class/misc/tpm0/device, + * is the sysfs directory of a TPM. A TPM sysfs directory should be uniquely + * recognizable by the file entries 'pcrs' and 'cancel'. + * Upon success 'true' is returned and the basebath buffer has '/cancel' + * appended. + */ +static bool tpm_passthrough_check_sysfs_cancel(char *basepath, size_t bufsz) +{ + char path[PATH_MAX]; + struct stat statbuf; + + snprintf(path, sizeof(path), "%s/pcrs", basepath); + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { + return false; + } + + snprintf(path, sizeof(path), "%s/cancel", basepath); + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { + return false; + } + + strncpy(basepath, path, bufsz); + + return true; +} + +/* + * Unless path or file descriptor set has been provided by user, + * determine the sysfs cancel file following kernel documentation + * in Documentation/ABI/stable/sysfs-class-tpm. + */ +static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) +{ + int fd = -1; + unsigned int idx; + DIR *pnp_dir; + char path[PATH_MAX]; + struct dirent entry, *result; + int len; + + if (tb->cancel_path) { + fd = qemu_open(tb->cancel_path, O_WRONLY); + if (fd < 0) { + error_report("Could not open TPM cancel path : %s", + strerror(errno)); + } + return fd; + } + + snprintf(path, sizeof(path), "/sys/class/misc"); + pnp_dir = opendir(path); + if (pnp_dir != NULL) { + while (readdir_r(pnp_dir, &entry, &result) == 0 && + result != NULL) { + /* + * only allow /sys/class/misc/tpm%u type of paths + */ + if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 || + len <= strlen("tpm") || + len != strlen(entry.d_name)) { + continue; + } + + snprintf(path, sizeof(path), "/sys/class/misc/%s/device", + entry.d_name); + if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) { + continue; + } + + fd = qemu_open(path, O_WRONLY); + break; + } + closedir(pnp_dir); + } + + if (fd >= 0) { + tb->cancel_path = g_strdup(path); + } + + return fd; +} + +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) +{ + const char *value; + + value = qemu_opt_get(opts, "cancel-path"); + if (value) { + tb->cancel_path = g_strdup(value); + } + + value = qemu_opt_get(opts, "path"); + if (!value) { + value = TPM_PASSTHROUGH_DEFAULT_DEVICE; + } + + tb->s.tpm_pt->tpm_dev = g_strdup(value); + + tb->path = g_strdup(tb->s.tpm_pt->tpm_dev); + + tb->s.tpm_pt->tpm_fd = qemu_open(tb->s.tpm_pt->tpm_dev, O_RDWR); + if (tb->s.tpm_pt->tpm_fd < 0) { + error_report("Cannot access TPM device using '%s': %s\n", + tb->s.tpm_pt->tpm_dev, strerror(errno)); + goto err_free_parameters; + } + + if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) { + error_report("'%s' is not a TPM device.\n", + tb->s.tpm_pt->tpm_dev); + goto err_close_tpmdev; + } + + return 0; + + err_close_tpmdev: + qemu_close(tb->s.tpm_pt->tpm_fd); + tb->s.tpm_pt->tpm_fd = -1; + + err_free_parameters: + g_free(tb->path); + tb->path = NULL; + + g_free(tb->s.tpm_pt->tpm_dev); + tb->s.tpm_pt->tpm_dev = NULL; + + return 1; +} + +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) +{ + TPMBackend *tb; + + tb = g_new0(TPMBackend, 1); + tb->s.tpm_pt = g_new0(TPMPassthruState, 1); + tb->id = g_strdup(id); + /* let frontend set the fe_model to proper value */ + tb->fe_model = -1; + + tb->ops = &tpm_passthrough_driver; + + if (tpm_passthrough_handle_device_opts(opts, tb)) { + goto err_exit; + } + + tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb); + if (tb->s.tpm_pt->cancel_fd < 0) { + goto err_exit; + } + + return tb; + +err_exit: + g_free(tb->id); + g_free(tb->s.tpm_pt); + g_free(tb); + + return NULL; +} + +static void tpm_passthrough_destroy(TPMBackend *tb) +{ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + + tpm_passthrough_cancel_cmd(tb); + + tpm_backend_thread_end(&tpm_pt->tbt); + + qemu_close(tpm_pt->tpm_fd); + qemu_close(tb->s.tpm_pt->cancel_fd); + + g_free(tb->id); + g_free(tb->path); + g_free(tb->cancel_path); + g_free(tb->s.tpm_pt->tpm_dev); + g_free(tb->s.tpm_pt); + g_free(tb); +} + +const TPMDriverOps tpm_passthrough_driver = { + .type = TPM_TYPE_PASSTHROUGH, + .desc = tpm_passthrough_create_desc, + .create = tpm_passthrough_create, + .destroy = tpm_passthrough_destroy, + .init = tpm_passthrough_init, + .startup_tpm = tpm_passthrough_startup_tpm, + .realloc_buffer = tpm_passthrough_realloc_buffer, + .reset = tpm_passthrough_reset, + .had_startup_error = tpm_passthrough_get_startup_error, + .deliver_request = tpm_passthrough_deliver_request, + .cancel_cmd = tpm_passthrough_cancel_cmd, + .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag, +}; + +static void tpm_passthrough_register(void) +{ + tpm_register_driver(&tpm_passthrough_driver); +} + +type_init(tpm_passthrough_register) diff --git a/tpm/tpm_tis.c b/tpm/tpm_tis.c new file mode 100644 index 0000000000..e93825eec1 --- /dev/null +++ b/tpm/tpm_tis.c @@ -0,0 +1,929 @@ +/* + * tpm_tis.c - QEMU's TPM TIS interface emulator + * + * Copyright (C) 2006,2010-2013 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * David Safford <safford@us.ibm.com> + * + * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * Implementation of the TIS interface according to specs found at + * http://www.trustedcomputinggroup.org. This implementation currently + * supports version 1.21, revision 1.0. + * In the developers menu choose the PC Client section then find the TIS + * specification. + */ + +#include "tpm_int.h" +#include "block/block.h" +#include "exec/address-spaces.h" +#include "hw/hw.h" +#include "hw/pc.h" +#include "hw/pci/pci_ids.h" +#include "tpm/tpm_tis.h" +#include "qemu-common.h" + +/*#define DEBUG_TIS */ + +#ifdef DEBUG_TIS +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +/* whether the STS interrupt is supported */ +#define RAISE_STS_IRQ + +/* tis registers */ +#define TPM_TIS_REG_ACCESS 0x00 +#define TPM_TIS_REG_INT_ENABLE 0x08 +#define TPM_TIS_REG_INT_VECTOR 0x0c +#define TPM_TIS_REG_INT_STATUS 0x10 +#define TPM_TIS_REG_INTF_CAPABILITY 0x14 +#define TPM_TIS_REG_STS 0x18 +#define TPM_TIS_REG_DATA_FIFO 0x24 +#define TPM_TIS_REG_DID_VID 0xf00 +#define TPM_TIS_REG_RID 0xf04 + +/* vendor-specific registers */ +#define TPM_TIS_REG_DEBUG 0xf90 + +#define TPM_TIS_STS_VALID (1 << 7) +#define TPM_TIS_STS_COMMAND_READY (1 << 6) +#define TPM_TIS_STS_TPM_GO (1 << 5) +#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4) +#define TPM_TIS_STS_EXPECT (1 << 3) +#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1) + +#define TPM_TIS_BURST_COUNT_SHIFT 8 +#define TPM_TIS_BURST_COUNT(X) \ + ((X) << TPM_TIS_BURST_COUNT_SHIFT) + +#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7) +#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5) +#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4) +#define TPM_TIS_ACCESS_SEIZE (1 << 3) +#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2) +#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1) +#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0) + +#define TPM_TIS_INT_ENABLED (1 << 31) +#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0) +#define TPM_TIS_INT_STS_VALID (1 << 1) +#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2) +#define TPM_TIS_INT_COMMAND_READY (1 << 7) + +#define TPM_TIS_INT_POLARITY_MASK (3 << 3) +#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3) + +#ifndef RAISE_STS_IRQ + +#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \ + TPM_TIS_INT_DATA_AVAILABLE | \ + TPM_TIS_INT_COMMAND_READY) + +#else + +#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \ + TPM_TIS_INT_DATA_AVAILABLE | \ + TPM_TIS_INT_STS_VALID | \ + TPM_TIS_INT_COMMAND_READY) + +#endif + +#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */ +#define TPM_TIS_CAPABILITIES_SUPPORTED (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \ + TPM_TIS_INTERRUPTS_SUPPORTED) + +#define TPM_TIS_TPM_DID 0x0001 +#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM +#define TPM_TIS_TPM_RID 0x0001 + +#define TPM_TIS_NO_DATA_BYTE 0xff + +/* local prototypes */ + +static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr, + unsigned size); + +/* utility functions */ + +static uint8_t tpm_tis_locality_from_addr(hwaddr addr) +{ + return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7); +} + +static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb) +{ + return be32_to_cpu(*(uint32_t *)&sb->buffer[2]); +} + +static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string) +{ +#ifdef DEBUG_TIS + uint32_t len, i; + + len = tpm_tis_get_size_from_buffer(sb); + DPRINTF("tpm_tis: %s length = %d\n", string, len); + for (i = 0; i < len; i++) { + if (i && !(i % 16)) { + DPRINTF("\n"); + } + DPRINTF("%.2X ", sb->buffer[i]); + } + DPRINTF("\n"); +#endif +} + +/* + * Send a request to the TPM. + */ +static void tpm_tis_tpm_send(TPMState *s, uint8_t locty) +{ + TPMTISEmuState *tis = &s->s.tis; + + tpm_tis_show_buffer(&tis->loc[locty].w_buffer, "tpm_tis: To TPM"); + + s->locty_number = locty; + s->locty_data = &tis->loc[locty]; + + /* + * w_offset serves as length indicator for length of data; + * it's reset when the response comes back + */ + tis->loc[locty].state = TPM_TIS_STATE_EXECUTION; + + s->be_driver->ops->deliver_request(s->be_driver); +} + +/* 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 uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty) +{ + uint8_t l; + + for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) { + if (l == locty) { + continue; + } + if ((s->s.tis.loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) { + return 1; + } + } + + return 0; +} + +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); + } +} + +/* abort -- this function switches the locality */ +static void tpm_tis_abort(TPMState *s, uint8_t locty) +{ + TPMTISEmuState *tis = &s->s.tis; + + tis->loc[locty].r_offset = 0; + tis->loc[locty].w_offset = 0; + + DPRINTF("tpm_tis: tis_abort: new active locality is %d\n", tis->next_locty); + + /* + * Need to react differently depending on who's aborting now and + * which locality will become active afterwards. + */ + if (tis->aborting_locty == tis->next_locty) { + tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY; + tis->loc[tis->aborting_locty].sts = TPM_TIS_STS_COMMAND_READY; + tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY); + } + + /* locality after abort is another one than the current one */ + tpm_tis_new_active_locality(s, tis->next_locty); + + tis->next_locty = TPM_TIS_NO_LOCALITY; + /* nobody's aborting a command anymore */ + tis->aborting_locty = TPM_TIS_NO_LOCALITY; +} + +/* prepare aborting current command */ +static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty) +{ + TPMTISEmuState *tis = &s->s.tis; + uint8_t busy_locty; + + tis->aborting_locty = locty; + tis->next_locty = newlocty; /* locality after successful abort */ + + /* + * only abort a command using an interrupt if currently executing + * a command AND if there's a valid connection to the vTPM. + */ + for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) { + if (tis->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) { + /* + * request the backend to cancel. Some backends may not + * support it + */ + s->be_driver->ops->cancel_cmd(s->be_driver); + return; + } + } + + tpm_tis_abort(s, locty); +} + +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 +} + +/* + * Callback from the TPM to indicate that the response was received. + */ +static void tpm_tis_receive_cb(TPMState *s, uint8_t locty) +{ + TPMTISEmuState *tis = &s->s.tis; + + assert(s->locty_number == locty); + + qemu_bh_schedule(tis->bh); +} + +/* + * Read a byte of response data + */ +static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty) +{ + TPMTISEmuState *tis = &s->s.tis; + uint32_t ret = TPM_TIS_NO_DATA_BYTE; + uint16_t len; + + if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) { + len = tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer); + + ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++]; + if (tis->loc[locty].r_offset >= len) { + /* got last byte */ + tis->loc[locty].sts = TPM_TIS_STS_VALID; +#ifdef RAISE_STS_IRQ + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID); +#endif + } + DPRINTF("tpm_tis: tpm_tis_data_read byte 0x%02x [%d]\n", + ret, tis->loc[locty].r_offset-1); + } + + return ret; +} + +#ifdef DEBUG_TIS +static void tpm_tis_dump_state(void *opaque, hwaddr addr) +{ + static const unsigned regs[] = { + TPM_TIS_REG_ACCESS, + TPM_TIS_REG_INT_ENABLE, + TPM_TIS_REG_INT_VECTOR, + TPM_TIS_REG_INT_STATUS, + TPM_TIS_REG_INTF_CAPABILITY, + TPM_TIS_REG_STS, + TPM_TIS_REG_DID_VID, + TPM_TIS_REG_RID, + 0xfff}; + int idx; + uint8_t locty = tpm_tis_locality_from_addr(addr); + hwaddr base = addr & ~0xfff; + TPMState *s = opaque; + TPMTISEmuState *tis = &s->s.tis; + + DPRINTF("tpm_tis: active locality : %d\n" + "tpm_tis: state of locality %d : %d\n" + "tpm_tis: register dump:\n", + tis->active_locty, + locty, tis->loc[locty].state); + + for (idx = 0; regs[idx] != 0xfff; idx++) { + DPRINTF("tpm_tis: 0x%04x : 0x%08x\n", regs[idx], + (uint32_t)tpm_tis_mmio_read(opaque, base + regs[idx], 4)); + } + + DPRINTF("tpm_tis: read offset : %d\n" + "tpm_tis: result buffer : ", + tis->loc[locty].r_offset); + for (idx = 0; + idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer); + idx++) { + DPRINTF("%c%02x%s", + tis->loc[locty].r_offset == idx ? '>' : ' ', + tis->loc[locty].r_buffer.buffer[idx], + ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : ""); + } + DPRINTF("\n" + "tpm_tis: write offset : %d\n" + "tpm_tis: request buffer: ", + tis->loc[locty].w_offset); + for (idx = 0; + idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer); + idx++) { + DPRINTF("%c%02x%s", + tis->loc[locty].w_offset == idx ? '>' : ' ', + tis->loc[locty].w_buffer.buffer[idx], + ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : ""); + } + DPRINTF("\n"); +} +#endif + +/* + * Read a register of the TIS interface + * See specs pages 33-63 for description of the registers + */ +static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr, + unsigned size) +{ + TPMState *s = opaque; + TPMTISEmuState *tis = &s->s.tis; + uint16_t offset = addr & 0xffc; + uint8_t shift = (addr & 0x3) * 8; + uint32_t val = 0xffffffff; + uint8_t locty = tpm_tis_locality_from_addr(addr); + uint32_t avail; + + if (s->be_driver->ops->had_startup_error(s->be_driver)) { + return val; + } + + switch (offset) { + case TPM_TIS_REG_ACCESS: + /* never show the SEIZE flag even though we use it internally */ + val = tis->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE; + /* the pending flag is always calculated */ + if (tpm_tis_check_request_use_except(s, locty)) { + val |= TPM_TIS_ACCESS_PENDING_REQUEST; + } + val |= !s->be_driver->ops->get_tpm_established_flag(s->be_driver); + break; + case TPM_TIS_REG_INT_ENABLE: + val = tis->loc[locty].inte; + break; + case TPM_TIS_REG_INT_VECTOR: + val = tis->irq_num; + break; + case TPM_TIS_REG_INT_STATUS: + val = tis->loc[locty].ints; + break; + case TPM_TIS_REG_INTF_CAPABILITY: + val = TPM_TIS_CAPABILITIES_SUPPORTED; + break; + case TPM_TIS_REG_STS: + if (tis->active_locty == locty) { + if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) { + val = TPM_TIS_BURST_COUNT( + tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer) + - tis->loc[locty].r_offset) | tis->loc[locty].sts; + } else { + avail = tis->loc[locty].w_buffer.size + - tis->loc[locty].w_offset; + /* + * byte-sized reads should not return 0x00 for 0x100 + * available bytes. + */ + if (size == 1 && avail > 0xff) { + avail = 0xff; + } + val = TPM_TIS_BURST_COUNT(avail) | tis->loc[locty].sts; + } + } + break; + case TPM_TIS_REG_DATA_FIFO: + if (tis->active_locty == locty) { + switch (tis->loc[locty].state) { + case TPM_TIS_STATE_COMPLETION: + val = tpm_tis_data_read(s, locty); + break; + default: + val = TPM_TIS_NO_DATA_BYTE; + break; + } + } + break; + case TPM_TIS_REG_DID_VID: + val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID; + break; + case TPM_TIS_REG_RID: + val = TPM_TIS_TPM_RID; + break; +#ifdef DEBUG_TIS + case TPM_TIS_REG_DEBUG: + tpm_tis_dump_state(opaque, addr); + break; +#endif + } + + if (shift) { + val >>= shift; + } + + DPRINTF("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val); + + return val; +} + +/* + * 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 & 0xfff; + uint8_t locty = tpm_tis_locality_from_addr(addr); + uint8_t active_locty, l; + int c, set_new_locty = 1; + uint16_t len; + + DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val); + + if (locty == 4 && !hw_access) { + DPRINTF("tpm_tis: Access to locality 4 only allowed from hardware\n"); + return; + } + + if (s->be_driver->ops->had_startup_error(s->be_driver)) { + return; + } + + 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 = (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; + } + + 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: + tis->loc[locty].sts = 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)) { + tis->loc[locty].sts = 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; + tis->loc[locty].sts = TPM_TIS_STS_VALID | + TPM_TIS_STS_DATA_AVAILABLE; + break; + default: + /* ignore */ + break; + } + } + break; + case TPM_TIS_REG_DATA_FIFO: + /* 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: Byte to send to TPM: %02x\n", (uint8_t)val); + if (tis->loc[locty].state == TPM_TIS_STATE_READY) { + tis->loc[locty].state = TPM_TIS_STATE_RECEPTION; + tis->loc[locty].sts = TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID; + } + + if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) { + 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; + } else { + tis->loc[locty].sts = 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 needIrq = !(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) { + tis->loc[locty].sts = TPM_TIS_STS_EXPECT | + TPM_TIS_STS_VALID; + } else { + /* packet complete */ + tis->loc[locty].sts = TPM_TIS_STS_VALID; + } +#ifdef RAISE_STS_IRQ + if (needIrq) { + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID); + } +#endif + } + } + break; + } +} + +static void tpm_tis_mmio_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + return tpm_tis_mmio_write_intern(opaque, addr, val, size, false); +} + +static const MemoryRegionOps tpm_tis_memory_ops = { + .read = tpm_tis_mmio_read, + .write = tpm_tis_mmio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static int tpm_tis_do_startup_tpm(TPMState *s) +{ + return s->be_driver->ops->startup_tpm(s->be_driver); +} + +/* + * This function is called when the machine starts, resets or due to + * S3 resume. + */ +static void tpm_tis_reset(DeviceState *dev) +{ + TPMState *s = TPM(dev); + TPMTISEmuState *tis = &s->s.tis; + int c; + + s->be_driver->ops->reset(s->be_driver); + + tis->active_locty = TPM_TIS_NO_LOCALITY; + tis->next_locty = TPM_TIS_NO_LOCALITY; + tis->aborting_locty = TPM_TIS_NO_LOCALITY; + + for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) { + tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS; + tis->loc[c].sts = 0; + tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL; + tis->loc[c].ints = 0; + tis->loc[c].state = TPM_TIS_STATE_IDLE; + + tis->loc[c].w_offset = 0; + s->be_driver->ops->realloc_buffer(&tis->loc[c].w_buffer); + tis->loc[c].r_offset = 0; + s->be_driver->ops->realloc_buffer(&tis->loc[c].r_buffer); + } + + tpm_tis_do_startup_tpm(s); +} + +static const VMStateDescription vmstate_tpm_tis = { + .name = "tpm", + .unmigratable = 1, +}; + +static Property tpm_tis_properties[] = { + DEFINE_PROP_UINT32("irq", TPMState, + s.tis.irq_num, TPM_TIS_IRQ), + DEFINE_PROP_STRING("tpmdev", TPMState, backend), + DEFINE_PROP_END_OF_LIST(), +}; + +static void tpm_tis_realizefn(DeviceState *dev, Error **errp) +{ + TPMState *s = TPM(dev); + TPMTISEmuState *tis = &s->s.tis; + + s->be_driver = qemu_find_tpm(s->backend); + if (!s->be_driver) { + error_setg(errp, "tpm_tis: backend driver with id %s could not be " + "found", s->backend); + return; + } + + s->be_driver->fe_model = TPM_MODEL_TPM_TIS; + + if (s->be_driver->ops->init(s->be_driver, s, tpm_tis_receive_cb)) { + error_setg(errp, "tpm_tis: backend driver with id %s could not be " + "initialized", s->backend); + return; + } + + if (tis->irq_num > 15) { + error_setg(errp, "tpm_tis: IRQ %d for TPM TIS is outside valid range " + "of 0 to 15.\n", tis->irq_num); + return; + } + + tis->bh = qemu_bh_new(tpm_tis_receive_bh, s); + + isa_init_irq(&s->busdev, &tis->irq, tis->irq_num); +} + +static void tpm_tis_initfn(Object *obj) +{ + ISADevice *dev = ISA_DEVICE(obj); + TPMState *s = TPM(obj); + + memory_region_init_io(&s->mmio, &tpm_tis_memory_ops, s, "tpm-tis-mmio", + TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT); + memory_region_add_subregion(isa_address_space(dev), TPM_TIS_ADDR_BASE, + &s->mmio); +} + +static void tpm_tis_uninitfn(Object *obj) +{ + TPMState *s = TPM(obj); + + memory_region_del_subregion(get_system_memory(), &s->mmio); + memory_region_destroy(&s->mmio); +} + +static void tpm_tis_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = tpm_tis_realizefn; + dc->props = tpm_tis_properties; + dc->reset = tpm_tis_reset; + dc->vmsd = &vmstate_tpm_tis; +} + +static const TypeInfo tpm_tis_info = { + .name = TYPE_TPM_TIS, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(TPMState), + .instance_init = tpm_tis_initfn, + .instance_finalize = tpm_tis_uninitfn, + .class_init = tpm_tis_class_init, +}; + +static void tpm_tis_register(void) +{ + type_register_static(&tpm_tis_info); + tpm_register_model(TPM_MODEL_TPM_TIS); +} + +type_init(tpm_tis_register) diff --git a/tpm/tpm_tis.h b/tpm/tpm_tis.h new file mode 100644 index 0000000000..0c8df80cce --- /dev/null +++ b/tpm/tpm_tis.h @@ -0,0 +1,80 @@ +/* + * tpm_tis.h - QEMU's TPM TIS interface emulator + * + * Copyright (C) 2006, 2010-2013 IBM Corporation + * + * Authors: + * Stefan Berger <stefanb@us.ibm.com> + * David Safford <safford@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * Implementation of the TIS interface according to specs found at + * http://www.trustedcomputinggroup.org + * + */ +#ifndef TPM_TPM_TIS_H +#define TPM_TPM_TIS_H + +#include "hw/isa.h" +#include "qemu-common.h" + +#define TPM_TIS_ADDR_BASE 0xFED40000 + +#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */ +#define TPM_TIS_LOCALITY_SHIFT 12 +#define TPM_TIS_NO_LOCALITY 0xff + +#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES) + +#define TPM_TIS_IRQ 5 + +#define TPM_TIS_BUFFER_MAX 4096 + +#define TYPE_TPM_TIS "tpm-tis" + + +typedef struct TPMSizedBuffer { + uint32_t size; + uint8_t *buffer; +} TPMSizedBuffer; + +typedef enum { + TPM_TIS_STATE_IDLE = 0, + TPM_TIS_STATE_READY, + TPM_TIS_STATE_COMPLETION, + TPM_TIS_STATE_EXECUTION, + TPM_TIS_STATE_RECEPTION, +} TPMTISState; + +/* locality data -- all fields are persisted */ +typedef struct TPMLocality { + TPMTISState state; + uint8_t access; + uint8_t sts; + uint32_t inte; + uint32_t ints; + + uint16_t w_offset; + uint16_t r_offset; + TPMSizedBuffer w_buffer; + TPMSizedBuffer r_buffer; +} TPMLocality; + +typedef struct TPMTISEmuState { + QEMUBH *bh; + uint32_t offset; + uint8_t buf[TPM_TIS_BUFFER_MAX]; + + uint8_t active_locty; + uint8_t aborting_locty; + uint8_t next_locty; + + TPMLocality loc[TPM_TIS_NUM_LOCALITIES]; + + qemu_irq irq; + uint32_t irq_num; +} TPMTISEmuState; + +#endif /* TPM_TPM_TIS_H */ diff --git a/trace-events b/trace-events index 8389d83568..d6a847d18a 100644 --- a/trace-events +++ b/trace-events @@ -473,6 +473,7 @@ scsi_request_sense(int target, int lun, int tag) "target %d lun %d tag %d" # vl.c vm_state_notify(int running, int reason) "running %d reason %d" +load_file(const char *name, const char *path) "name %s location %s" # block/qcow2.c qcow2_writev_start_req(void *co, int64_t sector, int nb_sectors) "co %p sector %" PRIx64 " nb_sectors %d" diff --git a/ui/console.c b/ui/console.c index 83a6fa3969..0f96177797 100644 --- a/ui/console.c +++ b/ui/console.c @@ -1742,7 +1742,7 @@ PixelFormat qemu_default_pixelformat(int bpp) static void register_types(void) { - register_char_driver("vc", text_console_init); + register_char_driver("vc", vc_init); } type_init(register_types); @@ -139,6 +139,7 @@ int main(int argc, char **argv) #include "sysemu/blockdev.h" #include "hw/block-common.h" #include "migration/block.h" +#include "tpm/tpm.h" #include "sysemu/dma.h" #include "audio/audio.h" #include "migration/migration.h" @@ -178,7 +179,8 @@ int main(int argc, char **argv) #define MAX_VIRTIO_CONSOLES 1 #define MAX_SCLP_CONSOLES 1 -static const char *data_dir; +static const char *data_dir[16]; +static int data_dir_idx; const char *bios_name = NULL; enum vga_retrace_method vga_retrace_method = VGA_RETRACE_DUMB; DisplayType display_type = DT_DEFAULT; @@ -491,6 +493,30 @@ static QemuOptsList qemu_object_opts = { }, }; +static QemuOptsList qemu_tpmdev_opts = { + .name = "tpmdev", + .implied_opt_name = "type", + .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head), + .desc = { + { + .name = "type", + .type = QEMU_OPT_STRING, + .help = "Type of TPM backend", + }, + { + .name = "cancel-path", + .type = QEMU_OPT_STRING, + .help = "Sysfs file entry for canceling TPM commands", + }, + { + .name = "path", + .type = QEMU_OPT_STRING, + .help = "Path to TPM device on the host", + }, + { /* end of list */ } + }, +}; + const char *qemu_get_vm_name(void) { return qemu_name; @@ -2251,14 +2277,16 @@ static int balloon_parse(const char *arg) char *qemu_find_file(int type, const char *name) { - int len; + int i; const char *subdir; char *buf; /* Try the name as a straight path first */ if (access(name, R_OK) == 0) { + trace_load_file(name, name); return g_strdup(name); } + switch (type) { case QEMU_FILE_TYPE_BIOS: subdir = ""; @@ -2269,14 +2297,16 @@ char *qemu_find_file(int type, const char *name) default: abort(); } - len = strlen(data_dir) + strlen(name) + strlen(subdir) + 2; - buf = g_malloc0(len); - snprintf(buf, len, "%s/%s%s", data_dir, subdir, name); - if (access(buf, R_OK)) { + + for (i = 0; i < data_dir_idx; i++) { + buf = g_strdup_printf("%s/%s%s", data_dir[i], subdir, name); + if (access(buf, R_OK) == 0) { + trace_load_file(name, buf); + return buf; + } g_free(buf); - return NULL; } - return buf; + return NULL; } static int device_help_func(QemuOpts *opts, void *opaque) @@ -2868,6 +2898,7 @@ int main(int argc, char **argv, char **envp) qemu_add_opts(&qemu_sandbox_opts); qemu_add_opts(&qemu_add_fd_opts); qemu_add_opts(&qemu_object_opts); + qemu_add_opts(&qemu_tpmdev_opts); runstate_init(); @@ -3231,6 +3262,13 @@ int main(int argc, char **argv, char **envp) } break; } +#ifdef CONFIG_TPM + case QEMU_OPTION_tpmdev: + if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) { + exit(1); + } + break; +#endif case QEMU_OPTION_mempath: mem_path = optarg; break; @@ -3252,7 +3290,9 @@ int main(int argc, char **argv, char **envp) add_device_config(DEV_GDB, optarg); break; case QEMU_OPTION_L: - data_dir = optarg; + if (data_dir_idx < ARRAY_SIZE(data_dir)) { + data_dir[data_dir_idx++] = optarg; + } break; case QEMU_OPTION_bios: bios_name = optarg; @@ -3892,12 +3932,15 @@ int main(int argc, char **argv, char **envp) /* If no data_dir is specified then try to find it relative to the executable path. */ - if (!data_dir) { - data_dir = os_find_datadir(argv[0]); + if (data_dir_idx < ARRAY_SIZE(data_dir)) { + data_dir[data_dir_idx] = os_find_datadir(argv[0]); + if (data_dir[data_dir_idx] != NULL) { + data_dir_idx++; + } } /* If all else fails use the install path specified when building. */ - if (!data_dir) { - data_dir = CONFIG_QEMU_DATADIR; + if (data_dir_idx < ARRAY_SIZE(data_dir)) { + data_dir[data_dir_idx++] = CONFIG_QEMU_DATADIR; } /* @@ -4108,6 +4151,12 @@ int main(int argc, char **argv, char **envp) exit(1); } +#ifdef CONFIG_TPM + if (tpm_init() < 0) { + exit(1); + } +#endif + /* init the bluetooth world */ if (foreach_device_config(DEV_BT, bt_parse)) exit(1); @@ -4353,6 +4402,9 @@ int main(int argc, char **argv, char **envp) bdrv_close_all(); pause_all_vcpus(); res_free(); +#ifdef CONFIG_TPM + tpm_cleanup(); +#endif return 0; } |