| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Light-weight single-linked queue.
- *
- * Entries are enqueued to the head of an llist, with no blocking.
- * This can happen in any context.
- *
- * Entries are dequeued using a spinlock to protect against multiple
- * access. The llist is staged in reverse order, and refreshed
- * from the llist when it exhausts.
- *
- * This is particularly suitable when work items are queued in BH or
- * IRQ context, and where work items are handled one at a time by
- * dedicated threads.
- */
- #include <linux/rcupdate.h>
- #include <linux/lwq.h>
- struct llist_node *__lwq_dequeue(struct lwq *q)
- {
- struct llist_node *this;
- if (lwq_empty(q))
- return NULL;
- spin_lock(&q->lock);
- this = q->ready;
- if (!this && !llist_empty(&q->new)) {
- /* ensure queue doesn't appear transiently lwq_empty */
- smp_store_release(&q->ready, (void *)1);
- this = llist_reverse_order(llist_del_all(&q->new));
- if (!this)
- q->ready = NULL;
- }
- if (this)
- q->ready = llist_next(this);
- spin_unlock(&q->lock);
- return this;
- }
- EXPORT_SYMBOL_GPL(__lwq_dequeue);
- /**
- * lwq_dequeue_all - dequeue all currently enqueued objects
- * @q: the queue to dequeue from
- *
- * Remove and return a linked list of llist_nodes of all the objects that were
- * in the queue. The first on the list will be the object that was least
- * recently enqueued.
- */
- struct llist_node *lwq_dequeue_all(struct lwq *q)
- {
- struct llist_node *r, *t, **ep;
- if (lwq_empty(q))
- return NULL;
- spin_lock(&q->lock);
- r = q->ready;
- q->ready = NULL;
- t = llist_del_all(&q->new);
- spin_unlock(&q->lock);
- ep = &r;
- while (*ep)
- ep = &(*ep)->next;
- *ep = llist_reverse_order(t);
- return r;
- }
- EXPORT_SYMBOL_GPL(lwq_dequeue_all);
- #if IS_ENABLED(CONFIG_LWQ_TEST)
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/wait_bit.h>
- #include <linux/kthread.h>
- #include <linux/delay.h>
- struct tnode {
- struct lwq_node n;
- int i;
- int c;
- };
- static int lwq_exercise(void *qv)
- {
- struct lwq *q = qv;
- int cnt;
- struct tnode *t;
- for (cnt = 0; cnt < 10000; cnt++) {
- wait_var_event(q, (t = lwq_dequeue(q, struct tnode, n)) != NULL);
- t->c++;
- if (lwq_enqueue(&t->n, q))
- wake_up_var(q);
- }
- while (!kthread_should_stop())
- schedule_timeout_idle(1);
- return 0;
- }
- static int lwq_test(void)
- {
- int i;
- struct lwq q;
- struct llist_node *l, **t1, *t2;
- struct tnode *t;
- struct task_struct *threads[8];
- printk(KERN_INFO "testing lwq....\n");
- lwq_init(&q);
- printk(KERN_INFO " lwq: run some threads\n");
- for (i = 0; i < ARRAY_SIZE(threads); i++)
- threads[i] = kthread_run(lwq_exercise, &q, "lwq-test-%d", i);
- for (i = 0; i < 100; i++) {
- t = kmalloc_obj(*t);
- if (!t)
- break;
- t->i = i;
- t->c = 0;
- if (lwq_enqueue(&t->n, &q))
- wake_up_var(&q);
- }
- /* wait for threads to exit */
- for (i = 0; i < ARRAY_SIZE(threads); i++)
- if (!IS_ERR_OR_NULL(threads[i]))
- kthread_stop(threads[i]);
- printk(KERN_INFO " lwq: dequeue first 50:");
- for (i = 0; i < 50 ; i++) {
- if (i && (i % 10) == 0) {
- printk(KERN_CONT "\n");
- printk(KERN_INFO " lwq: ... ");
- }
- t = lwq_dequeue(&q, struct tnode, n);
- if (t)
- printk(KERN_CONT " %d(%d)", t->i, t->c);
- kfree(t);
- }
- printk(KERN_CONT "\n");
- l = lwq_dequeue_all(&q);
- printk(KERN_INFO " lwq: delete the multiples of 3 (test lwq_for_each_safe())\n");
- lwq_for_each_safe(t, t1, t2, &l, n) {
- if ((t->i % 3) == 0) {
- t->i = -1;
- kfree(t);
- t = NULL;
- }
- }
- if (l)
- lwq_enqueue_batch(l, &q);
- printk(KERN_INFO " lwq: dequeue remaining:");
- while ((t = lwq_dequeue(&q, struct tnode, n)) != NULL) {
- printk(KERN_CONT " %d", t->i);
- kfree(t);
- }
- printk(KERN_CONT "\n");
- return 0;
- }
- module_init(lwq_test);
- #endif /* CONFIG_LWQ_TEST*/
|