msm: mhi_dev: Add uevent support

Clients need notification for connect, disconnect state
for the individual channels once they are updated by the
host. Send the events for clients listening to uevents
such as MBIMD for channel state updates. Client can also
poll for the current channel state through MHI ctrl uevent.

Change-Id: Ie764b4866e5e41df072a7a76105a740c89f91f17
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
diff --git a/drivers/platform/msm/mhi_dev/mhi.c b/drivers/platform/msm/mhi_dev/mhi.c
index 64abf7e..47d5292 100644
--- a/drivers/platform/msm/mhi_dev/mhi.c
+++ b/drivers/platform/msm/mhi_dev/mhi.c
@@ -71,11 +71,12 @@
 static void mhi_hwc_cb(void *priv, enum ipa_mhi_event_type event,
 	unsigned long data);
 static void mhi_ring_init_cb(void *user_data);
-static void mhi_update_state_info(uint32_t info);
+static void mhi_update_state_info(uint32_t uevent_idx, enum mhi_ctrl_info info);
 static int mhi_deinit(struct mhi_dev *mhi);
 static void mhi_dev_resume_init_with_link_up(struct ep_pcie_notify *notify);
 static int mhi_dev_pcie_notify_event;
 static void mhi_dev_transfer_completion_cb(void *mreq);
+static struct mhi_dev_uevent_info channel_state_info[MHI_MAX_CHANNELS];
 
 /*
  * mhi_dev_ring_cache_completion_cb () - Call back function called
@@ -454,7 +455,7 @@
 			return;
 		}
 
-		mhi_update_state_info(MHI_STATE_CONNECTED);
+		mhi_update_state_info(MHI_DEV_UEVENT_CTRL, MHI_STATE_CONNECTED);
 
 		ep_pcie_mask_irq_event(mhi_ctx->phandle,
 				EP_PCIE_INT_EVT_MHI_A7, true);
@@ -829,7 +830,7 @@
 
 	list_for_each_entry(info, &mhi_ctx->client_cb_list, list)
 		if (info->cb) {
-			mhi_ctrl_state_info(&state_data);
+			mhi_ctrl_state_info(info->cb_data.channel, &state_data);
 			info->cb_data.ctrl_info = state_data;
 			info->cb(&info->cb_data);
 		}
@@ -935,6 +936,8 @@
 	struct mhi_addr host_addr;
 	struct mhi_dev_channel *ch;
 	struct mhi_dev_ring *ring;
+	char *connected[2] = { "MHI_CHANNEL_STATE_12=CONNECTED", NULL};
+	char *disconnected[2] = { "MHI_CHANNEL_STATE_12=DISCONNECTED", NULL};
 
 	ch_id = el->generic.chid;
 	mhi_log(MHI_MSG_VERBOSE, "for channel:%d and cmd:%d\n",
@@ -1020,6 +1023,10 @@
 		/* Trigger callback to clients */
 		mhi_dev_trigger_cb();
 
+		mhi_update_state_info(ch_id, MHI_STATE_CONNECTED);
+		if (ch_id == MHI_CLIENT_MBIM_OUT)
+			kobject_uevent_env(&mhi_ctx->dev->kobj,
+						KOBJ_CHANGE, connected);
 		break;
 	case MHI_DEV_RING_EL_STOP:
 		if (ch_id >= HW_CHANNEL_BASE) {
@@ -1072,6 +1079,10 @@
 				pr_err("stop event send failed\n");
 
 			mutex_unlock(&ch->ch_lock);
+			mhi_update_state_info(ch_id, MHI_STATE_DISCONNECTED);
+			if (ch_id == MHI_CLIENT_MBIM_OUT)
+				kobject_uevent_env(&mhi_ctx->dev->kobj,
+						KOBJ_CHANGE, disconnected);
 		}
 		break;
 	case MHI_DEV_RING_EL_RESET:
@@ -1144,6 +1155,10 @@
 			if (rc)
 				pr_err("Error sending command completion event\n");
 			mutex_unlock(&ch->ch_lock);
+			mhi_update_state_info(ch_id, MHI_STATE_DISCONNECTED);
+			if (ch_id == MHI_CLIENT_MBIM_OUT)
+				kobject_uevent_env(&mhi_ctx->dev->kobj,
+						KOBJ_CHANGE, disconnected);
 		}
 		break;
 	default:
@@ -1325,7 +1340,7 @@
 	}
 
 	/* Update ctrl node */
-	mhi_update_state_info(MHI_STATE_DISCONNECTED);
+	mhi_update_state_info(MHI_DEV_UEVENT_CTRL, MHI_STATE_DISCONNECTED);
 
 	flush_workqueue(mhi->ring_init_wq);
 	flush_workqueue(mhi->pending_ring_wq);
@@ -1806,7 +1821,7 @@
 		mhi_dev_write_to_host(mhi, &data_transfer, NULL,
 				MHI_DEV_DMA_SYNC);
 	}
-	mhi_update_state_info(MHI_STATE_CONNECTED);
+	mhi_update_state_info(MHI_DEV_UEVENT_CTRL, MHI_STATE_CONNECTED);
 
 	atomic_set(&mhi->is_suspended, 0);
 
@@ -2367,7 +2382,7 @@
 	if (mhi_ctx->config_iatu || mhi_ctx->mhi_int)
 		enable_irq(mhi_ctx->mhi_irq);
 
-	mhi_update_state_info(MHI_STATE_CONNECTED);
+	mhi_update_state_info(MHI_DEV_UEVENT_CTRL, MHI_STATE_CONFIGURED);
 }
 
 static void mhi_ring_init_cb(void *data)
@@ -2427,28 +2442,32 @@
 }
 EXPORT_SYMBOL(mhi_register_state_cb);
 
-static void mhi_update_state_info(uint32_t info)
+static void mhi_update_state_info(uint32_t uevent_idx, enum mhi_ctrl_info info)
 {
 	struct mhi_dev_client_cb_reason reason;
 
-	mhi_ctx->ctrl_info = info;
+	if (uevent_idx == MHI_DEV_UEVENT_CTRL)
+		mhi_ctx->ctrl_info = info;
 
-	if (info == MHI_STATE_CONNECTED)
-		return;
+	channel_state_info[uevent_idx].ctrl_info = info;
 
-	reason.reason = MHI_DEV_CTRL_UPDATE;
-	uci_ctrl_update(&reason);
-}
-
-int mhi_ctrl_state_info(uint32_t *info)
-{
-	if (!info) {
-		pr_err("Invalid info\n");
-		return -EINVAL;
+	if (uevent_idx == MHI_CLIENT_QMI_OUT ||
+			uevent_idx == MHI_CLIENT_QMI_IN) {
+		/* For legacy reasons for QTI client */
+		reason.reason = MHI_DEV_CTRL_UPDATE;
+		uci_ctrl_update(&reason);
 	}
 
-	*info = mhi_ctx->ctrl_info;
-	mhi_log(MHI_MSG_VERBOSE, "ctrl:%d", mhi_ctx->ctrl_info);
+}
+
+int mhi_ctrl_state_info(uint32_t idx, uint32_t *info)
+{
+	if (idx == MHI_DEV_UEVENT_CTRL)
+		*info = mhi_ctx->ctrl_info;
+	else
+		*info = channel_state_info[idx].ctrl_info;
+
+	mhi_log(MHI_MSG_VERBOSE, "idx:%d, ctrl:%d", idx, *info);
 
 	return 0;
 }
@@ -2943,7 +2962,8 @@
 				"Failed to create IPC logging context\n");
 		}
 		mhi_uci_init();
-		mhi_update_state_info(MHI_STATE_CONFIGURED);
+		mhi_update_state_info(MHI_DEV_UEVENT_CTRL,
+						MHI_STATE_CONFIGURED);
 	}
 
 	INIT_WORK(&mhi_ctx->pcie_event, mhi_dev_pcie_handle_event);
diff --git a/drivers/platform/msm/mhi_dev/mhi.h b/drivers/platform/msm/mhi_dev/mhi.h
index 38e52e2..b9120fc 100644
--- a/drivers/platform/msm/mhi_dev/mhi.h
+++ b/drivers/platform/msm/mhi_dev/mhi.h
@@ -275,7 +275,7 @@
 #define MHI_ENV_VALUE			2
 #define MHI_MASK_ROWS_CH_EV_DB		4
 #define TRB_MAX_DATA_SIZE		8192
-#define MHI_CTRL_STATE			25
+#define MHI_CTRL_STATE			100
 #define IPA_DMA_SYNC                    1
 #define IPA_DMA_ASYNC                   0
 
@@ -607,6 +607,8 @@
 	bool				mhi_int;
 	/* Registered client callback list */
 	struct list_head		client_cb_list;
+
+	struct kobj_uevent_env		kobj_env;
 };
 
 struct mhi_req {
@@ -712,6 +714,14 @@
 	MHI_MAX_CHANNELS = 102,
 };
 
+/* Use ID 0 for legacy /dev/mhi_ctrl. Channel 0 is used for internal only */
+#define MHI_DEV_UEVENT_CTRL	0
+
+struct mhi_dev_uevent_info {
+	enum mhi_client_channel	channel;
+	enum mhi_ctrl_info	ctrl_info;
+};
+
 struct mhi_dev_iov {
 	void		*addr;
 	uint32_t	buf_size;
@@ -1251,6 +1261,9 @@
 
 /**
  * mhi_ctrl_state_info() - Provide MHI state info
+ *		@idx: Channel number idx. Look at channel_state_info and
+ *		pass the index for the corresponding channel.
+ *		@info: Return the control info.
  *		MHI_STATE=CONFIGURED - MHI device is present but not ready
  *					for data traffic.
  *		MHI_STATE=CONNECTED - MHI device is ready for data transfer.
@@ -1258,7 +1271,7 @@
  *		exposes device nodes for the supported MHI software
  *		channels.
  */
-int mhi_ctrl_state_info(uint32_t *info);
+int mhi_ctrl_state_info(uint32_t idx, uint32_t *info);
 
 /**
  * uci_ctrl_update() - Update UCI once TRE's are available for clients to
diff --git a/drivers/platform/msm/mhi_dev/mhi_uci.c b/drivers/platform/msm/mhi_dev/mhi_uci.c
index 244cf04..ed02d0d 100644
--- a/drivers/platform/msm/mhi_dev/mhi_uci.c
+++ b/drivers/platform/msm/mhi_dev/mhi_uci.c
@@ -441,6 +441,61 @@
 	return rc;
 }
 
+static void  mhi_parse_state(char *buf, int *nbytes, uint32_t info)
+{
+	switch (info) {
+	case MHI_STATE_CONNECTED:
+		*nbytes = scnprintf(buf, MHI_CTRL_STATE,
+			"CONNECTED");
+		break;
+	case MHI_STATE_DISCONNECTED:
+		*nbytes = scnprintf(buf, MHI_CTRL_STATE,
+			"DISCONNECTED");
+		break;
+	case MHI_STATE_CONFIGURED:
+	default:
+		*nbytes = scnprintf(buf, MHI_CTRL_STATE,
+			"CONFIGURED");
+		break;
+	}
+}
+
+static int mhi_state_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int rc, nbytes = 0;
+	uint32_t info = 0;
+	char buf[MHI_CTRL_STATE];
+
+	rc = mhi_ctrl_state_info(MHI_DEV_UEVENT_CTRL, &info);
+	if (rc) {
+		pr_err("Failed to obtain MHI_STATE\n");
+		return -EINVAL;
+	}
+
+	mhi_parse_state(buf, &nbytes, info);
+	add_uevent_var(env, "MHI_STATE=%s", buf);
+
+	rc = mhi_ctrl_state_info(MHI_CLIENT_QMI_OUT, &info);
+	if (rc) {
+		pr_err("Failed to obtain channel 14 state\n");
+		return -EINVAL;
+	}
+	nbytes = 0;
+	mhi_parse_state(buf, &nbytes, info);
+	add_uevent_var(env, "MHI_CHANNEL_STATE_14=%s", buf);
+
+	rc = mhi_ctrl_state_info(MHI_CLIENT_MBIM_OUT, &info);
+	if (rc) {
+		pr_err("Failed to obtain channel 12 state\n");
+		return -EINVAL;
+	}
+	nbytes = 0;
+	mhi_parse_state(buf, &nbytes, info);
+	add_uevent_var(env, "MHI_CHANNEL_STATE_12=%s", buf);
+
+	return 0;
+}
+
 static ssize_t mhi_uci_ctrl_client_read(struct file *file,
 		char __user *user_buf,
 		size_t count, loff_t *offp)
@@ -455,7 +510,7 @@
 		return -EINVAL;
 
 	uci_ctrl_handle = file->private_data;
-	rc = mhi_ctrl_state_info(&info);
+	rc = mhi_ctrl_state_info(MHI_CLIENT_QMI_OUT, &info);
 	if (rc)
 		return -EINVAL;
 
@@ -993,6 +1048,8 @@
 		uci_ctxt.cdev_ctrl = NULL;
 	}
 
+	uci_ctxt.mhi_uci_class->dev_uevent = mhi_state_uevent;
+
 	return 0;
 
 failed_char_add: