mei: support for fixed address clients

Fixed address is simplified FW client that doesn't require
connection and doesn't support flow control.
So it can be only one host client per fixed FW client.
Fixed client access is available only for drivers on mei bus,
connection from user-space is blocked.

Signed-off-by: Alexander Usyskin <[email protected]>
Signed-off-by: Tomas Winkler <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 98335b4..3670670 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -764,10 +764,18 @@
 
 static int mei_cl_set_connecting(struct mei_cl *cl, struct mei_me_client *me_cl)
 {
-	cl->me_cl = mei_me_cl_get(me_cl);
-	if (!cl->me_cl)
+	if (!mei_me_cl_get(me_cl))
 		return -ENOENT;
 
+	/* only one connection is allowed for fixed address clients */
+	if (me_cl->props.fixed_address) {
+		if (me_cl->connect_count) {
+			mei_me_cl_put(me_cl);
+			return -EBUSY;
+		}
+	}
+
+	cl->me_cl = me_cl;
 	cl->state = MEI_FILE_CONNECTING;
 	cl->me_cl->connect_count++;
 
@@ -860,6 +868,11 @@
 	if (!mei_cl_is_connected(cl))
 		return 0;
 
+	if (mei_cl_is_fixed_address(cl)) {
+		mei_cl_set_disconnected(cl);
+		return 0;
+	}
+
 	rets = pm_runtime_get(dev->dev);
 	if (rets < 0 && rets != -EINPROGRESS) {
 		pm_runtime_put_noidle(dev->dev);
@@ -1013,16 +1026,25 @@
 	struct mei_cl_cb *cb;
 	int rets;
 
-	if (WARN_ON(!cl || !cl->dev))
+	if (WARN_ON(!cl || !cl->dev || !me_cl))
 		return -ENODEV;
 
 	dev = cl->dev;
 
+	rets = mei_cl_set_connecting(cl, me_cl);
+	if (rets)
+		return rets;
+
+	if (mei_cl_is_fixed_address(cl)) {
+		cl->state = MEI_FILE_CONNECTED;
+		return 0;
+	}
+
 	rets = pm_runtime_get(dev->dev);
 	if (rets < 0 && rets != -EINPROGRESS) {
 		pm_runtime_put_noidle(dev->dev);
 		cl_err(dev, cl, "rpm: get failed %d\n", rets);
-		return rets;
+		goto nortpm;
 	}
 
 	cb = mei_io_cb_init(cl, MEI_FOP_CONNECT, file);
@@ -1030,9 +1052,6 @@
 	if (rets)
 		goto out;
 
-	rets = mei_cl_set_connecting(cl, me_cl);
-	if (rets)
-		goto out;
 	list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
 
 	/* run hbuf acquire last so we don't have to undo */
@@ -1063,6 +1082,7 @@
 
 	mei_io_cb_free(cb);
 
+nortpm:
 	if (!mei_cl_is_connected(cl))
 		mei_cl_set_disconnected(cl);
 
@@ -1109,12 +1129,21 @@
  */
 int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
 {
+	int rets;
+
 	if (WARN_ON(!cl || !cl->me_cl))
 		return -EINVAL;
 
 	if (cl->mei_flow_ctrl_creds > 0)
 		return 1;
 
+	if (mei_cl_is_fixed_address(cl)) {
+		rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL);
+		if (rets && rets != -EBUSY)
+			return rets;
+		return 1;
+	}
+
 	if (mei_cl_is_single_recv_buf(cl)) {
 		if (cl->me_cl->mei_flow_ctrl_creds > 0)
 			return 1;
@@ -1136,6 +1165,9 @@
 	if (WARN_ON(!cl || !cl->me_cl))
 		return -EINVAL;
 
+	if (mei_cl_is_fixed_address(cl))
+		return 0;
+
 	if (mei_cl_is_single_recv_buf(cl)) {
 		if (WARN_ON(cl->me_cl->mei_flow_ctrl_creds <= 0))
 			return -EINVAL;
@@ -1179,21 +1211,25 @@
 		cl_err(dev, cl, "no such me client\n");
 		return  -ENOTTY;
 	}
+
 	/* always allocate at least client max message */
 	length = max_t(size_t, length, mei_cl_mtu(cl));
+	cb = mei_cl_alloc_cb(cl, length, MEI_FOP_READ, fp);
+	if (!cb)
+		return -ENOMEM;
+
+	if (mei_cl_is_fixed_address(cl)) {
+		list_add_tail(&cb->list, &cl->rd_pending);
+		return 0;
+	}
 
 	rets = pm_runtime_get(dev->dev);
 	if (rets < 0 && rets != -EINPROGRESS) {
 		pm_runtime_put_noidle(dev->dev);
 		cl_err(dev, cl, "rpm: get failed %d\n", rets);
-		return rets;
+		goto nortpm;
 	}
 
-	cb = mei_cl_alloc_cb(cl, length, MEI_FOP_READ, fp);
-	rets = cb ? 0 : -ENOMEM;
-	if (rets)
-		goto out;
-
 	if (mei_hbuf_acquire(dev)) {
 		rets = mei_hbm_cl_flow_control_req(dev, cl);
 		if (rets < 0)
@@ -1201,6 +1237,7 @@
 
 		list_add_tail(&cb->list, &cl->rd_pending);
 	} else {
+		rets = 0;
 		list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
 	}
 
@@ -1208,7 +1245,7 @@
 	cl_dbg(dev, cl, "rpm: autosuspend\n");
 	pm_runtime_mark_last_busy(dev->dev);
 	pm_runtime_put_autosuspend(dev->dev);
-
+nortpm:
 	if (rets)
 		mei_io_cb_free(cb);
 
@@ -1256,7 +1293,7 @@
 	len = buf->size - cb->buf_idx;
 	msg_slots = mei_data2slots(len);
 
-	mei_hdr.host_addr = cl->host_client_id;
+	mei_hdr.host_addr = mei_cl_host_addr(cl);
 	mei_hdr.me_addr = mei_cl_me_id(cl);
 	mei_hdr.reserved = 0;
 	mei_hdr.internal = cb->internal;
@@ -1340,7 +1377,7 @@
 	cb->buf_idx = 0;
 	cl->writing_state = MEI_IDLE;
 
-	mei_hdr.host_addr = cl->host_client_id;
+	mei_hdr.host_addr = mei_cl_host_addr(cl);
 	mei_hdr.me_addr = mei_cl_me_id(cl);
 	mei_hdr.reserved = 0;
 	mei_hdr.msg_complete = 0;
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index 0762650..8d7f057 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -188,6 +188,18 @@
 	return mei_me_cl_uuid(cl->me_cl);
 }
 
+/**
+ * mei_cl_host_addr - client's host address
+ *
+ * @cl: host client
+ *
+ * Return: 0 for fixed address client, host address for dynamic client
+ */
+static inline u8 mei_cl_host_addr(const struct mei_cl *cl)
+{
+	return  mei_cl_is_fixed_address(cl) ? 0 : cl->host_client_id;
+}
+
 int mei_cl_disconnect(struct mei_cl *cl);
 void mei_cl_set_disconnected(struct mei_cl *cl);
 int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index f620824..a4f2831 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -150,7 +150,7 @@
 	memset(cmd, 0, len);
 
 	cmd->hbm_cmd = hbm_cmd;
-	cmd->host_addr = cl->host_client_id;
+	cmd->host_addr = mei_cl_host_addr(cl);
 	cmd->me_addr = mei_cl_me_id(cl);
 }
 
@@ -188,7 +188,7 @@
 static inline
 bool mei_hbm_cl_addr_equal(struct mei_cl *cl, struct mei_hbm_cl_cmd *cmd)
 {
-	return cl->host_client_id == cmd->host_addr &&
+	return  mei_cl_host_addr(cl) == cmd->host_addr &&
 		mei_cl_me_id(cl) == cmd->me_addr;
 }
 
diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c
index 7479567..3f34052 100644
--- a/drivers/misc/mei/interrupt.c
+++ b/drivers/misc/mei/interrupt.c
@@ -65,7 +65,7 @@
 static inline int mei_cl_hbm_equal(struct mei_cl *cl,
 			struct mei_msg_hdr *mei_hdr)
 {
-	return cl->host_client_id == mei_hdr->host_addr &&
+	return  mei_cl_host_addr(cl) == mei_hdr->host_addr &&
 		mei_cl_me_id(cl) == mei_hdr->me_addr;
 }