Extend the workqueue API so that singlethread workqueues can be exposed
in sysfs. Guarantee max_active is 1 by turning it read-only.

This allows admins to control the cpumask of a workqueue, and apply
the desired system cpu separation/isolation policy.

Signed-off-by: Tariq Toukan <tar...@nvidia.com>
---
 drivers/net/ethernet/mellanox/mlx5/core/cmd.c |  2 +-
 include/linux/workqueue.h                     |  4 ++++
 kernel/workqueue.c                            | 21 +++++++++++++++++--
 3 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/cmd.c 
b/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
index 1d91a0d0ab1d..5106820a5cd5 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
@@ -2033,7 +2033,7 @@ int mlx5_cmd_init(struct mlx5_core_dev *dev)
        create_msg_cache(dev);
 
        set_wqname(dev);
-       cmd->wq = create_singlethread_workqueue(cmd->wq_name);
+       cmd->wq = create_singlethread_sysfs_workqueue(cmd->wq_name);
        if (!cmd->wq) {
                mlx5_core_err(dev, "failed to create command workqueue\n");
                err = -ENOMEM;
diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h
index 26de0cae2a0a..d4d4ca2b041a 100644
--- a/include/linux/workqueue.h
+++ b/include/linux/workqueue.h
@@ -344,6 +344,7 @@ enum {
        __WQ_ORDERED            = 1 << 17, /* internal: workqueue is ordered */
        __WQ_LEGACY             = 1 << 18, /* internal: create*_workqueue() */
        __WQ_ORDERED_EXPLICIT   = 1 << 19, /* internal: 
alloc_ordered_workqueue() */
+       __WQ_MAX_ACTIVE_RO      = 1 << 20, /* internal: make max_active 
read-only */
 
        WQ_MAX_ACTIVE           = 512,    /* I like 512, better ideas? */
        WQ_MAX_UNBOUND_PER_CPU  = 4,      /* 4 * #cpus for unbound wq */
@@ -432,6 +433,9 @@ struct workqueue_struct *alloc_workqueue(const char *fmt,
                        WQ_MEM_RECLAIM, 1, (name))
 #define create_singlethread_workqueue(name)                            \
        alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
+#define create_singlethread_sysfs_workqueue(name)                      \
+       alloc_ordered_workqueue("%s", __WQ_MAX_ACTIVE_RO |              \
+                               __WQ_LEGACY | WQ_MEM_RECLAIM, name)
 
 extern void destroy_workqueue(struct workqueue_struct *wq);
 
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index c41c3c17b86a..a80d34726e68 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -4258,6 +4258,9 @@ struct workqueue_struct *alloc_workqueue(const char *fmt,
        if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
                flags |= WQ_UNBOUND;
 
+       if (flags & __WQ_MAX_ACTIVE_RO)
+               flags |= WQ_SYSFS;
+
        /* allocate wq and format name */
        if (flags & WQ_UNBOUND)
                tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);
@@ -5401,9 +5404,11 @@ static ssize_t max_active_store(struct device *dev,
 }
 static DEVICE_ATTR_RW(max_active);
 
+static struct device_attribute dev_attr_max_active_ro =
+       __ATTR(max_active, 0444, max_active_show, NULL);
+
 static struct attribute *wq_sysfs_attrs[] = {
        &dev_attr_per_cpu.attr,
-       &dev_attr_max_active.attr,
        NULL,
 };
 ATTRIBUTE_GROUPS(wq_sysfs);
@@ -5642,6 +5647,7 @@ static void wq_device_release(struct device *dev)
  */
 int workqueue_sysfs_register(struct workqueue_struct *wq)
 {
+       const struct device_attribute *max_active_entry;
        struct wq_device *wq_dev;
        int ret;
 
@@ -5650,7 +5656,8 @@ int workqueue_sysfs_register(struct workqueue_struct *wq)
         * attributes breaks ordering guarantee.  Disallow exposing ordered
         * workqueues.
         */
-       if (WARN_ON(wq->flags & __WQ_ORDERED_EXPLICIT))
+       if (WARN_ON(!(wq->flags & __WQ_MAX_ACTIVE_RO) &&
+                   wq->flags & __WQ_ORDERED_EXPLICIT))
                return -EINVAL;
 
        wq->wq_dev = wq_dev = kzalloc(sizeof(*wq_dev), GFP_KERNEL);
@@ -5675,6 +5682,16 @@ int workqueue_sysfs_register(struct workqueue_struct *wq)
                return ret;
        }
 
+       max_active_entry = wq->flags & __WQ_MAX_ACTIVE_RO ?
+               &dev_attr_max_active_ro : &dev_attr_max_active;
+
+       ret = device_create_file(&wq_dev->dev, max_active_entry);
+       if (ret) {
+               device_unregister(&wq_dev->dev);
+               wq->wq_dev = NULL;
+               return ret;
+       }
+
        if (wq->flags & WQ_UNBOUND) {
                struct device_attribute *attr;
 
-- 
2.21.0

Reply via email to