cregit-Linux how code gets into the kernel

Release 4.15 kernel/relay.c

Directory: kernel
/*
 * Public API and common code for kernel->userspace relay file support.
 *
 * See Documentation/filesystems/relay.txt for an overview.
 *
 * Copyright (C) 2002-2005 - Tom Zanussi (zanussi@us.ibm.com), IBM Corp
 * Copyright (C) 1999-2005 - Karim Yaghmour (karim@opersys.com)
 *
 * Moved to kernel/relay.c by Paul Mundt, 2006.
 * November 2006 - CPU hotplug support by Mathieu Desnoyers
 *      (mathieu.desnoyers@polymtl.ca)
 *
 * This file is released under the GPL.
 */
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/string.h>
#include <linux/relay.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/splice.h>

/* list of open channels, for cpu hotplug */
static DEFINE_MUTEX(relay_channels_mutex);
static LIST_HEAD(relay_channels);

/*
 * close() vm_op implementation for relay file mapping.
 */

static void relay_file_mmap_close(struct vm_area_struct *vma) { struct rchan_buf *buf = vma->vm_private_data; buf->chan->cb->buf_unmapped(buf, vma->vm_file); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe35100.00%1100.00%
Total35100.00%1100.00%

/* * fault() vm_op implementation for relay file mapping. */
static int relay_buf_fault(struct vm_fault *vmf) { struct page *page; struct rchan_buf *buf = vmf->vma->vm_private_data; pgoff_t pgoff = vmf->pgoff; if (!buf) return VM_FAULT_OOM; page = vmalloc_to_page(buf->start + (pgoff << PAGE_SHIFT)); if (!page) return VM_FAULT_SIGBUS; get_page(page); vmf->page = page; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe5772.15%133.33%
Nicholas Piggin2025.32%133.33%
Dave Jiang22.53%133.33%
Total79100.00%3100.00%

/* * vm_ops for relay file mappings. */ static const struct vm_operations_struct relay_file_mmap_ops = { .fault = relay_buf_fault, .close = relay_file_mmap_close, }; /* * allocate an array of pointers of struct page */
static struct page **relay_alloc_page_array(unsigned int n_pages) { const size_t pa_size = n_pages * sizeof(struct page *); if (pa_size > PAGE_SIZE) return vzalloc(pa_size); return kzalloc(pa_size, GFP_KERNEL); }

Contributors

PersonTokensPropCommitsCommitProp
Masami Hiramatsu4189.13%150.00%
Jesper Juhl510.87%150.00%
Total46100.00%2100.00%

/* * free an array of pointers of struct page */
static void relay_free_page_array(struct page **array) { kvfree(array); }

Contributors

PersonTokensPropCommitsCommitProp
Masami Hiramatsu1694.12%150.00%
Pekka J Enberg15.88%150.00%
Total17100.00%2100.00%

/** * relay_mmap_buf: - mmap channel buffer to process address space * @buf: relay channel buffer * @vma: vm_area_struct describing memory to be mapped * * Returns 0 if ok, negative on error * * Caller should already have grabbed mmap_sem. */
static int relay_mmap_buf(struct rchan_buf *buf, struct vm_area_struct *vma) { unsigned long length = vma->vm_end - vma->vm_start; struct file *filp = vma->vm_file; if (!buf) return -EBADF; if (length != (unsigned long)buf->chan->alloc_size) return -EINVAL; vma->vm_ops = &relay_file_mmap_ops; vma->vm_flags |= VM_DONTEXPAND; vma->vm_private_data = buf; buf->chan->cb->buf_mapped(buf, filp); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe9292.93%133.33%
Nicholas Piggin66.06%133.33%
Adrian Bunk11.01%133.33%
Total99100.00%3100.00%

/** * relay_alloc_buf - allocate a channel buffer * @buf: the buffer struct * @size: total size of the buffer * * Returns a pointer to the resulting buffer, %NULL if unsuccessful. The * passed in size will get page aligned, if it isn't already. */
static void *relay_alloc_buf(struct rchan_buf *buf, size_t *size) { void *mem; unsigned int i, j, n_pages; *size = PAGE_ALIGN(*size); n_pages = *size >> PAGE_SHIFT; buf->page_array = relay_alloc_page_array(n_pages); if (!buf->page_array) return NULL; for (i = 0; i < n_pages; i++) { buf->page_array[i] = alloc_page(GFP_KERNEL); if (unlikely(!buf->page_array[i])) goto depopulate; set_page_private(buf->page_array[i], (unsigned long)buf); } mem = vmap(buf->page_array, n_pages, VM_MAP, PAGE_KERNEL); if (!mem) goto depopulate; memset(mem, 0, *size); buf->page_count = n_pages; return mem; depopulate: for (j = 0; j < i; j++) __free_page(buf->page_array[j]); relay_free_page_array(buf->page_array); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe18190.95%250.00%
Tom Zanussi168.04%125.00%
Masami Hiramatsu21.01%125.00%
Total199100.00%4100.00%

/** * relay_create_buf - allocate and initialize a channel buffer * @chan: the relay channel * * Returns channel buffer if successful, %NULL otherwise. */
static struct rchan_buf *relay_create_buf(struct rchan *chan) { struct rchan_buf *buf; if (chan->n_subbufs > UINT_MAX / sizeof(size_t *)) return NULL; buf = kzalloc(sizeof(struct rchan_buf), GFP_KERNEL); if (!buf) return NULL; buf->padding = kmalloc(chan->n_subbufs * sizeof(size_t *), GFP_KERNEL); if (!buf->padding) goto free_buf; buf->start = relay_alloc_buf(buf, &chan->alloc_size); if (!buf->start) goto free_buf; buf->chan = chan; kref_get(&buf->chan->kref); return buf; free_buf: kfree(buf->padding); kfree(buf); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe12284.14%240.00%
Dan Carpenter2114.48%120.00%
Robert P. J. Day10.69%120.00%
Adrian Bunk10.69%120.00%
Total145100.00%5100.00%

/** * relay_destroy_channel - free the channel struct * @kref: target kernel reference that contains the relay channel * * Should only be called from kref_put(). */
static void relay_destroy_channel(struct kref *kref) { struct rchan *chan = container_of(kref, struct rchan, kref); kfree(chan); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe3096.77%150.00%
Adrian Bunk13.23%150.00%
Total31100.00%2100.00%

/** * relay_destroy_buf - destroy an rchan_buf struct and associated buffer * @buf: the buffer struct */
static void relay_destroy_buf(struct rchan_buf *buf) { struct rchan *chan = buf->chan; unsigned int i; if (likely(buf->start)) { vunmap(buf->start); for (i = 0; i < buf->page_count; i++) __free_page(buf->page_array[i]); relay_free_page_array(buf->page_array); } *per_cpu_ptr(chan->buf, buf->cpu) = NULL; kfree(buf->padding); kfree(buf); kref_put(&chan->kref, relay_destroy_channel); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe9485.45%120.00%
Mathieu Desnoyers98.18%120.00%
Akash Goel54.55%120.00%
Adrian Bunk10.91%120.00%
Masami Hiramatsu10.91%120.00%
Total110100.00%5100.00%

/** * relay_remove_buf - remove a channel buffer * @kref: target kernel reference that contains the relay buffer * * Removes the file from the filesystem, which also frees the * rchan_buf_struct and the channel buffer. Should only be called from * kref_put(). */
static void relay_remove_buf(struct kref *kref) { struct rchan_buf *buf = container_of(kref, struct rchan_buf, kref); relay_destroy_buf(buf); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe3096.77%150.00%
Adrian Bunk13.23%150.00%
Total31100.00%2100.00%

/** * relay_buf_empty - boolean, is the channel buffer empty? * @buf: channel buffer * * Returns 1 if the buffer is empty, 0 otherwise. */
static int relay_buf_empty(struct rchan_buf *buf) { return (buf->subbufs_produced - buf->subbufs_consumed) ? 0 : 1; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe2596.15%150.00%
Adrian Bunk13.85%150.00%
Total26100.00%2100.00%

/** * relay_buf_full - boolean, is the channel buffer full? * @buf: channel buffer * * Returns 1 if the buffer is full, 0 otherwise. */
int relay_buf_full(struct rchan_buf *buf) { size_t ready = buf->subbufs_produced - buf->subbufs_consumed; return (ready >= buf->chan->n_subbufs) ? 1 : 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe36100.00%1100.00%
Total36100.00%1100.00%

EXPORT_SYMBOL_GPL(relay_buf_full); /* * High-level relay kernel API and associated functions. */ /* * rchan_callback implementations defining default channel behavior. Used * in place of corresponding NULL values in client callback struct. */ /* * subbuf_start() default callback. Does nothing. */
static int subbuf_start_default_callback (struct rchan_buf *buf, void *subbuf, void *prev_subbuf, size_t prev_padding) { if (relay_buf_full(buf)) return 0; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe35100.00%1100.00%
Total35100.00%1100.00%

/* * buf_mapped() default callback. Does nothing. */
static void buf_mapped_default_callback(struct rchan_buf *buf, struct file *filp) { }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe15100.00%1100.00%
Total15100.00%1100.00%

/* * buf_unmapped() default callback. Does nothing. */
static void buf_unmapped_default_callback(struct rchan_buf *buf, struct file *filp) { }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe15100.00%1100.00%
Total15100.00%1100.00%

/* * create_buf_file_create() default callback. Does nothing. */
static struct dentry *create_buf_file_default_callback(const char *filename, struct dentry *parent, umode_t mode, struct rchan_buf *buf, int *is_global) { return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe3296.97%150.00%
Al Viro13.03%150.00%
Total33100.00%2100.00%

/* * remove_buf_file() default callback. Does nothing. */
static int remove_buf_file_default_callback(struct dentry *dentry) { return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe15100.00%1100.00%
Total15100.00%1100.00%

/* relay channel default callbacks */ static struct rchan_callbacks default_channel_callbacks = { .subbuf_start = subbuf_start_default_callback, .buf_mapped = buf_mapped_default_callback, .buf_unmapped = buf_unmapped_default_callback, .create_buf_file = create_buf_file_default_callback, .remove_buf_file = remove_buf_file_default_callback, }; /** * wakeup_readers - wake up readers waiting on a channel * @work: contains the channel buffer * * This is the function used to defer reader waking */
static void wakeup_readers(struct irq_work *work) { struct rchan_buf *buf; buf = container_of(work, struct rchan_buf, wakeup_work); wake_up_interruptible(&buf->read_wait); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe1952.78%133.33%
Peter Zijlstra1541.67%133.33%
David Howells25.56%133.33%
Total36100.00%3100.00%

/** * __relay_reset - reset a channel buffer * @buf: the channel buffer * @init: 1 if this is a first-time initialization * * See relay_reset() for description of effect. */
static void __relay_reset(struct rchan_buf *buf, unsigned int init) { size_t i; if (init) { init_waitqueue_head(&buf->read_wait); kref_init(&buf->kref); init_irq_work(&buf->wakeup_work, wakeup_readers); } else { irq_work_sync(&buf->wakeup_work); } buf->subbufs_produced = 0; buf->subbufs_consumed = 0; buf->bytes_consumed = 0; buf->finalized = 0; buf->data = buf->start; buf->offset = 0; for (i = 0; i < buf->chan->n_subbufs; i++) buf->padding[i] = 0; buf->chan->cb->subbuf_start(buf, buf->data, NULL, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe13795.14%133.33%
Peter Zijlstra64.17%133.33%
Tom Zanussi10.69%133.33%
Total144100.00%3100.00%

/** * relay_reset - reset the channel * @chan: the channel * * This has the effect of erasing all data from all channel buffers * and restarting the channel in its initial state. The buffers * are not freed, so any mappings are still in effect. * * NOTE. Care should be taken that the channel isn't actually * being used by anything when this call is made. */
void relay_reset(struct rchan *chan) { struct rchan_buf *buf; unsigned int i; if (!chan) return; if (chan->is_global && (buf = *per_cpu_ptr(chan->buf, 0))) { __relay_reset(buf, 0); return; } mutex_lock(&relay_channels_mutex); for_each_possible_cpu(i) if ((buf = *per_cpu_ptr(chan->buf, i))) __relay_reset(buf, 0); mutex_unlock(&relay_channels_mutex); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe4042.55%125.00%
Mathieu Desnoyers3031.91%125.00%
Akash Goel2324.47%125.00%
Lai Jiangshan11.06%125.00%
Total94100.00%4100.00%

EXPORT_SYMBOL_GPL(relay_reset);
static inline void relay_set_buf_dentry(struct rchan_buf *buf, struct dentry *dentry) { buf->dentry = dentry; d_inode(buf->dentry)->i_size = buf->early_bytes; }

Contributors

PersonTokensPropCommitsCommitProp
Eduard - Gabriel Munteanu2980.56%133.33%
Jens Axboe411.11%133.33%
David Howells38.33%133.33%
Total36100.00%3100.00%


static struct dentry *relay_create_buf_file(struct rchan *chan, struct rchan_buf *buf, unsigned int cpu) { struct dentry *dentry; char *tmpname; tmpname = kzalloc(NAME_MAX + 1, GFP_KERNEL); if (!tmpname) return NULL; snprintf(tmpname, NAME_MAX, "%s%d", chan->base_filename, cpu); /* Create file in fs */ dentry = chan->cb->create_buf_file(tmpname, chan->parent, S_IRUSR, buf, &chan->is_global); kfree(tmpname); return dentry; }

Contributors

PersonTokensPropCommitsCommitProp
Mathieu Desnoyers4343.88%133.33%
Jens Axboe3333.67%133.33%
Eduard - Gabriel Munteanu2222.45%133.33%
Total98100.00%3100.00%

/* * relay_open_buf - create a new relay channel buffer * * used by relay_open() and CPU hotplug. */
static struct rchan_buf *relay_open_buf(struct rchan *chan, unsigned int cpu) { struct rchan_buf *buf = NULL; struct dentry *dentry; if (chan->is_global) return *per_cpu_ptr(chan->buf, 0); buf = relay_create_buf(chan); if (!buf) return NULL; if (chan->has_base_filename) { dentry = relay_create_buf_file(chan, buf, cpu); if (!dentry) goto free_buf; relay_set_buf_dentry(buf, dentry); } else { /* Only retrieve global info, nothing more, nothing less */ dentry = chan->cb->create_buf_file(NULL, NULL, S_IRUSR, buf, &chan->is_global); if (WARN_ON(dentry)) goto free_buf; } buf->cpu = cpu; __relay_reset(buf, 1); if(chan->is_global) { *per_cpu_ptr(chan->buf, 0) = buf; buf->cpu = 0; } return buf; free_buf: relay_destroy_buf(buf); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Eduard - Gabriel Munteanu9451.37%120.00%
Akash Goel4625.14%240.00%
Mathieu Desnoyers2413.11%120.00%
Jens Axboe1910.38%120.00%
Total183100.00%5100.00%

/** * relay_close_buf - close a channel buffer * @buf: channel buffer * * Marks the buffer finalized and restores the default callbacks. * The channel buffer and channel buffer data structure are then freed * automatically when the last reference is given up. */
static void relay_close_buf(struct rchan_buf *buf) { buf->finalized = 1; irq_work_sync(&buf->wakeup_work); buf->chan->cb->remove_buf_file(buf->dentry); kref_put(&buf->kref, relay_remove_buf); }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe3368.75%133.33%
Dmitriy Monakhov1327.08%133.33%
Peter Zijlstra24.17%133.33%
Total48100.00%3100.00%


static void setup_callbacks(struct rchan *chan, struct rchan_callbacks *cb) { if (!cb) { chan->cb = &default_channel_callbacks; return; } if (!cb->subbuf_start) cb->subbuf_start = subbuf_start_default_callback; if (!cb->buf_mapped) cb->buf_mapped = buf_mapped_default_callback; if (!cb->buf_unmapped) cb->buf_unmapped = buf_unmapped_default_callback; if (!cb->create_buf_file) cb->create_buf_file = create_buf_file_default_callback; if (!cb->remove_buf_file) cb->remove_buf_file = remove_buf_file_default_callback; chan->cb = cb; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe102100.00%1100.00%
Total102100.00%1100.00%


int relay_prepare_cpu(unsigned int cpu) { struct rchan *chan; struct rchan_buf *buf; mutex_lock(&relay_channels_mutex); list_for_each_entry(chan, &relay_channels, list) { if ((buf = *per_cpu_ptr(chan->buf, cpu))) continue; buf = relay_open_buf(chan, cpu); if (!buf) { pr_err("relay: cpu %d buffer creation failed\n", cpu); mutex_unlock(&relay_channels_mutex); return -ENOMEM; } *per_cpu_ptr(chan->buf, cpu) = buf; } mutex_unlock(&relay_channels_mutex); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Mathieu Desnoyers6965.09%125.00%
Akash Goel2523.58%125.00%
Richard Weinberger109.43%125.00%
Akinobu Mita21.89%125.00%
Total106100.00%4100.00%

/** * relay_open - create a new relay channel * @base_filename: base name of files to create, %NULL for buffering only * @parent: dentry of parent directory, %NULL for root directory or buffer * @subbuf_size: size of sub-buffers * @n_subbufs: number of sub-buffers * @cb: client callback functions * @private_data: user-defined data * * Returns channel pointer if successful, %NULL otherwise. * * Creates a channel buffer for each cpu using the sizes and * attributes specified. The created channel buffer files * will be named base_filename0...base_filenameN-1. File * permissions will be %S_IRUSR. * * If opening a buffer (@parent = NULL) that you later wish to register * in a filesystem, call relay_late_setup_files() once the @parent dentry * is available. */
struct rchan *relay_open(const char *base_filename, struct dentry *parent, size_t subbuf_size, size_t n_subbufs, struct rchan_callbacks *cb, void *private_data) { unsigned int i; struct rchan *chan; struct rchan_buf *buf; if (!(subbuf_size && n_subbufs)) return NULL; if (subbuf_size > UINT_MAX / n_subbufs) return NULL; chan = kzalloc(sizeof(struct rchan), GFP_KERNEL); if (!chan) return NULL; chan->buf = alloc_percpu(struct rchan_buf *); chan->version = RELAYFS_CHANNEL_VERSION; chan->n_subbufs = n_subbufs; chan->subbuf_size = subbuf_size; chan->alloc_size = PAGE_ALIGN(subbuf_size * n_subbufs); chan->parent = parent; chan->private_data = private_data; if (base_filename) { chan->has_base_filename = 1; strlcpy(chan->base_filename, base_filename, NAME_MAX); } setup_callbacks(chan, cb); kref_init(&chan->kref); mutex_lock(&relay_channels_mutex); for_each_online_cpu(i) { buf = relay_open_buf(chan, i); if (!buf) goto free_bufs; *per_cpu_ptr(chan->buf, i) = buf; } list_add(&chan->list, &relay_channels); mutex_unlock(&relay_channels_mutex); return chan; free_bufs: for_each_possible_cpu(i) { if ((buf = *per_cpu_ptr(chan->buf, i))) relay_close_buf(buf); } kref_put(&chan->kref, relay_destroy_channel); mutex_unlock(&relay_channels_mutex); kfree(chan); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Jens Axboe16857.14%111.11%
Mathieu Desnoyers5819.73%111.11%
Akash Goel3712.59%111.11%
Eduard - Gabriel Munteanu124.08%111.11%
Dan Carpenter113.74%111.11%
Zhouyi Zhou51.70%111.11%
Lai Jiangshan10.34%111.11%
Jovi Zhangwei10.34%111.11%
Robert P. J. Day10.34%111.11%
Total294100.00%9100.00%

EXPORT_SYMBOL_GPL(relay_open); struct rchan_percpu_buf_dispatcher { struct rchan_buf *buf; struct dentry *dentry; }; /* Called in atomic context. */
static void __relay_set_buf_dentry(void *info) { struct rchan_percpu_buf_dispatcher *p = info; relay_set_buf_dentry(p->buf, p->dentry); }

Contributors

PersonTokensPropCommitsCommitProp
Eduard - Gabriel Munteanu28100.00%1100.00%
Total28100.00%1100.00%

/** * relay_late_setup_files - triggers file creation * @chan: channel to operate on * @base_filename: base name of files to create * @parent: dentry of parent directory, %NULL for root directory * * Returns 0 if successful, non-zero otherwise. * * Use to setup files for a previously buffer-only channel created * by relay_open() with a NULL parent dentry. * * For example, this is useful for perfomring early tracing in kernel, * before VFS is up and then exposing the early results once the dentry * is available. */
int relay_late_setup_files(struct rchan *chan, const char *base_filename, struct dentry *parent) { int err = 0; unsigned int i, curr_cpu; unsigned long flags; struct dentry *dentry; struct rchan_buf *buf; struct rchan_percpu_buf_dispatcher disp; if (!chan || !base_filename) return -EINVAL; strlcpy(chan->base_filename, base_filename, NAME_MAX); mutex_lock(&relay_channels_mutex); /* Is chan already set up? */ if (unlikely(chan->has_base_filename)) { mutex_unlock(&relay_channels_mutex); return -EEXIST; } chan->has_base_filename = 1; chan->parent = parent; if (chan->is_global) { err = -EINVAL; buf = *per_cpu_ptr(chan->buf, 0); if (!WARN_ON_ONCE(!buf)) { dentry = relay_create_buf_file(chan, buf, 0); if (dentry && !WARN_ON_ONCE(!chan->is_global)) { relay_set_buf_dentry(buf, dentry); err = 0; } } mutex_unlock(&relay_channels_mutex); return err; } curr_cpu = get_cpu(); /* * The CPU hotplug notifier ran before us and created buffers with * no files associated. So it's safe to call relay_setup_buf_file() * on all currently online CPUs. */ for_each_online_cpu(i) { buf = *per_cpu_ptr(chan->buf, i); if (unlikely(!buf)) { WARN_ONCE(1, KERN_ERR "CPU has no buffer!\n"); err = -EINVAL; break; } dentry = relay_create_buf_file(chan, buf, i); if (unlikely(!dentry)) { err = -EINVAL; break; } if (curr_cpu == i) { local_irq_save(flags); relay_set_buf_dentry(buf, dentry); local_irq_restore(flags); } else { disp.buf = buf; disp.dentry = dentry; smp_mb(); /* relay_channels_mutex must be held, so wait. */ err = smp_call_function_single(i, __relay_set_buf_dentry, &disp, 1); } if (unlikely(err)) break; } put_cpu(); mutex_unlock(&relay_channels_mutex); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Eduard - Gabriel Munteanu23668.01%120.00%
Akash Goel9928.53%240.00%
Jiri Slaby82.31%120.00%
Ingo Molnar41.15%120.00%
Total347100.00%5100.00%

EXPORT_SYMBOL_GPL(relay_late_setup_files); /** * relay_switch_subbuf - switch to a new sub-buffer * @buf: channel buffer * @length: size of current event * * Returns either the length passed in or 0 if full. * * Performs sub-buffer-switch tasks such as invoking callbacks, * updating padding counts, waking up readers, etc. */
size_t relay_switch_subbuf(struct rchan_buf *buf, size_t length) { void *old, *new; size_t old_subbuf, new_subbuf; if (unlikely(length > buf->chan->subbuf_size)) goto toobig; if (buf->offset != buf->chan->subbuf_size + 1) { buf->