tpm: Add TPM CRQ driver implementation

This patch adds a TPM driver for the CRQ interface as used by
the QEMU PAPR implementation.

Also add a Readme that explains the benefits and installation procedure
for the vTPM.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Alexey Kardashevskiy <aik@ozlabs.ru>
This commit is contained in:
Stefan Berger 2020-01-21 15:01:43 -05:00 committed by Alexey Kardashevskiy
parent 4f18bac8b0
commit a2ffcd9d65
9 changed files with 665 additions and 2 deletions

View File

@ -15,7 +15,7 @@ BOARD_TARGETS = tools_build romfs_build stage1 subdirs
SUBDIRS = slof
COMMON_LIBS = libc libbootmsg libbases libnvram libelf libhvcall libvirtio \
libusb libveth libe1k libnet libbootmenu
libusb libveth libe1k libnet libbootmenu libtpm
all: $(BOARD_TARGETS)
$(MAKE) boot_rom.bin

View File

@ -44,6 +44,7 @@ extern int SLOF_get_property(const char *node, const char *propname,
char **addr, int *len);
extern int SLOF_get_keystroke(void);
extern void SLOF_reset(void);
extern unsigned long SLOF_get_vtpm_unit(void);
#define offset_of(type, member) ((long) &((type *)0)->member)
#define container_of(ptr, type, member) ({ \

View File

@ -11,7 +11,7 @@
# ****************************************************************************/
SUBDIRS = libc libipmi libbootmsg libbases libnvram libelf libhvcall libvirtio \
libusb libveth libe1k libbcm libnet libbootmenu
libusb libveth libe1k libbcm libnet libbootmenu libtpm
all: subdirs

50
lib/libtpm/Makefile Normal file
View File

@ -0,0 +1,50 @@
# *****************************************************************************
# * Copyright (c) 2015-2020 IBM Corporation
# * All rights reserved.
# * This program and the accompanying materials
# * are made available under the terms of the BSD License
# * which accompanies this distribution, and is available at
# * http://www.opensource.org/licenses/bsd-license.php
# *
# * Contributors:
# * IBM Corporation - initial implementation
# ****************************************************************************/
TOPCMNDIR ?= ../..
CPPFLAGS = -I../libc/include $(CPUARCHDEF) -I$(INCLBRDDIR) \
-I$(INCLCMNDIR) -I$(INCLCMNDIR)/$(CPUARCH) -I$(SLOFCMNDIR)
CPPFLAGS += -I../libhvcall
LDFLAGS = -nostdlib
TARGET = ../libtpm.a
all: $(TARGET)
SRCS = tpm_drivers.c
OBJS = $(SRCS:%.c=%.o)
$(TARGET): $(OBJS)
$(AR) -rc $@ $(OBJS)
$(RANLIB) $@
clean:
$(RM) $(TARGET) $(OBJS)
distclean: clean
$(RM) Makefile.dep
# Rules for creating the dependency file:
depend:
$(RM) Makefile.dep
$(MAKE) Makefile.dep
Makefile.dep: Makefile
$(CC) -M $(CPPFLAGS) $(CFLAGS) $(SRCS) $(SRCSS) > Makefile.dep
# Include dependency file if available:
-include Makefile.dep

57
lib/libtpm/Readme Normal file
View File

@ -0,0 +1,57 @@
This directory hosts (v)TPM related code.
Background:
-----------
A TPM is a crypto chip that is found in many systems. Besides it offering
a secure key store, among other functionality, it is also used to implement
'trusted boot'. This is realized by code in the firmware measuring parts of the
firmware's code and data as well as system data, such as the boot block, and
logging these measurements and storing (extending) them in the TPM's platform
configuration register (PCR).
The benefits of having a TPM (or vTPM) in a system are:
- enablement of trusted boot; this allow us to eventually extend the chain of
trust from the hypervisor to the guests
- enablement of attestation so that one can verify what software is running on
a machine (OpenPTS, OpenAttestation)
- provides TPM functionality to VMs, which includes a standardized mechanism
to store keys and other blobs (Linux trusted keys, GNU TLS's TPM extensions)
QEMU/KVM + SLOF support:
------------------------
vTPM for QEMU/KVM pSeries virtual machines is support in QEMU 5.0.
To start a QEMU VM with an attached vTPM (swtpm), run the below shown commands.
The following will setup the vTPM so that its state will be stored in
/tmp/myvtpm1. A unique directory for each VM instance with attached vTPM
must be provided. Whenever QEMU is started, the swtpm has to be started
before it. The file 'boot_rom.bin' is SLOF with vTPM extensions built-in.
#> mkdir -p /tmp/mytpm1
#> swtpm socket --tpm2 --tpmstate dir=/tmp/mytpm1 \
--ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock
In another terminal:
#> sudo qemu-system-ppc64 -display sdl \
-machine pseries,accel=kvm \
-m 1024 -bios boot_rom.bin -boot menu=on \
-nodefaults -device VGA -device pci-ohci -device usb-kbd \
-chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-spapr,tpmdev=tpm0 \
-device spapr-vscsi,id=scsi0,reg=0x00002000 \
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x3,drive=drive-virtio-disk0,id=virtio-disk0 \
-drive file=test.img,format=raw,if=none,id=drive-virtio-disk0
Notes:
- The Linux kernel in the VM must have the tpm_ibmvtpm module available
or built-in. A recent kernel is needed that enables TPM 2.0 support
in this module.
- 'swtpm_ioctl --unix /tmp/mytpm1/swtpm-sock -s' can be used to gracefully
shut down the vTPM.

30
lib/libtpm/tcgbios_int.h Normal file
View File

@ -0,0 +1,30 @@
/*****************************************************************************
* Copyright (c) 2015-2020 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#ifndef TCGBIOS_INT_H
#define TCGBIOS_INT_H
#include <stdint.h>
struct tpm_req_header {
uint16_t tag;
uint32_t totlen;
uint32_t ordinal;
} __attribute__((packed));
struct tpm_rsp_header {
uint16_t tag;
uint32_t totlen;
uint32_t errcode;
} __attribute__((packed));
#endif /* TCGBIOS_INT_H */

437
lib/libtpm/tpm_drivers.c Normal file
View File

@ -0,0 +1,437 @@
/*****************************************************************************
* Copyright (c) 2015-2020 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include "string.h"
#include "helpers.h"
#include "byteorder.h"
#include "tcgbios_int.h"
#include "tpm_drivers.h"
#include "libhvcall.h"
#include "paflof.h"
#undef PAPR_VTPM_DEBUG
//#define PAPR_VTPM_DEBUG
#ifdef PAPR_VTPM_DEBUG
#define dprintf(_x ...) do { printf("VTPM CRQ: " _x); } while(0)
#else
#define dprintf(_x ...)
#endif
#define MIN(a, b) ((a) > (b) ? (b) : (a))
/* layout of the command request queue for vTPM; all fields are big endian */
struct crq {
uint8_t valid;
uint8_t msg;
uint16_t len;
uint32_t data;
uint64_t reserved;
} __attribute__((packed));
#define PAPR_VTPM_INIT_CRQ_COMMAND 0xC0
#define PAPR_VTPM_VALID_COMMAND 0x80
#define PAPR_VTPM_MSG_RESULT 0x80
/* crq.msg request types when crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND */
#define PAPR_VTPM_INIT_CRQ_RESULT 0x1
/* crq.msg request types when crq.valid = PAPR_VTPM_VALID_COMMAND */
#define PAPR_VTPM_GET_VERSION 0x1
#define PAPR_VTPM_TPM_COMMAND 0x2
#define PAPR_VTPM_GET_RTCE_BUFFER_SIZE 0x3
#define TPM2_DEFAULT_DURATION_SHORT 750000 /* us */
#define TPM2_DEFAULT_DURATION_MEDIUM 2000000 /* us */
#define TPM2_DEFAULT_DURATION_LONG 2000000 /* us */
static const uint32_t tpm2_durations[3] = {
TPM2_DEFAULT_DURATION_SHORT,
TPM2_DEFAULT_DURATION_MEDIUM,
TPM2_DEFAULT_DURATION_LONG,
};
#define QUEUE_SIZE 4096
/* state of the PAPR CRQ VTPM driver */
static struct {
/* whether it driver been initialized */
bool initialized;
/* unit number */
unsigned long unit;
/* CRQ queue address and size */
unsigned char *qaddr;
unsigned long qsize;
/* current q_entry */
unsigned int curr_q_entry;
/* current response CRQ */
struct crq *response;
/* power firmware defined state and error code */
vtpm_drv_state driver_state;
vtpm_drv_error driver_error;
/* size of buffer supported by hypervisor */
unsigned int buffer_size;
/* buffer for commands and responses */
char *buffer;
} spapr_vtpm = {
.qsize = QUEUE_SIZE,
.driver_state = VTPM_DRV_STATE_INVALID,
.driver_error = VTPM_DRV_ERROR_NO_FAILURE,
};
static void vtpm_drv_state_set(vtpm_drv_state s, vtpm_drv_error e)
{
spapr_vtpm.driver_state = s;
spapr_vtpm.driver_error = e;
}
static vtpm_drv_error vtpm_drv_error_get(void)
{
return spapr_vtpm.driver_error;
}
static struct crq *spapr_get_crq(void *qaddr, unsigned long q_entry)
{
return &((struct crq *)qaddr)[q_entry];
}
/*
* Get the crq where the response will be found. This
* function will clear the CRQ's valid field and advance
* the entry counter to the next entry.
*/
static struct crq *spapr_get_response_crq(void)
{
struct crq *crq;
dprintf("curr_q_entry = %d\n", spapr_vtpm.curr_q_entry);
crq = spapr_get_crq(spapr_vtpm.qaddr, spapr_vtpm.curr_q_entry);
memset(crq, 0, sizeof(*crq));
spapr_vtpm.curr_q_entry += 1;
if (spapr_vtpm.curr_q_entry == (spapr_vtpm.qsize / sizeof(struct crq)))
spapr_vtpm.curr_q_entry = 0;
return crq;
}
/*
* Send a message via CRQ and wait for the response
*/
static bool spapr_send_crq_and_wait(unsigned long unit,
struct crq *crq,
struct crq **response,
unsigned timeout,
vtpm_drv_state state1,
vtpm_drv_state state2)
{
long rc;
unsigned i;
*response = spapr_get_response_crq();
vtpm_drv_state_set(state1, VTPM_DRV_ERROR_NO_FAILURE);
rc = hv_send_crq(unit, (uint64_t *)crq);
if (rc != H_SUCCESS) {
vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
VTPM_DRV_ERROR_TPM_CRQ_ERROR);
return false;
}
vtpm_drv_state_set(state2, VTPM_DRV_ERROR_NO_FAILURE);
for (i = 0; i < timeout; i += 1000) {
if (((*response)->valid & PAPR_VTPM_MSG_RESULT))
return true;
SLOF_usleep(1000);
}
vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
VTPM_DRV_ERROR_WAIT_TIMEOUT);
dprintf("Received no response from CRQ\n");
return false;
}
/*
* Get parameters from the CRQ
*/
static bool spapr_vtpm_get_params(void)
{
struct crq crq, *response;
static bool completed = false; /* only once */
if (completed)
return true;
/* get the TPM's buffer size */
crq.valid = PAPR_VTPM_VALID_COMMAND;
crq.msg = PAPR_VTPM_GET_RTCE_BUFFER_SIZE;
if (!spapr_send_crq_and_wait(spapr_vtpm.unit, &crq, &response, 10,
VTPM_DRV_STATE_SEND_BUFSIZE_REQ,
VTPM_DRV_STATE_WAIT_BUFSIZE)) {
printf("%s: Failure getting RTCE buffer size from CRQ\n",
__func__);
return false;
}
vtpm_drv_state_set(VTPM_DRV_STATE_ALLOC_RTCE_BUF,
VTPM_DRV_ERROR_NO_FAILURE);
dprintf("RTCE buffer size: %u\n", be16_to_cpu(response->len));
spapr_vtpm.buffer_size = be16_to_cpu(response->len);
if (spapr_vtpm.buffer_size < 1024) {
printf("%s: RTCE buffer size of %u bytes is too small. "
"Minimum is 1024 bytes.\n", __func__,
spapr_vtpm.buffer_size);
vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
VTPM_DRV_ERROR_BAD_RTCE_SIZE);
return false;
}
spapr_vtpm.buffer = SLOF_alloc_mem(spapr_vtpm.buffer_size);
if (!spapr_vtpm.buffer) {
printf("%s: Could not allocate buffer of size %u.\n",
__func__, spapr_vtpm.buffer_size);
vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
VTPM_DRV_ERROR_BAD_RTCE_SIZE);
return false;
}
completed = true;
return true;
}
static bool spapr_vtpm_activate(void)
{
long rc;
struct crq crq, *response;
if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
printf("%s: CRQ: In failure mode\n", __func__);
return false;
}
vtpm_drv_state_set(VTPM_DRV_STATE_REG_CRQ,
VTPM_DRV_ERROR_NO_FAILURE);
rc = hv_reg_crq(spapr_vtpm.unit, (unsigned long)spapr_vtpm.qaddr,
spapr_vtpm.qsize);
if (rc != H_SUCCESS) {
vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
VTPM_DRV_ERROR_UNEXPECTED_REG_ERROR);
printf("%s: CRQ registration failed\n", __func__);
return false;
}
/* we always start with curr_q_entry 0 */
spapr_vtpm.curr_q_entry = 0;
if (!spapr_vtpm.initialized) {
crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND;
crq.msg = PAPR_VTPM_INIT_CRQ_RESULT;
if (!spapr_send_crq_and_wait(spapr_vtpm.unit,
&crq,
&response,
10,
VTPM_DRV_STATE_SEND_INIT,
VTPM_DRV_STATE_WAIT_INIT_COMP)) {
printf("%s: Initializing CRQ failed\n", __func__);
goto err_exit;
}
dprintf("Successfully initialized CRQ\n");
spapr_vtpm.initialized = true;
}
if (spapr_vtpm_get_params())
return true;
err_exit:
hv_free_crq(spapr_vtpm.unit);
spapr_vtpm.unit = 0;
return false;
}
void spapr_vtpm_finalize(void)
{
if (spapr_vtpm.unit) {
hv_free_crq(spapr_vtpm.unit);
spapr_vtpm.unit = 0;
}
}
/*
* Check whether we have a CRQ underneath us; if we do, the CRQ will
* be left open.
*/
static bool spapr_vtpm_probe(void)
{
if (!spapr_vtpm.qaddr) {
spapr_vtpm.qaddr = SLOF_alloc_mem(spapr_vtpm.qsize);
if (!spapr_vtpm.qaddr) {
printf("%s: Unable to allocate memory\n", __func__);
return false;
}
memset(spapr_vtpm.qaddr, 0, spapr_vtpm.qsize);
dprintf("getting FORTH vtpm-unit\n");
spapr_vtpm.unit = SLOF_get_vtpm_unit();
if (!spapr_vtpm.unit) {
printf("%s: Could not get valid vtpm-unit\n", __func__);
return false;
}
}
dprintf("vtpm_unit = %lx, buffer = %p\n",
spapr_vtpm.unit, spapr_vtpm.qaddr);
if (!spapr_vtpm_activate())
return false;
return true;
}
static bool spapr_vtpm_senddata(const uint8_t *const data, uint32_t len)
{
struct crq crq;
long rc;
if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
printf("%s: VTPM CRQ: In failure mode\n", __func__);
return false;
}
if (len > spapr_vtpm.buffer_size) {
printf("%s: VTPM CRQ: Send buffer too large: %u > %u\n",
__func__, len, spapr_vtpm.buffer_size);
return false;
}
spapr_vtpm.response = spapr_get_response_crq();
spapr_vtpm.response->data = (uint64_t)spapr_vtpm.buffer;
crq.valid = PAPR_VTPM_VALID_COMMAND;
crq.msg = PAPR_VTPM_TPM_COMMAND;
crq.len = cpu_to_be16(len);
crq.data = (uint64_t)spapr_vtpm.buffer;
memcpy(spapr_vtpm.buffer, data, MIN(len, spapr_vtpm.buffer_size));
vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
VTPM_DRV_ERROR_NO_FAILURE);
rc = hv_send_crq(spapr_vtpm.unit, (uint64_t *)&crq);
if (rc == H_SUCCESS)
vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_TPM_RSP,
VTPM_DRV_ERROR_NO_FAILURE);
else
vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
VTPM_DRV_ERROR_UNEXPECTED_SEND_ERROR);
return (rc == H_SUCCESS);
}
static bool spapr_vtpm_waitresponseready(enum tpm_duration_type to_t)
{
uint32_t timeout = tpm2_durations[to_t];
int i;
if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
printf("%s: VTPM CRQ: In failure mode\n", __func__);
return false;
}
for (i = 0; i < timeout; i += 1000) {
if (spapr_vtpm.response->valid & PAPR_VTPM_MSG_RESULT) {
/* TPM responded: move to Send tpm-cmd state */
vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
VTPM_DRV_ERROR_NO_FAILURE);
dprintf("Received response to TPM command\n");
return true;
}
SLOF_usleep(1000);
}
vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
VTPM_DRV_ERROR_WAIT_TIMEOUT);
dprintf("Received NO response to TPM command");
return false;
}
static bool spapr_vtpm_readresponse(uint8_t *buffer, uint32_t *len)
{
uint32_t length;
if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
printf("%s: VTPM CRQ: In failure mode\n", __func__);
return false;
}
length = MIN(*len, be32_to_cpu(spapr_vtpm.response->len));
memcpy(buffer, (void *)(uint64_t)spapr_vtpm.response->data, length);
dprintf("Length of copied response: %d\n", length);
spapr_vtpm.response = NULL;
*len = length;
return true;
}
/**** higher layer interface ****/
vtpm_drv_error spapr_vtpm_get_error(void)
{
return vtpm_drv_error_get();
}
void spapr_vtpm_set_error(vtpm_drv_error errcode)
{
spapr_vtpm.driver_error = errcode;
}
bool spapr_is_vtpm_present(void)
{
return spapr_vtpm_probe();
}
int spapr_transmit(uint8_t locty, struct tpm_req_header *req,
void *respbuffer, uint32_t *respbufferlen,
enum tpm_duration_type to_t)
{
if (!spapr_vtpm_senddata((uint8_t *)req, be32_to_cpu(req->totlen)) ||
!spapr_vtpm_waitresponseready(to_t) ||
!spapr_vtpm_readresponse(respbuffer, respbufferlen) ||
*respbufferlen < sizeof(struct tpm_rsp_header))
return -1;
return 0;
}

82
lib/libtpm/tpm_drivers.h Normal file
View File

@ -0,0 +1,82 @@
/*****************************************************************************
* Copyright (c) 2015-2020 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#ifndef TPM_DRIVERS_H
#define TPM_DRIVERS_H
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include "tcgbios_int.h"
enum tpm_duration_type {
TPM_DURATION_TYPE_SHORT = 0,
TPM_DURATION_TYPE_MEDIUM,
TPM_DURATION_TYPE_LONG,
};
/* firmware driver states */
typedef enum {
VTPM_DRV_STATE_INVALID = 0,
VTPM_DRV_STATE_INIT_CALLED = 1,
VTPM_DRV_STATE_REG_CRQ = 2,
VTPM_DRV_STATE_WAIT_INIT = 3,
VTPM_DRV_STATE_SEND_INIT = 4,
VTPM_DRV_STATE_FAILURE = 5,
VTPM_DRV_STATE_WAIT_INIT_COMP = 6,
VTPM_DRV_STATE_SEND_INIT_COMP = 7,
VTPM_DRV_STATE_SEND_GET_VERSION = 8,
VTPM_DRV_STATE_WAIT_VERSION = 9,
VTPM_DRV_STATE_CHECK_VERSION = 10,
VTPM_DRV_STATE_SEND_BUFSIZE_REQ = 11,
VTPM_DRV_STATE_WAIT_BUFSIZE = 12,
VTPM_DRV_STATE_ALLOC_RTCE_BUF = 13,
VTPM_DRV_STATE_SEND_TPM_CMD = 14,
VTPM_DRV_STATE_WAIT_TPM_RSP = 15,
} vtpm_drv_state;
/* firmware driver errors */
typedef enum {
VTPM_DRV_ERROR_NO_FAILURE = -1,
VTPM_DRV_ERROR_NOT_FOUND_TIMEOUT = 0,
VTPM_DRV_ERROR_UNEXPECTED_REG_ERROR = 1,
VTPM_DRV_ERROR_PARTNER_FAILED = 2,
VTPM_DRV_ERROR_UNEXPECTED_TSP_ERROR = 3,
VTPM_DRV_ERROR_TPM_PROTOCOL_ERROR = 4,
VTPM_DRV_ERROR_WAIT_TIMEOUT = 5,
VTPM_DRV_ERROR_UNEXPECTED_SEND_ERROR = 6,
VTPM_DRV_ERROR_CRQ_OPEN_FAIL = 7,
VTPM_DRV_ERROR_BAD_STATE = 8,
VTPM_DRV_ERROR_TPM_FAIL = 9,
VTPM_DRV_ERROR_TPM_CRQ_ERROR = 10,
VTPM_DRV_ERROR_BAD_VERSION = 11,
VTPM_DRV_ERROR_BAD_RTCE_SIZE = 12,
VTPM_DRV_ERROR_SML_FAILURE = 13,
VTPM_DRV_ERROR_SML_HANDED_OVER = 14,
} vtpm_drv_error;
/* the max. buffer size by the external TPM is 4k */
#define PAPR_VTPM_MAX_BUFFER_SIZE 4096
/* exported functions */
bool spapr_is_vtpm_present(void);
void spapr_vtpm_finalize(void);
vtpm_drv_error spapr_vtpm_get_error(void);
void spapr_vtpm_set_error(vtpm_drv_error errcode);
struct tpm_req_header;
int spapr_transmit(uint8_t locty, struct tpm_req_header *req,
void *respbuffer, uint32_t *respbufferlen,
enum tpm_duration_type to_t);
#endif /* TPM_DRIVERS_H */

View File

@ -235,3 +235,9 @@ void SLOF_reset(void)
{
forth_eval("reset-all");
}
unsigned long SLOF_get_vtpm_unit(void)
{
forth_eval("vtpm-unit");
return forth_pop();
}