These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / misc / mei / client.c
index b2b9f43..a6c87c7 100644 (file)
@@ -83,7 +83,7 @@ void mei_me_cl_put(struct mei_me_client *me_cl)
 }
 
 /**
- * __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
@@ -96,10 +96,24 @@ static void __mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl)
        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
  *
@@ -317,7 +331,7 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1,
 {
        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));
 }
 
 /**
@@ -541,11 +555,12 @@ void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
        init_waitqueue_head(&cl->wait);
        init_waitqueue_head(&cl->rx_wait);
        init_waitqueue_head(&cl->tx_wait);
+       init_waitqueue_head(&cl->ev_wait);
        INIT_LIST_HEAD(&cl->rd_completed);
        INIT_LIST_HEAD(&cl->rd_pending);
        INIT_LIST_HEAD(&cl->link);
-       INIT_LIST_HEAD(&cl->device_link);
        cl->writing_state = MEI_IDLE;
+       cl->state = MEI_FILE_INITIALIZING;
        cl->dev = dev;
 }
 
@@ -619,7 +634,7 @@ int mei_cl_link(struct mei_cl *cl, int id)
 }
 
 /**
- * mei_cl_unlink - remove me_cl from the list
+ * mei_cl_unlink - remove host client from the list
  *
  * @cl: host client
  *
@@ -667,24 +682,20 @@ void mei_host_client_init(struct work_struct *work)
 
        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_me_cl_put(me_cl);
-
-
        dev->dev_state = MEI_DEV_ENABLED;
        dev->reset_count = 0;
        mutex_unlock(&dev->device_lock);
 
+       mei_cl_bus_rescan(dev);
+
        pm_runtime_mark_last_busy(dev->dev);
        dev_dbg(dev->dev, "rpm: autosuspend\n");
        pm_runtime_autosuspend(dev->dev);
@@ -714,6 +725,173 @@ bool mei_hbuf_acquire(struct mei_device *dev)
        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)
+{
+       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++;
+
+       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
+ *     internal function runtime pm has to be already acquired
+ *
+ * @cl: host client
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+static int __mei_cl_disconnect(struct mei_cl *cl)
+{
+       struct mei_device *dev;
+       struct mei_cl_cb *cb;
+       int rets;
+
+       dev = cl->dev;
+
+       cl->state = MEI_FILE_DISCONNECTING;
+
+       cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL);
+       rets = cb ? 0 : -ENOMEM;
+       if (rets)
+               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)) {
+               rets = mei_cl_send_disconnect(cl, cb);
+               if (rets) {
+                       cl_err(dev, cl, "failed to disconnect.\n");
+                       goto out;
+               }
+       }
+
+       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);
+
+       rets = cl->status;
+       if (cl->state != MEI_FILE_DISCONNECT_REPLY) {
+               cl_dbg(dev, cl, "timeout on disconnect from FW client.\n");
+               rets = -ETIME;
+       }
+
+out:
+       /* we disconnect also on error */
+       mei_cl_set_disconnected(cl);
+       if (!rets)
+               cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
+
+       mei_io_cb_free(cb);
+       return rets;
+}
+
 /**
  * mei_cl_disconnect - disconnect host client from the me one
  *
@@ -726,7 +904,6 @@ bool mei_hbuf_acquire(struct mei_device *dev)
 int mei_cl_disconnect(struct mei_cl *cl)
 {
        struct mei_device *dev;
-       struct mei_cl_cb *cb;
        int rets;
 
        if (WARN_ON(!cl || !cl->dev))
@@ -736,9 +913,14 @@ int mei_cl_disconnect(struct mei_cl *cl)
 
        cl_dbg(dev, cl, "disconnecting");
 
-       if (cl->state != MEI_FILE_DISCONNECTING)
+       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);
@@ -746,49 +928,12 @@ int mei_cl_disconnect(struct mei_cl *cl)
                return rets;
        }
 
-       cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL);
-       rets = cb ? 0 : -ENOMEM;
-       if (rets)
-               goto free;
+       rets = __mei_cl_disconnect(cl);
 
-       if (mei_hbuf_acquire(dev)) {
-               if (mei_hbm_cl_disconnect_req(dev, cl)) {
-                       rets = -ENODEV;
-                       cl_err(dev, cl, "failed to disconnect.\n");
-                       goto free;
-               }
-               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_lock(&dev->device_lock);
-
-       if (MEI_FILE_DISCONNECTED == cl->state) {
-               rets = 0;
-               cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
-       } else {
-               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:
        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);
        return rets;
 }
 
@@ -801,53 +946,119 @@ free:
  *
  * 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;
        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);
@@ -855,45 +1066,52 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file)
        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_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)) {
-               cl->state = MEI_FILE_DISCONNECTED;
-               /* something went really wrong */
+               if (cl->state == MEI_FILE_DISCONNECT_REQUIRED) {
+                       mei_io_list_flush(&dev->ctrl_rd_list, cl);
+                       mei_io_list_flush(&dev->ctrl_wr_list, cl);
+                        /* ignore disconnect return valuue;
+                         * in case of failure reset will be invoked
+                         */
+                       __mei_cl_disconnect(cl);
+                       rets = -EFAULT;
+                       goto out;
+               }
+
+               /* 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);
+
+nortpm:
+       if (!mei_cl_is_connected(cl))
+               mei_cl_set_disconnected(cl);
+
        return rets;
 }
 
@@ -934,36 +1152,29 @@ err:
  * @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;
+       int rets;
 
-       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 (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 (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;
 }
 
 /**
@@ -973,45 +1184,243 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
  *
  * 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)
+{
+       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;
+               cl->me_cl->mei_flow_ctrl_creds--;
+       } else {
+               if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
+                       return -EINVAL;
+               cl->mei_flow_ctrl_creds--;
+       }
+       return 0;
+}
+
+/**
+ *  mei_cl_notify_fop2req - convert fop to proper request
+ *
+ * @fop: client notification start response command
+ *
+ * Return:  MEI_HBM_NOTIFICATION_START/STOP
+ */
+u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop)
+{
+       if (fop == MEI_FOP_NOTIFY_START)
+               return MEI_HBM_NOTIFICATION_START;
+       else
+               return MEI_HBM_NOTIFICATION_STOP;
+}
+
+/**
+ *  mei_cl_notify_req2fop - convert notification request top file operation type
+ *
+ * @req: hbm notification request type
+ *
+ * Return:  MEI_FOP_NOTIFY_START/STOP
+ */
+enum mei_cb_file_ops mei_cl_notify_req2fop(u8 req)
+{
+       if (req == MEI_HBM_NOTIFICATION_START)
+               return MEI_FOP_NOTIFY_START;
+       else
+               return MEI_FOP_NOTIFY_STOP;
+}
+
+/**
+ * mei_cl_irq_notify - send notification request in irq_thread context
+ *
+ * @cl: client
+ * @cb: callback block.
+ * @cmpl_list: complete list.
+ *
+ * Return: 0 on such and error otherwise.
+ */
+int mei_cl_irq_notify(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;
+       bool request;
+
+       msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
+       slots = mei_hbuf_empty_slots(dev);
+
+       if (slots < msg_slots)
+               return -EMSGSIZE;
+
+       request = mei_cl_notify_fop2req(cb->fop_type);
+       ret = mei_hbm_cl_notify_req(dev, cl, request);
+       if (ret) {
+               cl->status = ret;
+               list_move_tail(&cb->list, &cmpl_list->list);
+               return ret;
+       }
+
+       list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
+       return 0;
+}
+
+/**
+ * mei_cl_notify_request - send notification stop/start request
+ *
+ * @cl: host client
+ * @file: associate request with file
+ * @request: 1 for start or 0 for stop
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * Return: 0 on such and error otherwise.
+ */
+int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request)
 {
        struct mei_device *dev;
-       struct mei_me_client *me_cl;
+       struct mei_cl_cb *cb;
+       enum mei_cb_file_ops fop_type;
        int rets;
 
        if (WARN_ON(!cl || !cl->dev))
-               return -EINVAL;
+               return -ENODEV;
 
        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 (!dev->hbm_f_ev_supported) {
+               cl_dbg(dev, cl, "notifications not supported\n");
+               return -EOPNOTSUPP;
        }
 
-       if (me_cl->props.single_recv_buf) {
-               if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) {
-                       rets = -EINVAL;
+       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;
+       }
+
+       fop_type = mei_cl_notify_req2fop(request);
+       cb = mei_io_cb_init(cl, fop_type, file);
+       if (!cb) {
+               rets = -ENOMEM;
+               goto out;
+       }
+
+       if (mei_hbuf_acquire(dev)) {
+               if (mei_hbm_cl_notify_req(dev, cl, request)) {
+                       rets = -ENODEV;
                        goto out;
                }
-               me_cl->mei_flow_ctrl_creds--;
+               list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
        } else {
-               if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) {
-                       rets = -EINVAL;
-                       goto out;
-               }
-               cl->mei_flow_ctrl_creds--;
+               list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
+       }
+
+       mutex_unlock(&dev->device_lock);
+       wait_event_timeout(cl->wait, cl->notify_en == request,
+                       mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
+       mutex_lock(&dev->device_lock);
+
+       if (cl->notify_en != request) {
+               mei_io_list_flush(&dev->ctrl_rd_list, cl);
+               mei_io_list_flush(&dev->ctrl_wr_list, cl);
+               if (!cl->status)
+                       cl->status = -EFAULT;
        }
-       rets = 0;
+
+       rets = cl->status;
+
 out:
-       mei_me_cl_put(me_cl);
+       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);
        return rets;
 }
 
+/**
+ * mei_cl_notify - raise notification
+ *
+ * @cl: host client
+ *
+ * Locking: called under "dev->device_lock" lock
+ */
+void mei_cl_notify(struct mei_cl *cl)
+{
+       struct mei_device *dev;
+
+       if (!cl || !cl->dev)
+               return;
+
+       dev = cl->dev;
+
+       if (!cl->notify_en)
+               return;
+
+       cl_dbg(dev, cl, "notify event");
+       cl->notify_ev = true;
+       wake_up_interruptible_all(&cl->ev_wait);
+
+       if (cl->ev_async)
+               kill_fasync(&cl->ev_async, SIGIO, POLL_PRI);
+
+       mei_cl_bus_notify_event(cl);
+}
+
+/**
+ * mei_cl_notify_get - get or wait for notification event
+ *
+ * @cl: host client
+ * @block: this request is blocking
+ * @notify_ev: true if notification event was received
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * Return: 0 on such and error otherwise.
+ */
+int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev)
+{
+       struct mei_device *dev;
+       int rets;
+
+       *notify_ev = false;
+
+       if (WARN_ON(!cl || !cl->dev))
+               return -ENODEV;
+
+       dev = cl->dev;
+
+       if (!mei_cl_is_connected(cl))
+               return -ENODEV;
+
+       if (cl->notify_ev)
+               goto out;
+
+       if (!block)
+               return -EAGAIN;
+
+       mutex_unlock(&dev->device_lock);
+       rets = wait_event_interruptible(cl->ev_wait, cl->notify_ev);
+       mutex_lock(&dev->device_lock);
+
+       if (rets < 0)
+               return rets;
+
+out:
+       *notify_ev = cl->notify_ev;
+       cl->notify_ev = false;
+       return 0;
+}
+
 /**
  * mei_cl_read_start - the start read client message function.
  *
@@ -1025,7 +1434,6 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
 {
        struct mei_device *dev;
        struct mei_cl_cb *cb;
-       struct mei_me_client *me_cl;
        int rets;
 
        if (WARN_ON(!cl || !cl->dev))
@@ -1040,27 +1448,29 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
        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));
+       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)
@@ -1068,6 +1478,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
 
                list_add_tail(&cb->list, &cl->rd_pending);
        } else {
+               rets = 0;
                list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
        }
 
@@ -1075,7 +1486,7 @@ out:
        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);
 
@@ -1102,6 +1513,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
        u32 msg_slots;
        int slots;
        int rets;
+       bool first_chunk;
 
        if (WARN_ON(!cl || !cl->dev))
                return -ENODEV;
@@ -1110,7 +1522,9 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
 
        buf = &cb->buf;
 
-       rets = mei_cl_flow_ctrl_creds(cl);
+       first_chunk = cb->buf_idx == 0;
+
+       rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1;
        if (rets < 0)
                return rets;
 
@@ -1123,8 +1537,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
        len = buf->size - cb->buf_idx;
        msg_slots = mei_data2slots(len);
 
-       mei_hdr.host_addr = cl->host_client_id;
-       mei_hdr.me_addr = cl->me_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;
 
@@ -1157,12 +1571,14 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
        cb->buf_idx += mei_hdr.length;
        cb->completed = mei_hdr.msg_complete == 1;
 
-       if (mei_hdr.msg_complete) {
+       if (first_chunk) {
                if (mei_cl_flow_ctrl_reduce(cl))
                        return -EIO;
-               list_move_tail(&cb->list, &dev->write_waiting_list.list);
        }
 
+       if (mei_hdr.msg_complete)
+               list_move_tail(&cb->list, &dev->write_waiting_list.list);
+
        return 0;
 }
 
@@ -1181,6 +1597,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
        struct mei_device *dev;
        struct mei_msg_data *buf;
        struct mei_msg_hdr mei_hdr;
+       int size;
        int rets;
 
 
@@ -1192,10 +1609,10 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
 
        dev = cl->dev;
 
-
        buf = &cb->buf;
+       size = buf->size;
 
-       cl_dbg(dev, cl, "size=%d\n", buf->size);
+       cl_dbg(dev, cl, "size=%d\n", size);
 
        rets = pm_runtime_get(dev->dev);
        if (rets < 0 && rets != -EINPROGRESS) {
@@ -1207,8 +1624,8 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
        cb->buf_idx = 0;
        cl->writing_state = MEI_IDLE;
 
-       mei_hdr.host_addr = cl->host_client_id;
-       mei_hdr.me_addr = cl->me_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;
        mei_hdr.internal = cb->internal;
@@ -1219,21 +1636,21 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
 
        if (rets == 0) {
                cl_dbg(dev, cl, "No flow control credentials: not sending.\n");
-               rets = buf->size;
+               rets = size;
                goto out;
        }
        if (!mei_hbuf_acquire(dev)) {
                cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n");
-               rets = buf->size;
+               rets = size;
                goto out;
        }
 
        /* Check for a maximum length */
-       if (buf->size > mei_hbuf_max_len(dev)) {
+       if (size > mei_hbuf_max_len(dev)) {
                mei_hdr.length = mei_hbuf_max_len(dev);
                mei_hdr.msg_complete = 0;
        } else {
-               mei_hdr.length = buf->size;
+               mei_hdr.length = size;
                mei_hdr.msg_complete = 1;
        }
 
@@ -1241,22 +1658,21 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
        if (rets)
                goto err;
 
+       rets = mei_cl_flow_ctrl_reduce(cl);
+       if (rets)
+               goto err;
+
        cl->writing_state = MEI_WRITING;
        cb->buf_idx = mei_hdr.length;
        cb->completed = mei_hdr.msg_complete == 1;
 
 out:
-       if (mei_hdr.msg_complete) {
-               rets = mei_cl_flow_ctrl_reduce(cl);
-               if (rets < 0)
-                       goto err;
-
+       if (mei_hdr.msg_complete)
                list_add_tail(&cb->list, &dev->write_waiting_list.list);
-       } else {
+       else
                list_add_tail(&cb->list, &dev->write_list.list);
-       }
-
 
+       cb = NULL;
        if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
 
                mutex_unlock(&dev->device_lock);
@@ -1271,7 +1687,7 @@ out:
                }
        }
 
-       rets = buf->size;
+       rets = size;
 err:
        cl_dbg(dev, cl, "rpm: autosuspend\n");
        pm_runtime_mark_last_busy(dev->dev);
@@ -1289,20 +1705,38 @@ err:
  */
 void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
 {
-       if (cb->fop_type == MEI_FOP_WRITE) {
+       struct mei_device *dev = cl->dev;
+
+       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))
+               if (waitqueue_active(&cl->tx_wait)) {
                        wake_up_interruptible(&cl->tx_wait);
+               } else {
+                       pm_runtime_mark_last_busy(dev->dev);
+                       pm_request_autosuspend(dev->dev);
+               }
+               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:
+       case MEI_FOP_NOTIFY_STOP:
+       case MEI_FOP_NOTIFY_START:
+               if (waitqueue_active(&cl->wait))
+                       wake_up(&cl->wait);
+
+               break;
+       default:
+               BUG_ON(0);
        }
 }
 
@@ -1312,16 +1746,12 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
  *
  * @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);
 }
 
 
@@ -1343,6 +1773,12 @@ void mei_cl_all_wakeup(struct mei_device *dev)
                        cl_dbg(dev, cl, "Waking up writing client!\n");
                        wake_up_interruptible(&cl->tx_wait);
                }
+
+               /* synchronized under device mutex */
+               if (waitqueue_active(&cl->ev_wait)) {
+                       cl_dbg(dev, cl, "Waking up waiting for event clients!\n");
+                       wake_up_interruptible(&cl->ev_wait);
+               }
        }
 }