clear-pkgs-linux-iot-lts2018/0156-mei-dal-dynamic-applic...

1024 lines
26 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Yael Samet <yael.samet@intel.com>
Date: Tue, 5 Sep 2017 12:21:49 +0300
Subject: [PATCH] mei: dal: dynamic application loader
DAL stands for Dynamic Application Loader, it provides the ability
to run Java applets in a secured environment inside of Intel ME security
engine (ME). The Java applets are also named as trusted applications TAs.
The DAL driver exposes API for both user-space and kernel-space clients.
Both clients can download a trusted application/applet to the
DAL FW and communicate with it.
This patch adds the core of the DAL driver, the lowest level
of communication with DAL firmware.
Change-Id: I0d6d9af6039e888c8575c2f21dc37afbadc676da
Signed-off-by: Yael Samet <yael.samet@intel.com>
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
---
drivers/misc/mei/Kconfig | 3 +
drivers/misc/mei/Makefile | 1 +
drivers/misc/mei/dal/Kconfig | 7 +
drivers/misc/mei/dal/Makefile | 9 +
drivers/misc/mei/dal/dal_class.c | 778 +++++++++++++++++++++++++++++++
drivers/misc/mei/dal/dal_dev.h | 149 ++++++
6 files changed, 947 insertions(+)
create mode 100644 drivers/misc/mei/dal/Kconfig
create mode 100644 drivers/misc/mei/dal/Makefile
create mode 100644 drivers/misc/mei/dal/dal_class.c
create mode 100644 drivers/misc/mei/dal/dal_dev.h
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig
index 49de44e485a2..94961573f5f8 100644
--- a/drivers/misc/mei/Kconfig
+++ b/drivers/misc/mei/Kconfig
@@ -53,4 +53,7 @@ config INTEL_MEI_VIRTIO
The module will be called mei_virtio.
Enable this if your virtual machine supports virtual mei
device over virtio.
+
+source "drivers/misc/mei/dal/Kconfig"
+
source "drivers/misc/mei/spd/Kconfig"
diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile
index c632e72406d6..462fa25242d8 100644
--- a/drivers/misc/mei/Makefile
+++ b/drivers/misc/mei/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_INTEL_MEI_VIRTIO) += mei-virtio.o
mei-virtio-objs := hw-virtio.o
obj-$(CONFIG_INTEL_MEI_SPD) += spd/
+obj-$(CONFIG_INTEL_MEI_DAL) += dal/
diff --git a/drivers/misc/mei/dal/Kconfig b/drivers/misc/mei/dal/Kconfig
new file mode 100644
index 000000000000..5d8356ae931e
--- /dev/null
+++ b/drivers/misc/mei/dal/Kconfig
@@ -0,0 +1,7 @@
+config INTEL_MEI_DAL
+ tristate "Dynamic Application Loader for ME"
+ depends on INTEL_MEI
+ help
+ Dynamic Application Loader enables downloading java applets
+ to DAL FW and run it in a secure environment.
+ The DAL module exposes both user space api and kernel space api.
diff --git a/drivers/misc/mei/dal/Makefile b/drivers/misc/mei/dal/Makefile
new file mode 100644
index 000000000000..e073531bc88d
--- /dev/null
+++ b/drivers/misc/mei/dal/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+ccflags-y += -D__CHECK_ENDIAN__
+
+obj-$(CONFIG_INTEL_MEI_DAL) += mei_dal.o
+mei_dal-objs := dal_class.o
+mei_dal-objs += acp_parser.o
+mei_dal-objs += bh_external.o
+mei_dal-objs += bh_internal.o
diff --git a/drivers/misc/mei/dal/dal_class.c b/drivers/misc/mei/dal/dal_class.c
new file mode 100644
index 000000000000..607c63c3dca9
--- /dev/null
+++ b/drivers/misc/mei/dal/dal_class.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/*
+ * Copyright(c) 2016 - 2018 Intel Corporation. All rights reserved.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+
+#include <linux/mei_cl_bus.h>
+
+#include "bh_external.h"
+#include "bh_cmd_defs.h"
+#include "bh_errcode.h"
+#include "dal_dev.h"
+
+/*
+ * this class contains the 3 mei_cl_device, ivm, sdm, rtm.
+ * it is initialized during dal_probe and is used by the kernel space kdi
+ * to send/recv data to/from mei.
+ *
+ * this class must be initialized before the kernel space kdi uses it.
+ */
+struct class *dal_class;
+
+/**
+ * dal_dc_print - print client data for debug purpose
+ *
+ * @dev: device structure
+ * @dc: dal client
+ */
+void dal_dc_print(struct device *dev, struct dal_client *dc)
+{
+ if (!dc) {
+ dev_dbg(dev, "dc is null\n");
+ return;
+ }
+
+ dev_dbg(dev, "dc: intf = %d. expected to send: %d, sent: %d. expected to receive: %d, received: %d\n",
+ dc->intf,
+ dc->expected_msg_size_to_fw,
+ dc->bytes_sent_to_fw,
+ dc->expected_msg_size_from_fw,
+ dc->bytes_rcvd_from_fw);
+}
+
+/**
+ * dal_dc_update_read_state - update client read state
+ *
+ * @dc : dal client
+ * @len: received message length
+ *
+ * Locking: called under "ddev->context_lock" lock
+ */
+static void dal_dc_update_read_state(struct dal_client *dc, ssize_t len)
+{
+ struct dal_device *ddev = dc->ddev;
+
+ /* check BH msg magic, if it exists this is the header */
+ if (bh_msg_is_response(ddev->bh_fw_msg.msg, len)) {
+ struct bh_response_header *hdr =
+ (struct bh_response_header *)dc->ddev->bh_fw_msg.msg;
+
+ dc->expected_msg_size_from_fw = hdr->h.length;
+ dev_dbg(&ddev->dev, "expected_msg_size_from_fw = %d bytes read = %zd\n",
+ dc->expected_msg_size_from_fw, len);
+
+ /* clear data from the past. */
+ dc->bytes_rcvd_from_fw = 0;
+ }
+
+ /* update number of bytes rcvd */
+ dc->bytes_rcvd_from_fw += len;
+}
+
+/**
+ * dal_get_client_by_squence_number - find the client interface which
+ * the received message is sent to
+ *
+ * @ddev : dal device
+ *
+ * Return: kernel space interface or user space interface
+ */
+static enum dal_intf dal_get_client_by_squence_number(struct dal_device *ddev)
+{
+ struct bh_response_header *head;
+
+ if (!ddev->clients[DAL_INTF_KDI])
+ return DAL_INTF_CDEV;
+
+ head = (struct bh_response_header *)ddev->bh_fw_msg.msg;
+
+ dev_dbg(&ddev->dev, "msg seq = %llu\n", head->seq);
+
+ if (head->seq == ddev->clients[DAL_INTF_KDI]->seq)
+ return DAL_INTF_KDI;
+
+ return DAL_INTF_CDEV;
+}
+
+/**
+ * dal_recv_cb - callback to receive message from DAL FW over mei
+ *
+ * @cldev : mei client device
+ */
+static void dal_recv_cb(struct mei_cl_device *cldev)
+{
+ struct dal_device *ddev;
+ struct dal_client *dc;
+ enum dal_intf intf;
+ ssize_t len;
+ size_t ret;
+ bool is_unexpected_msg = false;
+
+ ddev = mei_cldev_get_drvdata(cldev);
+
+ /*
+ * read the msg from MEI
+ */
+ len = mei_cldev_recv(cldev, ddev->bh_fw_msg.msg, DAL_MAX_BUFFER_SIZE);
+ if (len < 0) {
+ dev_err(&cldev->dev, "recv failed %zd\n", len);
+ return;
+ }
+
+ /*
+ * lock to prevent read from MEI while writing to MEI and to
+ * deal with just one msg at the same time
+ */
+ mutex_lock(&ddev->context_lock);
+
+ /* save msg len */
+ ddev->bh_fw_msg.len = len;
+
+ /* set to which interface the msg should be sent */
+ if (bh_msg_is_response(ddev->bh_fw_msg.msg, len)) {
+ intf = dal_get_client_by_squence_number(ddev);
+ dev_dbg(&ddev->dev, "recv_cb(): Client set by sequence number\n");
+ dc = ddev->clients[intf];
+ } else if (!ddev->current_read_client) {
+ intf = DAL_INTF_CDEV;
+ dev_dbg(&ddev->dev, "recv_cb(): EXTRA msg received - curr == NULL\n");
+ dc = ddev->clients[intf];
+ is_unexpected_msg = true;
+ } else {
+ dc = ddev->current_read_client;
+ dev_dbg(&ddev->dev, "recv_cb(): FRAGMENT msg received - curr != NULL\n");
+ }
+
+ /* save the current read client */
+ ddev->current_read_client = dc;
+ /* In case of a client is not connected, dc might be NULL */
+ if (!dc)
+ goto out;
+
+ dev_dbg(&cldev->dev, "read client type %d data from mei client seq = %llu\n",
+ dc->intf, dc->seq);
+
+ /*
+ * save new msg in queue,
+ * if the queue is full all new messages will be thrown
+ */
+ ret = kfifo_in(&dc->read_queue, &ddev->bh_fw_msg.len, sizeof(len));
+ ret += kfifo_in(&dc->read_queue, ddev->bh_fw_msg.msg, len);
+ if (ret < len + sizeof(len))
+ dev_dbg(&ddev->dev, "queue is full - MSG THROWN\n");
+
+ dal_dc_update_read_state(dc, len);
+
+ /*
+ * To clear current client we check if the whole msg received
+ * for the current client
+ */
+ if (is_unexpected_msg ||
+ dc->bytes_rcvd_from_fw == dc->expected_msg_size_from_fw) {
+ dev_dbg(&ddev->dev, "recv_cb(): setting CURRENT_READER to NULL\n");
+ ddev->current_read_client = NULL;
+ }
+out:
+ /* wake up all clients waiting for read or write */
+ if (wq_has_sleeper(&ddev->wq))
+ wake_up_interruptible(&ddev->wq);
+
+ mutex_unlock(&ddev->context_lock);
+}
+
+/**
+ * dal_mei_enable - enable mei cldev
+ *
+ * @ddev: dal device
+ *
+ * Return: 0 on success
+ * <0 on failure
+ */
+static int dal_mei_enable(struct dal_device *ddev)
+{
+ int ret;
+
+ ret = mei_cldev_enable(ddev->cldev);
+ if (ret < 0) {
+ dev_err(&ddev->cldev->dev, "mei_cldev_enable_device() failed with ret = %d\n",
+ ret);
+ return ret;
+ }
+
+ /* register to mei bus callbacks */
+ ret = mei_cldev_register_rx_cb(ddev->cldev, dal_recv_cb);
+ if (ret) {
+ dev_err(&ddev->cldev->dev, "mei_cldev_register_event_cb() failed ret = %d\n",
+ ret);
+ goto err;
+ }
+
+ /* save pointer to the context in the device */
+ mei_cldev_set_drvdata(ddev->cldev, ddev);
+
+ return 0;
+err:
+ mei_cldev_disable(ddev->cldev);
+ return ret;
+}
+
+/**
+ * dal_wait_for_write - wait until the dal client is the first writer
+ * in writers queue
+ *
+ * @ddev: dal device
+ * @dc: dal client
+ *
+ * Return: 0 on success
+ * -ERESTARTSYS when wait was interrupted
+ * -ENODEV when the device was removed
+ */
+static int dal_wait_for_write(struct dal_device *ddev, struct dal_client *dc)
+{
+ if (wait_event_interruptible(ddev->wq,
+ list_first_entry(&ddev->writers,
+ struct dal_client,
+ wrlink) == dc ||
+ ddev->is_device_removed)) {
+ return -ERESTARTSYS;
+ }
+
+ /* if the device was removed indicate that to the caller */
+ if (ddev->is_device_removed)
+ return -ENODEV;
+
+ return 0;
+}
+
+/**
+ * dal_send_error_access_denied - put 'access denied' message
+ * into the client read queue. In-band error message.
+ *
+ * @dc: dal client
+ * @cmd: rejected message header
+ *
+ * Return: 0 on success
+ * -ENOMEM when client read queue is full
+ *
+ * Locking: called under "ddev->write_lock" lock
+ */
+static int dal_send_error_access_denied(struct dal_client *dc, const void *cmd)
+{
+ struct dal_device *ddev = dc->ddev;
+ struct bh_response_header res;
+ size_t len;
+ int ret;
+
+ mutex_lock(&ddev->context_lock);
+
+ bh_prep_access_denied_response(cmd, &res);
+ len = sizeof(res);
+
+ if (kfifo_in(&dc->read_queue, &len, sizeof(len)) != sizeof(len)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (kfifo_in(&dc->read_queue, &res, len) != len) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ ret = 0;
+
+out:
+ mutex_unlock(&ddev->context_lock);
+ return ret;
+}
+
+/**
+ * dal_is_kdi_msg - check if sequence is in kernel space sequence range
+ *
+ * Each interface (kernel space and user space) has different range of
+ * sequence number. This function checks if given number is in kernel space
+ * sequence range
+ *
+ * @hdr: command header
+ *
+ * Return: true when seq fits kernel space intf
+ * false when seq fits user space intf
+ */
+static bool dal_is_kdi_msg(const struct bh_command_header *hdr)
+{
+ return hdr->seq >= MSG_SEQ_START_NUMBER;
+}
+
+/**
+ * dal_validate_seq - validate that message sequence fits client interface,
+ * prevent user space client to use kernel space sequence
+ *
+ * @hdr: command header
+ * @count: message size
+ * @ctx: context - dal client
+ *
+ * Return: 0 when sequence match
+ * -EPERM when user space client uses kernel space sequence
+ *
+ * Locking: called under "ddev->write_lock" lock
+ */
+static int dal_validate_seq(const struct bh_command_header *hdr,
+ size_t count, void *ctx)
+{
+ struct dal_client *dc = ctx;
+
+ if (dc->intf != DAL_INTF_KDI && dal_is_kdi_msg(hdr))
+ return -EPERM;
+
+ return 0;
+}
+
+/*
+ * dal_write_filter_tbl - filter functions to validate that the message
+ * is being sent is valid, and the user client
+ * has the permissions to send it
+ */
+static const bh_filter_func dal_write_filter_tbl[] = {
+ dal_validate_seq,
+ NULL,
+};
+
+/**
+ * dal_write - write message to DAL FW over mei
+ *
+ * @dc: dal client
+ * @buf: the message.
+ * @count: message size
+ * @seq: message sequence (if client is kernel space client)
+ *
+ * Return: >=0 data length on success
+ * <0 on failure
+ */
+ssize_t dal_write(struct dal_client *dc, const void *buf, size_t count, u64 seq)
+{
+ struct dal_device *ddev = dc->ddev;
+ struct device *dev;
+ ssize_t wr;
+ ssize_t ret;
+ enum dal_intf intf = dc->intf;
+
+ dev = &ddev->dev;
+
+ dev_dbg(dev, "client interface %d\n", intf);
+ dal_dc_print(dev, dc);
+
+ /* lock for adding new client that want to write to fifo */
+ mutex_lock(&ddev->write_lock);
+ /* update client on latest msg seq number*/
+ dc->seq = seq;
+ dev_dbg(dev, "current_write_client seq = %llu\n", dc->seq);
+
+ /* put dc in the writers queue if not already set */
+ if (list_first_entry_or_null(&ddev->writers,
+ struct dal_client, wrlink) != dc) {
+ /* adding client to write queue - this is the first fragment */
+ const struct bh_command_header *hdr;
+
+ hdr = bh_msg_cmd_hdr(buf, count);
+ if (!hdr) {
+ dev_dbg(dev, "expected cmd hdr at first fragment\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = bh_filter_hdr(hdr, count, dc, dal_write_filter_tbl);
+ if (ret == -EPERM) {
+ ret = dal_send_error_access_denied(dc, buf);
+ ret = ret ? ret : count;
+ }
+ if (ret)
+ goto out;
+
+ dc->bytes_sent_to_fw = 0;
+ dc->expected_msg_size_to_fw = hdr->h.length;
+
+ list_add_tail(&dc->wrlink, &ddev->writers);
+ }
+
+ /* wait for current writer to finish his write session */
+ mutex_unlock(&ddev->write_lock);
+ ret = dal_wait_for_write(ddev, dc);
+ mutex_lock(&ddev->write_lock);
+ if (ret < 0)
+ goto out;
+
+ dev_dbg(dev, "before mei_cldev_send - client type %d\n", intf);
+
+ /* send msg via MEI */
+ wr = mei_cldev_send(ddev->cldev, (void *)buf, count);
+ if (wr != count) {
+ /* ENODEV can be issued upon internal reset */
+ if (wr != -ENODEV) {
+ dev_err(dev, "mei_cl_send() failed, write_bytes != count (%zd != %zu)\n",
+ wr, count);
+ ret = -EFAULT;
+ goto out;
+ }
+ /* if DAL FW client is disconnected, try to reconnect */
+ dev_dbg(dev, "try to reconnect to DAL FW cl\n");
+ ret = mei_cldev_disable(ddev->cldev);
+ if (ret < 0) {
+ dev_err(&ddev->cldev->dev, "failed to disable mei cl [%zd]\n",
+ ret);
+ goto out;
+ }
+ ret = dal_mei_enable(ddev);
+ if (ret < 0)
+ dev_err(&ddev->cldev->dev, "failed to reconnect to DAL FW client [%zd]\n",
+ ret);
+ else
+ ret = -EAGAIN;
+
+ goto out;
+ }
+
+ dev_dbg(dev, "wrote %zu bytes to fw - client type %d\n", wr, intf);
+
+ /* update client byte sent */
+ dc->bytes_sent_to_fw += count;
+ ret = wr;
+
+ if (dc->bytes_sent_to_fw != dc->expected_msg_size_to_fw) {
+ dev_dbg(dev, "expecting to write more data to DAL FW - client type %d\n",
+ intf);
+ goto write_more;
+ }
+out:
+ /* remove current dc from the queue */
+ list_del_init(&dc->wrlink);
+ if (list_empty(&ddev->writers))
+ wake_up_interruptible(&ddev->wq);
+
+write_more:
+ mutex_unlock(&ddev->write_lock);
+ return ret;
+}
+
+/**
+ * dal_wait_for_read - wait until the client (dc) will have data
+ * in his read queue
+ *
+ * @dc: dal client
+ *
+ * Return: 0 on success
+ * -ENODEV when the device was removed
+ * -ERESTARTSYS: when interrupted.
+ */
+int dal_wait_for_read(struct dal_client *dc)
+{
+ struct dal_device *ddev = dc->ddev;
+ struct device *dev = &ddev->dev;
+ int ret;
+
+ dal_dc_print(dev, dc);
+
+ dev_dbg(dev, "%s - client type %d kfifo status %d\n", __func__,
+ dc->intf, kfifo_is_empty(&dc->read_queue));
+
+ /* wait until there is data in the read_queue */
+ ret = wait_event_interruptible(ddev->wq,
+ !kfifo_is_empty(&dc->read_queue) ||
+ ddev->is_device_removed);
+
+ dev_dbg(dev, "%s - client type %d status %d\n", __func__,
+ dc->intf, ret);
+
+ /* FIXME: use reference counter */
+ if (ddev->is_device_removed) {
+ dev_dbg(dev, "woke up, device was removed\n");
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+/**
+ * dal_dc_destroy - destroy dal client
+ *
+ * @ddev: dal device
+ * @intf: device interface
+ *
+ * Locking: called under "ddev->context_lock" lock
+ */
+void dal_dc_destroy(struct dal_device *ddev, enum dal_intf intf)
+{
+ struct dal_client *dc;
+
+ dc = ddev->clients[intf];
+ if (!dc)
+ return;
+
+ kfifo_free(&dc->read_queue);
+ kfree(dc);
+ ddev->clients[intf] = NULL;
+}
+
+/**
+ * dal_dc_setup - initialize dal client
+ *
+ * @ddev: dal device
+ * @intf: device interface
+ *
+ * Return: 0 on success
+ * -EINVAL when client is already initialized
+ * -ENOMEM on memory allocation failure
+ */
+int dal_dc_setup(struct dal_device *ddev, enum dal_intf intf)
+{
+ int ret;
+ struct dal_client *dc;
+ size_t readq_sz;
+
+ if (ddev->clients[intf]) {
+ dev_err(&ddev->dev, "client already set\n");
+ return -EINVAL;
+ }
+
+ dc = kzalloc(sizeof(*dc), GFP_KERNEL);
+ if (!dc)
+ return -ENOMEM;
+
+ /* each buffer contains data and length */
+ readq_sz = (DAL_MAX_BUFFER_SIZE + sizeof(ddev->bh_fw_msg.len)) *
+ DAL_BUFFERS_PER_CLIENT;
+ ret = kfifo_alloc(&dc->read_queue, readq_sz, GFP_KERNEL);
+ if (ret) {
+ kfree(dc);
+ return ret;
+ }
+
+ dc->intf = intf;
+ dc->ddev = ddev;
+ INIT_LIST_HEAD(&dc->wrlink);
+ ddev->clients[intf] = dc;
+ return 0;
+}
+
+/**
+ * dal_dev_match - match function to find dal device
+ *
+ * Used to get dal device from dal_class by device id
+ *
+ * @dev: device structure
+ * @data: the device id
+ *
+ * Return: 1 on match
+ * 0 on mismatch
+ */
+static int dal_dev_match(struct device *dev, const void *data)
+{
+ struct dal_device *ddev;
+ const enum dal_dev_type *device_id =
+ (enum dal_dev_type *)data;
+
+ ddev = container_of(dev, struct dal_device, dev);
+
+ return ddev->device_id == *device_id;
+}
+
+/**
+ * dal_find_dev - get dal device from dal_class by device id
+ *
+ * @device_id: device id
+ *
+ * Return: pointer to the requested device
+ * NULL if the device wasn't found
+ */
+struct device *dal_find_dev(enum dal_dev_type device_id)
+{
+ return class_find_device(dal_class, NULL, &device_id, dal_dev_match);
+}
+
+/**
+ * dal_remove - dal remove callback in mei_cl_driver
+ *
+ * @cldev: mei client device
+ *
+ * Return: 0
+ */
+static int dal_remove(struct mei_cl_device *cldev)
+{
+ struct dal_device *ddev = mei_cldev_get_drvdata(cldev);
+
+ if (!ddev)
+ return 0;
+
+ ddev->is_device_removed = 1;
+ /* make sure the above is set */
+ smp_mb();
+ /* wakeup write waiters so we can unload */
+ if (waitqueue_active(&ddev->wq))
+ wake_up_interruptible(&ddev->wq);
+
+ mei_cldev_set_drvdata(cldev, NULL);
+
+ device_unregister(&ddev->dev);
+
+ mei_cldev_disable(cldev);
+
+ return 0;
+}
+
+/**
+ * dal_device_release - dal release callback in dev structure
+ *
+ * @dev: device structure
+ */
+static void dal_device_release(struct device *dev)
+{
+ struct dal_device *ddev = to_dal_device(dev);
+
+ kfree(ddev->bh_fw_msg.msg);
+ kfree(ddev);
+}
+
+/**
+ * dal_probe - dal probe callback in mei_cl_driver
+ *
+ * @cldev: mei client device
+ * @id: mei client device id
+ *
+ * Return: 0 on success
+ * <0 on failure
+ */
+static int dal_probe(struct mei_cl_device *cldev,
+ const struct mei_cl_device_id *id)
+{
+ struct dal_device *ddev;
+ struct device *pdev = &cldev->dev;
+ int ret;
+
+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
+ if (!ddev)
+ return -ENOMEM;
+
+ /* initialize the mutex and wait queue */
+ mutex_init(&ddev->context_lock);
+ mutex_init(&ddev->write_lock);
+ init_waitqueue_head(&ddev->wq);
+ INIT_LIST_HEAD(&ddev->writers);
+ ddev->cldev = cldev;
+ ddev->device_id = id->driver_info;
+
+ ddev->dev.parent = pdev;
+ ddev->dev.class = dal_class;
+ ddev->dev.release = dal_device_release;
+ dev_set_name(&ddev->dev, "dal%d", ddev->device_id);
+
+ ret = device_register(&ddev->dev);
+ if (ret) {
+ dev_err(pdev, "unable to register device\n");
+ goto err;
+ }
+
+ ddev->bh_fw_msg.msg = kzalloc(DAL_MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!ddev->bh_fw_msg.msg) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ret = dal_mei_enable(ddev);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+
+err:
+ device_unregister(&ddev->dev);
+ return ret;
+}
+
+/* DAL FW HECI client GUIDs */
+#define IVM_UUID UUID_LE(0x3c4852d6, 0xd47b, 0x4f46, \
+ 0xb0, 0x5e, 0xb5, 0xed, 0xc1, 0xaa, 0x44, 0x0e)
+#define SDM_UUID UUID_LE(0xdba4d603, 0xd7ed, 0x4931, \
+ 0x88, 0x23, 0x17, 0xad, 0x58, 0x57, 0x05, 0xd5)
+#define RTM_UUID UUID_LE(0x5565a099, 0x7fe2, 0x45c1, \
+ 0xa2, 0x2b, 0xd7, 0xe9, 0xdf, 0xea, 0x9a, 0x2e)
+
+#define DAL_DEV_ID(__uuid, __device_type) \
+ {.uuid = __uuid, \
+ .version = MEI_CL_VERSION_ANY, \
+ .driver_info = __device_type}
+
+/*
+ * dal_device_id - ids of dal FW devices,
+ * for all 3 dal FW clients (IVM, SDM and RTM)
+ */
+static const struct mei_cl_device_id dal_device_id[] = {
+ DAL_DEV_ID(IVM_UUID, DAL_MEI_DEVICE_IVM),
+ DAL_DEV_ID(SDM_UUID, DAL_MEI_DEVICE_SDM),
+ DAL_DEV_ID(RTM_UUID, DAL_MEI_DEVICE_RTM),
+ /* required last entry */
+ { }
+};
+MODULE_DEVICE_TABLE(mei, dal_device_id);
+
+static struct mei_cl_driver dal_driver = {
+ .id_table = dal_device_id,
+ .name = KBUILD_MODNAME,
+
+ .probe = dal_probe,
+ .remove = dal_remove,
+};
+
+/**
+ * mei_dal_exit - module exit function
+ */
+static void __exit mei_dal_exit(void)
+{
+ mei_cldev_driver_unregister(&dal_driver);
+
+ class_destroy(dal_class);
+}
+
+/**
+ * mei_dal_init - module init function
+ *
+ * Return: 0 on success
+ * <0 on failure
+ */
+static int __init mei_dal_init(void)
+{
+ int ret;
+
+ dal_class = class_create(THIS_MODULE, "dal");
+ if (IS_ERR(dal_class)) {
+ pr_err("couldn't create class\n");
+ return PTR_ERR(dal_class);
+ }
+
+ ret = mei_cldev_driver_register(&dal_driver);
+ if (ret < 0) {
+ pr_err("mei_cl_driver_register failed with status = %d\n", ret);
+ goto err_class;
+ }
+
+ return 0;
+
+err_class:
+ class_destroy(dal_class);
+ return ret;
+}
+
+module_init(mei_dal_init);
+module_exit(mei_dal_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel(R) MEI Dynamic Application Loader (DAL)");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mei/dal/dal_dev.h b/drivers/misc/mei/dal/dal_dev.h
new file mode 100644
index 000000000000..3cad5a85c5de
--- /dev/null
+++ b/drivers/misc/mei/dal/dal_dev.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */
+/*
+ * Copyright(c) 2016 - 2018 Intel Corporation. All rights reserved.
+ */
+
+#ifndef _DAL_KDI_H_
+#define _DAL_KDI_H_
+
+#include <linux/types.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/kfifo.h>
+
+#define DAL_MAX_BUFFER_SIZE 4096
+#define DAL_BUFFERS_PER_CLIENT 10
+
+#define DAL_CLIENTS_PER_DEVICE 2
+
+extern struct class *dal_class;
+
+/**
+ * enum dal_intf - dal interface type
+ *
+ * @DAL_INTF_KDI: (kdi) kernel space interface
+ * @DAL_INTF_CDEV: char device interface
+ */
+enum dal_intf {
+ DAL_INTF_KDI,
+ DAL_INTF_CDEV,
+};
+
+/**
+ * enum dal_dev_type - devices that are exposed to userspace
+ *
+ * @DAL_MEI_DEVICE_IVM: IVM - Intel/Issuer Virtual Machine
+ * @DAL_MEI_DEVICE_SDM: SDM - Security Domain Manager
+ * @DAL_MEI_DEVICE_RTM: RTM - Run Time Manager (Launcher)
+ *
+ * @DAL_MEI_DEVICE_MAX: max dal device type
+ */
+enum dal_dev_type {
+ DAL_MEI_DEVICE_IVM,
+ DAL_MEI_DEVICE_SDM,
+ DAL_MEI_DEVICE_RTM,
+
+ DAL_MEI_DEVICE_MAX
+};
+
+/**
+ * struct dal_client - host client
+ *
+ * @ddev: dal parent device
+ * @wrlink: link in the writers list
+ * @read_queue: queue of received messages from DAL FW
+ * @intf: client interface - user space or kernel space
+ *
+ * @seq: the sequence number of the last message sent (in kernel space API only)
+ * When a message is received from DAL FW, we use this sequence number
+ * to decide which client should get the message. If the sequence
+ * number of the message is equals to the kernel space sequence number,
+ * the kernel space client should get the message.
+ * Otherwise the user space client will get it.
+ * @expected_msg_size_from_fw: the expected msg size from DALFW
+ * @expected_msg_size_to_fw: the expected msg size that will be sent to DAL FW
+ * @bytes_rcvd_from_fw: number of bytes that were received from DAL FW
+ * @bytes_sent_to_fw: number of bytes that were sent to DAL FW
+ */
+struct dal_client {
+ struct dal_device *ddev;
+ struct list_head wrlink;
+ struct kfifo read_queue;
+ enum dal_intf intf;
+
+ u64 seq;
+ u32 expected_msg_size_from_fw;
+ u32 expected_msg_size_to_fw;
+ u32 bytes_rcvd_from_fw;
+ u32 bytes_sent_to_fw;
+};
+
+/**
+ * struct dal_bh_msg - msg received from DAL FW.
+ *
+ * @len: message length
+ * @msg: message buffer
+ */
+struct dal_bh_msg {
+ size_t len;
+ char *msg;
+};
+
+/**
+ * struct dal_device - DAL private device struct.
+ * each DAL device has a context (i.e IVM, SDM, RTM)
+ *
+ * @dev: device on a bus
+ * @cdev: character device
+ * @status: dal device status
+ *
+ * @context_lock: big device lock
+ * @write_lock: lock over write list
+ * @wq: dal clients wait queue. When client wants to send or receive message,
+ * he waits in this queue until he is ready
+ * @writers: write pending list
+ * @clients: clients on this device (userspace and kernel space)
+ * @bh_fw_msg: message which was received from DAL FW
+ * @current_read_client: current reading client (which receives message from
+ * DAL FW)
+ *
+ * @cldev: the MEI CL device which corresponds to a single DAL FW HECI client
+ *
+ * @is_device_removed: device removed flag
+ *
+ * @device_id: DAL device type
+ */
+struct dal_device {
+ struct device dev;
+ struct cdev cdev;
+#define DAL_DEV_OPENED 0
+ unsigned long status;
+
+ struct mutex context_lock; /* device lock */
+ struct mutex write_lock; /* write lock */
+ wait_queue_head_t wq;
+ struct list_head writers;
+ struct dal_client *clients[DAL_CLIENTS_PER_DEVICE];
+ struct dal_bh_msg bh_fw_msg;
+ struct dal_client *current_read_client;
+
+ struct mei_cl_device *cldev;
+
+ unsigned int is_device_removed :1;
+
+ unsigned int device_id;
+};
+
+#define to_dal_device(d) container_of(d, struct dal_device, dev)
+
+ssize_t dal_write(struct dal_client *dc,
+ const void *buf, size_t count, u64 seq);
+int dal_wait_for_read(struct dal_client *dc);
+
+struct device *dal_find_dev(enum dal_dev_type device_id);
+
+void dal_dc_print(struct device *dev, struct dal_client *dc);
+int dal_dc_setup(struct dal_device *ddev, enum dal_intf intf);
+void dal_dc_destroy(struct dal_device *ddev, enum dal_intf intf);
+
+#endif /* _DAL_KDI_H_ */
--
https://clearlinux.org