diff --git a/devicemodel/Makefile b/devicemodel/Makefile index 581b2e3dd..89723fbd8 100644 --- a/devicemodel/Makefile +++ b/devicemodel/Makefile @@ -105,6 +105,7 @@ SRCS += hw/pci/virtio/virtio_ipu.c SRCS += hw/pci/virtio/virtio_hyper_dmabuf.c SRCS += hw/pci/virtio/virtio_mei.c SRCS += hw/pci/virtio/virtio_coreu.c +SRCS += hw/pci/virtio/virtio_hdcp.c SRCS += hw/pci/virtio/virtio_rpmb.c SRCS += hw/pci/irq.c SRCS += hw/pci/uart.c diff --git a/devicemodel/hw/pci/virtio/virtio_hdcp.c b/devicemodel/hw/pci/virtio/virtio_hdcp.c new file mode 100644 index 000000000..e2cfa9e80 --- /dev/null +++ b/devicemodel/hw/pci/virtio/virtio_hdcp.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +/* + * HDCP Virtualization + * + * + * +---------------+ + * | | + * +----------+----------+ | +------------------+ + * | ACRN DM | | | Media APP | + * | +-----------------+ | | +------------------+ + * | | HDCP Backend | | | | + * | +-----------------+ | | +------------------+ + * +---------------------+ | | HDCP Libraries | + * | | +------------------+ + * | | | + * +------------------+ | +------------------+ + * | HDCP SOS Daemon | | | HDCP UOS Daemon | + * +------------------+ | +------------------+ + * | + * Service OS User Space | User OS User Space + * | + * -------------------------- | --------------------------- + * | + * Service OS Kernel Space | User OS Kernel Space + * | + * +------------------+ | +------------------+ + * | i915 HDCP Driver | | | HDCP Front End | + * +------------------+ | +------------------+ + * | | + * +-------------+ + * + * Above diagram illustrates the HDCP architecture in ACRN. In SOS, HDCP + * library being used by media app. In UOS, HDCP Daemon gets the HDCP + * request by open/read/write /dev/hdcp0 which is created by HDCP + * frontend, instead of accessing GPU. Then the HDCP frontend sends the + * requests to the HDCP backend thru virtio mechanism. HDCP backend talks to + * HDCP SOS daemon that will ask HDCP Kernel Driver to execute the requsted + * operation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dm.h" +#include "pci_core.h" +#include "virtio.h" + +#define NUM_PHYSICAL_PORTS_MAX 5 +#define VIRTIO_HDCP_RINGSZ 64 +#define VIRTIO_HDCP_NUMQ 1 +#define HDCP_DIR_BASE "/var/run/hdcp/" +#define HDCP_SDK_SOCKET_PATH HDCP_DIR_BASE ".sdk_socket" + +typedef enum _HDCP_API_TYPE +{ + HDCP_API_INVALID, + HDCP_API_CREATE, + HDCP_API_DESTROY, + HDCP_API_ENUMERATE_HDCP_DISPLAY, + HDCP_API_SENDSRMDATA, + HDCP_API_GETSRMVERSION, + HDCP_API_ENABLE, + HDCP_API_DISABLE, + HDCP_API_GETSTATUS, + HDCP_API_GETKSVLIST, + HDCP_API_REPORTSTATUS, + HDCP_API_TERM_MSG_LOOP, + HDCP_API_CREATE_CALLBACK, + HDCP_API_SET_PROTECTION_LEVEL, + HDCP_API_CONFIG, + HDCP_API_ILLEGAL +} HDCP_API_TYPE; + +typedef enum _PORT_EVENT +{ + PORT_EVENT_NONE = 0, + PORT_EVENT_PLUG_IN, // hot plug in + PORT_EVENT_PLUG_OUT, // hot plug out + PORT_EVENT_LINK_LOST, // HDCP authentication step3 fail +} PORT_EVENT; + +typedef struct _Port +{ + uint32_t Id; + int Status; + PORT_EVENT Event; +} Port; + +typedef enum _HDCP_STATUS +{ + HDCP_STATUS_SUCCESSFUL = 0, + HDCP_STATUS_ERROR_ALREADY_CREATED, + HDCP_STATUS_ERROR_INVALID_PARAMETER, + HDCP_STATUS_ERROR_NO_DISPLAY, + HDCP_STATUS_ERROR_REVOKED_DEVICE, + HDCP_STATUS_ERROR_SRM_INVALID, + HDCP_STATUS_ERROR_INSUFFICIENT_MEMORY, + HDCP_STATUS_ERROR_INTERNAL, + HDCP_STATUS_ERROR_SRM_NOT_RECENT, + HDCP_STATUS_ERROR_SRM_FILE_STORAGE, + HDCP_STATUS_ERROR_MAX_DEVICES_EXCEEDED, + HDCP_STATUS_ERROR_MAX_DEPTH_EXCEEDED, + HDCP_STATUS_ERROR_MSG_TRANSACTION, +} HDCP_STATUS; + +enum HDCP_CONFIG_TYPE +{ + INVALID_CONFIG = 0, // invalid configure type + SRM_STORAGE_CONFIG, // config to disable/enable SRM storage +}; + +typedef struct _HDCP_CONFIG +{ + enum HDCP_CONFIG_TYPE type; + bool disableSrmStorage; +} HDCP_CONFIG; + +/* HDCP socket data */ +struct SocketData +{ + union + { + uint8_t Bytes; + struct + { + uint32_t Size; + HDCP_API_TYPE Command; + HDCP_STATUS Status; + + uint8_t KsvCount; // Number of KSV in topology + uint8_t Depth; // Depth of topology + bool isType1Capable; // Port whether support HDCP2.2 + union + { + Port Ports[NUM_PHYSICAL_PORTS_MAX]; + Port SinglePort; + }; + + uint32_t PortCount; + + uint32_t SrmOrKsvListDataSz; + uint16_t SrmVersion; + + HDCP_CONFIG Config; + + uint8_t Level; + }; + }; +}; + +/* Per-device struct - VBS-U */ +struct virtio_hdcp { + struct virtio_base base; + struct virtio_vq_info queues[VIRTIO_HDCP_NUMQ]; + pthread_mutex_t mtx; + + int in_progress; + pthread_t rx_tid; + pthread_mutex_t rx_mtx; + pthread_cond_t rx_cond; + + /* socket handle to HDCP daemon */ + int fd; +}; + +/* VBS-U virtio_ops */ +static void virtio_hdcp_reset(void *); + +static struct virtio_ops virtio_hdcp_ops = { + "virtio_hdcp", /* our name */ + VIRTIO_HDCP_NUMQ, /* we support one virtqueue */ + 0, /* config reg size */ + virtio_hdcp_reset, /* reset */ + NULL, /* device-wide qnotify */ + NULL, /* read virtio config */ + NULL, /* write virtio config */ + NULL, /* apply negotiated features */ + NULL, /* called on guest set status */ +}; + +/* Debug printf */ +static int virtio_hdcp_debug; +#define DPRINTF(params) do { if (virtio_hdcp_debug) printf params; } while (0) +#define WPRINTF(params) (printf params) + +static void +virtio_hdcp_reset(void *vdev) +{ + struct virtio_hdcp *vhdcp = vdev; + + DPRINTF(("virtio_hdcp: device reset requested\n")); + virtio_reset_dev(&vhdcp->base); +} + +static int +SendMessage(int fd, uint8_t* data, int dataSz) +{ + int bytesRemaining = dataSz; + int offset = 0; + + while (bytesRemaining > 0) + { + ssize_t count = send(fd, &data[offset], bytesRemaining, MSG_NOSIGNAL); + if (-1 == count) + { + if ((EINTR == errno) || (EAGAIN == errno)) + { + continue; + } + + WPRINTF(("Failed to send!")); + return errno; + } + + bytesRemaining -= count; + offset += count; + } + return 0; +} + +static int +GetMessage(int fd, uint8_t* data, int dataSz) +{ + int count = 0; + int bytesRemaining = dataSz; + int offset = 0; + + while (bytesRemaining > 0) + { + count = read(fd, &data[offset], bytesRemaining); + if (-1 == count) + { + if ((EINTR == errno) || (EAGAIN == errno)) + { + continue; + } + + WPRINTF(("Failed to read!")); + return errno; + } + + if (0 == count) + { + WPRINTF(("Success to read, but the content is empty!")); + return ENOTCONN; + } + + bytesRemaining -= count; + offset += count; + } + return 0; +} + +static int +performMessageTransaction(int fd, struct SocketData data) +{ + int ret; + // Send the request to daemon + ret = SendMessage(fd, &data.Bytes, sizeof(data)); + if (ret < 0) { + WPRINTF(("send error\n")); + return ret; + } + + // Get reply from daemon + ret = GetMessage(fd, &data.Bytes, sizeof(data)); + if (ret < 0) { + WPRINTF(("recv error\n")); + return ret; + } + + return 0; +} + +static void * +virtio_hdcp_talk_to_daemon(void *param) +{ + struct virtio_hdcp *vhdcp = param; + struct virtio_vq_info *rvq = &vhdcp->queues[0]; + struct iovec iov; + uint16_t idx; + int ret; + struct SocketData *msg; + + for (;;) { + pthread_mutex_lock(&vhdcp->rx_mtx); + vhdcp->in_progress = 0; + ret = pthread_cond_wait(&vhdcp->rx_cond, &vhdcp->rx_mtx); + assert(ret == 0); + + vhdcp->in_progress = 1; + pthread_mutex_unlock(&vhdcp->rx_mtx); + + while(vq_has_descs(rvq)) { + vq_getchain(rvq, &idx, &iov, 1, NULL); + + msg = (struct SocketData*)(iov.iov_base); + + ret = performMessageTransaction(vhdcp->fd, *msg); + if (ret < 0) + { + close(vhdcp->fd); + vhdcp->fd = -1; + } + + /* release this chain and handle more */ + vq_relchain(rvq, idx, sizeof(struct SocketData)); + } + + vq_endchains(rvq, 1); + } +} + +static int +connect_hdcp_daemon() +{ + struct sockaddr_un addr; + int fd; + int ret; + + fd = socket(PF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) { + WPRINTF(("socket error %d\n", errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, HDCP_SDK_SOCKET_PATH); + + ret = connect(fd, &addr, sizeof(struct sockaddr_un)); + if (ret < 0) { + WPRINTF(("connect error %d\n", errno)); + close(fd); + return -1; + } + return fd; +} + +static void +virtio_hdcp_notify(void *vdev, struct virtio_vq_info *vq) +{ + struct virtio_hdcp *vhdcp = vdev; + + /* Any ring entries to process */ + if (!vq_has_descs(vq)) + return; + + vhdcp->fd = (vhdcp->fd < 0) ? connect_hdcp_daemon() : vhdcp->fd; + if (vhdcp->fd < 0) + { + WPRINTF(("Invalid HDCP daemon file descriptor\n")); + return; + } + + /* Signal the thread for processing */ + pthread_mutex_lock(&vhdcp->rx_mtx); + if (vhdcp->in_progress == 0) + pthread_cond_signal(&vhdcp->rx_cond); + pthread_mutex_unlock(&vhdcp->rx_mtx); +} + +static int +virtio_hdcp_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts) +{ + struct virtio_hdcp *vhdcp; + + pthread_mutexattr_t attr; + char tname[MAXCOMLEN + 1]; + int rc; + + vhdcp = calloc(1, sizeof(struct virtio_hdcp)); + if (!vhdcp) { + WPRINTF(("vhdcp init: fail to alloc virtio_hdcp\n")); + return -1; + } + + /* init mutex attribute properly */ + int mutexattr_type = virtio_uses_msix() + ? PTHREAD_MUTEX_DEFAULT + : PTHREAD_MUTEX_RECURSIVE; + + rc = pthread_mutexattr_init(&attr); + if (rc) + WPRINTF(("vhdcp init: mutexattr init fail, erro %d\n", rc)); + rc = pthread_mutexattr_settype(&attr, mutexattr_type); + if (rc) + WPRINTF(("vhdcp init: mutexattr_settype fail, erro %d\n", rc)); + rc = pthread_mutex_init(&vhdcp->mtx, &attr); + if (rc) + WPRINTF(("vhdcp init: mutexattr_settype fail, erro %d\n", rc)); + + DPRINTF(("vhdcp init: using VBS-U...\n")); + virtio_linkup(&vhdcp->base, &virtio_hdcp_ops, + vhdcp, dev, vhdcp->queues); + vhdcp->base.mtx = &vhdcp->mtx; + + vhdcp->queues[0].qsize = VIRTIO_HDCP_RINGSZ; + vhdcp->queues[0].notify = virtio_hdcp_notify; + + /* initialize config space */ + pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_HDCP); + pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID); + pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_CRYPTO); + pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_SIMPLECOMM_OTHER); + pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_HDCP); + pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID); + + if (virtio_interrupt_init(&vhdcp->base, virtio_uses_msix())) { + WPRINTF(("vhdcp init: interrupt init fail\n")); + free(vhdcp); + return -1; + } + + virtio_set_io_bar(&vhdcp->base, 0); + + /* + * connect to hdcp daemon in init phase + * + * @FIXME if failed connecting to HDCP daemon, the return value should + * be set appropriately for SOS not exposing the HDCP PCI device to UOS + */ + vhdcp->fd = connect_hdcp_daemon(); + if (vhdcp->fd < 0) { + WPRINTF(("connection to server failed\n")); + return -errno; + } + + vhdcp->in_progress = 0; + pthread_mutex_init(&vhdcp->rx_mtx, NULL); + pthread_cond_init(&vhdcp->rx_cond, NULL); + pthread_create(&vhdcp->rx_tid, NULL, + virtio_hdcp_talk_to_daemon, (void *)vhdcp); + snprintf(tname, sizeof(tname), "vthdcp-%d:%d tx", + dev->slot, dev->func); + pthread_setname_np(vhdcp->rx_tid, tname); + + return 0; +} + +static void +virtio_hdcp_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts) +{ + struct virtio_hdcp *vhdcp = (struct virtio_hdcp *)dev->arg; + + if (vhdcp) { + DPRINTF(("free struct virtio_hdcp\n")); + free(vhdcp); + } +} + +struct pci_vdev_ops pci_ops_virtio_hdcp = { + .class_name = "virtio-hdcp", + .vdev_init = virtio_hdcp_init, + .vdev_deinit = virtio_hdcp_deinit, + .vdev_barwrite = virtio_pci_write, + .vdev_barread = virtio_pci_read +}; + +DEFINE_PCI_DEVTYPE(pci_ops_virtio_hdcp);