}
/**
- * __mei_me_cl_del - delete me client form the list and decrease
+ * __mei_me_cl_del - delete me client from the list and decrease
* reference counter
*
* @dev: mei device
if (!me_cl)
return;
- list_del(&me_cl->list);
+ list_del_init(&me_cl->list);
mei_me_cl_put(me_cl);
}
+/**
+ * mei_me_cl_del - delete me client from the list and decrease
+ * reference counter
+ *
+ * @dev: mei device
+ * @me_cl: me client
+ */
+void mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl)
+{
+ down_write(&dev->me_clients_rwsem);
+ __mei_me_cl_del(dev, me_cl);
+ up_write(&dev->me_clients_rwsem);
+}
+
/**
* mei_me_cl_add - add me client to the list
*
{
return cl1 && cl2 &&
(cl1->host_client_id == cl2->host_client_id) &&
- (cl1->me_client_id == cl2->me_client_id);
+ (mei_cl_me_id(cl1) == mei_cl_me_id(cl2));
}
/**
INIT_LIST_HEAD(&cl->link);
INIT_LIST_HEAD(&cl->device_link);
cl->writing_state = MEI_IDLE;
+ cl->state = MEI_FILE_INITIALIZING;
cl->dev = dev;
}
}
/**
- * mei_cl_unlink - remove me_cl from the list
+ * mei_cl_unlink - remove host client from the list
*
* @cl: host client
*
me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
if (me_cl)
- mei_amthif_host_init(dev);
+ mei_amthif_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (me_cl)
- mei_wd_host_init(dev);
+ mei_wd_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid);
if (me_cl)
- mei_nfc_host_init(dev);
+ mei_nfc_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
return true;
}
+/**
+ * mei_cl_set_disconnected - set disconnected state and clear
+ * associated states and resources
+ *
+ * @cl: host client
+ */
+void mei_cl_set_disconnected(struct mei_cl *cl)
+{
+ struct mei_device *dev = cl->dev;
+
+ if (cl->state == MEI_FILE_DISCONNECTED ||
+ cl->state == MEI_FILE_INITIALIZING)
+ return;
+
+ cl->state = MEI_FILE_DISCONNECTED;
+ mei_io_list_flush(&dev->ctrl_rd_list, cl);
+ mei_io_list_flush(&dev->ctrl_wr_list, cl);
+ cl->mei_flow_ctrl_creds = 0;
+ cl->timer_count = 0;
+
+ if (!cl->me_cl)
+ return;
+
+ if (!WARN_ON(cl->me_cl->connect_count == 0))
+ cl->me_cl->connect_count--;
+
+ if (cl->me_cl->connect_count == 0)
+ cl->me_cl->mei_flow_ctrl_creds = 0;
+
+ mei_me_cl_put(cl->me_cl);
+ cl->me_cl = NULL;
+}
+
+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)
+ return -ENOENT;
+
+ cl->state = MEI_FILE_CONNECTING;
+ cl->me_cl->connect_count++;
+
+ return 0;
+}
+
+/*
+ * mei_cl_send_disconnect - send disconnect request
+ *
+ * @cl: host client
+ * @cb: callback block
+ *
+ * Return: 0, OK; otherwise, error.
+ */
+static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb)
+{
+ struct mei_device *dev;
+ int ret;
+
+ dev = cl->dev;
+
+ ret = mei_hbm_cl_disconnect_req(dev, cl);
+ cl->status = ret;
+ if (ret) {
+ cl->state = MEI_FILE_DISCONNECT_REPLY;
+ return ret;
+ }
+
+ list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
+ cl->timer_count = MEI_CONNECT_TIMEOUT;
+
+ return 0;
+}
+
+/**
+ * mei_cl_irq_disconnect - processes close related operation from
+ * interrupt thread context - send disconnect request
+ *
+ * @cl: client
+ * @cb: callback block.
+ * @cmpl_list: complete list.
+ *
+ * Return: 0, OK; otherwise, error.
+ */
+int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
+{
+ struct mei_device *dev = cl->dev;
+ u32 msg_slots;
+ int slots;
+ int ret;
+
+ msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
+ slots = mei_hbuf_empty_slots(dev);
+
+ if (slots < msg_slots)
+ return -EMSGSIZE;
+
+ ret = mei_cl_send_disconnect(cl, cb);
+ if (ret)
+ list_move_tail(&cb->list, &cmpl_list->list);
+
+ return ret;
+}
+
+
+
/**
* mei_cl_disconnect - disconnect host client from the me one
*
cl_dbg(dev, cl, "disconnecting");
- if (cl->state != MEI_FILE_DISCONNECTING)
+ if (!mei_cl_is_connected(cl))
return 0;
rets = pm_runtime_get(dev->dev);
return rets;
}
+ cl->state = MEI_FILE_DISCONNECTING;
+
cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL);
rets = cb ? 0 : -ENOMEM;
if (rets)
- goto free;
+ goto out;
+
+ cl_dbg(dev, cl, "add disconnect cb to control write list\n");
+ list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
if (mei_hbuf_acquire(dev)) {
- if (mei_hbm_cl_disconnect_req(dev, cl)) {
- rets = -ENODEV;
+ rets = mei_cl_send_disconnect(cl, cb);
+ if (rets) {
cl_err(dev, cl, "failed to disconnect.\n");
- goto free;
+ goto out;
}
- cl->timer_count = MEI_CONNECT_TIMEOUT;
- mdelay(10); /* Wait for hardware disconnection ready */
- list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
- } else {
- cl_dbg(dev, cl, "add disconnect cb to control write list\n");
- list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
-
}
- mutex_unlock(&dev->device_lock);
-
- wait_event_timeout(cl->wait,
- MEI_FILE_DISCONNECTED == cl->state,
- mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
+ mutex_unlock(&dev->device_lock);
+ wait_event_timeout(cl->wait, cl->state == MEI_FILE_DISCONNECT_REPLY,
+ mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
- if (MEI_FILE_DISCONNECTED == cl->state) {
- rets = 0;
- cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
- } else {
+ rets = cl->status;
+ if (cl->state != MEI_FILE_DISCONNECT_REPLY) {
cl_dbg(dev, cl, "timeout on disconnect from FW client.\n");
rets = -ETIME;
}
- mei_io_list_flush(&dev->ctrl_rd_list, cl);
- mei_io_list_flush(&dev->ctrl_wr_list, cl);
-free:
+out:
+ /* we disconnect also on error */
+ mei_cl_set_disconnected(cl);
+ if (!rets)
+ cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
+
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
*
* Return: true if other client is connected, false - otherwise.
*/
-bool mei_cl_is_other_connecting(struct mei_cl *cl)
+static bool mei_cl_is_other_connecting(struct mei_cl *cl)
{
struct mei_device *dev;
- struct mei_cl *ocl; /* the other client */
-
- if (WARN_ON(!cl || !cl->dev))
- return false;
+ struct mei_cl_cb *cb;
dev = cl->dev;
- list_for_each_entry(ocl, &dev->file_list, link) {
- if (ocl->state == MEI_FILE_CONNECTING &&
- ocl != cl &&
- cl->me_client_id == ocl->me_client_id)
+ list_for_each_entry(cb, &dev->ctrl_rd_list.list, list) {
+ if (cb->fop_type == MEI_FOP_CONNECT &&
+ mei_cl_me_id(cl) == mei_cl_me_id(cb->cl))
return true;
-
}
return false;
}
+/**
+ * mei_cl_send_connect - send connect request
+ *
+ * @cl: host client
+ * @cb: callback block
+ *
+ * Return: 0, OK; otherwise, error.
+ */
+static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb)
+{
+ struct mei_device *dev;
+ int ret;
+
+ dev = cl->dev;
+
+ ret = mei_hbm_cl_connect_req(dev, cl);
+ cl->status = ret;
+ if (ret) {
+ cl->state = MEI_FILE_DISCONNECT_REPLY;
+ return ret;
+ }
+
+ list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
+ cl->timer_count = MEI_CONNECT_TIMEOUT;
+ return 0;
+}
+
+/**
+ * mei_cl_irq_connect - send connect request in irq_thread context
+ *
+ * @cl: host client
+ * @cb: callback block
+ * @cmpl_list: complete list
+ *
+ * Return: 0, OK; otherwise, error.
+ */
+int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
+ struct mei_cl_cb *cmpl_list)
+{
+ struct mei_device *dev = cl->dev;
+ u32 msg_slots;
+ int slots;
+ int rets;
+
+ msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
+ slots = mei_hbuf_empty_slots(dev);
+
+ if (mei_cl_is_other_connecting(cl))
+ return 0;
+
+ if (slots < msg_slots)
+ return -EMSGSIZE;
+
+ rets = mei_cl_send_connect(cl, cb);
+ if (rets)
+ list_move_tail(&cb->list, &cmpl_list->list);
+
+ return rets;
+}
+
/**
* mei_cl_connect - connect host client to the me one
*
* @cl: host client
+ * @me_cl: me client
* @file: pointer to file structure
*
* Locking: called under "dev->device_lock" lock
*
* Return: 0 on success, <0 on failure.
*/
-int mei_cl_connect(struct mei_cl *cl, struct file *file)
+int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
+ struct file *file)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
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 */
if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) {
- cl->state = MEI_FILE_CONNECTING;
- if (mei_hbm_cl_connect_req(dev, cl)) {
- rets = -ENODEV;
+ rets = mei_cl_send_connect(cl, cb);
+ if (rets)
goto out;
- }
- cl->timer_count = MEI_CONNECT_TIMEOUT;
- list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
- } else {
- cl->state = MEI_FILE_INITIALIZING;
- list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
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_REPLY),
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (!mei_cl_is_connected(cl)) {
- cl->state = MEI_FILE_DISCONNECTED;
- /* something went really wrong */
+ /* timeout or something went really wrong */
if (!cl->status)
cl->status = -EFAULT;
-
- mei_io_list_flush(&dev->ctrl_rd_list, cl);
- mei_io_list_flush(&dev->ctrl_wr_list, cl);
}
rets = cl->status;
-
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
mei_io_cb_free(cb);
+
+ if (!mei_cl_is_connected(cl))
+ mei_cl_set_disconnected(cl);
+
return rets;
}
* @cl: private data of the file object
*
* Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
- * -ENOENT if mei_cl is not present
- * -EINVAL if single_recv_buf == 0
*/
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
{
- struct mei_device *dev;
- struct mei_me_client *me_cl;
- int rets = 0;
-
- if (WARN_ON(!cl || !cl->dev))
+ if (WARN_ON(!cl || !cl->me_cl))
return -EINVAL;
- dev = cl->dev;
-
if (cl->mei_flow_ctrl_creds > 0)
return 1;
- me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
- if (!me_cl) {
- cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
- return -ENOENT;
- }
-
- if (me_cl->mei_flow_ctrl_creds > 0) {
- rets = 1;
- if (WARN_ON(me_cl->props.single_recv_buf == 0))
- rets = -EINVAL;
+ if (mei_cl_is_single_recv_buf(cl)) {
+ if (cl->me_cl->mei_flow_ctrl_creds > 0)
+ return 1;
}
- mei_me_cl_put(me_cl);
- return rets;
+ return 0;
}
/**
*
* Return:
* 0 on success
- * -ENOENT when me client is not found
* -EINVAL when ctrl credits are <= 0
*/
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
{
- struct mei_device *dev;
- struct mei_me_client *me_cl;
- int rets;
-
- if (WARN_ON(!cl || !cl->dev))
+ if (WARN_ON(!cl || !cl->me_cl))
return -EINVAL;
- dev = cl->dev;
-
- me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
- if (!me_cl) {
- cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
- return -ENOENT;
- }
-
- if (me_cl->props.single_recv_buf) {
- if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) {
- rets = -EINVAL;
- goto out;
- }
- me_cl->mei_flow_ctrl_creds--;
+ if (mei_cl_is_single_recv_buf(cl)) {
+ if (WARN_ON(cl->me_cl->mei_flow_ctrl_creds <= 0))
+ return -EINVAL;
+ cl->me_cl->mei_flow_ctrl_creds--;
} else {
- if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) {
- rets = -EINVAL;
- goto out;
- }
+ if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
+ return -EINVAL;
cl->mei_flow_ctrl_creds--;
}
- rets = 0;
-out:
- mei_me_cl_put(me_cl);
- return rets;
+ return 0;
}
/**
{
struct mei_device *dev;
struct mei_cl_cb *cb;
- struct mei_me_client *me_cl;
int rets;
if (WARN_ON(!cl || !cl->dev))
if (!list_empty(&cl->rd_pending))
return -EBUSY;
- me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
- if (!me_cl) {
- cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
+ if (!mei_me_cl_is_active(cl->me_cl)) {
+ cl_err(dev, cl, "no such me client\n");
return -ENOTTY;
}
/* always allocate at least client max message */
- length = max_t(size_t, length, me_cl->props.max_msg_length);
- mei_me_cl_put(me_cl);
+ length = max_t(size_t, length, mei_cl_mtu(cl));
rets = pm_runtime_get(dev->dev);
if (rets < 0 && rets != -EINPROGRESS) {
msg_slots = mei_data2slots(len);
mei_hdr.host_addr = cl->host_client_id;
- mei_hdr.me_addr = cl->me_client_id;
+ mei_hdr.me_addr = mei_cl_me_id(cl);
mei_hdr.reserved = 0;
mei_hdr.internal = cb->internal;
cl->writing_state = MEI_IDLE;
mei_hdr.host_addr = cl->host_client_id;
- mei_hdr.me_addr = cl->me_client_id;
+ mei_hdr.me_addr = mei_cl_me_id(cl);
mei_hdr.reserved = 0;
mei_hdr.msg_complete = 0;
mei_hdr.internal = cb->internal;
*/
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
{
- if (cb->fop_type == MEI_FOP_WRITE) {
+ switch (cb->fop_type) {
+ case MEI_FOP_WRITE:
mei_io_cb_free(cb);
- cb = NULL;
cl->writing_state = MEI_WRITE_COMPLETE;
if (waitqueue_active(&cl->tx_wait))
wake_up_interruptible(&cl->tx_wait);
+ break;
- } else if (cb->fop_type == MEI_FOP_READ) {
+ case MEI_FOP_READ:
list_add_tail(&cb->list, &cl->rd_completed);
if (waitqueue_active(&cl->rx_wait))
wake_up_interruptible_all(&cl->rx_wait);
else
mei_cl_bus_rx_event(cl);
+ break;
+
+ case MEI_FOP_CONNECT:
+ case MEI_FOP_DISCONNECT:
+ if (waitqueue_active(&cl->wait))
+ wake_up(&cl->wait);
+ break;
+ default:
+ BUG_ON(0);
}
}
*
* @dev: mei device
*/
-
void mei_cl_all_disconnect(struct mei_device *dev)
{
struct mei_cl *cl;
- list_for_each_entry(cl, &dev->file_list, link) {
- cl->state = MEI_FILE_DISCONNECTED;
- cl->mei_flow_ctrl_creds = 0;
- cl->timer_count = 0;
- }
+ list_for_each_entry(cl, &dev->file_list, link)
+ mei_cl_set_disconnected(cl);
}