/* * Lockout state transitions: * READY -> INUSE -+-> LOCKED -+-> READY -> etc. * \-----------/ * WIN_READY: window can be used by HW * WIN_INUSE: window is in use * WIN_LOCKED: window is filled up and is being processed by the buffer * handling code * * All state transitions happen automatically, except for the LOCKED->READY, * which needs to be signalled by the buffer code by calling * intel_th_msc_window_unlock(). * * When the interrupt handler has to switch to the next window, it checks * whether it's READY, and if it is, it performs the switch and tracing * continues. If it's LOCKED, it stops the trace.
*/ enum lockout_state {
WIN_READY = 0,
WIN_INUSE,
WIN_LOCKED
};
/** * struct msc_window - multiblock mode window descriptor * @entry: window list linkage (msc::win_list) * @pgoff: page offset into the buffer that this window starts at * @lockout: lockout state, see comment below * @lo_lock: lockout state serialization * @nr_blocks: number of blocks (pages) in this window * @nr_segs: number of segments in this window (<= @nr_blocks) * @msc: pointer to the MSC device * @_sgt: array of block descriptors * @sgt: array of block descriptors
*/ struct msc_window { struct list_head entry; unsignedlong pgoff; enum lockout_state lockout;
spinlock_t lo_lock; unsignedint nr_blocks; unsignedint nr_segs; struct msc *msc; struct sg_table _sgt; struct sg_table *sgt;
};
/** * struct msc_iter - iterator for msc buffer * @entry: msc::iter_list linkage * @msc: pointer to the MSC device * @start_win: oldest window * @win: current window * @offset: current logical offset into the buffer * @start_block: oldest block in the window * @block: block number in the window * @block_off: offset into current block * @wrap_count: block wrapping handling * @eof: end of buffer reached
*/ struct msc_iter { struct list_head entry; struct msc *msc; struct msc_window *start_win; struct msc_window *win; unsignedlong offset; struct scatterlist *start_block; struct scatterlist *block; unsignedint block_off; unsignedint wrap_count; unsignedint eof;
};
/** * struct msc - MSC device representation * @reg_base: register window base address for the entire MSU * @msu_base: register window base address for this MSC * @thdev: intel_th_device pointer * @mbuf: MSU buffer, if assigned * @mbuf_priv: MSU buffer's private data, if @mbuf * @work: a work to stop the trace when the buffer is full * @win_list: list of windows in multiblock mode * @single_sgt: single mode buffer * @cur_win: current window * @switch_on_unlock: window to switch to when it becomes available * @nr_pages: total number of pages allocated for this buffer * @single_sz: amount of data in single mode * @single_wrap: single mode wrap occurred * @base: buffer's base pointer * @base_addr: buffer's base address * @orig_addr: MSC0 buffer's base address * @orig_sz: MSC0 buffer's size * @user_count: number of users of the buffer * @mmap_count: number of mappings * @buf_mutex: mutex to serialize access to buffer-related bits * @iter_list: list of open file descriptor iterators * @stop_on_full: stop the trace if the current window is full * @enabled: MSC is enabled * @wrap: wrapping is enabled * @do_irq: IRQ resource is available, handle interrupts * @multi_is_broken: multiblock mode enabled (not disabled by PCI drvdata) * @mode: MSC operating mode * @burst_len: write burst length * @index: number of this MSC in the MSU
*/ struct msc { void __iomem *reg_base; void __iomem *msu_base; struct intel_th_device *thdev;
/** * msc_is_last_win() - check if a window is the last one for a given MSC * @win: window * Return: true if @win is the last window in MSC's multiblock buffer
*/ staticinlinebool msc_is_last_win(struct msc_window *win)
{ return win->entry.next == &win->msc->win_list;
}
/** * msc_next_window() - return next window in the multiblock buffer * @win: current window * * Return: window following the current one
*/ staticstruct msc_window *msc_next_window(struct msc_window *win)
{ if (msc_is_last_win(win)) return list_first_entry(&win->msc->win_list, struct msc_window,
entry);
if (msc_block_wrapped(bdesc)) return (size_t)win->nr_blocks << PAGE_SHIFT;
size += msc_total_sz(bdesc); if (msc_block_last_written(bdesc)) break;
}
return size;
}
/** * msc_find_window() - find a window matching a given sg_table * @msc: MSC device * @sgt: SG table of the window * @nonempty: skip over empty windows * * Return: MSC window structure pointer or NULL if the window * could not be found.
*/ staticstruct msc_window *
msc_find_window(struct msc *msc, struct sg_table *sgt, bool nonempty)
{ struct msc_window *win; unsignedint found = 0;
if (list_empty(&msc->win_list)) return NULL;
/* * we might need a radix tree for this, depending on how * many windows a typical user would allocate; ideally it's * something like 2, in which case we're good
*/
list_for_each_entry(win, &msc->win_list, entry) { if (win->sgt == sgt)
found++;
/* skip the empty ones */ if (nonempty && msc_block_is_empty(msc_win_base(win))) continue;
if (found) return win;
}
return NULL;
}
/** * msc_oldest_window() - locate the window with oldest data * @msc: MSC device * * This should only be used in multiblock mode. Caller should hold the * msc::user_count reference. * * Return: the oldest window with valid data
*/ staticstruct msc_window *msc_oldest_window(struct msc *msc)
{ struct msc_window *win;
if (list_empty(&msc->win_list)) return NULL;
win = msc_find_window(msc, msc_next_window(msc->cur_win)->sgt, true); if (win) return win;
/** * msc_win_oldest_sg() - locate the oldest block in a given window * @win: window to look at * * Return: index of the block with the oldest data
*/ staticstruct scatterlist *msc_win_oldest_sg(struct msc_window *win)
{ unsignedint blk; struct scatterlist *sg; struct msc_block_desc *bdesc = msc_win_base(win);
/* without wrapping, first block is the oldest */ if (!msc_block_wrapped(bdesc)) return msc_win_base_sg(win);
/* * with wrapping, last written block contains both the newest and the * oldest data for this window.
*/
for_each_sg(win->sgt->sgl, sg, win->nr_segs, blk) { struct msc_block_desc *bdesc = sg_virt(sg);
iter = kzalloc(sizeof(*iter), GFP_KERNEL); if (!iter) return ERR_PTR(-ENOMEM);
mutex_lock(&msc->buf_mutex);
/* * Reading and tracing are mutually exclusive; if msc is * enabled, open() will fail; otherwise existing readers * will prevent enabling the msc and the rest of fops don't * need to worry about it.
*/ if (msc->enabled) {
kfree(iter);
iter = ERR_PTR(-EBUSY); goto unlock;
}
/* * start with the block with oldest data; if data has wrapped * in this window, it should be in this block
*/ if (msc_block_wrapped(msc_iter_bdesc(iter)))
iter->wrap_count = 2;
}
staticint msc_iter_win_start(struct msc_iter *iter, struct msc *msc)
{ /* already started, nothing to do */ if (iter->start_win) return 0;
iter->start_win = msc_oldest_window(msc); if (!iter->start_win) return -EINVAL;
/* wrapping */ if (iter->wrap_count && iter->block == iter->start_block) {
iter->wrap_count--; if (!iter->wrap_count) /* copied newest data from the wrapped block */ return msc_iter_win_advance(iter);
}
/* no wrapping, check for last written block */ if (!iter->wrap_count && msc_block_last_written(msc_iter_bdesc(iter))) /* copied newest data for the window */ return msc_iter_win_advance(iter);
/* no wrapping, sanity check in case there is no last written block */ if (!iter->wrap_count && iter->block == iter->start_block) return msc_iter_win_advance(iter);
return 0;
}
/** * msc_buffer_iterate() - go through multiblock buffer's data * @iter: iterator structure * @size: amount of data to scan * @data: callback's private data * @fn: iterator callback * * This will start at the window which will be written to next (containing * the oldest data) and work its way to the current window, calling @fn * for each chunk of data as it goes. * * Caller should have msc::user_count reference to make sure the buffer * doesn't disappear from under us. * * Return: amount of data actually scanned.
*/ static ssize_t
msc_buffer_iterate(struct msc_iter *iter, size_t size, void *data, unsignedlong (*fn)(void *, void *, size_t))
{ struct msc *msc = iter->msc;
size_t len = size; unsignedint advance;
if (iter->eof) return 0;
/* start with the oldest window */ if (msc_iter_win_start(iter, msc)) return 0;
/* * If block wrapping happened, we need to visit the last block * twice, because it contains both the oldest and the newest * data in this window. * * First time (wrap_count==2), in the very beginning, to collect * the oldest data, which is in the range * (data_bytes..DATA_IN_PAGE). * * Second time (wrap_count==1), it's just like any other block, * containing data in the range of [MSC_BDESC..data_bytes].
*/ if (iter->block == iter->start_block && iter->wrap_count == 2) {
tocopy = DATA_IN_PAGE - data_bytes;
src += data_bytes;
}
if (ret) { if (expect == WIN_READY && old == WIN_LOCKED) return -EBUSY;
/* from intel_th_msc_window_unlock(), don't warn if not locked */ if (expect == WIN_LOCKED && old == new) return 0;
dev_warn_ratelimited(msc_dev(win->msc), "expected lockout state %d, got %d\n",
expect, old);
}
return ret;
} /** * msc_configure() - set up MSC hardware * @msc: the MSC device to configure * * Program storage mode, wrapping, burst length and trace buffer address * into a given MSC. Then, enable tracing and set msc::enabled. * The latter is serialized on msc::buf_mutex, so make sure to hold it. * * Return: %0 for success or a negative error code otherwise.
*/ staticint msc_configure(struct msc *msc)
{
u32 reg;
lockdep_assert_held(&msc->buf_mutex);
if (msc->mode > MSC_MODE_MULTI) return -EINVAL;
if (msc->mode == MSC_MODE_MULTI) { if (msc_win_set_lockout(msc->cur_win, WIN_READY, WIN_INUSE)) return -EBUSY;
mutex_lock(&msc->buf_mutex); if (msc->enabled) {
msc_disable(msc);
atomic_dec(&msc->user_count);
}
mutex_unlock(&msc->buf_mutex);
}
/** * msc_buffer_contig_alloc() - allocate a contiguous buffer for SINGLE mode * @msc: MSC device * @size: allocation size in bytes * * This modifies msc::base, which requires msc::buf_mutex to serialize, so the * caller is expected to hold it. * * Return: 0 on success, -errno otherwise.
*/ staticint msc_buffer_contig_alloc(struct msc *msc, unsignedlong size)
{ unsignedlong nr_pages = size >> PAGE_SHIFT; unsignedint order = get_order(size); struct page *page; int ret;
if (!size) return 0;
ret = sg_alloc_table(&msc->single_sgt, 1, GFP_KERNEL); if (ret) goto err_out;
ret = -ENOMEM;
page = alloc_pages(GFP_KERNEL | __GFP_ZERO | GFP_DMA32, order); if (!page) goto err_free_sgt;
for (off = 0; off < msc->nr_pages << PAGE_SHIFT; off += PAGE_SIZE) { struct page *page = virt_to_page(msc->base + off);
__free_page(page);
}
msc->nr_pages = 0;
}
/** * msc_buffer_contig_get_page() - find a page at a given offset * @msc: MSC configured in SINGLE mode * @pgoff: page offset * * Return: page, if @pgoff is within the range, NULL otherwise.
*/ staticstruct page *msc_buffer_contig_get_page(struct msc *msc, unsignedlong pgoff)
{ if (pgoff >= msc->nr_pages) return NULL;
if (is_vmalloc_addr(addr)) return vmalloc_to_page(addr);
return sg_page(sg);
}
/** * msc_buffer_win_alloc() - alloc a window for a multiblock mode * @msc: MSC device * @nr_blocks: number of pages in this window * * This modifies msc::win_list and msc::base, which requires msc::buf_mutex * to serialize, so the caller is expected to hold it. * * Return: 0 on success, -errno otherwise.
*/ staticint msc_buffer_win_alloc(struct msc *msc, unsignedint nr_blocks)
{ struct msc_window *win; int ret = -ENOMEM;
if (!nr_blocks) return 0;
win = kzalloc(sizeof(*win), GFP_KERNEL); if (!win) return -ENOMEM;
/** * msc_buffer_win_free() - free a window from MSC's window list * @msc: MSC device * @win: window to free * * This modifies msc::win_list and msc::base, which requires msc::buf_mutex * to serialize, so the caller is expected to hold it.
*/ staticvoid msc_buffer_win_free(struct msc *msc, struct msc_window *win)
{
msc->nr_pages -= win->nr_blocks;
if (msc->mbuf && msc->mbuf->free_window)
msc->mbuf->free_window(msc->mbuf_priv, win->sgt); else
__msc_buffer_win_free(msc, win);
kfree(win);
}
/** * msc_buffer_relink() - set up block descriptors for multiblock mode * @msc: MSC device * * This traverses msc::win_list, which requires msc::buf_mutex to serialize, * so the caller is expected to hold it.
*/ staticvoid msc_buffer_relink(struct msc *msc)
{ struct msc_window *win, *next_win;
/* * Last window's next_win should point to the first window * and MSC_SW_TAG_LASTWIN should be set.
*/ if (msc_is_last_win(win)) {
sw_tag |= MSC_SW_TAG_LASTWIN;
next_win = list_first_entry(&msc->win_list, struct msc_window, entry);
} else {
next_win = list_next_entry(win, entry);
}
/* * Similarly to last window, last block should point * to the first one.
*/ if (blk == win->nr_segs - 1) {
sw_tag |= MSC_SW_TAG_LASTBLK;
bdesc->next_blk = msc_win_base_pfn(win);
} else {
dma_addr_t addr = sg_dma_address(sg_next(sg));
for (i = 0; i < nr_wins; i++) {
ret = msc_buffer_win_alloc(msc, nr_pages[i]); if (ret) {
msc_buffer_multi_free(msc); return ret;
}
}
msc_buffer_relink(msc);
return 0;
}
/** * msc_buffer_free() - free buffers for MSC * @msc: MSC device * * Free MSC's storage buffers. * * This modifies msc::win_list and msc::base, which requires msc::buf_mutex to * serialize, so the caller is expected to hold it.
*/ staticvoid msc_buffer_free(struct msc *msc)
{
msc_buffer_set_wb(msc);
/** * msc_buffer_alloc() - allocate a buffer for MSC * @msc: MSC device * @nr_pages: number of pages for each window * @nr_wins: number of windows * * Allocate a storage buffer for MSC, depending on the msc::mode, it will be * either done via msc_buffer_contig_alloc() for SINGLE operation mode or * msc_buffer_win_alloc() for multiblock operation. The latter allocates one * window per invocation, so in multiblock mode this can be called multiple * times for the same MSC to allocate multiple windows. * * This modifies msc::win_list and msc::base, which requires msc::buf_mutex * to serialize, so the caller is expected to hold it. * * Return: 0 on success, -errno otherwise.
*/ staticint msc_buffer_alloc(struct msc *msc, unsignedlong *nr_pages, unsignedint nr_wins)
{ int ret;
/* -1: buffer not allocated */ if (atomic_read(&msc->user_count) != -1) return -EBUSY;
if (msc->mode == MSC_MODE_SINGLE) { if (nr_wins != 1) return -EINVAL;
ret = msc_buffer_contig_alloc(msc, nr_pages[0] << PAGE_SHIFT);
} elseif (msc->mode == MSC_MODE_MULTI) {
ret = msc_buffer_multi_alloc(msc, nr_pages, nr_wins);
} else {
ret = -EINVAL;
}
if (!ret) {
msc_buffer_set_uc(msc);
/* allocation should be visible before the counter goes to 0 */
smp_mb__before_atomic();
if (WARN_ON_ONCE(atomic_cmpxchg(&msc->user_count, -1, 0) != -1)) return -EINVAL;
}
return ret;
}
/** * msc_buffer_unlocked_free_unless_used() - free a buffer unless it's in use * @msc: MSC device * * This will free MSC buffer unless it is in use or there is no allocated * buffer. * Caller needs to hold msc::buf_mutex. * * Return: 0 on successful deallocation or if there was no buffer to * deallocate, -EBUSY if there are active users.
*/ staticint msc_buffer_unlocked_free_unless_used(struct msc *msc)
{ int count, ret = 0;
count = atomic_cmpxchg(&msc->user_count, 0, -1);
/* > 0: buffer is allocated and has users */ if (count > 0)
ret = -EBUSY; /* 0: buffer is allocated, no users */ elseif (!count)
msc_buffer_free(msc); /* < 0: no buffer, nothing to do */
return ret;
}
/** * msc_buffer_free_unless_used() - free a buffer unless it's in use * @msc: MSC device * * This is a locked version of msc_buffer_unlocked_free_unless_used(). * * Return: 0 on successful deallocation or if there was no buffer to * deallocate, -EBUSY if there are active users.
*/ staticint msc_buffer_free_unless_used(struct msc *msc)
{ int ret;
mutex_lock(&msc->buf_mutex);
ret = msc_buffer_unlocked_free_unless_used(msc);
mutex_unlock(&msc->buf_mutex);
return ret;
}
/** * msc_buffer_get_page() - get MSC buffer page at a given offset * @msc: MSC device * @pgoff: page offset into the storage buffer * * This traverses msc::win_list, so holding msc::buf_mutex is expected from * the caller. * * Return: page if @pgoff corresponds to a valid buffer page or NULL.
*/ staticstruct page *msc_buffer_get_page(struct msc *msc, unsignedlong pgoff)
{ struct msc_window *win; struct scatterlist *sg; unsignedint blk;
if (msc->mode == MSC_MODE_SINGLE) return msc_buffer_contig_get_page(msc, pgoff);
/** * struct msc_win_to_user_struct - data for copy_to_user() callback * @buf: userspace buffer to copy data to * @offset: running offset
*/ struct msc_win_to_user_struct { char __user *buf; unsignedlong offset;
};
/** * msc_win_to_user() - iterator for msc_buffer_iterate() to copy data to user * @data: callback's private data * @src: source buffer * @len: amount of data to copy from the source buffer * * Return: >= %0 for success or -errno for error.
*/ staticunsignedlong msc_win_to_user(void *data, void *src, size_t len)
{ struct msc_win_to_user_struct *u = data; unsignedlong ret;
ret = copy_to_user(u->buf + u->offset, src, len);
u->offset += len - ret;
/** * intel_th_msc_window_unlock - put the window back in rotation * @dev: MSC device to which this relates * @sgt: buffer's sg_table for the window, does nothing if NULL
*/ void intel_th_msc_window_unlock(struct device *dev, struct sg_table *sgt)
{ struct msc *msc = dev_get_drvdata(dev); struct msc_window *win;
if (!sgt) return;
win = msc_find_window(msc, sgt, false); if (!win) return;
/* grab the window before we do the switch */
win = msc->cur_win; if (!win) return IRQ_HANDLED;
next_win = msc_next_window(win); if (!next_win) return IRQ_HANDLED;
/* next window: if READY, proceed, if LOCKED, stop the trace */ if (msc_win_set_lockout(next_win, WIN_READY, WIN_INUSE)) { if (msc->stop_on_full)
schedule_work(&msc->work); else
msc->switch_on_unlock = next_win;
return IRQ_HANDLED;
}
/* current window: INUSE -> LOCKED */
msc_win_set_lockout(win, WIN_INUSE, WIN_LOCKED);
msc_win_switch(msc);
if (msc->mbuf && msc->mbuf->ready)
msc->mbuf->ready(msc->mbuf_priv, win->sgt,
msc_win_total_sz(win));
ret = kstrtoul(buf, 10, &val); if (ret) return ret;
if (val != 1) return -EINVAL;
ret = -EINVAL;
mutex_lock(&msc->buf_mutex); /* * Window switch can only happen in the "multi" mode. * If a external buffer is engaged, they have the full * control over window switching.
*/ if (msc->mode == MSC_MODE_MULTI && !msc->mbuf)
ret = msc_win_switch(msc);
mutex_unlock(&msc->buf_mutex);
/* * Buffers should not be used at this point except if the * output character device is still open and the parent * device gets detached from its bus, which is a FIXME.
*/
ret = msc_buffer_free_unless_used(msc);
WARN_ON_ONCE(ret);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.