/*
|
* Copyright (C) 2016-2018 Spreadtrum Communications Inc.
|
* This software is licensed under the terms of the GNU General Public
|
* License version 2, as published by the Free Software Foundation, and
|
* may be copied, distributed, and modified under those terms.
|
|
* This program is distributed in the hope that 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.
|
*/
|
|
#include "ioctl.h"
|
#include "pcie.h"
|
#include "edma_engine.h"
|
|
enum PCIE_ARG_INDEX {
|
PCIE_ARG_ADDR,
|
PCIE_ARG_VALUE,
|
PCIE_ARG_SIZE,
|
PCIE_ARG_BAR,
|
PCIE_ARG_RUN,
|
PCIE_ARG_OFFSET,
|
PCIE_ARG_VIR,
|
PCIE_ARG_REGION,
|
PCIE_ARG_TWO_LINK,
|
PCIE_ARG_MODE,
|
PCIE_ARG_MAX,
|
};
|
|
static struct arg_t pcie_args[PCIE_ARG_MAX] = {
|
{PCIE_ARG_ADDR, "addr", 0},
|
{PCIE_ARG_VALUE, "value", 0},
|
{PCIE_ARG_SIZE, "size", 4},
|
{PCIE_ARG_BAR, "bar", 0},
|
{PCIE_ARG_OFFSET, "offset", 0},
|
{PCIE_ARG_RUN, "run", 0},
|
{PCIE_ARG_VIR, "vir", 0},
|
{PCIE_ARG_REGION, "region", 0},
|
{PCIE_ARG_TWO_LINK, "link", 0},
|
{PCIE_ARG_MODE, "mode", 0},
|
};
|
|
struct arg_t *pcie_arg_index(unsigned int index)
|
{
|
int i;
|
|
for (i = 0; i < PCIE_ARG_MAX; i++) {
|
if (index == pcie_args[i].id)
|
return &pcie_args[i];
|
|
}
|
return NULL;
|
}
|
|
static unsigned long args_value(int index)
|
{
|
int i;
|
|
for (i = 0; i < PCIE_ARG_MAX; i++) {
|
if (index == pcie_args[i].id)
|
return pcie_args[i].def;
|
}
|
|
return -1;
|
}
|
|
static int hwcopy(unsigned char *dest, unsigned char *src, int len)
|
{
|
int i;
|
|
for (i = 0; i < (len / 4); i++) {
|
*((int *)(dest)) = *((int *)(src));
|
dest += 4;
|
src += 4;
|
}
|
for (i = 0; i < (len % 4); i++) {
|
*dest = *src;
|
dest++;
|
src++;
|
}
|
|
return 0;
|
}
|
|
static int cmdline_args(char *cmdline, char *cmd, struct arg_t *args, int argc)
|
{
|
int status = 0;
|
char *end;
|
char *argname;
|
char *cp;
|
int DONE;
|
int FOUND = 0;
|
|
unsigned short base;
|
unsigned long result, value;
|
unsigned long val;
|
unsigned long i;
|
unsigned long j;
|
|
for (i = 0; i < argc; i++)
|
args[i].def = 0;
|
|
/* get cmd */
|
while (*cmdline == ' ' || *cmdline == '\t')
|
cmdline++;
|
|
while (*cmdline != ' ' && *cmdline != '\t' && *cmdline != '\0') {
|
*cmd = *cmdline;
|
cmd++;
|
cmdline++;
|
}
|
*cmd = '\0';
|
if (*cmdline == '\0')
|
goto WEDONE;
|
|
*cmdline = '\0';
|
cmdline++;
|
while (*cmdline == ' ' || *cmdline == '\t')
|
cmdline++;
|
|
end = cmdline;
|
while (*end == ' ' || *end == '\t')
|
end++;
|
|
/*
|
* Parse cmdline
|
*/
|
DONE = (*end == '\0') ? 1 : 0;
|
while (!DONE) {
|
/* get the register name */
|
while (*end != '=' && *end != '\0')
|
end++;
|
if (*end == '\0') {
|
status = 1;
|
goto WEDONE;
|
}
|
*end = '\0';
|
argname = cmdline;
|
/* now get value to write to register */
|
cmdline = ++end;
|
/* if there's whitespace after the '=', exit with an error */
|
if (*end == ' ' || *end == '\t' || *end == '\n') {
|
status = 1;
|
goto WEDONE;
|
}
|
while (*end != ' ' && *end != '\t' && *end != '\n'
|
&& *end != '\0')
|
end++;
|
if (*end == '\0')
|
DONE = 1;
|
else
|
*end = '\0';
|
|
if (!strcmp(argname, "file") || !strcmp(argname, "filec")) {
|
val = 1;
|
} else {
|
/* get the base, convert value to base-10 if necessary */
|
val = 0;
|
result = 0;
|
cp = cmdline;
|
if (cp[0] == '0' && (cp[1] == 'x' || cp[1] == 'X')) {
|
base = 16;
|
cp += 2;
|
|
} else {
|
base = 10;
|
}
|
while (isxdigit(*cp)) {
|
value = isdigit(*cp) ? (*cp - '0')
|
: ((islower(*cp) ? toupper(*cp) : *cp) -
|
'A' + 10);
|
|
result = result * base + value;
|
cp++;
|
}
|
|
val = result;
|
}
|
|
FOUND = 0;
|
/*
|
* verify the register arg is valid, and if the value is not
|
* too big, write it to the corresponding location in arg_vals
|
*/
|
for (j = 0; j < argc && !FOUND; j++) {
|
if (!strcmp(argname, args[j].name)) {
|
args[j].def = val;
|
FOUND = 1;
|
}
|
}
|
if (!FOUND) {
|
PCIE_ERR("arg %s err\n", argname);
|
status = 0;
|
goto WEDONE;
|
}
|
|
/*
|
* point cmdline and end to next non-whitespace
|
* (next argument)
|
*/
|
cmdline = ++end;
|
while (*cmdline == ' ' || *cmdline == '\t' || *cmdline == '\n')
|
cmdline++;
|
end = cmdline;
|
/*
|
* if, after skipping whitespace, we hit end of line or EOF,
|
* we're done
|
*/
|
if (*end == '\0')
|
DONE = 1;
|
}
|
|
WEDONE:
|
return status;
|
}
|
|
static int pcie_cmd_proc(struct char_drv_info *dev, unsigned char *input,
|
int input_len, struct tlv *replay)
|
{
|
int ret, bar, offset, i, run, flag;
|
unsigned long addr, value, size, mode;
|
unsigned char cmd[64], string[512] = { 0 }, *buf, *mem;
|
struct pcicmd *pcicmd;
|
struct dma_buf dm;
|
struct wcn_pcie_info *priv;
|
|
priv = dev->pcie_dev_info;
|
|
/* IRAM */
|
pcicmd = (struct pcicmd *) (pcie_bar_vmem(priv, 0) +
|
cp2_test_addr1 + cp2_test_addr2);
|
|
ret = cmdline_args(input, cmd, pcie_args, PCIE_ARG_MAX);
|
if (ret) {
|
PCIE_INFO("cmdline_args err\n");
|
return -1;
|
} else if (!strcmp("mread", cmd)) {
|
addr = args_value(PCIE_ARG_ADDR);
|
size = args_value(PCIE_ARG_SIZE);
|
|
replay->t = 1;
|
replay->l = size;
|
memcpy(replay->v, (unsigned char *)addr, replay->l);
|
} else if (!strcmp("mwrite", cmd)) {
|
addr = args_value(PCIE_ARG_ADDR);
|
value = args_value(PCIE_ARG_VALUE);
|
size = args_value(PCIE_ARG_SIZE);
|
PCIE_INFO("memwrite addr=0x%lx value=0x%lx size=%ld\n",
|
addr, value, size);
|
memcpy((char *)(addr), (char *)&value, 4);
|
} else if (!strcmp("init", cmd)) {
|
struct inbound_reg *ibreg =
|
(struct inbound_reg *) ibreg_base(priv, 0);
|
|
if (ibreg == NULL) {
|
PCIE_ERR("ibreg(0) NULL\n");
|
return -1;
|
}
|
ibreg->lower_target_addr = 0x40000000;
|
ibreg->upper_target_addr = 0x00000000;
|
ibreg->type = 0x00000000;
|
ibreg->limit = 0x00FFFFFF;
|
ibreg->en = 0xc0000000;
|
replay->t = 1;
|
replay->l = sizeof(struct inbound_reg);
|
hwcopy(replay->v, (unsigned char *)ibreg, replay->l);
|
} else if (!strcmp("config", cmd)) {
|
size = args_value(PCIE_ARG_SIZE);
|
if (size < 256)
|
replay->l = 256;
|
else
|
replay->l = size;
|
replay->t = 1;
|
pcie_config_read(priv, 0, replay->v, replay->l);
|
} else if (!strcmp("bwrite", cmd)) {
|
bar = args_value(PCIE_ARG_BAR);
|
offset = args_value(PCIE_ARG_OFFSET);
|
value = args_value(PCIE_ARG_VALUE);
|
|
sprintf(string, "bwrite bar=%d offset=0x%x value=0x%lx\n", bar,
|
offset, value);
|
PCIE_INFO("do %s\n", string);
|
pcie_bar_write(priv, bar, offset, (char *)(&value), 4);
|
replay->t = 0;
|
replay->l = strlen(string);
|
memcpy(replay->v, string, replay->l);
|
} else if (!strcmp("bread", cmd)) {
|
bar = args_value(PCIE_ARG_BAR);
|
offset = args_value(PCIE_ARG_OFFSET);
|
size = args_value(PCIE_ARG_SIZE);
|
buf = pcie_bar_vmem(priv, bar) + offset;
|
PCIE_INFO("kernel bread bar=%d offset=0x%x size=0x%lx\n", bar,
|
offset, size);
|
replay->t = 1;
|
replay->l = size;
|
hwcopy(replay->v, buf, replay->l);
|
} else if (!strcmp("dmalloc", cmd)) {
|
size = args_value(PCIE_ARG_SIZE);
|
|
ret = dmalloc(priv, &dm, size);
|
if (!ret) {
|
sprintf(string, "dmalloc(%ld) 0x%lx, 0x%lx ok\n",
|
size, dm.vir, dm.phy);
|
} else
|
sprintf(string, "dmalloc(%ld) fail\n", size);
|
replay->t = 0;
|
replay->l = strlen(string);
|
memcpy(replay->v, string, replay->l);
|
} else if (!strcmp("malloc", cmd)) {
|
size = args_value(PCIE_ARG_SIZE);
|
mem = (unsigned char *)mpool_malloc(size);
|
|
} else if (!strcmp("outbound", cmd)) {
|
addr = args_value(PCIE_ARG_ADDR);
|
size = args_value(PCIE_ARG_SIZE);
|
run = args_value(PCIE_ARG_RUN);
|
|
pcicmd->addr1 = addr;
|
pcicmd->arg[0] = size;
|
pcicmd->arg[1] = run;
|
pcicmd->cmd = 0x00000002;
|
flag = 1;
|
i = 10000;
|
while (i--) {
|
if (pcicmd->cmd == MAGIC_VALUE) {
|
replay->t = 0;
|
replay->l = 128;
|
memcpy(replay->v,
|
pcie_bar_vmem(priv, 0) + cp2_test_addr3 +
|
cp2_test_addr4, 128);
|
sprintf(string, "ok\n");
|
replay->t = 0;
|
replay->l = strlen(string);
|
memcpy(replay->v, string, replay->l);
|
flag = 0;
|
break;
|
}
|
usleep_range(1000, 2000);
|
}
|
if (flag == 1) {
|
sprintf(string, "cmd timeout\n");
|
replay->t = 0;
|
replay->l = strlen(string);
|
memcpy(replay->v, string, replay->l);
|
}
|
} else if (!strcmp("inbound", cmd)) {
|
bar = args_value(PCIE_ARG_BAR);
|
offset = args_value(PCIE_ARG_OFFSET);
|
size = args_value(PCIE_ARG_SIZE);
|
run = args_value(PCIE_ARG_RUN);
|
|
PCIE_INFO("inbound(%d,%ld,%d)\n", offset, size, run);
|
|
mem = kmalloc(size, GFP_KERNEL);
|
buf = kmalloc(size, GFP_KERNEL);
|
if ((!mem) || (!buf)) {
|
kfree(mem);
|
kfree(buf);
|
PCIE_ERR("kmalloc(%ld) err\n", size);
|
return 0;
|
}
|
for (i = 0; i < run; i++) {
|
memcpy(buf + 0, (char *)(&i), 4);
|
memset(buf + 4, (char)i, size - 4);
|
memcpy(pcie_bar_vmem(priv, bar) + offset, buf,
|
size);
|
memcpy(mem, pcie_bar_vmem(priv, bar) + offset,
|
size);
|
if (memcmp(buf, mem, size)) {
|
sprintf(string, "inbound run %d err\n", i);
|
break;
|
}
|
}
|
if (i == run) {
|
sprintf(string, "inbound(0x%x,0x%lx,0x%x) ok\n", offset,
|
size, run);
|
}
|
PCIE_INFO("%s", string);
|
replay->t = 0;
|
replay->l = strlen(string);
|
memcpy(replay->v, string, replay->l);
|
|
kfree(mem);
|
kfree(buf);
|
} else if (!strcmp("lo_start", cmd)) {
|
mode = args_value(PCIE_ARG_MODE);
|
lo_start(mode);
|
} else if (!strcmp("lo_stop", cmd))
|
lo_stop();
|
else
|
PCIE_INFO("unknown cmd %s\n", cmd);
|
|
return 0;
|
}
|
|
int hexdump(char *name, char *buf, int len)
|
{
|
int i, count;
|
unsigned int *p;
|
|
count = len / 32;
|
count += 1;
|
PCIE_INFO("%s %s hex(len=%d):\n", __func__, name, len);
|
for (i = 0; i < count; i++) {
|
p = (unsigned int *)(buf + i * 32);
|
PCIE_INFO("mem[0x%04x] 0x%08x,0x%08x,0x%08x,0x%08x,"
|
"0x%08x,0x%08x,0x%08x,0x%08x,\n",
|
i * 32, p[0], p[1], p[2], p[3],
|
p[4], p[5], p[6], p[7]);
|
}
|
|
return 0;
|
}
|
|
static int char_open(struct inode *inode, struct file *filp)
|
{
|
struct char_drv_info *dev;
|
|
PCIE_INFO("%s\n", __func__);
|
dev = container_of(inode->i_cdev, struct char_drv_info, testcdev);
|
filp->private_data = dev;
|
|
return 0;
|
}
|
|
static ssize_t char_write(struct file *filp, const char __user *buffer,
|
size_t count, loff_t *offset)
|
{
|
PCIE_INFO("%s\n", __func__);
|
|
return 0;
|
}
|
|
static ssize_t char_read(struct file *filp, char __user *buffer, size_t count,
|
loff_t *offset)
|
{
|
PCIE_INFO("%s\n", __func__);
|
|
return 0;
|
}
|
|
static int char_release(struct inode *inode, struct file *filp)
|
{
|
PCIE_INFO("%s\n", __func__);
|
|
return 0;
|
}
|
|
static long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
{
|
int ret;
|
struct tlv *tlv;
|
unsigned char *buf[2];
|
struct char_drv_info *dev = filp->private_data;
|
|
buf[0] = kmalloc(4096, GFP_KERNEL);
|
buf[1] = kmalloc(4096, GFP_KERNEL);
|
|
tlv = (struct tlv *) buf[1];
|
|
ret = copy_from_user(buf[0], (char __user *)arg, 64);
|
PCIE_INFO("input:%s\n", buf[0]);
|
|
pcie_cmd_proc(dev, buf[0], 4096, (struct tlv *) buf[1]);
|
|
/* sizeof(struct tlv) + tlv->l */
|
ret = copy_to_user((char __user *)arg, buf[1], 4096);
|
|
kfree(buf[0]);
|
kfree(buf[1]);
|
|
return 0;
|
}
|
|
static const struct file_operations fop = {
|
.owner = THIS_MODULE,
|
.open = char_open,
|
.release = char_release,
|
.write = char_write,
|
.read = char_read,
|
.unlocked_ioctl = char_ioctl,
|
};
|
|
static int ioctlcmd_init(struct wcn_pcie_info *bus)
|
{
|
int ret;
|
dev_t dev;
|
struct char_drv_info *drv;
|
|
drv = kmalloc(sizeof(struct char_drv_info), GFP_KERNEL);
|
if (!drv) {
|
ret = -ENOMEM;
|
return ret;
|
}
|
|
bus->p_char = drv;
|
drv->pcie_dev_info = bus;
|
drv->major = 321;
|
dev = MKDEV(drv->major, 0);
|
ret = register_chrdev_region(dev, 1, "char");
|
if (ret) {
|
alloc_chrdev_region(&dev, 0, 1, "char");
|
drv->major = MAJOR(dev);
|
}
|
drv->testcdev.owner = THIS_MODULE;
|
cdev_init(&(drv->testcdev), &fop);
|
cdev_add(&(drv->testcdev), dev, 1);
|
|
drv->myclass = class_create(THIS_MODULE, "char_class");
|
drv->mydev = device_create(drv->myclass, NULL, dev, NULL, "kchar");
|
PCIE_INFO("module init ok ...\n");
|
|
return 0;
|
}
|
|
int ioctlcmd_deinit(struct wcn_pcie_info *bus)
|
{
|
dev_t dev;
|
struct char_drv_info *drv = bus->p_char;
|
|
dev = MKDEV(drv->major, 0);
|
|
device_destroy(drv->myclass, dev);
|
class_destroy(drv->myclass);
|
|
cdev_del(&(drv->testcdev));
|
unregister_chrdev_region(dev, 1);
|
PCIE_INFO("module exit ok....\n");
|
|
return 0;
|
}
|
|
int dbg_attach_bus(struct wcn_pcie_info *bus)
|
{
|
return ioctlcmd_init(bus);
|
}
|