mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-08-18 08:11:07 +00:00
Device trap has great impact on latency of real time (RT) tasks. This patch provide a virtio poll mode to avoid trap. According to the virtio spec, backend devices can declare the notification is not needed so that frontend will never trap. This means the backends make commitment to the frontends they have a poll mechanism which don’t need any frontends notification. This patch uses a periodic timer to give backends pseudo notifications so that drive them processing data in their virtqueues. People should choose a appropriate notification peroid interval to use this poll mode. Too big interval may cause virtqueue processing latency while too small interval may cause high SOS CPU usage. The suggested interval is between 100us to 1ms. The poll mode is not enabled by default and traditional trap notification mode will be used. To use poll mode for RT with interval 1ms. You can add following acrn-dm parameter. --virtio_poll 1000000 Tracked-On: #1956 Signed-off-by: Jie Deng <jie.deng@intel.com> Acked-by: Yu Wang <yu1.wang@intel.com>
482 lines
11 KiB
C
482 lines
11 KiB
C
/*
|
|
* 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 <sys/cdefs.h>
|
|
#include <sys/param.h>
|
|
#include <sys/uio.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#include <pthread.h>
|
|
#include <sysexits.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#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, BACKEND_VBSU);
|
|
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);
|