515 lines
12 KiB
C
515 lines
12 KiB
C
/*
|
|
* rawio.c - a debugfs based framework for reading/writing registers
|
|
* from a I/O device.
|
|
* With pluggable rawio drivers, it can support PCI devices, I2C devices,
|
|
* memory mapped I/O devices, etc.
|
|
* It's designed for helping debug Linux device drivers on embedded system or
|
|
* SoC platforms.
|
|
*
|
|
* Copyright (c) 2013 Bin Gao <bin.gao@intel.com>
|
|
*
|
|
* This file is released under the GPLv2
|
|
*
|
|
*
|
|
* Two files are created in debugfs root folder: rawio_cmd and rawio_output.
|
|
* To read or write via the rawio debugfs interface, first echo a rawio
|
|
* command to the file rawio_cmd, then cat the file rawio_output:
|
|
* $ echo "<rawio command>" > /sys/kernel/debug/rawio_cmd
|
|
* $ cat /sys/kernel/debug/rawio_output
|
|
* The cat command is required for both read and write operations.
|
|
* For details of rawio command format, see specific rawio drivers.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/types.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/uaccess.h>
|
|
#include "rawio.h"
|
|
|
|
#define SHOW_NUM_PER_LINE (32 / active_width)
|
|
#define LINE_WIDTH 32
|
|
#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
|
|
|
|
static struct dentry *rawio_cmd_dentry, *rawio_output_dentry;
|
|
static char rawio_cmd_buf[RAWIO_CMD_LEN], rawio_err_buf[RAWIO_ERR_LEN + 1];
|
|
static DEFINE_MUTEX(rawio_lock);
|
|
static LIST_HEAD(rawio_driver_head);
|
|
static struct rawio_driver *active_driver;
|
|
static enum width active_width;
|
|
static enum ops active_ops;
|
|
static u64 args_val[RAWIO_ARGS_MAX];
|
|
static u8 args_postfix[RAWIO_ARGS_MAX];
|
|
static int num_args_val;
|
|
|
|
static void store_value(u64 *where, void *value, enum type type)
|
|
{
|
|
switch (type) {
|
|
case TYPE_U8:
|
|
*(u8 *)where = *(u8 *)value;
|
|
break;
|
|
case TYPE_U16:
|
|
*(u16 *)where = *(u16 *)value;
|
|
break;
|
|
case TYPE_U32:
|
|
*(u32 *)where = *(u32 *)value;
|
|
break;
|
|
case TYPE_U64:
|
|
*where = *(u64 *)value;
|
|
break;
|
|
case TYPE_S8:
|
|
*(s8 *)where = *(s8 *)value;
|
|
break;
|
|
case TYPE_S16:
|
|
*(s16 *)where = *(s16 *)value;
|
|
break;
|
|
case TYPE_S32:
|
|
*(s32 *)where = *(s32 *)value;
|
|
break;
|
|
case TYPE_S64:
|
|
*(s64 *)where = *(s64 *)value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int rawio_register_driver(struct rawio_driver *driver)
|
|
{
|
|
mutex_lock(&rawio_lock);
|
|
list_add_tail(&driver->list, &rawio_driver_head);
|
|
mutex_unlock(&rawio_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rawio_register_driver);
|
|
|
|
int rawio_unregister_driver(struct rawio_driver *driver)
|
|
{
|
|
mutex_lock(&rawio_lock);
|
|
list_del(&driver->list);
|
|
mutex_unlock(&rawio_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rawio_unregister_driver);
|
|
|
|
void rawio_err(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(rawio_err_buf, RAWIO_ERR_LEN, fmt, args);
|
|
va_end(args);
|
|
}
|
|
EXPORT_SYMBOL_GPL(rawio_err);
|
|
|
|
static int parse_arguments(char *input, char **args)
|
|
{
|
|
int count, located;
|
|
char *p = input;
|
|
int input_len = strlen(input);
|
|
|
|
count = 0;
|
|
located = 0;
|
|
while (*p != 0) {
|
|
if (p - input >= input_len)
|
|
break;
|
|
|
|
/* Locate the first character of a argument */
|
|
if (!IS_WHITESPACE(*p)) {
|
|
if (!located) {
|
|
located = 1;
|
|
args[count++] = p;
|
|
if (count > RAWIO_ARGS_MAX)
|
|
break;
|
|
}
|
|
} else {
|
|
if (located) {
|
|
*p = 0;
|
|
located = 0;
|
|
}
|
|
}
|
|
p++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int parse_driver_args(struct rawio_driver *driver, char **arg_list,
|
|
int num_args, enum ops ops, u64 *arg_val, u8 *postfix)
|
|
{
|
|
int i;
|
|
size_t str_len;
|
|
enum type type;
|
|
u64 value;
|
|
char *str;
|
|
char *args_postfix;
|
|
|
|
for (i = 0; i < num_args; i++) {
|
|
switch (ops) {
|
|
case OPS_RD:
|
|
type = driver->args_rd_types[i];
|
|
args_postfix = driver->args_rd_postfix;
|
|
break;
|
|
case OPS_WR:
|
|
type = driver->args_wr_types[i];
|
|
args_postfix = driver->args_wr_postfix;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args_postfix[i]) {
|
|
str = (char *) arg_list[i];
|
|
str_len = strlen(str);
|
|
if (str[str_len - 1] == args_postfix[i]) {
|
|
postfix[i] = 1;
|
|
str[str_len - 1] = 0;
|
|
} else {
|
|
postfix[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (kstrtou64(arg_list[i], 0, &value))
|
|
goto failed;
|
|
store_value(arg_val + i, &value, type);
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed:
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid argument %s, usage:\n", arg_list[i]);
|
|
strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
|
|
strlen(rawio_err_buf));
|
|
return -EINVAL;
|
|
}
|
|
|
|
static struct rawio_driver *find_driver(const char *name)
|
|
{
|
|
struct rawio_driver *driver;
|
|
|
|
mutex_lock(&rawio_lock);
|
|
list_for_each_entry(driver, &rawio_driver_head, list) {
|
|
if (!strncmp(driver->name, name, strlen(name))) {
|
|
mutex_unlock(&rawio_lock);
|
|
return driver;
|
|
}
|
|
}
|
|
mutex_unlock(&rawio_lock);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ssize_t rawio_cmd_write(struct file *file, const char __user *buf,
|
|
size_t len, loff_t *offset)
|
|
{
|
|
char cmd[RAWIO_CMD_LEN];
|
|
char *arg_list[RAWIO_ARGS_MAX];
|
|
int num_args;
|
|
enum ops ops;
|
|
enum width width;
|
|
struct rawio_driver *driver;
|
|
|
|
rawio_err_buf[0] = 0;
|
|
|
|
if (len >= RAWIO_CMD_LEN) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN, "command is too long.\n"
|
|
"max allowed command length is %d\n",
|
|
RAWIO_CMD_LEN);
|
|
goto done;
|
|
}
|
|
|
|
if (copy_from_user(cmd, buf, len)) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"copy_from_user() failed.\n");
|
|
goto done;
|
|
}
|
|
cmd[len] = 0;
|
|
|
|
rawio_cmd_buf[0] = 0;
|
|
strncpy(rawio_cmd_buf, cmd, len);
|
|
rawio_cmd_buf[len] = 0;
|
|
|
|
num_args = parse_arguments(cmd, arg_list);
|
|
if (num_args < RAWIO_ARGS_MIN) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid command(too few arguments)\n");
|
|
goto done;
|
|
}
|
|
if (num_args > RAWIO_ARGS_MAX) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid command(too many arguments)\n");
|
|
goto done;
|
|
}
|
|
|
|
/* arg 0: ops(read/write) and width (8/16/32/64 bit) */
|
|
if (arg_list[0][0] == 'r')
|
|
ops = OPS_RD;
|
|
else if (arg_list[0][0] == 'w')
|
|
ops = OPS_WR;
|
|
else {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid operation: %c, only r and w are supported\n",
|
|
arg_list[0][0]);
|
|
goto done;
|
|
}
|
|
|
|
if (strlen(arg_list[0]) >= 3) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid bus width: %s, only 1 2 4 8 are supported\n",
|
|
arg_list[0] + 1);
|
|
goto done;
|
|
}
|
|
|
|
if (strlen(arg_list[0]) == 1)
|
|
width = WIDTH_DEFAULT;
|
|
else {
|
|
switch (arg_list[0][1]) {
|
|
case '1':
|
|
width = WIDTH_1;
|
|
break;
|
|
case '2':
|
|
width = WIDTH_2;
|
|
break;
|
|
case '4':
|
|
width = WIDTH_4;
|
|
break;
|
|
case '8':
|
|
width = WIDTH_8;
|
|
break;
|
|
default:
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"invalid bus width: %c, only 1 2 4 8 are supported\n",
|
|
arg_list[0][1]);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* arg1: driver name */
|
|
driver = find_driver(arg_list[1]);
|
|
if (!driver) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"unsupported driver type: %s\n", arg_list[1]);
|
|
goto done;
|
|
}
|
|
|
|
if (width == WIDTH_DEFAULT)
|
|
width = driver->default_width;
|
|
|
|
if (!(width & driver->supported_width)) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"unsupported driver width: %s\n", arg_list[0]);
|
|
goto done;
|
|
}
|
|
|
|
/* arg2, ..., argn: driver specific arguments */
|
|
num_args = num_args - 2;
|
|
if (((ops == OPS_RD) && (num_args > driver->args_rd_max_num)) ||
|
|
((ops == OPS_WR) && (num_args > driver->args_wr_max_num))) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"too many arguments, usage:\n");
|
|
strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
|
|
strlen(rawio_err_buf));
|
|
goto done;
|
|
}
|
|
if (((ops == OPS_RD) && (num_args < driver->args_rd_min_num)) ||
|
|
((ops == OPS_WR) && (num_args < driver->args_wr_min_num))) {
|
|
snprintf(rawio_err_buf, RAWIO_ERR_LEN,
|
|
"too few arguments, usage:\n");
|
|
strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
|
|
strlen(rawio_err_buf));
|
|
goto done;
|
|
}
|
|
|
|
if (parse_driver_args(driver, arg_list + 2, num_args, ops,
|
|
args_val, args_postfix))
|
|
goto done;
|
|
|
|
active_driver = driver;
|
|
active_width = width;
|
|
active_ops = ops;
|
|
num_args_val = num_args;
|
|
done:
|
|
return len;
|
|
}
|
|
|
|
static int rawio_output_show(struct seq_file *s, void *unused)
|
|
{
|
|
u32 start, end, start_nature, end_nature;
|
|
int ret, i, comp1, comp2, output_len;
|
|
void *output;
|
|
char seq_buf[16];
|
|
|
|
mutex_lock(&rawio_lock);
|
|
|
|
if (strlen(rawio_err_buf) > 0) {
|
|
seq_puts(s, rawio_err_buf);
|
|
mutex_unlock(&rawio_lock);
|
|
return 0;
|
|
}
|
|
|
|
active_driver->s = s;
|
|
|
|
if (active_ops == OPS_WR) {
|
|
ret = active_driver->ops->write(active_driver, active_width,
|
|
args_val, args_postfix, num_args_val);
|
|
if (ret)
|
|
seq_puts(s, rawio_err_buf);
|
|
else
|
|
seq_puts(s, "write succeeded.\n");
|
|
|
|
mutex_unlock(&rawio_lock);
|
|
return 0;
|
|
}
|
|
|
|
if (active_driver->ops->read_and_show) {
|
|
ret = active_driver->ops->read_and_show(active_driver,
|
|
active_width, args_val, args_postfix, num_args_val);
|
|
if (ret)
|
|
seq_puts(s, rawio_err_buf);
|
|
mutex_unlock(&rawio_lock);
|
|
return 0;
|
|
}
|
|
|
|
ret = active_driver->ops->read(active_driver, active_width, args_val,
|
|
args_postfix, num_args_val, &output, &output_len);
|
|
if (ret) {
|
|
seq_puts(s, rawio_err_buf);
|
|
mutex_unlock(&rawio_lock);
|
|
return 0;
|
|
}
|
|
|
|
start_nature = (u32)args_val[active_driver->addr_pos];
|
|
start = (start_nature / LINE_WIDTH) * LINE_WIDTH;
|
|
end_nature = start_nature + (output_len - 1) * active_width;
|
|
end = (end_nature / LINE_WIDTH + 1) * LINE_WIDTH - active_width;
|
|
comp1 = (start_nature - start) / active_width;
|
|
comp2 = (end - end_nature) / active_width;
|
|
|
|
mutex_unlock(&rawio_lock);
|
|
|
|
for (i = 0; i < comp1 + comp2 + output_len; i++) {
|
|
if ((i % SHOW_NUM_PER_LINE) == 0) {
|
|
snprintf(seq_buf, sizeof(seq_buf), "[%08x]",
|
|
(u32)start + i * 4);
|
|
seq_puts(s, seq_buf);
|
|
}
|
|
if (i < comp1 || i >= output_len + comp1) {
|
|
switch (active_width) {
|
|
case WIDTH_8:
|
|
seq_puts(s, " ****************");
|
|
break;
|
|
case WIDTH_4:
|
|
seq_puts(s, " ********");
|
|
break;
|
|
case WIDTH_2:
|
|
seq_puts(s, " ****");
|
|
break;
|
|
case WIDTH_1:
|
|
seq_puts(s, " **");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
switch (active_width) {
|
|
case WIDTH_8:
|
|
snprintf(seq_buf, sizeof(seq_buf), "[%016llx]",
|
|
*((u64 *)output + i - comp1));
|
|
seq_puts(s, seq_buf);
|
|
break;
|
|
case WIDTH_4:
|
|
snprintf(seq_buf, sizeof(seq_buf), " %08x",
|
|
*((u32 *)output + i - comp1));
|
|
seq_puts(s, seq_buf);
|
|
break;
|
|
case WIDTH_2:
|
|
snprintf(seq_buf, sizeof(seq_buf), " %04x",
|
|
*((u16 *)output + i - comp1));
|
|
seq_puts(s, seq_buf);
|
|
break;
|
|
case WIDTH_1:
|
|
snprintf(seq_buf, sizeof(seq_buf), " %02x",
|
|
*((u8 *)output + i - comp1));
|
|
seq_puts(s, seq_buf);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((i + 1) % SHOW_NUM_PER_LINE == 0)
|
|
seq_puts(s, "\n");
|
|
}
|
|
|
|
kfree(output);
|
|
return 0;
|
|
}
|
|
|
|
static int rawio_cmd_show(struct seq_file *s, void *unused)
|
|
{
|
|
seq_puts(s, rawio_cmd_buf);
|
|
return 0;
|
|
}
|
|
|
|
static int rawio_cmd_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, rawio_cmd_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations rawio_cmd_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rawio_cmd_open,
|
|
.read = seq_read,
|
|
.write = rawio_cmd_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int rawio_output_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, rawio_output_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations rawio_output_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rawio_output_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int __init rawio_init(void)
|
|
{
|
|
rawio_cmd_dentry = debugfs_create_file("rawio_cmd",
|
|
S_IFREG | S_IRUGO | S_IWUSR, NULL, NULL, &rawio_cmd_fops);
|
|
rawio_output_dentry = debugfs_create_file("rawio_output",
|
|
S_IFREG | S_IRUGO, NULL, NULL, &rawio_output_fops);
|
|
if (!rawio_cmd_dentry || !rawio_output_dentry) {
|
|
pr_err("rawio: can't create debugfs node\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
module_init(rawio_init);
|
|
|
|
static void __exit rawio_exit(void)
|
|
{
|
|
debugfs_remove(rawio_cmd_dentry);
|
|
debugfs_remove(rawio_output_dentry);
|
|
}
|
|
module_exit(rawio_exit);
|
|
|
|
MODULE_DESCRIPTION("Raw IO read/write utility framework driver");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_AUTHOR("Bin Gao <bin.gao@intel.com>");
|
|
MODULE_LICENSE("GPL v2");
|