| From: John Ogness <john.ogness@linutronix.de> |
| Date: Tue, 12 Feb 2019 15:29:44 +0100 |
| Subject: [PATCH 06/25] printk-rb: add blocking reader support |
| |
| Add a blocking read function for readers. An irq_work function is |
| used to signal the wait queue so that write notification can |
| be triggered from any context. |
| |
| Signed-off-by: John Ogness <john.ogness@linutronix.de> |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| --- |
| include/linux/printk_ringbuffer.h | 20 +++++++++++++ |
| lib/printk_ringbuffer.c | 55 ++++++++++++++++++++++++++++++++++++++ |
| 2 files changed, 75 insertions(+) |
| |
| --- a/include/linux/printk_ringbuffer.h |
| +++ b/include/linux/printk_ringbuffer.h |
| @@ -2,8 +2,10 @@ |
| #ifndef _LINUX_PRINTK_RINGBUFFER_H |
| #define _LINUX_PRINTK_RINGBUFFER_H |
| |
| +#include <linux/irq_work.h> |
| #include <linux/atomic.h> |
| #include <linux/percpu.h> |
| +#include <linux/wait.h> |
| |
| struct prb_cpulock { |
| atomic_t owner; |
| @@ -22,6 +24,10 @@ struct printk_ringbuffer { |
| |
| struct prb_cpulock *cpulock; |
| atomic_t ctx; |
| + |
| + struct wait_queue_head *wq; |
| + atomic_long_t wq_counter; |
| + struct irq_work *wq_work; |
| }; |
| |
| struct prb_entry { |
| @@ -59,6 +65,15 @@ struct prb_iterator { |
| #define DECLARE_STATIC_PRINTKRB(name, szbits, cpulockptr) \ |
| static char _##name##_buffer[1 << (szbits)] \ |
| __aligned(__alignof__(long)); \ |
| +static DECLARE_WAIT_QUEUE_HEAD(_##name##_wait); \ |
| +static void _##name##_wake_work_func(struct irq_work *irq_work) \ |
| +{ \ |
| + wake_up_interruptible_all(&_##name##_wait); \ |
| +} \ |
| +static struct irq_work _##name##_wake_work = { \ |
| + .func = _##name##_wake_work_func, \ |
| + .flags = ATOMIC_INIT(IRQ_WORK_LAZY), \ |
| +}; \ |
| static struct printk_ringbuffer name = { \ |
| .buffer = &_##name##_buffer[0], \ |
| .size_bits = szbits, \ |
| @@ -68,6 +83,9 @@ static struct printk_ringbuffer name = { |
| .reserve = ATOMIC_LONG_INIT(-111 * sizeof(long)), \ |
| .cpulock = cpulockptr, \ |
| .ctx = ATOMIC_INIT(0), \ |
| + .wq = &_##name##_wait, \ |
| + .wq_counter = ATOMIC_LONG_INIT(0), \ |
| + .wq_work = &_##name##_wake_work, \ |
| } |
| |
| /* writer interface */ |
| @@ -80,6 +98,8 @@ void prb_iter_init(struct prb_iterator * |
| u64 *seq); |
| void prb_iter_copy(struct prb_iterator *dest, struct prb_iterator *src); |
| int prb_iter_next(struct prb_iterator *iter, char *buf, int size, u64 *seq); |
| +int prb_iter_wait_next(struct prb_iterator *iter, char *buf, int size, |
| + u64 *seq); |
| int prb_iter_data(struct prb_iterator *iter, char *buf, int size, u64 *seq); |
| |
| /* utility functions */ |
| --- a/lib/printk_ringbuffer.c |
| +++ b/lib/printk_ringbuffer.c |
| @@ -1,4 +1,5 @@ |
| // SPDX-License-Identifier: GPL-2.0 |
| +#include <linux/sched.h> |
| #include <linux/smp.h> |
| #include <linux/string.h> |
| #include <linux/errno.h> |
| @@ -154,6 +155,7 @@ static bool push_tail(struct printk_ring |
| void prb_commit(struct prb_handle *h) |
| { |
| struct printk_ringbuffer *rb = h->rb; |
| + bool changed = false; |
| struct prb_entry *e; |
| unsigned long head; |
| unsigned long res; |
| @@ -175,6 +177,7 @@ void prb_commit(struct prb_handle *h) |
| } |
| e->seq = ++rb->seq; |
| head += e->size; |
| + changed = true; |
| } |
| atomic_long_set_release(&rb->head, res); |
| atomic_dec(&rb->ctx); |
| @@ -185,6 +188,18 @@ void prb_commit(struct prb_handle *h) |
| } |
| |
| prb_unlock(rb->cpulock, h->cpu); |
| + |
| + if (changed) { |
| + atomic_long_inc(&rb->wq_counter); |
| + if (wq_has_sleeper(rb->wq)) { |
| +#ifdef CONFIG_IRQ_WORK |
| + irq_work_queue(rb->wq_work); |
| +#else |
| + if (!in_nmi()) |
| + wake_up_interruptible_all(rb->wq); |
| +#endif |
| + } |
| + } |
| } |
| |
| /* |
| @@ -437,3 +452,43 @@ int prb_iter_next(struct prb_iterator *i |
| |
| return 1; |
| } |
| + |
| +/* |
| + * prb_iter_wait_next: Advance to the next record, blocking if none available. |
| + * @iter: Iterator tracking the current position. |
| + * @buf: A buffer to store the data of the next record. May be NULL. |
| + * @size: The size of @buf. (Ignored if @buf is NULL.) |
| + * @seq: The sequence number of the next record. May be NULL. |
| + * |
| + * If a next record is already available, this function works like |
| + * prb_iter_next(). Otherwise block interruptible until a next record is |
| + * available. |
| + * |
| + * When a next record is available, @iter is advanced and (if specified) |
| + * the data and/or sequence number of that record are provided. |
| + * |
| + * This function might sleep. |
| + * |
| + * Returns 1 if @iter was advanced, -EINVAL if @iter is now invalid, or |
| + * -ERESTARTSYS if interrupted by a signal. |
| + */ |
| +int prb_iter_wait_next(struct prb_iterator *iter, char *buf, int size, u64 *seq) |
| +{ |
| + unsigned long last_seen; |
| + int ret; |
| + |
| + for (;;) { |
| + last_seen = atomic_long_read(&iter->rb->wq_counter); |
| + |
| + ret = prb_iter_next(iter, buf, size, seq); |
| + if (ret != 0) |
| + break; |
| + |
| + ret = wait_event_interruptible(*iter->rb->wq, |
| + last_seen != atomic_long_read(&iter->rb->wq_counter)); |
| + if (ret < 0) |
| + break; |
| + } |
| + |
| + return ret; |
| +} |