Commit af336cab authored by Alexander Usyskin's avatar Alexander Usyskin Committed by Greg Kroah-Hartman

mei: limit the number of queued writes

Limit the number of queued writes per client.
Writes above this threshold are blocked till place
in the transmit queue is available.
The limit is configurable via sysfs and defaults to 50.
The implementation should provide blocking I/O behavior.
Prior to this change one would end up in the hands of OOM.
Signed-off-by: default avatarAlexander Usyskin <alexander.usyskin@intel.com>
Signed-off-by: default avatarTomas Winkler <tomas.winkler@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 257355a4
...@@ -45,3 +45,12 @@ Contact: Tomas Winkler <tomas.winkler@intel.com> ...@@ -45,3 +45,12 @@ Contact: Tomas Winkler <tomas.winkler@intel.com>
Description: Display the driver HBM protocol version. Description: Display the driver HBM protocol version.
The HBM protocol version supported by the driver. The HBM protocol version supported by the driver.
What: /sys/class/mei/meiN/tx_queue_limit
Date: Jan 2018
KernelVersion: 4.16
Contact: Tomas Winkler <tomas.winkler@intel.com>
Description: Configure tx queue limit
Set maximal number of pending writes
per opened session.
...@@ -74,6 +74,23 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, ...@@ -74,6 +74,23 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
goto out; goto out;
} }
while (cl->tx_cb_queued >= bus->tx_queue_limit) {
mutex_unlock(&bus->device_lock);
rets = wait_event_interruptible(cl->tx_wait,
cl->writing_state == MEI_WRITE_COMPLETE ||
(!mei_cl_is_connected(cl)));
mutex_lock(&bus->device_lock);
if (rets) {
if (signal_pending(current))
rets = -EINTR;
goto out;
}
if (!mei_cl_is_connected(cl)) {
rets = -ENODEV;
goto out;
}
}
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL); cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL);
if (!cb) { if (!cb) {
rets = -ENOMEM; rets = -ENOMEM;
......
...@@ -349,6 +349,36 @@ void mei_io_cb_free(struct mei_cl_cb *cb) ...@@ -349,6 +349,36 @@ void mei_io_cb_free(struct mei_cl_cb *cb)
kfree(cb); kfree(cb);
} }
/**
* mei_tx_cb_queue - queue tx callback
*
* Locking: called under "dev->device_lock" lock
*
* @cb: mei callback struct
* @head: an instance of list to queue on
*/
static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb,
struct list_head *head)
{
list_add_tail(&cb->list, head);
cb->cl->tx_cb_queued++;
}
/**
* mei_tx_cb_dequeue - dequeue tx callback
*
* Locking: called under "dev->device_lock" lock
*
* @cb: mei callback struct to dequeue and free
*/
static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb)
{
if (!WARN_ON(cb->cl->tx_cb_queued == 0))
cb->cl->tx_cb_queued--;
mei_io_cb_free(cb);
}
/** /**
* mei_io_cb_init - allocate and initialize io callback * mei_io_cb_init - allocate and initialize io callback
* *
...@@ -377,49 +407,37 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, ...@@ -377,49 +407,37 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl,
} }
/** /**
* __mei_io_list_flush_cl - removes and frees cbs belonging to cl. * mei_io_list_flush_cl - removes cbs belonging to the cl.
* *
* @head: an instance of our list structure * @head: an instance of our list structure
* @cl: host client, can be NULL for flushing the whole list * @cl: host client
* @free: whether to free the cbs
*/ */
static void __mei_io_list_flush_cl(struct list_head *head, static void mei_io_list_flush_cl(struct list_head *head,
const struct mei_cl *cl, bool free) const struct mei_cl *cl)
{ {
struct mei_cl_cb *cb, *next; struct mei_cl_cb *cb, *next;
/* enable removing everything if no cl is specified */
list_for_each_entry_safe(cb, next, head, list) { list_for_each_entry_safe(cb, next, head, list) {
if (!cl || mei_cl_cmp_id(cl, cb->cl)) { if (mei_cl_cmp_id(cl, cb->cl))
list_del_init(&cb->list); list_del_init(&cb->list);
if (free)
mei_io_cb_free(cb);
}
} }
} }
/** /**
* mei_io_list_flush_cl - removes list entry belonging to cl. * mei_io_tx_list_free_cl - removes cb belonging to the cl and free them
* *
* @head: An instance of our list structure * @head: An instance of our list structure
* @cl: host client * @cl: host client
*/ */
static inline void mei_io_list_flush_cl(struct list_head *head, static void mei_io_tx_list_free_cl(struct list_head *head,
const struct mei_cl *cl) const struct mei_cl *cl)
{ {
__mei_io_list_flush_cl(head, cl, false); struct mei_cl_cb *cb, *next;
}
/** list_for_each_entry_safe(cb, next, head, list) {
* mei_io_list_free_cl - removes cb belonging to cl and free them if (mei_cl_cmp_id(cl, cb->cl))
* mei_tx_cb_dequeue(cb);
* @head: An instance of our list structure }
* @cl: host client
*/
static inline void mei_io_list_free_cl(struct list_head *head,
const struct mei_cl *cl)
{
__mei_io_list_flush_cl(head, cl, true);
} }
/** /**
...@@ -538,8 +556,8 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) ...@@ -538,8 +556,8 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
dev = cl->dev; dev = cl->dev;
cl_dbg(dev, cl, "remove list entry belonging to cl\n"); cl_dbg(dev, cl, "remove list entry belonging to cl\n");
mei_io_list_free_cl(&cl->dev->write_list, cl); mei_io_tx_list_free_cl(&cl->dev->write_list, cl);
mei_io_list_free_cl(&cl->dev->write_waiting_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_wr_list, cl);
mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
mei_io_list_free_fp(&cl->rd_pending, fp); mei_io_list_free_fp(&cl->rd_pending, fp);
...@@ -756,8 +774,8 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) ...@@ -756,8 +774,8 @@ static void mei_cl_set_disconnected(struct mei_cl *cl)
return; return;
cl->state = MEI_FILE_DISCONNECTED; cl->state = MEI_FILE_DISCONNECTED;
mei_io_list_free_cl(&dev->write_list, cl); mei_io_tx_list_free_cl(&dev->write_list, cl);
mei_io_list_free_cl(&dev->write_waiting_list, cl); mei_io_tx_list_free_cl(&dev->write_waiting_list, cl);
mei_io_list_flush_cl(&dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&dev->ctrl_rd_list, cl);
mei_io_list_flush_cl(&dev->ctrl_wr_list, cl); mei_io_list_flush_cl(&dev->ctrl_wr_list, cl);
mei_cl_wake_all(cl); mei_cl_wake_all(cl);
...@@ -1693,9 +1711,9 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) ...@@ -1693,9 +1711,9 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
out: out:
if (mei_hdr.msg_complete) if (mei_hdr.msg_complete)
list_add_tail(&cb->list, &dev->write_waiting_list); mei_tx_cb_enqueue(cb, &dev->write_waiting_list);
else else
list_add_tail(&cb->list, &dev->write_list); mei_tx_cb_enqueue(cb, &dev->write_list);
cb = NULL; cb = NULL;
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
...@@ -1741,7 +1759,7 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) ...@@ -1741,7 +1759,7 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
switch (cb->fop_type) { switch (cb->fop_type) {
case MEI_FOP_WRITE: case MEI_FOP_WRITE:
mei_io_cb_free(cb); mei_tx_cb_dequeue(cb);
cl->writing_state = MEI_WRITE_COMPLETE; cl->writing_state = MEI_WRITE_COMPLETE;
if (waitqueue_active(&cl->tx_wait)) { if (waitqueue_active(&cl->tx_wait)) {
wake_up_interruptible(&cl->tx_wait); wake_up_interruptible(&cl->tx_wait);
......
...@@ -97,7 +97,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, ...@@ -97,7 +97,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
int pos = 0; int pos = 0;
int ret; int ret;
#define HDR " |me|host|state|rd|wr|\n" #define HDR " |me|host|state|rd|wr|wrq\n"
if (!dev) if (!dev)
return -ENODEV; return -ENODEV;
...@@ -130,9 +130,10 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, ...@@ -130,9 +130,10 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
list_for_each_entry(cl, &dev->file_list, link) { list_for_each_entry(cl, &dev->file_list, link) {
pos += scnprintf(buf + pos, bufsz - pos, pos += scnprintf(buf + pos, bufsz - pos,
"%3d|%2d|%4d|%5d|%2d|%2d|\n", "%3d|%2d|%4d|%5d|%2d|%2d|%3u\n",
i, mei_cl_me_id(cl), cl->host_client_id, cl->state, i, mei_cl_me_id(cl), cl->host_client_id, cl->state,
!list_empty(&cl->rd_completed), cl->writing_state); !list_empty(&cl->rd_completed), cl->writing_state,
cl->tx_cb_queued);
i++; i++;
} }
out: out:
......
...@@ -383,6 +383,7 @@ void mei_device_init(struct mei_device *dev, ...@@ -383,6 +383,7 @@ void mei_device_init(struct mei_device *dev,
INIT_LIST_HEAD(&dev->write_waiting_list); INIT_LIST_HEAD(&dev->write_waiting_list);
INIT_LIST_HEAD(&dev->ctrl_wr_list); INIT_LIST_HEAD(&dev->ctrl_wr_list);
INIT_LIST_HEAD(&dev->ctrl_rd_list); INIT_LIST_HEAD(&dev->ctrl_rd_list);
dev->tx_queue_limit = MEI_TX_QUEUE_LIMIT_DEFAULT;
INIT_DELAYED_WORK(&dev->timer_work, mei_timer); INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
INIT_WORK(&dev->reset_work, mei_reset_work); INIT_WORK(&dev->reset_work, mei_reset_work);
......
...@@ -291,6 +291,27 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, ...@@ -291,6 +291,27 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
goto out; goto out;
} }
while (cl->tx_cb_queued >= dev->tx_queue_limit) {
if (file->f_flags & O_NONBLOCK) {
rets = -EAGAIN;
goto out;
}
mutex_unlock(&dev->device_lock);
rets = wait_event_interruptible(cl->tx_wait,
cl->writing_state == MEI_WRITE_COMPLETE ||
(!mei_cl_is_connected(cl)));
mutex_lock(&dev->device_lock);
if (rets) {
if (signal_pending(current))
rets = -EINTR;
goto out;
}
if (!mei_cl_is_connected(cl)) {
rets = -ENODEV;
goto out;
}
}
*offset = 0; *offset = 0;
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file);
if (!cb) { if (!cb) {
...@@ -580,6 +601,12 @@ static __poll_t mei_poll(struct file *file, poll_table *wait) ...@@ -580,6 +601,12 @@ static __poll_t mei_poll(struct file *file, poll_table *wait)
mei_cl_read_start(cl, mei_cl_mtu(cl), file); mei_cl_read_start(cl, mei_cl_mtu(cl), file);
} }
if (req_events & (POLLOUT | POLLWRNORM)) {
poll_wait(file, &cl->tx_wait, wait);
if (cl->tx_cb_queued < dev->tx_queue_limit)
mask |= POLLOUT | POLLWRNORM;
}
out: out:
mutex_unlock(&dev->device_lock); mutex_unlock(&dev->device_lock);
return mask; return mask;
...@@ -749,10 +776,48 @@ static ssize_t hbm_ver_drv_show(struct device *device, ...@@ -749,10 +776,48 @@ static ssize_t hbm_ver_drv_show(struct device *device,
} }
static DEVICE_ATTR_RO(hbm_ver_drv); static DEVICE_ATTR_RO(hbm_ver_drv);
static ssize_t tx_queue_limit_show(struct device *device,
struct device_attribute *attr, char *buf)
{
struct mei_device *dev = dev_get_drvdata(device);
u8 size = 0;
mutex_lock(&dev->device_lock);
size = dev->tx_queue_limit;
mutex_unlock(&dev->device_lock);
return snprintf(buf, PAGE_SIZE, "%u\n", size);
}
static ssize_t tx_queue_limit_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct mei_device *dev = dev_get_drvdata(device);
u8 limit;
unsigned int inp;
int err;
err = kstrtouint(buf, 10, &inp);
if (err)
return err;
if (inp > MEI_TX_QUEUE_LIMIT_MAX || inp < MEI_TX_QUEUE_LIMIT_MIN)
return -EINVAL;
limit = inp;
mutex_lock(&dev->device_lock);
dev->tx_queue_limit = limit;
mutex_unlock(&dev->device_lock);
return count;
}
static DEVICE_ATTR_RW(tx_queue_limit);
static struct attribute *mei_attrs[] = { static struct attribute *mei_attrs[] = {
&dev_attr_fw_status.attr, &dev_attr_fw_status.attr,
&dev_attr_hbm_ver.attr, &dev_attr_hbm_ver.attr,
&dev_attr_hbm_ver_drv.attr, &dev_attr_hbm_ver_drv.attr,
&dev_attr_tx_queue_limit.attr,
NULL NULL
}; };
ATTRIBUTE_GROUPS(mei); ATTRIBUTE_GROUPS(mei);
......
...@@ -210,6 +210,7 @@ struct mei_cl_cb { ...@@ -210,6 +210,7 @@ struct mei_cl_cb {
* @timer_count: watchdog timer for operation completion * @timer_count: watchdog timer for operation completion
* @notify_en: notification - enabled/disabled * @notify_en: notification - enabled/disabled
* @notify_ev: pending notification event * @notify_ev: pending notification event
* @tx_cb_queued: number of tx callbacks in queue
* @writing_state: state of the tx * @writing_state: state of the tx
* @rd_pending: pending read credits * @rd_pending: pending read credits
* @rd_completed: completed read * @rd_completed: completed read
...@@ -234,6 +235,7 @@ struct mei_cl { ...@@ -234,6 +235,7 @@ struct mei_cl {
u8 timer_count; u8 timer_count;
u8 notify_en; u8 notify_en;
u8 notify_ev; u8 notify_ev;
u8 tx_cb_queued;
enum mei_file_transaction_states writing_state; enum mei_file_transaction_states writing_state;
struct list_head rd_pending; struct list_head rd_pending;
struct list_head rd_completed; struct list_head rd_completed;
...@@ -241,6 +243,10 @@ struct mei_cl { ...@@ -241,6 +243,10 @@ struct mei_cl {
struct mei_cl_device *cldev; struct mei_cl_device *cldev;
}; };
#define MEI_TX_QUEUE_LIMIT_DEFAULT 50
#define MEI_TX_QUEUE_LIMIT_MAX 255
#define MEI_TX_QUEUE_LIMIT_MIN 30
/** /**
* struct mei_hw_ops - hw specific ops * struct mei_hw_ops - hw specific ops
* *
...@@ -359,6 +365,7 @@ const char *mei_pg_state_str(enum mei_pg_state state); ...@@ -359,6 +365,7 @@ const char *mei_pg_state_str(enum mei_pg_state state);
* @write_waiting_list : write completion list * @write_waiting_list : write completion list
* @ctrl_wr_list : pending control write list * @ctrl_wr_list : pending control write list
* @ctrl_rd_list : pending control read list * @ctrl_rd_list : pending control read list
* @tx_queue_limit: tx queues per client linit
* *
* @file_list : list of opened handles * @file_list : list of opened handles
* @open_handle_count: number of opened handles * @open_handle_count: number of opened handles
...@@ -423,6 +430,7 @@ struct mei_device { ...@@ -423,6 +430,7 @@ struct mei_device {
struct list_head write_waiting_list; struct list_head write_waiting_list;
struct list_head ctrl_wr_list; struct list_head ctrl_wr_list;
struct list_head ctrl_rd_list; struct list_head ctrl_rd_list;
u8 tx_queue_limit;
struct list_head file_list; struct list_head file_list;
long open_handle_count; long open_handle_count;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment