/*
* Intel(R) Trace Hub Memory Storage Unit
*
* Copyright (C) 2014-2015 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/types.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/sizes.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#ifdef CONFIG_X86
#include <asm/set_memory.h>
#endif
#include "intel_th.h"
#include "msu.h"
#define msc_dev(x) (&(x)->thdev->dev)
/**
* struct msc_block - multiblock mode block descriptor
* @bdesc: pointer to hardware descriptor (beginning of the block)
* @addr: physical address of the block
*/
struct msc_block {
struct msc_block_desc *bdesc;
dma_addr_t addr;
};
/**
* 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
* @nr_blocks: number of blocks (pages) in this window
* @block: array of block descriptors
*/
struct msc_window {
struct list_head entry;
unsigned long pgoff;
unsigned int nr_blocks;
struct msc *msc;
struct msc_block block[0];
};
/**
* 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;
unsigned long offset;
int start_block;
int block;
unsigned int block_off;
unsigned int wrap_count;
unsigned int eof;
};
/**
* struct msc - MSC device representation
* @reg_base: register window base address
* @thdev: intel_th_device pointer
* @win_list: list of windows in multiblock mode
* @single_sgt: single mode buffer
* @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
* @user_count: number of users of the buffer
* @mmap_count: number of mappings
* @buf_mutex: mutex to serialize access to buffer-related bits
* @enabled: MSC is enabled
* @wrap: wrapping is enabled
* @mode: MSC operating mode
* @burst_len: write burst length
* @index: number of this MSC in the MSU
*/
struct msc {
void __iomem *reg_base;
struct intel_th_device *thdev;
struct list_head win_list;
struct sg_table single_sgt;
unsigned long nr_pages;
unsigned long single_sz;
unsigned int single_wrap : 1;
void *base;
dma_addr_t base_addr;
/* <0: no buffer, 0: no users, >0: active users */
atomic_t user_count;
atomic_t mmap_count;
struct mutex buf_mutex;
struct list_head iter_list;
/* config */
unsigned int enabled : 1,
wrap : 1;
unsigned int mode;
unsigned int burst_len;
unsigned int index;
};
static inline bool msc_block_is_empty(struct msc_block_desc *bdesc)
{
/* header hasn't been written */
if (!bdesc->valid_dw)
return true;
/* valid_dw includes the header */
if (!msc_data_sz(bdesc))
return true;
return false;
}
/**
* 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
*/
static struct msc_window *msc_oldest_window(struct msc *msc)
{
struct msc_window *win;
u32 reg = ioread32(msc->reg_base + REG_MSU_MSC0NWSA);
unsigned long win_addr = (unsigned long)reg << PAGE_SHIFT;
unsigned int 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->block[0].addr == win_addr)
found++;
/* skip the empty ones */
if (msc_block_is_empty(win->block[0].bdesc))
continue;
if (found)
return win;
}
return list_entry(msc->win_list.next, struct msc_window, entry);
}
/**
* msc_win_oldest_block() - locate the oldest block in a given window
* @win: window to look at
*
* Return: index of the block with the oldest data
*/
static unsigned int msc_win_oldest_block(struct msc_window *win)
{
unsigned int blk;
struct msc_block_desc *bdesc = win->block[0].bdesc;
/* without wrapping, first block is the oldest */
if (!msc_block_wrapped(bdesc))
return 0;
/*
* with wrapping, last written block contains both the newest and the
* oldest data for this window.
*/
for (blk = 0; blk < win->nr_blocks; blk++) {
bdesc = win->block[blk].bdesc;
if (msc_block_last_written(bdesc))
return blk;
}
return 0;
}
/**
* 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
*/
static inline bool 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
*/
static struct msc_window *msc_next_window(struct msc_window *win)
{
if (msc_is_last_win(win))
return list_entry(win->msc->win_list.next, struct msc_window,
entry);
return list_entry(win->entry.next, struct msc_window, entry);
}
static struct msc_block_desc *msc_iter_bdesc(struct msc_iter *iter)
{
return iter->win->block[iter->block].bdesc;
}
static void msc_iter_init(struct msc_iter *iter)
{
memset(iter, 0, sizeof(*iter));
iter->start_block = -1;
iter->block = -1;
}
static struct msc_iter *msc_iter_install(struct msc *msc)
{
struct msc_iter *iter;
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
if (!iter)
return ERR_PTR(
|