2020-08-18 12:11:02

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 00/13] mei: add support for virtual tags

Add support for mei virtualization for ACRN but might be used for other purposes such as sandboxing.
ACRN is an open-source hypervisor maintained by The Linux Foundation.
The support for ACRN was added in kernel in v5.3.
The patches were part of the ACRN service OS kernel for a while.
https://github.com/projectacrn/acrn-kernel

Only selected platforms, notable for IoT usage, such as APL-I, have
firmware that supports vtags.


Alexander Usyskin (10):
mei: hbm: add capabilities message
mei: restrict vtag support to hbm version 2.2
mei: add vtag support bit in client properties
mei: bump hbm version to 2.2
mei: add a spin lock to protect rd_completed queue
mei: add a vtag map for each client
mei: handle tx queue flushing for vtag connections
mei: bus: use zero vtag for bus clients.
mei: bus: unconditionally enable clients with vtag support
mei: add connect with vtag ioctl

Tomas Winkler (3):
mei: add support for mei extended header.
mei: docs: add vtag ioctl documentation
mei: virtio: virtualization frontend driver

Documentation/ABI/testing/sysfs-bus-mei | 7 +
Documentation/driver-api/mei/mei.rst | 37 +
drivers/misc/mei/Kconfig | 10 +
drivers/misc/mei/Makefile | 3 +
drivers/misc/mei/bus-fixup.c | 12 +
drivers/misc/mei/bus.c | 89 ++-
drivers/misc/mei/client.c | 423 ++++++++++--
drivers/misc/mei/client.h | 22 +-
drivers/misc/mei/debugfs.c | 9 +-
drivers/misc/mei/hbm.c | 101 ++-
drivers/misc/mei/hbm.h | 2 +
drivers/misc/mei/hw-virtio.c | 874 ++++++++++++++++++++++++
drivers/misc/mei/hw.h | 150 +++-
drivers/misc/mei/interrupt.c | 113 ++-
drivers/misc/mei/main.c | 284 +++++++-
drivers/misc/mei/mei_dev.h | 34 +-
include/uapi/linux/mei.h | 49 ++
17 files changed, 2084 insertions(+), 135 deletions(-)
create mode 100644 drivers/misc/mei/hw-virtio.c

--
2.25.4


2020-08-18 12:11:26

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 01/13] mei: hbm: add capabilities message

From: Alexander Usyskin <[email protected]>

The new capabilities command in HBM version 2.2 allows
performing capabilities handshake between the firmware
and the host driver. The driver requests a capability
by setting the appropriate bit in 24bit wide bitmask and
the fw responses with the bit set providing the requested
capability is supported.

Bump copyright year in affected files.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/debugfs.c | 1 +
drivers/misc/mei/hbm.c | 72 ++++++++++++++++++++++++++++++++++++++
drivers/misc/mei/hbm.h | 2 ++
drivers/misc/mei/hw.h | 31 ++++++++++++++++
drivers/misc/mei/mei_dev.h | 2 ++
5 files changed, 108 insertions(+)

diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c
index a26c716c61a1..72ee572ad9b4 100644
--- a/drivers/misc/mei/debugfs.c
+++ b/drivers/misc/mei/debugfs.c
@@ -103,6 +103,7 @@ static int mei_dbgfs_devstate_show(struct seq_file *m, void *unused)
seq_printf(m, "\tFA: %01d\n", dev->hbm_f_fa_supported);
seq_printf(m, "\tOS: %01d\n", dev->hbm_f_os_supported);
seq_printf(m, "\tDR: %01d\n", dev->hbm_f_dr_supported);
+ seq_printf(m, "\tCAP: %01d\n", dev->hbm_f_cap_supported);
}

seq_printf(m, "pg: %s, %s\n",
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index 308caee86920..3a227d9363d5 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -325,6 +325,37 @@ static int mei_hbm_dma_setup_req(struct mei_device *dev)
return 0;
}

+/**
+ * mei_hbm_capabilities_req - request capabilities
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success and < 0 on failure
+ */
+static int mei_hbm_capabilities_req(struct mei_device *dev)
+{
+ struct mei_msg_hdr mei_hdr;
+ struct hbm_capability_request req;
+ int ret;
+
+ mei_hbm_hdr(&mei_hdr, sizeof(req));
+
+ memset(&req, 0, sizeof(req));
+ req.hbm_cmd = MEI_HBM_CAPABILITIES_REQ_CMD;
+
+ ret = mei_hbm_write_message(dev, &mei_hdr, &req);
+ if (ret) {
+ dev_err(dev->dev,
+ "capabilities request write failed: ret = %d.\n", ret);
+ return ret;
+ }
+
+ dev->hbm_state = MEI_HBM_CAP_SETUP;
+ dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT;
+ mei_schedule_stall_timer(dev);
+ return 0;
+}
+
/**
* mei_hbm_enum_clients_req - sends enumeration client request message.
*
@@ -1042,6 +1073,12 @@ static void mei_hbm_config_features(struct mei_device *dev)
(dev->version.major_version == HBM_MAJOR_VERSION_DR &&
dev->version.minor_version >= HBM_MINOR_VERSION_DR))
dev->hbm_f_dr_supported = 1;
+
+ dev->hbm_f_cap_supported = 0;
+ if (dev->version.major_version > HBM_MAJOR_VERSION_CAP ||
+ (dev->version.major_version == HBM_MAJOR_VERSION_CAP &&
+ dev->version.minor_version >= HBM_MINOR_VERSION_CAP))
+ dev->hbm_f_cap_supported = 1;
}

/**
@@ -1138,6 +1175,13 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
return -EPROTO;
}

+ if (dev->hbm_f_cap_supported) {
+ if (mei_hbm_capabilities_req(dev))
+ return -EIO;
+ wake_up(&dev->wait_hbm_start);
+ break;
+ }
+
if (dev->hbm_f_dr_supported) {
if (mei_dmam_ring_alloc(dev))
dev_info(dev->dev, "running w/o dma ring\n");
@@ -1159,6 +1203,34 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
wake_up(&dev->wait_hbm_start);
break;

+ case MEI_HBM_CAPABILITIES_RES_CMD:
+ dev_dbg(dev->dev, "hbm: capabilities response: message received.\n");
+
+ dev->init_clients_timer = 0;
+
+ if (dev->hbm_state != MEI_HBM_CAP_SETUP) {
+ dev_err(dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n",
+ dev->dev_state, dev->hbm_state);
+ return -EPROTO;
+ }
+
+ if (dev->hbm_f_dr_supported) {
+ if (mei_dmam_ring_alloc(dev))
+ dev_info(dev->dev, "running w/o dma ring\n");
+ if (mei_dma_ring_is_allocated(dev)) {
+ if (mei_hbm_dma_setup_req(dev))
+ return -EIO;
+ break;
+ }
+ }
+
+ dev->hbm_f_dr_supported = 0;
+ mei_dmam_ring_free(dev);
+
+ if (mei_hbm_enum_clients_req(dev))
+ return -EIO;
+ break;
+
case MEI_HBM_DMA_SETUP_RES_CMD:
dev_dbg(dev->dev, "hbm: dma setup response: message received.\n");

diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h
index 5aa58cffdd2e..4d95e38e4ddf 100644
--- a/drivers/misc/mei/hbm.h
+++ b/drivers/misc/mei/hbm.h
@@ -16,6 +16,7 @@ struct mei_cl;
*
* @MEI_HBM_IDLE : protocol not started
* @MEI_HBM_STARTING : start request message was sent
+ * @MEI_HBM_CAP_SETUP : capabilities request message was sent
* @MEI_HBM_DR_SETUP : dma ring setup request message was sent
* @MEI_HBM_ENUM_CLIENTS : enumeration request was sent
* @MEI_HBM_CLIENT_PROPERTIES : acquiring clients properties
@@ -25,6 +26,7 @@ struct mei_cl;
enum mei_hbm_state {
MEI_HBM_IDLE = 0,
MEI_HBM_STARTING,
+ MEI_HBM_CAP_SETUP,
MEI_HBM_DR_SETUP,
MEI_HBM_ENUM_CLIENTS,
MEI_HBM_CLIENT_PROPERTIES,
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index 26fa92cb7f7a..539d89ba1c61 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -76,6 +76,12 @@
#define HBM_MINOR_VERSION_DR 1
#define HBM_MAJOR_VERSION_DR 2

+/*
+ * MEI version with capabilities message support
+ */
+#define HBM_MINOR_VERSION_CAP 2
+#define HBM_MAJOR_VERSION_CAP 2
+
/* Host bus message command opcode */
#define MEI_HBM_CMD_OP_MSK 0x7f
/* Host bus message command RESPONSE */
@@ -121,6 +127,9 @@
#define MEI_HBM_DMA_SETUP_REQ_CMD 0x12
#define MEI_HBM_DMA_SETUP_RES_CMD 0x92

+#define MEI_HBM_CAPABILITIES_REQ_CMD 0x13
+#define MEI_HBM_CAPABILITIES_RES_CMD 0x93
+
/*
* MEI Stop Reason
* used by hbm_host_stop_request.reason
@@ -533,4 +542,26 @@ struct hbm_dma_ring_ctrl {
u32 reserved4;
} __packed;

+/**
+ * struct hbm_capability_request - capability request from host to fw
+ *
+ * @hbm_cmd : bus message command header
+ * @capability_requested: bitmask of capabilities requested by host
+ */
+struct hbm_capability_request {
+ u8 hbm_cmd;
+ u8 capability_requested[3];
+} __packed;
+
+/**
+ * struct hbm_capability_response - capability response from fw to host
+ *
+ * @hbm_cmd : bus message command header
+ * @capability_granted: bitmask of capabilities granted by FW
+ */
+struct hbm_capability_response {
+ u8 hbm_cmd;
+ u8 capability_granted[3];
+} __packed;
+
#endif
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index d3a4f54c0ae7..e18af7dfd9ff 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -426,6 +426,7 @@ struct mei_fw_version {
* @hbm_f_ie_supported : hbm feature immediate reply to enum request
* @hbm_f_os_supported : hbm feature support OS ver message
* @hbm_f_dr_supported : hbm feature dma ring supported
+ * @hbm_f_cap_supported : hbm feature capabilities message supported
*
* @fw_ver : FW versions
*
@@ -510,6 +511,7 @@ struct mei_device {
unsigned int hbm_f_ie_supported:1;
unsigned int hbm_f_os_supported:1;
unsigned int hbm_f_dr_supported:1;
+ unsigned int hbm_f_cap_supported:1;

struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS];

--
2.25.4

2020-08-18 12:11:38

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 05/13] mei: bump hbm version to 2.2

From: Alexander Usyskin <[email protected]>

Bump HBM version to 2.2 to indicate vtag support.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/hw.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index c3c628132b50..8bac86c4d86b 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -25,7 +25,7 @@
/*
* MEI Version
*/
-#define HBM_MINOR_VERSION 1
+#define HBM_MINOR_VERSION 2
#define HBM_MAJOR_VERSION 2

/*
--
2.25.4

2020-08-18 12:11:56

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 07/13] mei: add a vtag map for each client

From: Alexander Usyskin <[email protected]>

Vtag map is a list of tuples of vtag and file pointer (struct
mei_cl_vtag) associated with a particular me host client.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/client.c | 168 ++++++++++++++++++++++++++++++++++++-
drivers/misc/mei/client.h | 3 +
drivers/misc/mei/main.c | 68 ++++++++++++++-
drivers/misc/mei/mei_dev.h | 17 ++++
4 files changed, 251 insertions(+), 5 deletions(-)

diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 276021f99666..3904fce18261 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -354,6 +354,27 @@ static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb)
mei_io_cb_free(cb);
}

+/**
+ * mei_cl_set_read_by_fp - set pending_read flag to vtag struct for given fp
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * @cl: mei client
+ * @fp: pointer to file structure
+ */
+static void mei_cl_set_read_by_fp(const struct mei_cl *cl,
+ const struct file *fp)
+{
+ struct mei_cl_vtag *cl_vtag;
+
+ list_for_each_entry(cl_vtag, &cl->vtag_map, list) {
+ if (cl_vtag->fp == fp) {
+ cl_vtag->pending_read = true;
+ return;
+ }
+ }
+}
+
/**
* mei_io_cb_init - allocate and initialize io callback
*
@@ -435,6 +456,19 @@ static void mei_io_list_free_fp(struct list_head *head, const struct file *fp)
mei_io_cb_free(cb);
}

+/**
+ * mei_cl_free_pending - free pending cb
+ *
+ * @cl: host client
+ */
+static void mei_cl_free_pending(struct mei_cl *cl)
+{
+ struct mei_cl_cb *cb;
+
+ cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);
+ mei_io_cb_free(cb);
+}
+
/**
* mei_cl_alloc_cb - a convenient wrapper for allocating read cb
*
@@ -544,7 +578,9 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl);
mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl);
mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
- mei_io_list_free_fp(&cl->rd_pending, fp);
+ /* free pending cb only in final flush */
+ if (!fp)
+ mei_cl_free_pending(cl);
spin_lock(&cl->rd_completed_lock);
mei_io_list_free_fp(&cl->rd_completed, fp);
spin_unlock(&cl->rd_completed_lock);
@@ -565,6 +601,7 @@ static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
init_waitqueue_head(&cl->rx_wait);
init_waitqueue_head(&cl->tx_wait);
init_waitqueue_head(&cl->ev_wait);
+ INIT_LIST_HEAD(&cl->vtag_map);
spin_lock_init(&cl->rd_completed_lock);
INIT_LIST_HEAD(&cl->rd_completed);
INIT_LIST_HEAD(&cl->rd_pending);
@@ -1237,8 +1274,117 @@ static int mei_cl_tx_flow_ctrl_creds_reduce(struct mei_cl *cl)
return 0;
}

+/**
+ * mei_cl_vtag_alloc - allocate and fill the vtag structure
+ *
+ * @fp: pointer to file structure
+ * @vtag: vm tag
+ *
+ * Return:
+ * * Pointer to allocated struct - on success
+ * * ERR_PTR(-ENOMEM) on memory allocation failure
+ */
+struct mei_cl_vtag *mei_cl_vtag_alloc(struct file *fp, u8 vtag)
+{
+ struct mei_cl_vtag *cl_vtag;
+
+ cl_vtag = kzalloc(sizeof(*cl_vtag), GFP_KERNEL);
+ if (!cl_vtag)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&cl_vtag->list);
+ cl_vtag->vtag = vtag;
+ cl_vtag->fp = fp;
+
+ return cl_vtag;
+}
+
+/**
+ * mei_cl_fp_by_vtag - obtain the file pointer by vtag
+ *
+ * @cl: host client
+ * @vtag: vm tag
+ *
+ * Return:
+ * * A file pointer - on success
+ * * ERR_PTR(-ENOENT) if vtag is not found in the client vtag list
+ */
+const struct file *mei_cl_fp_by_vtag(const struct mei_cl *cl, u8 vtag)
+{
+ struct mei_cl_vtag *vtag_l;
+
+ list_for_each_entry(vtag_l, &cl->vtag_map, list)
+ if (vtag_l->vtag == vtag)
+ return vtag_l->fp;
+
+ return ERR_PTR(-ENOENT);
+}
+
+/**
+ * mei_cl_reset_read_by_vtag - reset pending_read flag by given vtag
+ *
+ * @cl: host client
+ * @vtag: vm tag
+ */
+static void mei_cl_reset_read_by_vtag(const struct mei_cl *cl, u8 vtag)
+{
+ struct mei_cl_vtag *vtag_l;
+
+ list_for_each_entry(vtag_l, &cl->vtag_map, list) {
+ if (vtag_l->vtag == vtag) {
+ vtag_l->pending_read = false;
+ break;
+ }
+ }
+}
+
+/**
+ * mei_cl_read_vtag_add_fc - add flow control for next pending reader
+ * in the vtag list
+ *
+ * @cl: host client
+ */
+static void mei_cl_read_vtag_add_fc(struct mei_cl *cl)
+{
+ struct mei_cl_vtag *cl_vtag;
+
+ list_for_each_entry(cl_vtag, &cl->vtag_map, list) {
+ if (cl_vtag->pending_read) {
+ if (mei_cl_enqueue_ctrl_wr_cb(cl,
+ mei_cl_mtu(cl),
+ MEI_FOP_READ,
+ cl_vtag->fp))
+ cl->rx_flow_ctrl_creds++;
+ break;
+ }
+ }
+}
+
+/**
+ * mei_cl_vt_support_check - check if client support vtags
+ *
+ * @cl: host client
+ *
+ * Return:
+ * * 0 - supported, or not connected at all
+ * * -EOPNOTSUPP - vtags are not supported by client
+ */
+int mei_cl_vt_support_check(const struct mei_cl *cl)
+{
+ struct mei_device *dev = cl->dev;
+
+ if (!dev->hbm_f_vt_supported)
+ return -EOPNOTSUPP;
+
+ if (!cl->me_cl)
+ return 0;
+
+ return cl->me_cl->props.vt_supported ? 0 : -EOPNOTSUPP;
+}
+
/**
* mei_cl_add_rd_completed - add read completed callback to list with lock
+ * and vtag check
*
* @cl: host client
* @cb: callback block
@@ -1246,6 +1392,20 @@ static int mei_cl_tx_flow_ctrl_creds_reduce(struct mei_cl *cl)
*/
void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb)
{
+ const struct file *fp;
+
+ if (!mei_cl_vt_support_check(cl)) {
+ fp = mei_cl_fp_by_vtag(cl, cb->vtag);
+ if (IS_ERR(fp)) {
+ /* client already disconnected, discarding */
+ mei_io_cb_free(cb);
+ return;
+ }
+ cb->fp = fp;
+ mei_cl_reset_read_by_vtag(cl, cb->vtag);
+ mei_cl_read_vtag_add_fc(cl);
+ }
+
spin_lock(&cl->rd_completed_lock);
list_add_tail(&cb->list, &cl->rd_completed);
spin_unlock(&cl->rd_completed_lock);
@@ -1520,13 +1680,17 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
return 0;

/* HW currently supports only one pending read */
- if (cl->rx_flow_ctrl_creds)
+ if (cl->rx_flow_ctrl_creds) {
+ mei_cl_set_read_by_fp(cl, fp);
return -EBUSY;
+ }

cb = mei_cl_enqueue_ctrl_wr_cb(cl, length, MEI_FOP_READ, fp);
if (!cb)
return -ENOMEM;

+ mei_cl_set_read_by_fp(cl, fp);
+
rets = pm_runtime_get(dev->dev);
if (rets < 0 && rets != -EINPROGRESS) {
pm_runtime_put_noidle(dev->dev);
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index bd57c64f6c1a..64143d4ec758 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -146,6 +146,9 @@ struct mei_cl_cb *mei_cl_enqueue_ctrl_wr_cb(struct mei_cl *cl, size_t length,
const struct file *fp);
int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp);

+struct mei_cl_vtag *mei_cl_vtag_alloc(struct file *fp, u8 vtag);
+const struct file *mei_cl_fp_by_vtag(const struct mei_cl *cl, u8 vtag);
+int mei_cl_vt_support_check(const struct mei_cl *cl);
/*
* MEI input output function prototype
*/
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 441bdea4d4c1..401bf8743689 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -80,6 +80,27 @@ static int mei_open(struct inode *inode, struct file *file)
return err;
}

+/**
+ * mei_cl_vtag_remove_by_fp - remove vtag that corresponds to fp from list
+ *
+ * @cl: host client
+ * @fp: pointer to file structure
+ *
+ */
+static void mei_cl_vtag_remove_by_fp(const struct mei_cl *cl,
+ const struct file *fp)
+{
+ struct mei_cl_vtag *vtag_l, *next;
+
+ list_for_each_entry_safe(vtag_l, next, &cl->vtag_map, list) {
+ if (vtag_l->fp == fp) {
+ list_del(&vtag_l->list);
+ kfree(vtag_l);
+ return;
+ }
+ }
+}
+
/**
* mei_release - the release function
*
@@ -101,17 +122,35 @@ static int mei_release(struct inode *inode, struct file *file)

mutex_lock(&dev->device_lock);

+ mei_cl_vtag_remove_by_fp(cl, file);
+
+ if (!list_empty(&cl->vtag_map)) {
+ cl_dbg(dev, cl, "not the last vtag\n");
+ mei_cl_flush_queues(cl, file);
+ rets = 0;
+ goto out;
+ }
+
rets = mei_cl_disconnect(cl);
+ /*
+ * Check again: This is necessary since disconnect releases the lock
+ * and another client can connect in the meantime.
+ */
+ if (!list_empty(&cl->vtag_map)) {
+ cl_dbg(dev, cl, "not the last vtag after disconnect\n");
+ mei_cl_flush_queues(cl, file);
+ goto out;
+ }

- mei_cl_flush_queues(cl, file);
+ mei_cl_flush_queues(cl, NULL);
cl_dbg(dev, cl, "removing\n");

mei_cl_unlink(cl);
+ kfree(cl);

+out:
file->private_data = NULL;

- kfree(cl);
-
mutex_unlock(&dev->device_lock);
return rets;
}
@@ -237,6 +276,28 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
mutex_unlock(&dev->device_lock);
return rets;
}
+
+/**
+ * mei_cl_vtag_by_fp - obtain the vtag by file pointer
+ *
+ * @cl: host client
+ * @fp: pointer to file structure
+ *
+ * Return: vtag value on success, otherwise 0
+ */
+static u8 mei_cl_vtag_by_fp(const struct mei_cl *cl, const struct file *fp)
+{
+ struct mei_cl_vtag *cl_vtag;
+
+ if (!fp)
+ return 0;
+
+ list_for_each_entry(cl_vtag, &cl->vtag_map, list)
+ if (cl_vtag->fp == fp)
+ return cl_vtag->vtag;
+ return 0;
+}
+
/**
* mei_write - the write function.
*
@@ -314,6 +375,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
rets = -ENOMEM;
goto out;
}
+ cb->vtag = mei_cl_vtag_by_fp(cl, file);

rets = copy_from_user(cb->buf.data, ubuf, length);
if (rets) {
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index 1219edea3243..2f4cc1a8aae8 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -193,6 +193,21 @@ struct mei_cl_cb {
u32 blocking:1;
};

+/**
+ * struct mei_cl_vtag - file pointer to vtag mapping structure
+ *
+ * @list: link in map queue
+ * @fp: file pointer
+ * @vtag: corresponding vtag
+ * @pending_read: the read is pending on this file
+ */
+struct mei_cl_vtag {
+ struct list_head list;
+ const struct file *fp;
+ u8 vtag;
+ u8 pending_read:1;
+};
+
/**
* struct mei_cl - me client host representation
* carried in file->private_data
@@ -209,6 +224,7 @@ struct mei_cl_cb {
* @me_cl: fw client connected
* @fp: file associated with client
* @host_client_id: host id
+ * @vtag_map: vtag map
* @tx_flow_ctrl_creds: transmit flow credentials
* @rx_flow_ctrl_creds: receive flow credentials
* @timer_count: watchdog timer for operation completion
@@ -235,6 +251,7 @@ struct mei_cl {
struct mei_me_client *me_cl;
const struct file *fp;
u8 host_client_id;
+ struct list_head vtag_map;
u8 tx_flow_ctrl_creds;
u8 rx_flow_ctrl_creds;
u8 timer_count;
--
2.25.4

2020-08-18 12:12:45

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 02/13] mei: restrict vtag support to hbm version 2.2

From: Alexander Usyskin <[email protected]>

The vtag allows partitioning the mei messages into virtual groups/channels.
Vtags are supported for firmwares with HBM version 2.2 and newer
and only when a firmware confirms the support via capability handshake.
This change only define vtag restrictions in order to make
the series bisectable. Everything will be enabled when driver HBM
version is set to 2.2.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/debugfs.c | 1 +
drivers/misc/mei/hbm.c | 15 +++++++++++++++
drivers/misc/mei/hw.h | 9 +++++++++
drivers/misc/mei/mei_dev.h | 2 ++
4 files changed, 27 insertions(+)

diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c
index 72ee572ad9b4..b98f6f9a4896 100644
--- a/drivers/misc/mei/debugfs.c
+++ b/drivers/misc/mei/debugfs.c
@@ -103,6 +103,7 @@ static int mei_dbgfs_devstate_show(struct seq_file *m, void *unused)
seq_printf(m, "\tFA: %01d\n", dev->hbm_f_fa_supported);
seq_printf(m, "\tOS: %01d\n", dev->hbm_f_os_supported);
seq_printf(m, "\tDR: %01d\n", dev->hbm_f_dr_supported);
+ seq_printf(m, "\tVT: %01d\n", dev->hbm_f_vt_supported);
seq_printf(m, "\tCAP: %01d\n", dev->hbm_f_cap_supported);
}

diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index 3a227d9363d5..0513b8a4ea88 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -342,6 +342,8 @@ static int mei_hbm_capabilities_req(struct mei_device *dev)

memset(&req, 0, sizeof(req));
req.hbm_cmd = MEI_HBM_CAPABILITIES_REQ_CMD;
+ if (dev->hbm_f_vt_supported)
+ req.capability_requested[0] = HBM_CAP_VT;

ret = mei_hbm_write_message(dev, &mei_hdr, &req);
if (ret) {
@@ -1074,6 +1076,14 @@ static void mei_hbm_config_features(struct mei_device *dev)
dev->version.minor_version >= HBM_MINOR_VERSION_DR))
dev->hbm_f_dr_supported = 1;

+ /* VTag Support */
+ dev->hbm_f_vt_supported = 0;
+ if (dev->version.major_version > HBM_MAJOR_VERSION_VT ||
+ (dev->version.major_version == HBM_MAJOR_VERSION_VT &&
+ dev->version.minor_version >= HBM_MINOR_VERSION_VT))
+ dev->hbm_f_vt_supported = 1;
+
+ /* Capability message Support */
dev->hbm_f_cap_supported = 0;
if (dev->version.major_version > HBM_MAJOR_VERSION_CAP ||
(dev->version.major_version == HBM_MAJOR_VERSION_CAP &&
@@ -1112,6 +1122,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
struct hbm_host_enum_response *enum_res;
struct hbm_dma_setup_response *dma_setup_res;
struct hbm_add_client_request *add_cl_req;
+ struct hbm_capability_response *capability_res;
int ret;

struct mei_hbm_cl_cmd *cl_cmd;
@@ -1214,6 +1225,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr)
return -EPROTO;
}

+ capability_res = (struct hbm_capability_response *)mei_msg;
+ if (!(capability_res->capability_granted[0] & HBM_CAP_VT))
+ dev->hbm_f_vt_supported = 0;
+
if (dev->hbm_f_dr_supported) {
if (mei_dmam_ring_alloc(dev))
dev_info(dev->dev, "running w/o dma ring\n");
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index 539d89ba1c61..13e4cb68a0e6 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -76,6 +76,12 @@
#define HBM_MINOR_VERSION_DR 1
#define HBM_MAJOR_VERSION_DR 2

+/*
+ * MEI version with vm tag support
+ */
+#define HBM_MINOR_VERSION_VT 2
+#define HBM_MAJOR_VERSION_VT 2
+
/*
* MEI version with capabilities message support
*/
@@ -542,6 +548,9 @@ struct hbm_dma_ring_ctrl {
u32 reserved4;
} __packed;

+/* virtual tag supported */
+#define HBM_CAP_VT BIT(0)
+
/**
* struct hbm_capability_request - capability request from host to fw
*
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index e18af7dfd9ff..f80cc6463f18 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -426,6 +426,7 @@ struct mei_fw_version {
* @hbm_f_ie_supported : hbm feature immediate reply to enum request
* @hbm_f_os_supported : hbm feature support OS ver message
* @hbm_f_dr_supported : hbm feature dma ring supported
+ * @hbm_f_vt_supported : hbm feature vtag supported
* @hbm_f_cap_supported : hbm feature capabilities message supported
*
* @fw_ver : FW versions
@@ -511,6 +512,7 @@ struct mei_device {
unsigned int hbm_f_ie_supported:1;
unsigned int hbm_f_os_supported:1;
unsigned int hbm_f_dr_supported:1;
+ unsigned int hbm_f_vt_supported:1;
unsigned int hbm_f_cap_supported:1;

struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS];
--
2.25.4

2020-08-18 12:12:56

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 08/13] mei: handle tx queue flushing for vtag connections

From: Alexander Usyskin <[email protected]>

Since multiple file pointers (fp) can be associated
with a single host client, upon close() only objects
associated with the fp has to flushed from the tx queues.
The control queues should be flushed only when all
the connections are closed and the client is disconnected.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/client.c | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 3904fce18261..d5c3f7d54634 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -429,14 +429,16 @@ static void mei_io_list_flush_cl(struct list_head *head,
*
* @head: An instance of our list structure
* @cl: host client
+ * @fp: file pointer (matching cb file object), may be NULL
*/
static void mei_io_tx_list_free_cl(struct list_head *head,
- const struct mei_cl *cl)
+ const struct mei_cl *cl,
+ const struct file *fp)
{
struct mei_cl_cb *cb, *next;

list_for_each_entry_safe(cb, next, head, list) {
- if (cl == cb->cl)
+ if (cl == cb->cl && (!fp || fp == cb->fp))
mei_tx_cb_dequeue(cb);
}
}
@@ -574,13 +576,14 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
dev = cl->dev;

cl_dbg(dev, cl, "remove list entry belonging to cl\n");
- mei_io_tx_list_free_cl(&cl->dev->write_list, cl);
- mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl);
- mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl);
- mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
- /* free pending cb only in final flush */
- if (!fp)
+ mei_io_tx_list_free_cl(&cl->dev->write_list, cl, fp);
+ mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl, fp);
+ /* free pending and control cb only in final flush */
+ if (!fp) {
+ mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl);
+ mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
mei_cl_free_pending(cl);
+ }
spin_lock(&cl->rd_completed_lock);
mei_io_list_free_fp(&cl->rd_completed, fp);
spin_unlock(&cl->rd_completed_lock);
@@ -798,8 +801,8 @@ static void mei_cl_set_disconnected(struct mei_cl *cl)
return;

cl->state = MEI_FILE_DISCONNECTED;
- mei_io_tx_list_free_cl(&dev->write_list, cl);
- mei_io_tx_list_free_cl(&dev->write_waiting_list, cl);
+ mei_io_tx_list_free_cl(&dev->write_list, cl, NULL);
+ mei_io_tx_list_free_cl(&dev->write_waiting_list, cl, NULL);
mei_io_list_flush_cl(&dev->ctrl_rd_list, cl);
mei_io_list_flush_cl(&dev->ctrl_wr_list, cl);
mei_cl_wake_all(cl);
--
2.25.4

2020-08-18 12:12:58

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 12/13] mei: docs: add vtag ioctl documentation

Add structured documenation for the new vtag ioctl

Signed-off-by: Tomas Winkler <[email protected]>
---
Documentation/driver-api/mei/mei.rst | 37 ++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)

diff --git a/Documentation/driver-api/mei/mei.rst b/Documentation/driver-api/mei/mei.rst
index c800d8e5f422..cea0b69ec216 100644
--- a/Documentation/driver-api/mei/mei.rst
+++ b/Documentation/driver-api/mei/mei.rst
@@ -42,6 +42,11 @@ The session is terminated calling :c:func:`close(int fd)`.

A code snippet for an application communicating with Intel AMTHI client:

+In order to support virtualization or sandboxing a trusted supervisor
+can use :c:macro:`MEI_CONNECT_CLIENT_IOCTL_VTAG` to create
+virtual channels with an Intel ME feature. Not all features support
+virtual channels such client with answer EOPNOTSUPP.
+
.. code-block:: C

struct mei_connect_client_data data;
@@ -110,6 +115,38 @@ Connect to firmware Feature/Client.
data that can be sent or received. (e.g. if MTU=2K, can send
requests up to bytes 2k and received responses up to 2k bytes).

+IOCTL_MEI_CONNECT_CLIENT_VTAG:
+------------------------------
+
+.. code-block:: none
+
+ Usage:
+
+ struct mei_connect_client_data_vtag client_data_vtag;
+
+ ioctl(fd, IOCTL_MEI_CONNECT_CLIENT_VTAG, &client_data_vtag);
+
+ Inputs:
+
+ struct mei_connect_client_data_vtag - contain the following
+ Input field:
+
+ in_client_uuid - GUID of the FW Feature that needs
+ to connect to.
+ vtag - virtual tag [1, 255]
+
+ Outputs:
+ out_client_properties - Client Properties: MTU and Protocol Version.
+
+ Error returns:
+
+ ENOTTY No such client (i.e. wrong GUID) or connection is not allowed.
+ EINVAL Wrong IOCTL Number or tag == 0
+ ENODEV Device or Connection is not initialized or ready.
+ ENOMEM Unable to allocate memory to client internal data.
+ EFAULT Fatal Error (e.g. Unable to access user input data)
+ EBUSY Connection Already Open
+ EOPNOTSUPP Vtag is not supported

IOCTL_MEI_NOTIFY_SET
---------------------
--
2.25.4

2020-08-18 12:13:35

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 04/13] mei: add support for mei extended header.

Add an extend header beyond existing 4 bytes of the mei message header.
The extension is of variable length, starting with meta header
that contains the number of headers and the overall size of
the extended headers excluding meta header itself followed by
TLV list of extended headers. Currently only supported extension is
the vtag. From the HW perspective the extended headers is already
part of the payload.

Signed-off-by: Tomas Winkler <[email protected]>
Signed-off-by: Alexander Usyskin <[email protected]>
---
drivers/misc/mei/client.c | 189 ++++++++++++++++++++++++-----------
drivers/misc/mei/hbm.c | 14 +--
drivers/misc/mei/hw.h | 93 ++++++++++++++++-
drivers/misc/mei/interrupt.c | 113 ++++++++++++++++++---
drivers/misc/mei/mei_dev.h | 11 +-
5 files changed, 334 insertions(+), 86 deletions(-)

diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 2572887d99b6..a52799590dc7 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -378,6 +378,8 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl,
cb->cl = cl;
cb->buf_idx = 0;
cb->fop_type = type;
+ cb->vtag = 0;
+
return cb;
}

@@ -1518,21 +1520,67 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
return rets;
}

+static inline u8 mei_ext_hdr_set_vtag(struct mei_ext_hdr *ext, u8 vtag)
+{
+ ext->type = MEI_EXT_HDR_VTAG;
+ ext->ext_payload[0] = vtag;
+ ext->length = mei_data2slots(sizeof(*ext));
+ return ext->length;
+}
+
/**
- * mei_msg_hdr_init - initialize mei message header
+ * mei_msg_hdr_init - allocate and initialize mei message header
*
- * @mei_hdr: mei message header
* @cb: message callback structure
+ *
+ * Return: a pointer to initialized header
*/
-static void mei_msg_hdr_init(struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *cb)
+static struct mei_msg_hdr *mei_msg_hdr_init(const struct mei_cl_cb *cb)
{
+ size_t hdr_len;
+ struct mei_ext_meta_hdr *meta;
+ struct mei_ext_hdr *ext;
+ struct mei_msg_hdr *mei_hdr;
+ bool is_ext, is_vtag;
+
+ if (!cb)
+ return ERR_PTR(-EINVAL);
+
+ /* Extended header for vtag is attached only on the first fragment */
+ is_vtag = (cb->vtag && cb->buf_idx == 0);
+ is_ext = is_vtag;
+
+ /* Compute extended header size */
+ hdr_len = sizeof(*mei_hdr);
+
+ if (!is_ext)
+ goto setup_hdr;
+
+ hdr_len += sizeof(*meta);
+ if (is_vtag)
+ hdr_len += sizeof(*ext);
+
+setup_hdr:
+ mei_hdr = kzalloc(hdr_len, GFP_KERNEL);
+ if (!mei_hdr)
+ return ERR_PTR(-ENOMEM);
+
mei_hdr->host_addr = mei_cl_host_addr(cb->cl);
mei_hdr->me_addr = mei_cl_me_id(cb->cl);
- mei_hdr->length = 0;
- mei_hdr->reserved = 0;
- mei_hdr->msg_complete = 0;
- mei_hdr->dma_ring = 0;
mei_hdr->internal = cb->internal;
+ mei_hdr->extended = is_ext;
+
+ if (!is_ext)
+ goto out;
+
+ meta = (struct mei_ext_meta_hdr *)mei_hdr->extension;
+ if (is_vtag) {
+ meta->count++;
+ meta->size += mei_ext_hdr_set_vtag(meta->hdrs, cb->vtag);
+ }
+out:
+ mei_hdr->length = hdr_len - sizeof(*mei_hdr);
+ return mei_hdr;
}

/**
@@ -1550,10 +1598,11 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
{
struct mei_device *dev;
struct mei_msg_data *buf;
- struct mei_msg_hdr mei_hdr;
- size_t hdr_len = sizeof(mei_hdr);
- size_t len;
+ struct mei_msg_hdr *mei_hdr = NULL;
+ size_t hdr_len;
size_t hbuf_len, dr_len;
+ size_t buf_len;
+ size_t data_len;
int hbuf_slots;
u32 dr_slots;
u32 dma_len;
@@ -1579,7 +1628,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
return 0;
}

- len = buf->size - cb->buf_idx;
+ buf_len = buf->size - cb->buf_idx;
data = buf->data + cb->buf_idx;
hbuf_slots = mei_hbuf_empty_slots(dev);
if (hbuf_slots < 0) {
@@ -1591,42 +1640,54 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
dr_slots = mei_dma_ring_empty_slots(dev);
dr_len = mei_slots2data(dr_slots);

- mei_msg_hdr_init(&mei_hdr, cb);
+ mei_hdr = mei_msg_hdr_init(cb);
+ if (IS_ERR(mei_hdr)) {
+ rets = PTR_ERR(mei_hdr);
+ mei_hdr = NULL;
+ goto err;
+ }
+
+ cl_dbg(dev, cl, "Extended Header %d vtag = %d\n",
+ mei_hdr->extended, cb->vtag);
+
+ hdr_len = sizeof(*mei_hdr) + mei_hdr->length;

/**
* Split the message only if we can write the whole host buffer
* otherwise wait for next time the host buffer is empty.
*/
- if (len + hdr_len <= hbuf_len) {
- mei_hdr.length = len;
- mei_hdr.msg_complete = 1;
+ if (hdr_len + buf_len <= hbuf_len) {
+ data_len = buf_len;
+ mei_hdr->msg_complete = 1;
} else if (dr_slots && hbuf_len >= hdr_len + sizeof(dma_len)) {
- mei_hdr.dma_ring = 1;
- if (len > dr_len)
- len = dr_len;
+ mei_hdr->dma_ring = 1;
+ if (buf_len > dr_len)
+ buf_len = dr_len;
else
- mei_hdr.msg_complete = 1;
+ mei_hdr->msg_complete = 1;

- mei_hdr.length = sizeof(dma_len);
- dma_len = len;
+ data_len = sizeof(dma_len);
+ dma_len = buf_len;
data = &dma_len;
} else if ((u32)hbuf_slots == mei_hbuf_depth(dev)) {
- len = hbuf_len - hdr_len;
- mei_hdr.length = len;
+ buf_len = hbuf_len - hdr_len;
+ data_len = buf_len;
} else {
+ kfree(mei_hdr);
return 0;
}
+ mei_hdr->length += data_len;

- if (mei_hdr.dma_ring)
- mei_dma_ring_write(dev, buf->data + cb->buf_idx, len);
+ if (mei_hdr->dma_ring)
+ mei_dma_ring_write(dev, buf->data + cb->buf_idx, buf_len);
+ rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len);

- rets = mei_write_message(dev, &mei_hdr, hdr_len, data, mei_hdr.length);
if (rets)
goto err;

cl->status = 0;
cl->writing_state = MEI_WRITING;
- cb->buf_idx += len;
+ cb->buf_idx += buf_len;

if (first_chunk) {
if (mei_cl_tx_flow_ctrl_creds_reduce(cl)) {
@@ -1635,12 +1696,14 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
}
}

- if (mei_hdr.msg_complete)
+ if (mei_hdr->msg_complete)
list_move_tail(&cb->list, &dev->write_waiting_list);

+ kfree(mei_hdr);
return 0;

err:
+ kfree(mei_hdr);
cl->status = rets;
list_move_tail(&cb->list, cmpl_list);
return rets;
@@ -1659,9 +1722,11 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
{
struct mei_device *dev;
struct mei_msg_data *buf;
- struct mei_msg_hdr mei_hdr;
- size_t hdr_len = sizeof(mei_hdr);
- size_t len, hbuf_len, dr_len;
+ struct mei_msg_hdr *mei_hdr = NULL;
+ size_t hdr_len;
+ size_t hbuf_len, dr_len;
+ size_t buf_len;
+ size_t data_len;
int hbuf_slots;
u32 dr_slots;
u32 dma_len;
@@ -1678,9 +1743,9 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
dev = cl->dev;

buf = &cb->buf;
- len = buf->size;
+ buf_len = buf->size;

- cl_dbg(dev, cl, "len=%zd\n", len);
+ cl_dbg(dev, cl, "buf_len=%zd\n", buf_len);

blocking = cb->blocking;
data = buf->data;
@@ -1700,17 +1765,27 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
if (rets < 0)
goto err;

- mei_msg_hdr_init(&mei_hdr, cb);
+ mei_hdr = mei_msg_hdr_init(cb);
+ if (IS_ERR(mei_hdr)) {
+ rets = -PTR_ERR(mei_hdr);
+ mei_hdr = NULL;
+ goto err;
+ }
+
+ cl_dbg(dev, cl, "Extended Header %d vtag = %d\n",
+ mei_hdr->extended, cb->vtag);
+
+ hdr_len = sizeof(*mei_hdr) + mei_hdr->length;

if (rets == 0) {
cl_dbg(dev, cl, "No flow control credentials: not sending.\n");
- rets = len;
+ rets = buf_len;
goto out;
}

if (!mei_hbuf_acquire(dev)) {
cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n");
- rets = len;
+ rets = buf_len;
goto out;
}

@@ -1724,29 +1799,30 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
dr_slots = mei_dma_ring_empty_slots(dev);
dr_len = mei_slots2data(dr_slots);

- if (len + hdr_len <= hbuf_len) {
- mei_hdr.length = len;
- mei_hdr.msg_complete = 1;
+ if (hdr_len + buf_len <= hbuf_len) {
+ data_len = buf_len;
+ mei_hdr->msg_complete = 1;
} else if (dr_slots && hbuf_len >= hdr_len + sizeof(dma_len)) {
- mei_hdr.dma_ring = 1;
- if (len > dr_len)
- len = dr_len;
+ mei_hdr->dma_ring = 1;
+ if (buf_len > dr_len)
+ buf_len = dr_len;
else
- mei_hdr.msg_complete = 1;
+ mei_hdr->msg_complete = 1;

- mei_hdr.length = sizeof(dma_len);
- dma_len = len;
+ data_len = sizeof(dma_len);
+ dma_len = buf_len;
data = &dma_len;
} else {
- len = hbuf_len - hdr_len;
- mei_hdr.length = len;
+ buf_len = hbuf_len - hdr_len;
+ data_len = buf_len;
}

- if (mei_hdr.dma_ring)
- mei_dma_ring_write(dev, buf->data, len);
+ mei_hdr->length += data_len;
+
+ if (mei_hdr->dma_ring)
+ mei_dma_ring_write(dev, buf->data, buf_len);
+ rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len);

- rets = mei_write_message(dev, &mei_hdr, hdr_len,
- data, mei_hdr.length);
if (rets)
goto err;

@@ -1755,12 +1831,12 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
goto err;

cl->writing_state = MEI_WRITING;
- cb->buf_idx = len;
+ cb->buf_idx = buf_len;
/* restore return value */
- len = buf->size;
+ buf_len = buf->size;

out:
- if (mei_hdr.msg_complete)
+ if (mei_hdr->msg_complete)
mei_tx_cb_enqueue(cb, &dev->write_waiting_list);
else
mei_tx_cb_enqueue(cb, &dev->write_list);
@@ -1785,7 +1861,7 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
}
}

- rets = len;
+ rets = buf_len;
err:
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
@@ -1793,10 +1869,11 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
free:
mei_io_cb_free(cb);

+ kfree(mei_hdr);
+
return rets;
}

-
/**
* mei_cl_complete - processes completed operation for a client
*
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index 0513b8a4ea88..a97eb5d47705 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -125,19 +125,15 @@ void mei_hbm_reset(struct mei_device *dev)
/**
* mei_hbm_hdr - construct hbm header
*
- * @hdr: hbm header
+ * @mei_hdr: hbm header
* @length: payload length
*/

-static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length)
+static inline void mei_hbm_hdr(struct mei_msg_hdr *mei_hdr, size_t length)
{
- hdr->host_addr = 0;
- hdr->me_addr = 0;
- hdr->length = length;
- hdr->msg_complete = 1;
- hdr->dma_ring = 0;
- hdr->reserved = 0;
- hdr->internal = 0;
+ memset(mei_hdr, 0, sizeof(*mei_hdr));
+ mei_hdr->length = length;
+ mei_hdr->msg_complete = 1;
}

/**
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index ea0a2e459282..c3c628132b50 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -197,10 +197,95 @@ enum mei_cl_connect_status {
/*
* Client Disconnect Status
*/
-enum mei_cl_disconnect_status {
+enum mei_cl_disconnect_status {
MEI_CL_DISCONN_SUCCESS = MEI_HBMS_SUCCESS
};

+/**
+ * enum mei_ext_hdr_type - extended header type used in
+ * extended header TLV
+ *
+ * @MEI_EXT_HDR_NONE: sentinel
+ * @MEI_EXT_HDR_VTAG: vtag header
+ */
+enum mei_ext_hdr_type {
+ MEI_EXT_HDR_NONE = 0,
+ MEI_EXT_HDR_VTAG = 1,
+};
+
+/**
+ * struct mei_ext_hdr - extend header descriptor (TLV)
+ * @type: enum mei_ext_hdr_type
+ * @length: length excluding descriptor
+ * @ext_payload: payload of the specific extended header
+ * @hdr: place holder for actual header
+ */
+struct mei_ext_hdr {
+ u8 type;
+ u8 length;
+ u8 ext_payload[2];
+ u8 hdr[0];
+};
+
+/**
+ * struct mei_ext_meta_hdr - extend header meta data
+ * @count: number of headers
+ * @size: total size of the extended header list excluding meta header
+ * @reserved: reserved
+ * @hdrs: extended headers TLV list
+ */
+struct mei_ext_meta_hdr {
+ u8 count;
+ u8 size;
+ u8 reserved[2];
+ struct mei_ext_hdr hdrs[0];
+};
+
+/*
+ * Extended header iterator functions
+ */
+/**
+ * mei_ext_hdr - extended header iterator begin
+ *
+ * @meta: meta header of the extended header list
+ *
+ * Return:
+ * The first extended header
+ */
+static inline struct mei_ext_hdr *mei_ext_begin(struct mei_ext_meta_hdr *meta)
+{
+ return meta->hdrs;
+}
+
+/**
+ * mei_ext_last - check if the ext is the last one in the TLV list
+ *
+ * @meta: meta header of the extended header list
+ * @ext: a meta header on the list
+ *
+ * Return: true if ext is the last header on the list
+ */
+static inline bool mei_ext_last(struct mei_ext_meta_hdr *meta,
+ struct mei_ext_hdr *ext)
+{
+ return (u8 *)ext >= (u8 *)meta + sizeof(*meta) + (meta->size * 4);
+}
+
+/**
+ *mei_ext_next - following extended header on the TLV list
+ *
+ * @ext: current extend header
+ *
+ * Context: The function does not check for the overflows,
+ * one should call mei_ext_last before.
+ *
+ * Return: The following extend header after @ext
+ */
+static inline struct mei_ext_hdr *mei_ext_next(struct mei_ext_hdr *ext)
+{
+ return (struct mei_ext_hdr *)(ext->hdr + (ext->length * 4));
+}
+
/**
* struct mei_msg_hdr - MEI BUS Interface Section
*
@@ -208,6 +293,7 @@ enum mei_cl_disconnect_status {
* @host_addr: host address
* @length: message length
* @reserved: reserved
+ * @extended: message has extended header
* @dma_ring: message is on dma ring
* @internal: message is internal
* @msg_complete: last packet of the message
@@ -217,7 +303,8 @@ struct mei_msg_hdr {
u32 me_addr:8;
u32 host_addr:8;
u32 length:9;
- u32 reserved:4;
+ u32 reserved:3;
+ u32 extended:1;
u32 dma_ring:1;
u32 internal:1;
u32 msg_complete:1;
@@ -227,8 +314,6 @@ struct mei_msg_hdr {
/* The length is up to 9 bits */
#define MEI_MSG_MAX_LEN_MASK GENMASK(9, 0)

-#define MEI_MSG_HDR_MAX 2
-
struct mei_bus_message {
u8 hbm_cmd;
u8 data[];
diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c
index c70a8c74cc57..326955b04fda 100644
--- a/drivers/misc/mei/interrupt.c
+++ b/drivers/misc/mei/interrupt.c
@@ -61,16 +61,21 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl,
*
* @dev: mei device
* @hdr: message header
+ * @discard_len: the length of the message to discard (excluding header)
*/
-static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
+static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr,
+ size_t discard_len)
{
- if (hdr->dma_ring)
- mei_dma_ring_read(dev, NULL, hdr->extension[0]);
+ if (hdr->dma_ring) {
+ mei_dma_ring_read(dev, NULL,
+ hdr->extension[dev->rd_msg_hdr_count - 2]);
+ discard_len = 0;
+ }
/*
* no need to check for size as it is guarantied
* that length fits into rd_msg_buf
*/
- mei_read_slots(dev, dev->rd_msg_buf, hdr->length);
+ mei_read_slots(dev, dev->rd_msg_buf, discard_len);
dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n",
MEI_HDR_PRM(hdr));
}
@@ -80,18 +85,29 @@ static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
*
* @cl: reading client
* @mei_hdr: header of mei client message
+ * @meta: extend meta header
* @cmpl_list: completion list
*
* Return: always 0
*/
static int mei_cl_irq_read_msg(struct mei_cl *cl,
struct mei_msg_hdr *mei_hdr,
+ struct mei_ext_meta_hdr *meta,
struct list_head *cmpl_list)
{
struct mei_device *dev = cl->dev;
struct mei_cl_cb *cb;
+
size_t buf_sz;
u32 length;
+ int ext_len;
+
+ length = mei_hdr->length;
+ ext_len = 0;
+ if (mei_hdr->extended) {
+ ext_len = sizeof(*meta) + mei_slots2data(meta->size);
+ length -= ext_len;
+ }

cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);
if (!cb) {
@@ -105,13 +121,50 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl,
list_add_tail(&cb->list, &cl->rd_pending);
}

+ if (mei_hdr->extended) {
+ struct mei_ext_hdr *ext;
+ struct mei_ext_hdr *vtag = NULL;
+
+ ext = mei_ext_begin(meta);
+ do {
+ switch (ext->type) {
+ case MEI_EXT_HDR_VTAG:
+ vtag = ext;
+ break;
+ case MEI_EXT_HDR_NONE:
+ fallthrough;
+ default:
+ cb->status = -EPROTO;
+ break;
+ }
+
+ ext = mei_ext_next(ext);
+ } while (!mei_ext_last(meta, ext));
+
+ if (!vtag) {
+ cl_dbg(dev, cl, "vtag not found in extended header.\n");
+ cb->status = -EPROTO;
+ goto discard;
+ }
+
+ cl_dbg(dev, cl, "vtag: %d\n", vtag->ext_payload[0]);
+ if (cb->vtag && cb->vtag != vtag->ext_payload[0]) {
+ cl_err(dev, cl, "mismatched tag: %d != %d\n",
+ cb->vtag, vtag->ext_payload[0]);
+ cb->status = -EPROTO;
+ goto discard;
+ }
+ cb->vtag = vtag->ext_payload[0];
+ }
+
if (!mei_cl_is_connected(cl)) {
cl_dbg(dev, cl, "not connected\n");
cb->status = -ENODEV;
goto discard;
}

- length = mei_hdr->dma_ring ? mei_hdr->extension[0] : mei_hdr->length;
+ if (mei_hdr->dma_ring)
+ length = mei_hdr->extension[mei_data2slots(ext_len)];

buf_sz = length + cb->buf_idx;
/* catch for integer overflow */
@@ -129,11 +182,13 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl,
goto discard;
}

- if (mei_hdr->dma_ring)
+ if (mei_hdr->dma_ring) {
mei_dma_ring_read(dev, cb->buf.data + cb->buf_idx, length);
-
- /* for DMA read 0 length to generate an interrupt to the device */
- mei_read_slots(dev, cb->buf.data + cb->buf_idx, mei_hdr->length);
+ /* for DMA read 0 length to generate interrupt to the device */
+ mei_read_slots(dev, cb->buf.data + cb->buf_idx, 0);
+ } else {
+ mei_read_slots(dev, cb->buf.data + cb->buf_idx, length);
+ }

cb->buf_idx += length;

@@ -150,7 +205,7 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl,
discard:
if (cb)
list_move_tail(&cb->list, cmpl_list);
- mei_irq_discard_msg(dev, mei_hdr);
+ mei_irq_discard_msg(dev, mei_hdr, length);
return 0;
}

@@ -265,11 +320,16 @@ int mei_irq_read_handler(struct mei_device *dev,
struct list_head *cmpl_list, s32 *slots)
{
struct mei_msg_hdr *mei_hdr;
+ struct mei_ext_meta_hdr *meta_hdr = NULL;
struct mei_cl *cl;
int ret;
+ u32 ext_meta_hdr_u32;
+ int i;
+ int ext_hdr_end;

if (!dev->rd_msg_hdr[0]) {
dev->rd_msg_hdr[0] = mei_read_hdr(dev);
+ dev->rd_msg_hdr_count = 1;
(*slots)--;
dev_dbg(dev->dev, "slots =%08x.\n", *slots);

@@ -292,10 +352,34 @@ int mei_irq_read_handler(struct mei_device *dev,
goto end;
}

+ ext_hdr_end = 1;
+
+ if (mei_hdr->extended) {
+ if (!dev->rd_msg_hdr[1]) {
+ ext_meta_hdr_u32 = mei_read_hdr(dev);
+ dev->rd_msg_hdr[1] = ext_meta_hdr_u32;
+ dev->rd_msg_hdr_count++;
+ (*slots)--;
+ dev_dbg(dev->dev, "extended header is %08x\n",
+ ext_meta_hdr_u32);
+ }
+ meta_hdr = ((struct mei_ext_meta_hdr *)
+ dev->rd_msg_hdr + 1);
+ ext_hdr_end = meta_hdr->size + 2;
+ for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) {
+ dev->rd_msg_hdr[i] = mei_read_hdr(dev);
+ dev_dbg(dev->dev, "extended header %d is %08x\n", i,
+ dev->rd_msg_hdr[i]);
+ dev->rd_msg_hdr_count++;
+ (*slots)--;
+ }
+ }
+
if (mei_hdr->dma_ring) {
- dev->rd_msg_hdr[1] = mei_read_hdr(dev);
+ dev->rd_msg_hdr[ext_hdr_end] = mei_read_hdr(dev);
+ dev->rd_msg_hdr_count++;
(*slots)--;
- mei_hdr->length = 0;
+ mei_hdr->length -= sizeof(dev->rd_msg_hdr[ext_hdr_end]);
}

/* HBM message */
@@ -326,7 +410,7 @@ int mei_irq_read_handler(struct mei_device *dev,
*/
if (hdr_is_fixed(mei_hdr) ||
dev->dev_state == MEI_DEV_POWER_DOWN) {
- mei_irq_discard_msg(dev, mei_hdr);
+ mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length);
ret = 0;
goto reset_slots;
}
@@ -336,12 +420,13 @@ int mei_irq_read_handler(struct mei_device *dev,
goto end;
}

- ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list);
+ ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list);


reset_slots:
/* reset the number of slots and header */
memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr));
+ dev->rd_msg_hdr_count = 0;
*slots = mei_count_full_read_slots(dev);
if (*slots == -EOVERFLOW) {
/* overflow - reset */
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index f80cc6463f18..fefa5b53a6d0 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -174,6 +174,7 @@ struct mei_cl;
* @fop_type: file operation type
* @buf: buffer for data associated with the callback
* @buf_idx: last read index
+ * @vtag: virtual tag
* @fp: pointer to file structure
* @status: io status of the cb
* @internal: communication between driver and FW flag
@@ -185,6 +186,7 @@ struct mei_cl_cb {
enum mei_cb_file_ops fop_type;
struct mei_msg_data buf;
size_t buf_idx;
+ u8 vtag;
const struct file *fp;
int status;
u32 internal:1;
@@ -413,6 +415,7 @@ struct mei_fw_version {
*
* @rd_msg_buf : control messages buffer
* @rd_msg_hdr : read message header storage
+ * @rd_msg_hdr_count : how many dwords were already read from header
*
* @hbuf_is_ready : query if the host host/write buffer is ready
* @dr_dscr: DMA ring descriptors: TX, RX, and CTRL
@@ -496,7 +499,8 @@ struct mei_device {
#endif /* CONFIG_PM */

unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE];
- u32 rd_msg_hdr[MEI_MSG_HDR_MAX];
+ u32 rd_msg_hdr[MEI_RD_MSG_BUF_SIZE];
+ int rd_msg_hdr_count;

/* write buffer */
bool hbuf_is_ready;
@@ -750,10 +754,11 @@ static inline void mei_dbgfs_deregister(struct mei_device *dev) {}
int mei_register(struct mei_device *dev, struct device *parent);
void mei_deregister(struct mei_device *dev);

-#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d dma=%1d internal=%1d comp=%1d"
+#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d dma=%1d ext=%1d internal=%1d comp=%1d"
#define MEI_HDR_PRM(hdr) \
(hdr)->host_addr, (hdr)->me_addr, \
- (hdr)->length, (hdr)->dma_ring, (hdr)->internal, (hdr)->msg_complete
+ (hdr)->length, (hdr)->dma_ring, (hdr)->extended, \
+ (hdr)->internal, (hdr)->msg_complete

ssize_t mei_fw_status2str(struct mei_fw_status *fw_sts, char *buf, size_t len);
/**
--
2.25.4

2020-08-18 12:13:36

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 06/13] mei: add a spin lock to protect rd_completed queue

From: Alexander Usyskin <[email protected]>

In order to support vtags we need to access read completed
queue out of driver big lock.
Add a spin lock to protect rd_completed queue.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/bus.c | 6 ++---
drivers/misc/mei/client.c | 47 +++++++++++++++++++++++++++++++++-----
drivers/misc/mei/client.h | 7 ++++--
drivers/misc/mei/main.c | 6 ++---
drivers/misc/mei/mei_dev.h | 2 ++
5 files changed, 54 insertions(+), 14 deletions(-)

diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index 2e7ac53a4152..fc20a0da5c24 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -152,7 +152,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length,
if (timeout) {
rets = wait_event_interruptible_timeout
(cl->rx_wait,
- (!list_empty(&cl->rd_completed)) ||
+ mei_cl_read_cb(cl, NULL) ||
(!mei_cl_is_connected(cl)),
msecs_to_jiffies(timeout));
if (rets == 0)
@@ -165,7 +165,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length,
} else {
if (wait_event_interruptible
(cl->rx_wait,
- (!list_empty(&cl->rd_completed)) ||
+ mei_cl_read_cb(cl, NULL) ||
(!mei_cl_is_connected(cl)))) {
if (signal_pending(current))
return -EINTR;
@@ -198,7 +198,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length,
rets = r_length;

free:
- mei_io_cb_free(cb);
+ mei_cl_del_rd_completed(cl, cb);
out:
mutex_unlock(&bus->device_lock);

diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index a52799590dc7..276021f99666 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -507,15 +507,19 @@ struct mei_cl_cb *mei_cl_enqueue_ctrl_wr_cb(struct mei_cl *cl, size_t length,
*
* Return: cb on success, NULL if cb is not found
*/
-struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)
+struct mei_cl_cb *mei_cl_read_cb(struct mei_cl *cl, const struct file *fp)
{
struct mei_cl_cb *cb;
+ struct mei_cl_cb *ret_cb = NULL;

+ spin_lock(&cl->rd_completed_lock);
list_for_each_entry(cb, &cl->rd_completed, list)
- if (!fp || fp == cb->fp)
- return cb;
-
- return NULL;
+ if (!fp || fp == cb->fp) {
+ ret_cb = cb;
+ break;
+ }
+ spin_unlock(&cl->rd_completed_lock);
+ return ret_cb;
}

/**
@@ -541,7 +545,9 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl);
mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
mei_io_list_free_fp(&cl->rd_pending, fp);
+ spin_lock(&cl->rd_completed_lock);
mei_io_list_free_fp(&cl->rd_completed, fp);
+ spin_unlock(&cl->rd_completed_lock);

return 0;
}
@@ -559,6 +565,7 @@ static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
init_waitqueue_head(&cl->rx_wait);
init_waitqueue_head(&cl->tx_wait);
init_waitqueue_head(&cl->ev_wait);
+ spin_lock_init(&cl->rd_completed_lock);
INIT_LIST_HEAD(&cl->rd_completed);
INIT_LIST_HEAD(&cl->rd_pending);
INIT_LIST_HEAD(&cl->link);
@@ -1230,6 +1237,34 @@ static int mei_cl_tx_flow_ctrl_creds_reduce(struct mei_cl *cl)
return 0;
}

+/**
+ * mei_cl_add_rd_completed - add read completed callback to list with lock
+ *
+ * @cl: host client
+ * @cb: callback block
+ *
+ */
+void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb)
+{
+ spin_lock(&cl->rd_completed_lock);
+ list_add_tail(&cb->list, &cl->rd_completed);
+ spin_unlock(&cl->rd_completed_lock);
+}
+
+/**
+ * mei_cl_del_rd_completed - free read completed callback with lock
+ *
+ * @cl: host client
+ * @cb: callback block
+ *
+ */
+void mei_cl_del_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb)
+{
+ spin_lock(&cl->rd_completed_lock);
+ mei_io_cb_free(cb);
+ spin_unlock(&cl->rd_completed_lock);
+}
+
/**
* mei_cl_notify_fop2req - convert fop to proper request
*
@@ -1897,7 +1932,7 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
break;

case MEI_FOP_READ:
- list_add_tail(&cb->list, &cl->rd_completed);
+ mei_cl_add_rd_completed(cl, cb);
if (!mei_cl_is_fixed_address(cl) &&
!WARN_ON(!cl->rx_flow_ctrl_creds))
cl->rx_flow_ctrl_creds--;
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index 0d0f36373a4b..bd57c64f6c1a 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -133,8 +133,11 @@ int mei_cl_unlink(struct mei_cl *cl);

struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);

-struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,
- const struct file *fp);
+struct mei_cl_cb *mei_cl_read_cb(struct mei_cl *cl, const struct file *fp);
+
+void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb);
+void mei_cl_del_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb);
+
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
enum mei_cb_file_ops type,
const struct file *fp);
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 86ef5c1a7928..441bdea4d4c1 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -178,7 +178,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,

mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->rx_wait,
- !list_empty(&cl->rd_completed) ||
+ mei_cl_read_cb(cl, file) ||
!mei_cl_is_connected(cl))) {
if (signal_pending(current))
return -EINTR;
@@ -229,7 +229,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
goto out;

free:
- mei_io_cb_free(cb);
+ mei_cl_del_rd_completed(cl, cb);
*offset = 0;

out:
@@ -572,7 +572,7 @@ static __poll_t mei_poll(struct file *file, poll_table *wait)
if (req_events & (EPOLLIN | EPOLLRDNORM)) {
poll_wait(file, &cl->rx_wait, wait);

- if (!list_empty(&cl->rd_completed))
+ if (mei_cl_read_cb(cl, file))
mask |= EPOLLIN | EPOLLRDNORM;
else
mei_cl_read_start(cl, mei_cl_mtu(cl), file);
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index fefa5b53a6d0..1219edea3243 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -217,6 +217,7 @@ struct mei_cl_cb {
* @tx_cb_queued: number of tx callbacks in queue
* @writing_state: state of the tx
* @rd_pending: pending read credits
+ * @rd_completed_lock: protects rd_completed queue
* @rd_completed: completed read
*
* @cldev: device on the mei client bus
@@ -242,6 +243,7 @@ struct mei_cl {
u8 tx_cb_queued;
enum mei_file_transaction_states writing_state;
struct list_head rd_pending;
+ spinlock_t rd_completed_lock; /* protects rd_completed queue */
struct list_head rd_completed;

struct mei_cl_device *cldev;
--
2.25.4

2020-08-18 12:13:42

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 03/13] mei: add vtag support bit in client properties

From: Alexander Usyskin <[email protected]>

Vtag support is on a client basis, meaning not every client
supports it. The vtag capability is communicated via the client properties
structure during client enumeration process.
Export the propertiy via sysfs.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
Documentation/ABI/testing/sysfs-bus-mei | 7 +++++++
drivers/misc/mei/bus.c | 11 +++++++++++
drivers/misc/mei/client.h | 12 ++++++++++++
drivers/misc/mei/debugfs.c | 7 ++++---
drivers/misc/mei/hw.h | 15 ++++++++++++++-
5 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-mei b/Documentation/ABI/testing/sysfs-bus-mei
index 3d37e2796d5a..6e9a105fe5cb 100644
--- a/Documentation/ABI/testing/sysfs-bus-mei
+++ b/Documentation/ABI/testing/sysfs-bus-mei
@@ -41,6 +41,13 @@ Contact: Tomas Winkler <[email protected]>
Description: Stores mei client fixed address, if any
Format: %d

+What: /sys/bus/mei/devices/.../vtag
+Date: Nov 2020
+KernelVersion: 5.9
+Contact: Tomas Winkler <[email protected]>
+Description: Stores mei client vtag support status
+ Format: %d
+
What: /sys/bus/mei/devices/.../max_len
Date: Nov 2019
KernelVersion: 5.5
diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index a6dfc3ce1db2..2e7ac53a4152 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -810,6 +810,16 @@ static ssize_t fixed_show(struct device *dev, struct device_attribute *a,
}
static DEVICE_ATTR_RO(fixed);

+static ssize_t vtag_show(struct device *dev, struct device_attribute *a,
+ char *buf)
+{
+ struct mei_cl_device *cldev = to_mei_cl_device(dev);
+ bool vt = mei_me_cl_vt(cldev->me_cl);
+
+ return sprintf(buf, "%d", vt);
+}
+static DEVICE_ATTR_RO(vtag);
+
static ssize_t max_len_show(struct device *dev, struct device_attribute *a,
char *buf)
{
@@ -827,6 +837,7 @@ static struct attribute *mei_cldev_attrs[] = {
&dev_attr_modalias.attr,
&dev_attr_max_conn.attr,
&dev_attr_fixed.attr,
+ &dev_attr_vtag.attr,
&dev_attr_max_len.attr,
NULL,
};
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index 2f8954def591..0d0f36373a4b 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -93,6 +93,18 @@ static inline u8 mei_me_cl_fixed(const struct mei_me_client *me_cl)
return me_cl->props.fixed_address;
}

+/**
+ * mei_me_cl_vt - return me client vtag supported status
+ *
+ * @me_cl: me client
+ *
+ * Return: true if me client supports vt tagging
+ */
+static inline bool mei_me_cl_vt(const struct mei_me_client *me_cl)
+{
+ return me_cl->props.vt_supported == 1;
+}
+
/**
* mei_me_cl_max_len - return me client max msg length
*
diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c
index b98f6f9a4896..3ab1a431d810 100644
--- a/drivers/misc/mei/debugfs.c
+++ b/drivers/misc/mei/debugfs.c
@@ -27,7 +27,7 @@ static int mei_dbgfs_meclients_show(struct seq_file *m, void *unused)

down_read(&dev->me_clients_rwsem);

- seq_puts(m, " |id|fix| UUID |con|msg len|sb|refc|\n");
+ seq_puts(m, " |id|fix| UUID |con|msg len|sb|refc|vt|\n");

/* if the driver is not enabled the list won't be consistent */
if (dev->dev_state != MEI_DEV_ENABLED)
@@ -37,14 +37,15 @@ static int mei_dbgfs_meclients_show(struct seq_file *m, void *unused)
if (!mei_me_cl_get(me_cl))
continue;

- seq_printf(m, "%2d|%2d|%3d|%pUl|%3d|%7d|%2d|%4d|\n",
+ seq_printf(m, "%2d|%2d|%3d|%pUl|%3d|%7d|%2d|%4d|%2d|\n",
i++, me_cl->client_id,
me_cl->props.fixed_address,
&me_cl->props.protocol_name,
me_cl->props.max_number_of_connections,
me_cl->props.max_msg_length,
me_cl->props.single_recv_buf,
- kref_read(&me_cl->refcnt));
+ kref_read(&me_cl->refcnt),
+ me_cl->props.vt_supported);
mei_me_cl_put(me_cl);
}

diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index 13e4cb68a0e6..ea0a2e459282 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -314,13 +314,26 @@ struct hbm_host_enum_response {
u8 valid_addresses[32];
} __packed;

+/**
+ * struct mei_client_properties - mei client properties
+ *
+ * @protocol_name: guid of the client
+ * @protocol_version: client protocol version
+ * @max_number_of_connections: number of possible connections.
+ * @fixed_address: fixed me address (0 if the client is dynamic)
+ * @single_recv_buf: 1 if all connections share a single receive buffer.
+ * @vt_supported: the client support vtag
+ * @reserved: reserved
+ * @max_msg_length: MTU of the client
+ */
struct mei_client_properties {
uuid_le protocol_name;
u8 protocol_version;
u8 max_number_of_connections;
u8 fixed_address;
u8 single_recv_buf:1;
- u8 reserved:7;
+ u8 vt_supported:1;
+ u8 reserved:6;
u32 max_msg_length;
} __packed;

--
2.25.4

2020-08-18 12:14:26

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 10/13] mei: bus: unconditionally enable clients with vtag support

From: Alexander Usyskin <[email protected]>

The list of clients is only visible via mei client bus.
Enabling vtag clients on the mei client bus allows user-space to
enumerate clients with vtag support by traversing the mei bus on sysfs.
This feature is required for ACRN device model service.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/bus-fixup.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c
index 07ba16d46690..4e30fa98fe7d 100644
--- a/drivers/misc/mei/bus-fixup.c
+++ b/drivers/misc/mei/bus-fixup.c
@@ -463,6 +463,17 @@ static void mei_nfc(struct mei_cl_device *cldev)
dev_dbg(bus->dev, "end of fixup match = %d\n", cldev->do_match);
}

+/**
+ * vt_support - enable on bus clients with vtag support
+ *
+ * @cldev: me clients device
+ */
+static void vt_support(struct mei_cl_device *cldev)
+{
+ if (cldev->me_cl->props.vt_supported == 1)
+ cldev->do_match = 1;
+}
+
#define MEI_FIXUP(_uuid, _hook) { _uuid, _hook }

static struct mei_fixup {
@@ -476,6 +487,7 @@ static struct mei_fixup {
MEI_FIXUP(MEI_UUID_WD, mei_wd),
MEI_FIXUP(MEI_UUID_MKHIF_FIX, mei_mkhi_fix),
MEI_FIXUP(MEI_UUID_HDCP, whitelist),
+ MEI_FIXUP(MEI_UUID_ANY, vt_support),
};

/**
--
2.25.4

2020-08-18 12:14:34

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 11/13] mei: add connect with vtag ioctl

From: Alexander Usyskin <[email protected]>

This IOCTL is used to associate the current file descriptor
with a FW Client (given by UUID), and virtual tag (vtag).
The IOCTL opens a communication channel between a host client
and a FW client on a tagged channel. From this point on,
every reader and write will communicate with the associated
FW client on the tagged channel. Upon close() the communication
is terminated.

The IOCTL argument is a struct with a union that contains
the input parameter and the output parameter for this IOCTL.

The input parameter is UUID of the FW Client, a vtag [0,255]
The output parameter is the properties of the FW client

Clients that do not support tagged connection
will respond with -EOPNOTSUPP

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/main.c | 210 ++++++++++++++++++++++++++++++++++++---
include/uapi/linux/mei.h | 49 +++++++++
2 files changed, 243 insertions(+), 16 deletions(-)

diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 401bf8743689..9f6682033ed7 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -395,17 +395,18 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
* mei_ioctl_connect_client - the connect to fw client IOCTL function
*
* @file: private data of the file object
- * @data: IOCTL connect data, input and output parameters
+ * @in_client_uuid: requested UUID for connection
+ * @client: IOCTL connect data, output parameters
*
* Locking: called under "dev->device_lock" lock
*
* Return: 0 on success, <0 on failure.
*/
static int mei_ioctl_connect_client(struct file *file,
- struct mei_connect_client_data *data)
+ const uuid_le *in_client_uuid,
+ struct mei_client *client)
{
struct mei_device *dev;
- struct mei_client *client;
struct mei_me_client *me_cl;
struct mei_cl *cl;
int rets;
@@ -413,18 +414,15 @@ static int mei_ioctl_connect_client(struct file *file,
cl = file->private_data;
dev = cl->dev;

- if (dev->dev_state != MEI_DEV_ENABLED)
- return -ENODEV;
-
if (cl->state != MEI_FILE_INITIALIZING &&
cl->state != MEI_FILE_DISCONNECTED)
return -EBUSY;

/* find ME client we're trying to connect to */
- me_cl = mei_me_cl_by_uuid(dev, &data->in_client_uuid);
+ me_cl = mei_me_cl_by_uuid(dev, in_client_uuid);
if (!me_cl) {
dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
- &data->in_client_uuid);
+ in_client_uuid);
rets = -ENOTTY;
goto end;
}
@@ -434,7 +432,7 @@ static int mei_ioctl_connect_client(struct file *file,
!dev->allow_fixed_address : !dev->hbm_f_fa_supported;
if (forbidden) {
dev_dbg(dev->dev, "Connection forbidden to FW Client UUID = %pUl\n",
- &data->in_client_uuid);
+ in_client_uuid);
rets = -ENOTTY;
goto end;
}
@@ -448,7 +446,6 @@ static int mei_ioctl_connect_client(struct file *file,
me_cl->props.max_msg_length);

/* prepare the output buffer */
- client = &data->out_client_properties;
client->max_msg_length = me_cl->props.max_msg_length;
client->protocol_version = me_cl->props.protocol_version;
dev_dbg(dev->dev, "Can connect?\n");
@@ -460,6 +457,135 @@ static int mei_ioctl_connect_client(struct file *file,
return rets;
}

+/**
+ * mei_vt_support_check - check if client support vtags
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * @dev: mei_device
+ * @uuid: client UUID
+ *
+ * Return:
+ * 0 - supported
+ * -ENOTTY - no such client
+ * -EOPNOTSUPP - vtags are not supported by client
+ */
+static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid)
+{
+ struct mei_me_client *me_cl;
+ int ret;
+
+ if (!dev->hbm_f_vt_supported)
+ return -EOPNOTSUPP;
+
+ me_cl = mei_me_cl_by_uuid(dev, uuid);
+ if (!me_cl) {
+ dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
+ uuid);
+ return -ENOTTY;
+ }
+ ret = me_cl->props.vt_supported ? 0 : -EOPNOTSUPP;
+ mei_me_cl_put(me_cl);
+
+ return ret;
+}
+
+/**
+ * mei_ioctl_connect_vtag - connect to fw client with vtag IOCTL function
+ *
+ * @file: private data of the file object
+ * @in_client_uuid: requested UUID for connection
+ * @client: IOCTL connect data, output parameters
+ * @vtag: vm tag
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+static int mei_ioctl_connect_vtag(struct file *file,
+ const uuid_le *in_client_uuid,
+ struct mei_client *client,
+ u8 vtag)
+{
+ struct mei_device *dev;
+ struct mei_cl *cl;
+ struct mei_cl *pos;
+ struct mei_cl_vtag *cl_vtag;
+
+ cl = file->private_data;
+ dev = cl->dev;
+
+ dev_dbg(dev->dev, "FW Client %pUl vtag %d\n", in_client_uuid, vtag);
+
+ switch (cl->state) {
+ case MEI_FILE_DISCONNECTED:
+ if (mei_cl_vtag_by_fp(cl, file) != vtag) {
+ dev_err(dev->dev, "reconnect with different vtag\n");
+ return -EINVAL;
+ }
+ break;
+ case MEI_FILE_INITIALIZING:
+ /* malicious connect from another thread may push vtag */
+ if (!IS_ERR(mei_cl_fp_by_vtag(cl, vtag))) {
+ dev_err(dev->dev, "vtag already filled\n");
+ return -EINVAL;
+ }
+
+ list_for_each_entry(pos, &dev->file_list, link) {
+ if (pos == cl)
+ continue;
+ if (!pos->me_cl)
+ continue;
+
+ /* only search for same UUID */
+ if (uuid_le_cmp(*mei_cl_uuid(pos), *in_client_uuid))
+ continue;
+
+ /* if tag already exist try another fp */
+ if (!IS_ERR(mei_cl_fp_by_vtag(pos, vtag)))
+ continue;
+
+ /* replace cl with acquired one */
+ dev_dbg(dev->dev, "replacing with existing cl\n");
+ mei_cl_unlink(cl);
+ kfree(cl);
+ file->private_data = pos;
+ cl = pos;
+ break;
+ }
+
+ cl_vtag = mei_cl_vtag_alloc(file, vtag);
+ if (IS_ERR(cl_vtag))
+ return -ENOMEM;
+
+ list_add_tail(&cl_vtag->list, &cl->vtag_map);
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ while (cl->state != MEI_FILE_INITIALIZING &&
+ cl->state != MEI_FILE_DISCONNECTED &&
+ cl->state != MEI_FILE_CONNECTED) {
+ mutex_unlock(&dev->device_lock);
+ wait_event_timeout(cl->wait,
+ (cl->state == MEI_FILE_CONNECTED ||
+ cl->state == MEI_FILE_DISCONNECTED ||
+ cl->state == MEI_FILE_DISCONNECT_REQUIRED ||
+ cl->state == MEI_FILE_DISCONNECT_REPLY),
+ mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
+ mutex_lock(&dev->device_lock);
+ }
+
+ if (!mei_cl_is_connected(cl))
+ return mei_ioctl_connect_client(file, in_client_uuid, client);
+
+ client->max_msg_length = cl->me_cl->props.max_msg_length;
+ client->protocol_version = cl->me_cl->props.protocol_version;
+
+ return 0;
+}
+
/**
* mei_ioctl_client_notify_request -
* propagate event notification request to client
@@ -516,7 +642,11 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
{
struct mei_device *dev;
struct mei_cl *cl = file->private_data;
- struct mei_connect_client_data connect_data;
+ struct mei_connect_client_data conn;
+ struct mei_connect_client_data_vtag conn_vtag;
+ const uuid_le *cl_uuid;
+ struct mei_client *props;
+ u8 vtag;
u32 notify_get, notify_req;
int rets;

@@ -537,20 +667,68 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
switch (cmd) {
case IOCTL_MEI_CONNECT_CLIENT:
dev_dbg(dev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n");
- if (copy_from_user(&connect_data, (char __user *)data,
- sizeof(connect_data))) {
+ if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) {
+ dev_dbg(dev->dev, "failed to copy data from userland\n");
+ rets = -EFAULT;
+ goto out;
+ }
+ cl_uuid = &conn.in_client_uuid;
+ props = &conn.out_client_properties;
+ vtag = 0;
+
+ rets = mei_vt_support_check(dev, cl_uuid);
+ if (rets == -ENOTTY)
+ goto out;
+ if (!rets)
+ rets = mei_ioctl_connect_vtag(file, cl_uuid, props,
+ vtag);
+ else
+ rets = mei_ioctl_connect_client(file, cl_uuid, props);
+ if (rets)
+ goto out;
+
+ /* if all is ok, copying the data back to user. */
+ if (copy_to_user((char __user *)data, &conn, sizeof(conn))) {
+ dev_dbg(dev->dev, "failed to copy data to userland\n");
+ rets = -EFAULT;
+ goto out;
+ }
+
+ break;
+
+ case IOCTL_MEI_CONNECT_CLIENT_VTAG:
+ dev_dbg(dev->dev, "IOCTL_MEI_CONNECT_CLIENT_VTAG\n");
+ if (copy_from_user(&conn_vtag, (char __user *)data,
+ sizeof(conn_vtag))) {
dev_dbg(dev->dev, "failed to copy data from userland\n");
rets = -EFAULT;
goto out;
}

- rets = mei_ioctl_connect_client(file, &connect_data);
+ cl_uuid = &conn_vtag.connect.in_client_uuid;
+ props = &conn_vtag.out_client_properties;
+ vtag = conn_vtag.connect.vtag;
+
+ rets = mei_vt_support_check(dev, cl_uuid);
+ if (rets == -EOPNOTSUPP)
+ dev_dbg(dev->dev, "FW Client %pUl does not support vtags\n",
+ cl_uuid);
+ if (rets)
+ goto out;
+
+ if (!vtag) {
+ dev_dbg(dev->dev, "vtag can't be zero\n");
+ rets = -EINVAL;
+ goto out;
+ }
+
+ rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag);
if (rets)
goto out;

/* if all is ok, copying the data back to user. */
- if (copy_to_user((char __user *)data, &connect_data,
- sizeof(connect_data))) {
+ if (copy_to_user((char __user *)data, &conn_vtag,
+ sizeof(conn_vtag))) {
dev_dbg(dev->dev, "failed to copy data to userland\n");
rets = -EFAULT;
goto out;
diff --git a/include/uapi/linux/mei.h b/include/uapi/linux/mei.h
index c6aec86cc5de..4f3638489d01 100644
--- a/include/uapi/linux/mei.h
+++ b/include/uapi/linux/mei.h
@@ -66,4 +66,53 @@ struct mei_connect_client_data {
*/
#define IOCTL_MEI_NOTIFY_GET _IOR('H', 0x03, __u32)

+/**
+ * struct mei_connect_client_vtag - mei client information struct with vtag
+ *
+ * @in_client_uuid: UUID of client to connect
+ * @vtag: virtual tag
+ * @reserved: reserved for future use
+ */
+struct mei_connect_client_vtag {
+ uuid_le in_client_uuid;
+ __u8 vtag;
+ __u8 reserved[3];
+};
+
+/**
+ * struct mei_connect_client_data_vtag - IOCTL connect data union
+ *
+ * @connect: input connect data
+ * @out_client_properties: output client data
+ */
+struct mei_connect_client_data_vtag {
+ union {
+ struct mei_connect_client_vtag connect;
+ struct mei_client out_client_properties;
+ };
+};
+
+/**
+ * DOC:
+ * This IOCTL is used to associate the current file descriptor with a
+ * FW Client (given by UUID), and virtual tag (vtag).
+ * The IOCTL opens a communication channel between a host client and
+ * a FW client on a tagged channel. From this point on, every read
+ * and write will communicate with the associated FW client with
+ * on the tagged channel.
+ * Upone close() the communication is terminated.
+ *
+ * The IOCTL argument is a struct with a union that contains
+ * the input parameter and the output parameter for this IOCTL.
+ *
+ * The input parameter is UUID of the FW Client, a vtag [0,255]
+ * The output parameter is the properties of the FW client
+ * (FW protocool version and max message size).
+ *
+ * Clients that do not support tagged connection
+ * will respond with -EOPNOTSUPP.
+ */
+#define IOCTL_MEI_CONNECT_CLIENT_VTAG \
+ _IOWR('H', 0x04, struct mei_connect_client_data_vtag)
+
#endif /* _LINUX_MEI_H */
--
2.25.4

2020-08-18 12:14:36

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

This frontend driver implements MEI hw interface based on virtio
framework to let MEI driver work without changes under virtualization.
It requires a backend service in the ACRN device-model on the service
OS side to make it work. The backend service will emulate mei routing
and assign vtags for each mei vritio device.

The backend service is available in ACRN device-model at github.
For more information, please refer to https://projectacrn.org

The ACRN virtio sub device id for MEI is is 0x8602.

Signed-off-by: Tomas Winkler <[email protected]>
Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Wang Yu <[email protected]>
Signed-off-by: Liu Shuo <[email protected]>
---
drivers/misc/mei/Kconfig | 10 +
drivers/misc/mei/Makefile | 3 +
drivers/misc/mei/hw-virtio.c | 874 +++++++++++++++++++++++++++++++++++
3 files changed, 887 insertions(+)
create mode 100644 drivers/misc/mei/hw-virtio.c

diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig
index f5fd5b786607..c06581ffa7bd 100644
--- a/drivers/misc/mei/Kconfig
+++ b/drivers/misc/mei/Kconfig
@@ -46,4 +46,14 @@ config INTEL_MEI_TXE
Supported SoCs:
Intel Bay Trail

+config INTEL_MEI_VIRTIO
+ tristate "Intel MEI interface emulation with virtio framework"
+ select INTEL_MEI
+ depends on X86 && PCI && VIRTIO_PCI
+ help
+ This module implements mei hw emulation over virtio transport.
+ The module will be called mei_virtio.
+ Enable this if your virtual machine supports virtual mei
+ device over virtio.
+
source "drivers/misc/mei/hdcp/Kconfig"
diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile
index f1c76f7ee804..52aefaab5c1b 100644
--- a/drivers/misc/mei/Makefile
+++ b/drivers/misc/mei/Makefile
@@ -22,6 +22,9 @@ obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
mei-txe-objs := pci-txe.o
mei-txe-objs += hw-txe.o

+obj-$(CONFIG_INTEL_MEI_VIRTIO) += mei-virtio.o
+mei-virtio-objs := hw-virtio.o
+
mei-$(CONFIG_EVENT_TRACING) += mei-trace.o
CFLAGS_mei-trace.o = -I$(src)

diff --git a/drivers/misc/mei/hw-virtio.c b/drivers/misc/mei/hw-virtio.c
new file mode 100644
index 000000000000..899dc1c5e7ca
--- /dev/null
+++ b/drivers/misc/mei/hw-virtio.c
@@ -0,0 +1,874 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2018-2020, Intel Corporation.
+ */
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ids.h>
+#include <linux/atomic.h>
+
+#include "mei_dev.h"
+#include "hbm.h"
+#include "client.h"
+
+#define MEI_VIRTIO_RPM_TIMEOUT 500
+/* ACRN virtio device types */
+#ifndef VIRTIO_ID_MEI
+#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */
+#endif
+
+/**
+ * struct mei_virtio_cfg - settings passed from the virtio backend
+ * @buf_depth: read buffer depth in slots (4bytes)
+ * @hw_ready: hw is ready for operation
+ * @host_reset: synchronize reset with virtio backend
+ * @reserved: reserved for alignment
+ * @fw_status: FW status
+ */
+struct mei_virtio_cfg {
+ u32 buf_depth;
+ u8 hw_ready;
+ u8 host_reset;
+ u8 reserved[2];
+ u32 fw_status[MEI_FW_STATUS_MAX];
+} __packed;
+
+struct mei_virtio_hw {
+ struct mei_device mdev;
+ char name[32];
+
+ struct virtqueue *in;
+ struct virtqueue *out;
+
+ bool host_ready;
+ struct work_struct intr_handler;
+
+ u32 *recv_buf;
+ u8 recv_rdy;
+ size_t recv_sz;
+ u32 recv_idx;
+ u32 recv_len;
+
+ /* send buffer */
+ atomic_t hbuf_ready;
+ const void *send_hdr;
+ const void *send_buf;
+
+ struct mei_virtio_cfg cfg;
+};
+
+#define to_virtio_hw(_dev) container_of(_dev, struct mei_virtio_hw, mdev)
+
+/**
+ * mei_virtio_fw_status() - read status register of mei
+ * @dev: mei device
+ * @fw_status: fw status register values
+ *
+ * Return: always 0
+ */
+static int mei_virtio_fw_status(struct mei_device *dev,
+ struct mei_fw_status *fw_status)
+{
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ fw_status->count = MEI_FW_STATUS_MAX;
+ virtio_cread_bytes(vdev, offsetof(struct mei_virtio_cfg, fw_status),
+ fw_status->status, sizeof(fw_status->status));
+ return 0;
+}
+
+/**
+ * mei_virtio_pg_state() - translate internal pg state
+ * to the mei power gating state
+ * There is no power management in ACRN mode always return OFF
+ * @dev: mei device
+ *
+ * Return:
+ * * MEI_PG_OFF - if aliveness is on (always)
+ * * MEI_PG_ON - (never)
+ */
+static inline enum mei_pg_state mei_virtio_pg_state(struct mei_device *dev)
+{
+ return MEI_PG_OFF;
+}
+
+/**
+ * mei_virtio_hw_config() - configure hw dependent settings
+ *
+ * @dev: mei device
+ *
+ * Return: always 0
+ */
+static int mei_virtio_hw_config(struct mei_device *dev)
+{
+ return 0;
+}
+
+/**
+ * mei_virtio_hbuf_empty_slots() - counts write empty slots.
+ * @dev: the device structure
+ *
+ * Return: always return frontend buf size if buffer is ready, 0 otherwise
+ */
+static int mei_virtio_hbuf_empty_slots(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ return (atomic_read(&hw->hbuf_ready) == 1) ? hw->cfg.buf_depth : 0;
+}
+
+/**
+ * mei_virtio_hbuf_is_ready() - checks if write buffer is ready
+ * @dev: the device structure
+ *
+ * Return: true if hbuf is ready
+ */
+static bool mei_virtio_hbuf_is_ready(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ return atomic_read(&hw->hbuf_ready) == 1;
+}
+
+/**
+ * mei_virtio_hbuf_max_depth() - returns depth of FE write buffer.
+ * @dev: the device structure
+ *
+ * Return: size of frontend write buffer in bytes
+ */
+static u32 mei_virtio_hbuf_depth(const struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ return hw->cfg.buf_depth;
+}
+
+/**
+ * mei_virtio_intr_clear() - clear and stop interrupts
+ * @dev: the device structure
+ */
+static void mei_virtio_intr_clear(struct mei_device *dev)
+{
+ /*
+ * In our virtio solution, there are two types of interrupts,
+ * vq interrupt and config change interrupt.
+ * 1) start/reset rely on virtio config changed interrupt;
+ * 2) send/recv rely on virtio virtqueue interrupts.
+ * They are all virtual interrupts. So, we don't have corresponding
+ * operation to do here.
+ */
+}
+
+/**
+ * mei_virtio_intr_enable() - enables mei BE virtqueues callbacks
+ * @dev: the device structure
+ */
+static void mei_virtio_intr_enable(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ virtio_config_enable(vdev);
+
+ virtqueue_enable_cb(hw->in);
+ virtqueue_enable_cb(hw->out);
+}
+
+/**
+ * mei_virtio_intr_disable() - disables mei BE virtqueues callbacks
+ *
+ * @dev: the device structure
+ */
+static void mei_virtio_intr_disable(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ virtio_config_disable(vdev);
+
+ virtqueue_disable_cb(hw->in);
+ virtqueue_disable_cb(hw->out);
+}
+
+/**
+ * mei_virtio_synchronize_irq() - wait for pending IRQ handlers for all
+ * virtqueue
+ * @dev: the device structure
+ */
+static void mei_virtio_synchronize_irq(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ /*
+ * Now, all IRQ handlers are converted to workqueue.
+ * Change synchronize irq to flush this work.
+ */
+ flush_work(&hw->intr_handler);
+}
+
+static void mei_virtio_free_outbufs(struct mei_virtio_hw *hw)
+{
+ kfree(hw->send_hdr);
+ kfree(hw->send_buf);
+ hw->send_hdr = NULL;
+ hw->send_buf = NULL;
+}
+
+/**
+ * mei_virtio_write_message() - writes a message to mei virtio back-end service.
+ * @dev: the device structure
+ * @hdr: mei header of message
+ * @hdr_len: header length
+ * @data: message payload will be written
+ * @data_len: message payload length
+ *
+ * Return:
+ * * 0: on success
+ * * -EIO: if write has failed
+ * * -ENOMEM: on memory allocation failure
+ */
+static int mei_virtio_write_message(struct mei_device *dev,
+ const void *hdr, size_t hdr_len,
+ const void *data, size_t data_len)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct scatterlist sg[2];
+ const void *hbuf, *dbuf;
+ int ret;
+
+ if (WARN_ON(!atomic_add_unless(&hw->hbuf_ready, -1, 0)))
+ return -EIO;
+
+ hbuf = kmemdup(hdr, hdr_len, GFP_KERNEL);
+ hw->send_hdr = hbuf;
+
+ dbuf = kmemdup(data, data_len, GFP_KERNEL);
+ hw->send_buf = dbuf;
+
+ if (!hbuf || !dbuf) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ sg_init_table(sg, 2);
+ sg_set_buf(&sg[0], hbuf, hdr_len);
+ sg_set_buf(&sg[1], dbuf, data_len);
+
+ ret = virtqueue_add_outbuf(hw->out, sg, 2, hw, GFP_KERNEL);
+ if (ret) {
+ dev_err(dev->dev, "failed to add outbuf\n");
+ goto fail;
+ }
+
+ virtqueue_kick(hw->out);
+ return 0;
+fail:
+
+ mei_virtio_free_outbufs(hw);
+
+ return ret;
+}
+
+/**
+ * mei_virtio_count_full_read_slots() - counts read full slots.
+ * @dev: the device structure
+ *
+ * Return: -EOVERFLOW if overflow, otherwise filled slots count
+ */
+static int mei_virtio_count_full_read_slots(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ if (hw->recv_idx > hw->recv_len)
+ return -EOVERFLOW;
+
+ return hw->recv_len - hw->recv_idx;
+}
+
+/**
+ * mei_virtio_read_hdr() - Reads 32bit dword from mei virtio receive buffer
+ *
+ * @dev: the device structure
+ *
+ * Return: 32bit dword of receive buffer (u32)
+ */
+static inline u32 mei_virtio_read_hdr(const struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ WARN_ON(hw->cfg.buf_depth < hw->recv_idx + 1);
+
+ return hw->recv_buf[hw->recv_idx++];
+}
+
+static int mei_virtio_read(struct mei_device *dev, unsigned char *buffer,
+ unsigned long len)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ u32 slots = mei_data2slots(len);
+
+ if (WARN_ON(hw->cfg.buf_depth < hw->recv_idx + slots))
+ return -EOVERFLOW;
+
+ /*
+ * Assumption: There is only one MEI message in recv_buf each time.
+ * Backend service need follow this rule too.
+ */
+ memcpy(buffer, hw->recv_buf + hw->recv_idx, len);
+ hw->recv_idx += slots;
+
+ return 0;
+}
+
+static bool mei_virtio_pg_is_enabled(struct mei_device *dev)
+{
+ return false;
+}
+
+static bool mei_virtio_pg_in_transition(struct mei_device *dev)
+{
+ return false;
+}
+
+static void mei_virtio_add_recv_buf(struct mei_virtio_hw *hw)
+{
+ struct scatterlist sg;
+
+ if (hw->recv_rdy) /* not needed */
+ return;
+
+ /* refill the recv_buf to IN virtqueue to get next message */
+ sg_init_one(&sg, hw->recv_buf, mei_slots2data(hw->cfg.buf_depth));
+ hw->recv_len = 0;
+ hw->recv_idx = 0;
+ hw->recv_rdy = 1;
+ virtqueue_add_inbuf(hw->in, &sg, 1, hw->recv_buf, GFP_KERNEL);
+ virtqueue_kick(hw->in);
+}
+
+/**
+ * mei_virtio_hw_is_ready() - check whether the BE(hw) has turned ready
+ * @dev: mei device
+ * Return: bool
+ */
+static bool mei_virtio_hw_is_ready(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ virtio_cread(vdev, struct mei_virtio_cfg,
+ hw_ready, &hw->cfg.hw_ready);
+
+ dev_dbg(dev->dev, "hw ready %d\n", hw->cfg.hw_ready);
+
+ return hw->cfg.hw_ready;
+}
+
+/**
+ * mei_virtio_hw_reset - resets virtio hw.
+ *
+ * @dev: the device structure
+ * @intr_enable: virtio use data/config callbacks
+ *
+ * Return: 0 on success an error code otherwise
+ */
+static int mei_virtio_hw_reset(struct mei_device *dev, bool intr_enable)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ dev_dbg(dev->dev, "hw reset\n");
+
+ dev->recvd_hw_ready = false;
+ hw->host_ready = false;
+ atomic_set(&hw->hbuf_ready, 0);
+ hw->recv_len = 0;
+ hw->recv_idx = 0;
+
+ hw->cfg.host_reset = 1;
+ virtio_cwrite(vdev, struct mei_virtio_cfg,
+ host_reset, &hw->cfg.host_reset);
+
+ mei_virtio_hw_is_ready(dev);
+
+ if (intr_enable)
+ mei_virtio_intr_enable(dev);
+
+ return 0;
+}
+
+/**
+ * mei_virtio_hw_reset_release() - release device from the reset
+ * @dev: the device structure
+ */
+static void mei_virtio_hw_reset_release(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev->dev);
+
+ dev_dbg(dev->dev, "hw reset release\n");
+ hw->cfg.host_reset = 0;
+ virtio_cwrite(vdev, struct mei_virtio_cfg,
+ host_reset, &hw->cfg.host_reset);
+}
+
+/**
+ * mei_virtio_hw_ready_wait() - wait until the virtio(hw) has turned ready
+ * or timeout is reached
+ * @dev: mei device
+ *
+ * Return: 0 on success, error otherwise
+ */
+static int mei_virtio_hw_ready_wait(struct mei_device *dev)
+{
+ mutex_unlock(&dev->device_lock);
+ wait_event_timeout(dev->wait_hw_ready,
+ dev->recvd_hw_ready,
+ mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT));
+ mutex_lock(&dev->device_lock);
+ if (!dev->recvd_hw_ready) {
+ dev_err(dev->dev, "wait hw ready failed\n");
+ return -ETIMEDOUT;
+ }
+
+ dev->recvd_hw_ready = false;
+ return 0;
+}
+
+/**
+ * mei_virtio_hw_start() - hw start routine
+ * @dev: mei device
+ *
+ * Return: 0 on success, error otherwise
+ */
+static int mei_virtio_hw_start(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+ int ret;
+
+ dev_dbg(dev->dev, "hw start\n");
+ mei_virtio_hw_reset_release(dev);
+
+ ret = mei_virtio_hw_ready_wait(dev);
+ if (ret)
+ return ret;
+
+ mei_virtio_add_recv_buf(hw);
+ atomic_set(&hw->hbuf_ready, 1);
+ dev_dbg(dev->dev, "hw is ready\n");
+ hw->host_ready = true;
+
+ return 0;
+}
+
+/**
+ * mei_virtio_host_is_ready() - check whether the FE has turned ready
+ * @dev: mei device
+ *
+ * Return: bool
+ */
+static bool mei_virtio_host_is_ready(struct mei_device *dev)
+{
+ struct mei_virtio_hw *hw = to_virtio_hw(dev);
+
+ dev_dbg(dev->dev, "host ready %d\n", hw->host_ready);
+
+ return hw->host_ready;
+}
+
+/**
+ * mei_virtio_data_in() - The callback of recv virtqueue of virtio mei
+ * @vq: receiving virtqueue
+ */
+static void mei_virtio_data_in(struct virtqueue *vq)
+{
+ struct mei_virtio_hw *hw = vq->vdev->priv;
+
+ /* disable interrupts (enabled again from in the interrupt worker) */
+ virtqueue_disable_cb(hw->in);
+
+ schedule_work(&hw->intr_handler);
+}
+
+/**
+ * mei_virtio_data_out() - The callback of send virtqueue of virtio mei
+ * @vq: transmitting virtqueue
+ */
+static void mei_virtio_data_out(struct virtqueue *vq)
+{
+ struct mei_virtio_hw *hw = vq->vdev->priv;
+
+ schedule_work(&hw->intr_handler);
+}
+
+static void mei_virtio_intr_handler(struct work_struct *work)
+{
+ struct mei_virtio_hw *hw =
+ container_of(work, struct mei_virtio_hw, intr_handler);
+ struct mei_device *dev = &hw->mdev;
+ LIST_HEAD(complete_list);
+ s32 slots;
+ int rets = 0;
+ void *data;
+ unsigned int len;
+
+ mutex_lock(&dev->device_lock);
+
+ if (dev->dev_state == MEI_DEV_DISABLED) {
+ dev_warn(dev->dev, "Interrupt in disabled state.\n");
+ mei_virtio_intr_disable(dev);
+ goto end;
+ }
+
+ /* check if ME wants a reset */
+ if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) {
+ dev_warn(dev->dev, "BE service not ready: resetting.\n");
+ schedule_work(&dev->reset_work);
+ goto end;
+ }
+
+ /* check if we need to start the dev */
+ if (!mei_host_is_ready(dev)) {
+ if (mei_hw_is_ready(dev)) {
+ dev_dbg(dev->dev, "we need to start the dev.\n");
+ dev->recvd_hw_ready = true;
+ wake_up(&dev->wait_hw_ready);
+ } else {
+ dev_warn(dev->dev, "Spurious Interrupt\n");
+ }
+ goto end;
+ }
+
+ /* read */
+ if (hw->recv_rdy) {
+ data = virtqueue_get_buf(hw->in, &len);
+ if (!data || !len) {
+ dev_dbg(dev->dev, "No data %d", len);
+ } else {
+ dev_dbg(dev->dev, "data_in %d\n", len);
+ WARN_ON(data != hw->recv_buf);
+ hw->recv_len = mei_data2slots(len);
+ hw->recv_rdy = 0;
+ }
+ }
+
+ /* write */
+ if (!atomic_read(&hw->hbuf_ready)) {
+ if (!virtqueue_get_buf(hw->out, &len)) {
+ dev_warn(dev->dev, "Failed to getbuf\n");
+ } else {
+ mei_virtio_free_outbufs(hw);
+ atomic_inc(&hw->hbuf_ready);
+ }
+ }
+
+ /* check slots available for reading */
+ slots = mei_count_full_read_slots(dev);
+ while (slots > 0) {
+ dev_dbg(dev->dev, "slots to read = %08x\n", slots);
+ rets = mei_irq_read_handler(dev, &complete_list, &slots);
+
+ if (rets &&
+ (dev->dev_state != MEI_DEV_RESETTING &&
+ dev->dev_state != MEI_DEV_POWER_DOWN)) {
+ dev_err(dev->dev, "mei_irq_read_handler ret = %d.\n",
+ rets);
+ schedule_work(&dev->reset_work);
+ goto end;
+ }
+ }
+
+ dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
+
+ mei_irq_write_handler(dev, &complete_list);
+
+ dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
+
+ mei_irq_compl_handler(dev, &complete_list);
+
+ mei_virtio_add_recv_buf(hw);
+
+end:
+ if (dev->dev_state != MEI_DEV_DISABLED) {
+ if (!virtqueue_enable_cb(hw->in))
+ schedule_work(&hw->intr_handler);
+ }
+
+ mutex_unlock(&dev->device_lock);
+}
+
+static void mei_virtio_config_changed(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw = vdev->priv;
+ struct mei_device *dev = &hw->mdev;
+
+ virtio_cread(vdev, struct mei_virtio_cfg,
+ hw_ready, &hw->cfg.hw_ready);
+
+ if (dev->dev_state == MEI_DEV_DISABLED) {
+ dev_dbg(dev->dev, "disabled state don't start\n");
+ return;
+ }
+
+ /* Run intr handler once to handle reset notify */
+ schedule_work(&hw->intr_handler);
+}
+
+static void mei_virtio_remove_vqs(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw = vdev->priv;
+
+ virtqueue_detach_unused_buf(hw->in);
+ hw->recv_len = 0;
+ hw->recv_idx = 0;
+ hw->recv_rdy = 0;
+
+ virtqueue_detach_unused_buf(hw->out);
+
+ mei_virtio_free_outbufs(hw);
+
+ vdev->config->del_vqs(vdev);
+}
+
+/*
+ * There are two virtqueues, one is for send and another is for recv.
+ */
+static int mei_virtio_init_vqs(struct mei_virtio_hw *hw,
+ struct virtio_device *vdev)
+{
+ struct virtqueue *vqs[2];
+
+ vq_callback_t *cbs[] = {
+ mei_virtio_data_in,
+ mei_virtio_data_out,
+ };
+ static const char * const names[] = {
+ "in",
+ "out",
+ };
+ int ret;
+
+ ret = virtio_find_vqs(vdev, 2, vqs, cbs, names, NULL);
+ if (ret)
+ return ret;
+
+ hw->in = vqs[0];
+ hw->out = vqs[1];
+
+ return 0;
+}
+
+static const struct mei_hw_ops mei_virtio_ops = {
+ .fw_status = mei_virtio_fw_status,
+ .pg_state = mei_virtio_pg_state,
+
+ .host_is_ready = mei_virtio_host_is_ready,
+
+ .hw_is_ready = mei_virtio_hw_is_ready,
+ .hw_reset = mei_virtio_hw_reset,
+ .hw_config = mei_virtio_hw_config,
+ .hw_start = mei_virtio_hw_start,
+
+ .pg_in_transition = mei_virtio_pg_in_transition,
+ .pg_is_enabled = mei_virtio_pg_is_enabled,
+
+ .intr_clear = mei_virtio_intr_clear,
+ .intr_enable = mei_virtio_intr_enable,
+ .intr_disable = mei_virtio_intr_disable,
+ .synchronize_irq = mei_virtio_synchronize_irq,
+
+ .hbuf_free_slots = mei_virtio_hbuf_empty_slots,
+ .hbuf_is_ready = mei_virtio_hbuf_is_ready,
+ .hbuf_depth = mei_virtio_hbuf_depth,
+
+ .write = mei_virtio_write_message,
+
+ .rdbuf_full_slots = mei_virtio_count_full_read_slots,
+ .read_hdr = mei_virtio_read_hdr,
+ .read = mei_virtio_read,
+};
+
+static int mei_virtio_probe(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw;
+ int ret;
+
+ hw = devm_kzalloc(&vdev->dev, sizeof(*hw), GFP_KERNEL);
+ if (!hw)
+ return -ENOMEM;
+
+ vdev->priv = hw;
+
+ INIT_WORK(&hw->intr_handler, mei_virtio_intr_handler);
+
+ ret = mei_virtio_init_vqs(hw, vdev);
+ if (ret)
+ goto vqs_failed;
+
+ virtio_cread(vdev, struct mei_virtio_cfg,
+ buf_depth, &hw->cfg.buf_depth);
+
+ hw->recv_buf = kzalloc(mei_slots2data(hw->cfg.buf_depth), GFP_KERNEL);
+ if (!hw->recv_buf) {
+ ret = -ENOMEM;
+ goto hbuf_failed;
+ }
+ atomic_set(&hw->hbuf_ready, 0);
+
+ virtio_device_ready(vdev);
+
+ mei_device_init(&hw->mdev, &vdev->dev, &mei_virtio_ops);
+
+ pm_runtime_get_noresume(&vdev->dev);
+ pm_runtime_set_active(&vdev->dev);
+ pm_runtime_enable(&vdev->dev);
+
+ ret = mei_start(&hw->mdev);
+ if (ret)
+ goto mei_start_failed;
+
+ pm_runtime_set_autosuspend_delay(&vdev->dev, MEI_VIRTIO_RPM_TIMEOUT);
+ pm_runtime_use_autosuspend(&vdev->dev);
+
+ ret = mei_register(&hw->mdev, &vdev->dev);
+ if (ret)
+ goto mei_failed;
+
+ pm_runtime_put(&vdev->dev);
+
+ return 0;
+
+mei_failed:
+ mei_stop(&hw->mdev);
+mei_start_failed:
+ mei_cancel_work(&hw->mdev);
+ mei_disable_interrupts(&hw->mdev);
+ kfree(hw->recv_buf);
+hbuf_failed:
+ vdev->config->del_vqs(vdev);
+vqs_failed:
+ return ret;
+}
+
+static int __maybe_unused mei_virtio_pm_runtime_idle(struct device *device)
+{
+ struct virtio_device *vdev = dev_to_virtio(device);
+ struct mei_virtio_hw *hw = vdev->priv;
+
+ dev_dbg(&vdev->dev, "rpm: mei_virtio : runtime_idle\n");
+
+ if (!hw)
+ return -ENODEV;
+
+ if (mei_write_is_idle(&hw->mdev))
+ pm_runtime_autosuspend(device);
+
+ return -EBUSY;
+}
+
+static int __maybe_unused mei_virtio_pm_runtime_suspend(struct device *device)
+{
+ return 0;
+}
+
+static int __maybe_unused mei_virtio_pm_runtime_resume(struct device *device)
+{
+ return 0;
+}
+
+static int __maybe_unused mei_virtio_freeze(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw = vdev->priv;
+
+ dev_dbg(&vdev->dev, "freeze\n");
+
+ if (!hw)
+ return -ENODEV;
+
+ mei_stop(&hw->mdev);
+ mei_disable_interrupts(&hw->mdev);
+ cancel_work_sync(&hw->intr_handler);
+ vdev->config->reset(vdev);
+ mei_virtio_remove_vqs(vdev);
+
+ return 0;
+}
+
+static int __maybe_unused mei_virtio_restore(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw = vdev->priv;
+ int ret;
+
+ dev_dbg(&vdev->dev, "restore\n");
+
+ if (!hw)
+ return -ENODEV;
+
+ ret = mei_virtio_init_vqs(hw, vdev);
+ if (ret)
+ return ret;
+
+ virtio_device_ready(vdev);
+
+ ret = mei_restart(&hw->mdev);
+ if (ret)
+ return ret;
+
+ /* Start timer if stopped in suspend */
+ schedule_delayed_work(&hw->mdev.timer_work, HZ);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mei_virtio_pm_ops = {
+ SET_RUNTIME_PM_OPS(mei_virtio_pm_runtime_suspend,
+ mei_virtio_pm_runtime_resume,
+ mei_virtio_pm_runtime_idle)
+};
+
+static void mei_virtio_remove(struct virtio_device *vdev)
+{
+ struct mei_virtio_hw *hw = vdev->priv;
+
+ mei_stop(&hw->mdev);
+ mei_disable_interrupts(&hw->mdev);
+ cancel_work_sync(&hw->intr_handler);
+ mei_deregister(&hw->mdev);
+ vdev->config->reset(vdev);
+ mei_virtio_remove_vqs(vdev);
+ kfree(hw->recv_buf);
+ pm_runtime_disable(&vdev->dev);
+}
+
+static struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_MEI, VIRTIO_DEV_ANY_ID },
+ { }
+};
+
+static struct virtio_driver mei_virtio_driver = {
+ .id_table = id_table,
+ .probe = mei_virtio_probe,
+ .remove = mei_virtio_remove,
+ .config_changed = mei_virtio_config_changed,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .pm = &mei_virtio_pm_ops,
+ },
+#ifdef CONFIG_PM_SLEEP
+ .freeze = mei_virtio_freeze,
+ .restore = mei_virtio_restore,
+#endif
+};
+
+module_virtio_driver(mei_virtio_driver);
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Virtio MEI frontend driver");
+MODULE_LICENSE("GPL v2");
--
2.25.4

2020-08-18 12:15:11

by Tomas Winkler

[permalink] [raw]
Subject: [char-misc-next 09/13] mei: bus: use zero vtag for bus clients.

From: Alexander Usyskin <[email protected]>

The zero vtag is required for the read flow to work also for
devices on the mei client bus.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
---
drivers/misc/mei/bus.c | 72 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index fc20a0da5c24..9cdaa7f3af23 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -495,6 +495,68 @@ static void mei_cl_bus_module_put(struct mei_cl_device *cldev)
module_put(cldev->bus->dev->driver->owner);
}

+/**
+ * mei_cl_bus_vtag - get bus vtag entry wrapper
+ * The tag for bus client is always first.
+ *
+ * @cl: host client
+ *
+ * Return: bus vtag or NULL
+ */
+static inline struct mei_cl_vtag *mei_cl_bus_vtag(struct mei_cl *cl)
+{
+ return list_first_entry_or_null(&cl->vtag_map,
+ struct mei_cl_vtag, list);
+}
+
+/**
+ * mei_cl_bus_vtag_alloc - add bus client entry to vtag map
+ *
+ * @cldev: me client device
+ *
+ * Return:
+ * * 0 on success
+ * * -ENOMEM if memory allocation failed
+ */
+static int mei_cl_bus_vtag_alloc(struct mei_cl_device *cldev)
+{
+ struct mei_cl *cl = cldev->cl;
+ struct mei_cl_vtag *cl_vtag;
+
+ /*
+ * Bail out if the client does not supports vtags
+ * or has already allocated one
+ */
+ if (mei_cl_vt_support_check(cl) || mei_cl_bus_vtag(cl))
+ return 0;
+
+ cl_vtag = mei_cl_vtag_alloc(NULL, 0);
+ if (IS_ERR(cl_vtag))
+ return -ENOMEM;
+
+ list_add_tail(&cl_vtag->list, &cl->vtag_map);
+
+ return 0;
+}
+
+/**
+ * mei_cl_bus_vtag_free - remove the bus entry from vtag map
+ *
+ * @cldev: me client device
+ */
+static void mei_cl_bus_vtag_free(struct mei_cl_device *cldev)
+{
+ struct mei_cl *cl = cldev->cl;
+ struct mei_cl_vtag *cl_vtag;
+
+ cl_vtag = mei_cl_bus_vtag(cl);
+ if (!cl_vtag)
+ return;
+
+ list_del(&cl_vtag->list);
+ kfree(cl_vtag);
+}
+
/**
* mei_cldev_enable - enable me client device
* create connection with me client
@@ -531,9 +593,15 @@ int mei_cldev_enable(struct mei_cl_device *cldev)
goto out;
}

+ ret = mei_cl_bus_vtag_alloc(cldev);
+ if (ret)
+ goto out;
+
ret = mei_cl_connect(cl, cldev->me_cl, NULL);
- if (ret < 0)
+ if (ret < 0) {
dev_err(&cldev->dev, "cannot connect\n");
+ mei_cl_bus_vtag_free(cldev);
+ }

out:
mutex_unlock(&bus->device_lock);
@@ -586,6 +654,8 @@ int mei_cldev_disable(struct mei_cl_device *cldev)

mutex_lock(&bus->device_lock);

+ mei_cl_bus_vtag_free(cldev);
+
if (!mei_cl_is_connected(cl)) {
dev_dbg(bus->dev, "Already disconnected\n");
err = 0;
--
2.25.4

2020-11-25 23:18:57

by Michael S. Tsirkin

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> +#ifndef VIRTIO_ID_MEI
> +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */
> +#endif

Just noticed now that this driver landed upstream. Can I ask that you
guys please register IDs with the virtio TC and not just pick a number
at random? In particular this is way outside allowed range.

IDs should also be listed in include/uapi/linux/virtio_ids.h

If people just pick random numbers like this collistions are unavoidable.

List of IDs is part of virtio spec, chapter "Device Types".

Please do this change now before this goes out to production!

Thanks!

--
MST

2020-11-25 23:27:29

by Tomas Winkler

[permalink] [raw]
Subject: RE: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

>
> On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > +#ifndef VIRTIO_ID_MEI
> > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
>
> Just noticed now that this driver landed upstream. Can I ask that you guys
> please register IDs with the virtio TC and not just pick a number at random?
> In particular this is way outside allowed range.
>
> IDs should also be listed in include/uapi/linux/virtio_ids.h
>
> If people just pick random numbers like this collistions are unavoidable.
>
> List of IDs is part of virtio spec, chapter "Device Types".
>
> Please do this change now before this goes out to production!
Okay, this was assigned by ACRN, my impression was it's already registered.
Will take care of.
Thanks
Tomas

2020-11-26 11:40:51

by Michael S. Tsirkin

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Wed, Nov 25, 2020 at 09:18:04PM +0000, Winkler, Tomas wrote:
> >
> > On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > > +#ifndef VIRTIO_ID_MEI
> > > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
> >
> > Just noticed now that this driver landed upstream. Can I ask that you guys
> > please register IDs with the virtio TC and not just pick a number at random?
> > In particular this is way outside allowed range.
> >
> > IDs should also be listed in include/uapi/linux/virtio_ids.h
> >
> > If people just pick random numbers like this collistions are unavoidable.
> >
> > List of IDs is part of virtio spec, chapter "Device Types".
> >
> > Please do this change now before this goes out to production!
> Okay, this was assigned by ACRN, my impression was it's already registered.
> Will take care of.
> Thanks
> Tomas

Found this:
https://projectacrn.github.io/latest/developer-guides/hld/hld-virtio-devices.html

Supported Virtio Devices
All the BE virtio drivers are implemented using the ACRN virtio APIs, and the FE drivers are reusing the standard Linux FE virtio drivers. For the devices with FE drivers available in the Linux kernel, they should use standard virtio Vendor ID/Device ID and Subsystem Vendor ID/Subsystem Device ID. For other devices within ACRN, their temporary IDs are listed in the following table.

Table 5 Virtio Devices without existing FE drivers in Linux
virtio device Vendor ID Device ID Subvendor ID Subdevice ID
RPMB 0x8086 0x8601 0x8086 0xFFFF
HECI 0x8086 0x8602 0x8086 0xFFFE
audio 0x8086 0x8603 0x8086 0xFFFD
IPU 0x8086 0x8604 0x8086 0xFFFC
TSN/AVB 0x8086 0x8605 0x8086 0xFFFB
hyper_dmabuf 0x8086 0x8606 0x8086 0xFFFA
HDCP 0x8086 0x8607 0x8086 0xFFF9
COREU 0x8086 0x8608 0x8086 0xFFF8
I2C 0x8086 0x860a 0x8086 0xFFF6
GPIO 0x8086 0x8609 0x8086 0xFFF7

so IIUC what you are using is a temporary ID which is fine for
experimentation but not for production.

Note it does take a couple of weeks for a TC to vote on reserving an ID
so pls do not delay this too much.

--
MST

2020-12-03 21:57:07

by Michael S. Tsirkin

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Wed, Nov 25, 2020 at 09:18:04PM +0000, Winkler, Tomas wrote:
> >
> > On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > > +#ifndef VIRTIO_ID_MEI
> > > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
> >
> > Just noticed now that this driver landed upstream. Can I ask that you guys
> > please register IDs with the virtio TC and not just pick a number at random?
> > In particular this is way outside allowed range.
> >
> > IDs should also be listed in include/uapi/linux/virtio_ids.h
> >
> > If people just pick random numbers like this collistions are unavoidable.
> >
> > List of IDs is part of virtio spec, chapter "Device Types".
> >
> > Please do this change now before this goes out to production!
> Okay, this was assigned by ACRN, my impression was it's already registered.
> Will take care of.
> Thanks
> Tomas

Well nothing happened yet.

I think at this point we really should revert this patch before Linux is
released so in the next version the correct ID can be used instead of a reserved one.
Otherwise Linux will be stuck supporting this forever and will conflict
with hypervisors using this for what this range is for which is
experimental use.

Greg, any opinion on that?

--
MST

2020-12-03 22:06:12

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Thu, Dec 03, 2020 at 04:51:10PM -0500, Michael S. Tsirkin wrote:
> On Wed, Nov 25, 2020 at 09:18:04PM +0000, Winkler, Tomas wrote:
> > >
> > > On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > > > +#ifndef VIRTIO_ID_MEI
> > > > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
> > >
> > > Just noticed now that this driver landed upstream. Can I ask that you guys
> > > please register IDs with the virtio TC and not just pick a number at random?
> > > In particular this is way outside allowed range.
> > >
> > > IDs should also be listed in include/uapi/linux/virtio_ids.h
> > >
> > > If people just pick random numbers like this collistions are unavoidable.
> > >
> > > List of IDs is part of virtio spec, chapter "Device Types".
> > >
> > > Please do this change now before this goes out to production!
> > Okay, this was assigned by ACRN, my impression was it's already registered.
> > Will take care of.
> > Thanks
> > Tomas
>
> Well nothing happened yet.
>
> I think at this point we really should revert this patch before Linux is
> released so in the next version the correct ID can be used instead of a reserved one.
> Otherwise Linux will be stuck supporting this forever and will conflict
> with hypervisors using this for what this range is for which is
> experimental use.
>
> Greg, any opinion on that?

I will be glad to revert it, what's the git commit id?

thanks,

greg k-h

2020-12-05 19:44:17

by Michael S. Tsirkin

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Thu, Dec 03, 2020 at 11:01:45PM +0100, Greg Kroah-Hartman wrote:
> On Thu, Dec 03, 2020 at 04:51:10PM -0500, Michael S. Tsirkin wrote:
> > On Wed, Nov 25, 2020 at 09:18:04PM +0000, Winkler, Tomas wrote:
> > > >
> > > > On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > > > > +#ifndef VIRTIO_ID_MEI
> > > > > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
> > > >
> > > > Just noticed now that this driver landed upstream. Can I ask that you guys
> > > > please register IDs with the virtio TC and not just pick a number at random?
> > > > In particular this is way outside allowed range.
> > > >
> > > > IDs should also be listed in include/uapi/linux/virtio_ids.h
> > > >
> > > > If people just pick random numbers like this collistions are unavoidable.
> > > >
> > > > List of IDs is part of virtio spec, chapter "Device Types".
> > > >
> > > > Please do this change now before this goes out to production!
> > > Okay, this was assigned by ACRN, my impression was it's already registered.
> > > Will take care of.
> > > Thanks
> > > Tomas
> >
> > Well nothing happened yet.
> >
> > I think at this point we really should revert this patch before Linux is
> > released so in the next version the correct ID can be used instead of a reserved one.
> > Otherwise Linux will be stuck supporting this forever and will conflict
> > with hypervisors using this for what this range is for which is
> > experimental use.
> >
> > Greg, any opinion on that?
>
> I will be glad to revert it, what's the git commit id?

commit d162219c655c8cf8003128a13840d6c1e183fb80
Author: Tomas Winkler <[email protected]>
Date: Tue Aug 18 14:51:47 2020 +0300

mei: virtio: virtualization frontend driver


I sent a revert on list in case it makes things easier,
but it's just a result of a plain git revert,
build-tested.


> thanks,
>
> greg k-h

2020-12-06 09:40:21

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [char-misc-next 13/13] mei: virtio: virtualization frontend driver

On Sat, Dec 05, 2020 at 02:40:10PM -0500, Michael S. Tsirkin wrote:
> On Thu, Dec 03, 2020 at 11:01:45PM +0100, Greg Kroah-Hartman wrote:
> > On Thu, Dec 03, 2020 at 04:51:10PM -0500, Michael S. Tsirkin wrote:
> > > On Wed, Nov 25, 2020 at 09:18:04PM +0000, Winkler, Tomas wrote:
> > > > >
> > > > > On Tue, Aug 18, 2020 at 02:51:47PM +0300, Tomas Winkler wrote:
> > > > > > +#ifndef VIRTIO_ID_MEI
> > > > > > +#define VIRTIO_ID_MEI 0xFFFE /* virtio mei */ #endif
> > > > >
> > > > > Just noticed now that this driver landed upstream. Can I ask that you guys
> > > > > please register IDs with the virtio TC and not just pick a number at random?
> > > > > In particular this is way outside allowed range.
> > > > >
> > > > > IDs should also be listed in include/uapi/linux/virtio_ids.h
> > > > >
> > > > > If people just pick random numbers like this collistions are unavoidable.
> > > > >
> > > > > List of IDs is part of virtio spec, chapter "Device Types".
> > > > >
> > > > > Please do this change now before this goes out to production!
> > > > Okay, this was assigned by ACRN, my impression was it's already registered.
> > > > Will take care of.
> > > > Thanks
> > > > Tomas
> > >
> > > Well nothing happened yet.
> > >
> > > I think at this point we really should revert this patch before Linux is
> > > released so in the next version the correct ID can be used instead of a reserved one.
> > > Otherwise Linux will be stuck supporting this forever and will conflict
> > > with hypervisors using this for what this range is for which is
> > > experimental use.
> > >
> > > Greg, any opinion on that?
> >
> > I will be glad to revert it, what's the git commit id?
>
> commit d162219c655c8cf8003128a13840d6c1e183fb80
> Author: Tomas Winkler <[email protected]>
> Date: Tue Aug 18 14:51:47 2020 +0300
>
> mei: virtio: virtualization frontend driver
>
>
> I sent a revert on list in case it makes things easier,
> but it's just a result of a plain git revert,
> build-tested.

Thanks, that made it even easier for me, now queued up.

greg k-h