blob: 04f87b4325f54a6cfb867913ce602eed33632be7 [file] [log] [blame]
/** @file bt_proc.c
*
* @brief This file handle the functions for proc files
*
* Copyright (C) 2007-2018, Marvell International Ltd.
*
* This software file (the "File") is distributed by Marvell International
* Ltd. under the terms of the GNU General Public License Version 2, June 1991
* (the "License"). You may use, redistribute and/or modify this File in
* accordance with the terms and conditions of the License, a copy of which
* is available along with the File in the gpl.txt file or by writing to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 or on the worldwide web at http://www.gnu.org/licenses/gpl.txt.
*
* THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED. The License provides additional details about
* this warranty disclaimer.
*
*/
#include <linux/proc_fs.h>
#include "bt_drv.h"
#include "bt_sdio.h"
/** proc diretory root */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
#define PROC_DIR NULL
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
#define PROC_DIR (&proc_root)
#else
#define PROC_DIR proc_net
#endif
/** Proc mbt directory entry */
static struct proc_dir_entry *proc_mbt;
#define CMD52_STR_LEN 50
static char cmd52_string[CMD52_STR_LEN];
/** proc data structure */
struct proc_data {
/** Read length */
int rdlen;
/** Read buffer */
char *rdbuf;
/** Write length */
int wrlen;
/** Maximum write length */
int maxwrlen;
/** Write buffer */
char *wrbuf;
/** Private structure */
struct _bt_private *pbt;
void (*on_close) (struct inode *, struct file *);
};
/** Default file permission */
#define DEFAULT_FILE_PERM 0644
/** Bluetooth device offset */
#define OFFSET_BT_DEV 0x01
/** Bluetooth adapter offset */
#define OFFSET_BT_ADAPTER 0x02
/** Show integer */
#define SHOW_INT 0x10
/** Show hex */
#define SHOW_HEX 0x20
/** Show string */
#define SHOW_STRING 0x40
/** Device size */
#define item_dev_size(n) (sizeof((bt_dev_t *)0)->n)
/** Device address */
#define item_dev_addr(n) ((t_ptr) &((bt_dev_t *)0)->n)
/** Adapter size */
#define item_adapter_size(n) (sizeof((bt_adapter *)0)->n)
/** Adapter address */
#define item_adapter_addr(n) ((t_ptr) &((bt_adapter *)0)->n)
static struct item_data config_items[] = {
#ifdef DEBUG_LEVEL1
{"drvdbg", sizeof(u32), (t_ptr)&mbt_drvdbg, 0, SHOW_HEX}
,
#endif
{"idle_timeout", item_dev_size(idle_timeout), 0,
item_dev_addr(idle_timeout), OFFSET_BT_DEV | SHOW_HEX}
,
{"psmode", item_dev_size(psmode), 0, item_dev_addr(psmode),
OFFSET_BT_DEV | SHOW_INT}
,
{"pscmd", item_dev_size(pscmd), 0, item_dev_addr(pscmd),
OFFSET_BT_DEV | SHOW_INT}
,
{"hsmode", item_dev_size(hsmode), 0, item_dev_addr(hsmode),
OFFSET_BT_DEV | SHOW_INT}
,
{"hscmd", item_dev_size(hscmd), 0, item_dev_addr(hscmd),
OFFSET_BT_DEV | SHOW_INT}
,
{"gpio_gap", item_dev_size(gpio_gap), 0, item_dev_addr(gpio_gap),
OFFSET_BT_DEV | SHOW_HEX}
,
{"hscfgcmd", item_dev_size(hscfgcmd), 0, item_dev_addr(hscfgcmd),
OFFSET_BT_DEV | SHOW_INT}
,
{"sdio_pull_cfg", item_dev_size(sdio_pull_cfg), 0,
item_dev_addr(sdio_pull_cfg), OFFSET_BT_DEV | SHOW_HEX}
,
{"sdio_pull_ctrl", item_dev_size(sdio_pull_ctrl), 0,
item_dev_addr(sdio_pull_ctrl), OFFSET_BT_DEV | SHOW_INT}
,
{"test_mode", item_dev_size(test_mode), 0, item_dev_addr(test_mode),
OFFSET_BT_DEV | SHOW_INT}
,
};
static struct item_data status_items[] = {
{"version", item_adapter_size(drv_ver), 0, item_adapter_addr(drv_ver),
OFFSET_BT_ADAPTER | SHOW_STRING},
{"tx_dnld_rdy", item_dev_size(tx_dnld_rdy), 0,
item_dev_addr(tx_dnld_rdy),
OFFSET_BT_DEV | SHOW_INT},
{"psmode", item_adapter_size(psmode), 0, item_adapter_addr(psmode),
OFFSET_BT_ADAPTER | SHOW_INT},
{"hs_state", item_adapter_size(hs_state), 0,
item_adapter_addr(hs_state),
OFFSET_BT_ADAPTER | SHOW_INT},
{"hs_skip", item_adapter_size(hs_skip), 0, item_adapter_addr(hs_skip),
OFFSET_BT_ADAPTER | SHOW_INT},
{"ps_state", item_adapter_size(ps_state), 0,
item_adapter_addr(ps_state),
OFFSET_BT_ADAPTER | SHOW_INT},
{"WakeupTries", item_adapter_size(WakeupTries), 0,
item_adapter_addr(WakeupTries), OFFSET_BT_ADAPTER | SHOW_INT},
{"irq_recv", item_adapter_size(irq_recv), 0,
item_adapter_addr(irq_recv),
OFFSET_BT_ADAPTER | SHOW_INT},
{"irq_done", item_adapter_size(irq_done), 0,
item_adapter_addr(irq_done),
OFFSET_BT_ADAPTER | SHOW_INT},
{"skb_pending", item_adapter_size(skb_pending), 0,
item_adapter_addr(skb_pending), OFFSET_BT_ADAPTER | SHOW_INT},
};
static struct item_data debug_items[] = {
{"sdcmd52rw", 0, (t_ptr)cmd52_string, 0, SHOW_STRING},
};
/**
* @brief convert string to number
*
* @param s pointer to numbered string
* @return converted number from string s
*/
int
string_to_number(char *s)
{
int r = 0;
int base = 0;
int pn = 1;
if (strncmp(s, "-", 1) == 0) {
pn = -1;
s++;
}
if ((strncmp(s, "0x", 2) == 0) || (strncmp(s, "0X", 2) == 0)) {
base = 16;
s += 2;
} else
base = 10;
for (s = s; *s != 0; s++) {
if ((*s >= '0') && (*s <= '9'))
r = (r * base) + (*s - '0');
else if ((*s >= 'A') && (*s <= 'F'))
r = (r * base) + (*s - 'A' + 10);
else if ((*s >= 'a') && (*s <= 'f'))
r = (r * base) + (*s - 'a' + 10);
else
break;
}
return r * pn;
}
/**
* @brief Create cmd52 string
*
* @param priv A pointer to bt_private structure
* @return BT_STATUS_SUCCESS
*/
static int
form_cmd52_string(bt_private *priv)
{
ENTER();
memset(cmd52_string, 0, CMD52_STR_LEN);
snprintf(cmd52_string, CMD52_STR_LEN - 1, "BT: %d 0x%0x 0x%02X",
priv->bt_dev.cmd52_func, priv->bt_dev.cmd52_reg,
priv->bt_dev.cmd52_val);
LEAVE();
return BT_STATUS_SUCCESS;
}
/*
* @brief Parse cmd52 string
*
* @param buffer A pointer user buffer
* @param len Length of user buffer
* @param func Parsed func number
* @param reg Parsed reg value
* @param val Parsed value to set
* @return BT_STATUS_SUCCESS
*/
static int
parse_cmd52_string(const char __user * buffer, size_t len,
int *func, int *reg, int *val)
{
int ret = BT_STATUS_SUCCESS;
char *string = NULL;
char *pos = NULL;
ENTER();
string = kzalloc(CMD52_STR_LEN, GFP_KERNEL);
if (!string) {
PRINTM(ERROR, "BT: Can not alloc mem for cmd52 string\n");
LEAVE();
return -ENOMEM;
}
memcpy(string, buffer + strlen("sdcmd52rw="),
len - strlen("sdcmd52rw="));
string = strstrip(string);
*func = -1;
*reg = -1;
*val = -1;
/* Get func */
pos = strsep(&string, " \t");
if (pos)
*func = string_to_number(pos);
/* Get reg */
pos = strsep(&string, " \t");
if (pos)
*reg = string_to_number(pos);
/* Get val (optional) */
pos = strsep(&string, " \t");
if (pos)
*val = string_to_number(pos);
kfree(string);
LEAVE();
return ret;
}
/**
* @brief This function handle generic proc file close
*
* @param inode A pointer to inode structure
* @param file A pointer to file structure
* @return BT_STATUS_SUCCESS
*/
static int
proc_close(struct inode *inode, struct file *file)
{
struct proc_data *pdata = file->private_data;
ENTER();
if (pdata) {
if (pdata->on_close != NULL)
pdata->on_close(inode, file);
kfree(pdata->rdbuf);
kfree(pdata->wrbuf);
kfree(pdata);
}
LEAVE();
return BT_STATUS_SUCCESS;
}
/**
* @brief This function handle generic proc file read
*
* @param file A pointer to file structure
* @param buffer A pointer to output buffer
* @param len number of byte to read
* @param offset A pointer to offset of file
* @return number of output data
*/
static ssize_t
proc_read(struct file *file, char __user * buffer, size_t len, loff_t * offset)
{
loff_t pos = *offset;
struct proc_data *pdata = (struct proc_data *)file->private_data;
if ((!pdata->rdbuf) || (pos < 0))
return -EINVAL;
if (pos >= pdata->rdlen)
return 0;
if (len > pdata->rdlen - pos)
len = pdata->rdlen - pos;
if (copy_to_user(buffer, pdata->rdbuf + pos, len))
return -EFAULT;
*offset = pos + len;
return len;
}
/**
* @brief This function handle generic proc file write
*
* @param file A pointer to file structure
* @param buffer A pointer to input buffer
* @param len number of byte to write
* @param offset A pointer to offset of file
* @return number of input data
*/
static ssize_t
proc_write(struct file *file,
const char __user * buffer, size_t len, loff_t * offset)
{
loff_t pos = *offset;
struct proc_data *pdata = (struct proc_data *)file->private_data;
int func = 0, reg = 0, val = 0;
int config_data = 0;
char *line = NULL;
if (!pdata->wrbuf || (pos < 0))
return -EINVAL;
if (pos >= pdata->maxwrlen)
return 0;
if (len > pdata->maxwrlen - pos)
len = pdata->maxwrlen - pos;
if (copy_from_user(pdata->wrbuf + pos, buffer, len))
return -EFAULT;
if (!strncmp(pdata->wrbuf + pos, "fw_reload", strlen("fw_reload"))) {
if (!strncmp
(pdata->wrbuf + pos, "fw_reload=", strlen("fw_reload="))) {
line = pdata->wrbuf + pos;
line += strlen("fw_reload") + 1;
config_data = string_to_number(line);
} else
config_data = FW_RELOAD_SDIO_INBAND_RESET;
PRINTM(MSG, "Request fw_reload=%d\n", config_data);
bt_request_fw_reload(pdata->pbt, config_data);
}
if (!strncmp(pdata->wrbuf + pos, "sdcmd52rw=", strlen("sdcmd52rw="))) {
parse_cmd52_string(pdata->wrbuf + pos, len, &func, &reg, &val);
sd_write_cmd52_val(pdata->pbt, func, reg, val);
}
if (!strncmp(pdata->wrbuf + pos, "debug_dump", strlen("debug_dump"))) {
bt_dump_sdio_regs(pdata->pbt);
bt_dump_firmware_info_v2(pdata->pbt);
}
if (pos + len > pdata->wrlen)
pdata->wrlen = len + file->f_pos;
*offset = pos + len;
return len;
}
/**
* @brief This function handle the generic file close
*
* @param inode A pointer to inode structure
* @param file A pointer to file structure
* @return N/A
*/
static void
proc_on_close(struct inode *inode, struct file *file)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
struct proc_private_data *priv = PDE_DATA(inode);
#else
struct proc_private_data *priv = PDE(inode)->data;
#endif
struct proc_data *pdata = file->private_data;
char *line;
int i;
ENTER();
if (!pdata->wrlen)
return;
line = pdata->wrbuf;
while (line[0]) {
for (i = 0; i < priv->num_items; i++) {
if (!strncmp
(line, priv->pdata[i].name,
strlen(priv->pdata[i].name))) {
line += strlen(priv->pdata[i].name) + 1;
if (priv->pdata[i].size == 1)
*((u8 *)priv->pdata[i].addr) =
(u8)string_to_number(line);
else if (priv->pdata[i].size == 2)
*((u16 *) priv->pdata[i].addr) =
(u16) string_to_number(line);
else if (priv->pdata[i].size == 4)
*((u32 *)priv->pdata[i].addr) =
(u32)string_to_number(line);
}
}
while (line[0] && line[0] != '\n')
line++;
if (line[0])
line++;
}
if (priv->pbt->bt_dev.hscmd || priv->pbt->bt_dev.pscmd
|| priv->pbt->bt_dev.sdio_pull_ctrl
|| priv->pbt->bt_dev.test_mode || priv->pbt->bt_dev.hscfgcmd) {
bt_prepare_command(priv->pbt);
wake_up_interruptible(&priv->pbt->MainThread.waitQ);
}
LEAVE();
return;
}
/**
* @brief This function handle the generic file open
*
* @param inode A pointer to inode structure
* @param file A pointer to file structure
* @return BT_STATUS_SUCCESS or other error no.
*/
static int
proc_open(struct inode *inode, struct file *file)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
struct proc_private_data *priv = PDE_DATA(inode);
#else
struct proc_private_data *priv = PDE(inode)->data;
#endif
struct proc_data *pdata;
int i;
char *p;
u32 val = 0;
ENTER();
priv->pbt->adapter->skb_pending =
skb_queue_len(&priv->pbt->adapter->tx_queue);
file->private_data = kzalloc(sizeof(struct proc_data), GFP_KERNEL);
if (file->private_data == NULL) {
PRINTM(ERROR, "BT: Can not alloc mem for proc_data\n");
LEAVE();
return -ENOMEM;
}
pdata = (struct proc_data *)file->private_data;
pdata->pbt = priv->pbt;
pdata->rdbuf = kmalloc(priv->bufsize, GFP_KERNEL);
if (pdata->rdbuf == NULL) {
PRINTM(ERROR, "BT: Can not alloc mem for rdbuf\n");
kfree(file->private_data);
LEAVE();
return -ENOMEM;
}
if (priv->fileflag == DEFAULT_FILE_PERM) {
pdata->wrbuf = kzalloc(priv->bufsize, GFP_KERNEL);
if (pdata->wrbuf == NULL) {
PRINTM(ERROR, "BT: Can not alloc mem for wrbuf\n");
kfree(pdata->rdbuf);
kfree(file->private_data);
return -ENOMEM;
}
pdata->maxwrlen = priv->bufsize;
pdata->on_close = proc_on_close;
}
p = pdata->rdbuf;
for (i = 0; i < priv->num_items; i++) {
if (priv->pdata[i].size == 1)
val = *((u8 *)priv->pdata[i].addr);
else if (priv->pdata[i].size == 2)
val = *((u16 *) priv->pdata[i].addr);
else if (priv->pdata[i].size == 4)
val = *((u32 *)priv->pdata[i].addr);
if (priv->pdata[i].flag & SHOW_INT)
p += sprintf(p, "%s=%d\n", priv->pdata[i].name, val);
else if (priv->pdata[i].flag & SHOW_HEX)
p += sprintf(p, "%s=0x%x\n", priv->pdata[i].name, val);
else if (priv->pdata[i].flag & SHOW_STRING) {
if (!strncmp
(priv->pdata[i].name, "sdcmd52rw",
strlen("sdcmd52rw"))) {
sd_read_cmd52_val(priv->pbt);
form_cmd52_string(priv->pbt);
}
p += sprintf(p, "%s=%s\n", priv->pdata[i].name,
(char *)priv->pdata[i].addr);
}
}
pdata->rdlen = strlen(pdata->rdbuf);
LEAVE();
return BT_STATUS_SUCCESS;
}
/** Proc read ops */
static const struct file_operations proc_read_ops = {
.read = proc_read,
.open = proc_open,
.release = proc_close
};
/** Proc Read-Write ops */
static const struct file_operations proc_rw_ops = {
.read = proc_read,
.write = proc_write,
.open = proc_open,
.release = proc_close
};
static struct proc_private_data proc_files[] = {
{"status", S_IRUGO, 1024,
sizeof(status_items) / sizeof(status_items[0]),
&status_items[0], NULL, &proc_read_ops}
,
{"config", DEFAULT_FILE_PERM, 512,
sizeof(config_items) / sizeof(config_items[0]), &config_items[0], NULL,
&proc_rw_ops}
,
{"debug", DEFAULT_FILE_PERM, 512,
sizeof(debug_items) / sizeof(debug_items[0]), &debug_items[0], NULL,
&proc_rw_ops}
,
};
/**
* @brief This function initializes proc entry
*
* @param priv A pointer to bt_private structure
* @param m_dev A pointer to struct m_dev
* @param seq Sequence number
*
* @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE
*/
int
bt_proc_init(bt_private *priv, struct m_dev *m_dev, int seq)
{
int ret = BT_STATUS_SUCCESS;
struct proc_dir_entry *entry;
int i, j;
ENTER();
memset(cmd52_string, 0, CMD52_STR_LEN);
if (proc_mbt) {
priv->dev_proc[seq].proc_entry =
proc_mkdir(m_dev->name, proc_mbt);
if (!priv->dev_proc[seq].proc_entry) {
PRINTM(ERROR, "BT: Could not mkdir %s!\n", m_dev->name);
ret = BT_STATUS_FAILURE;
goto done;
}
priv->dev_proc[seq].pfiles =
kmalloc(sizeof(proc_files), GFP_ATOMIC);
if (!priv->dev_proc[seq].pfiles) {
PRINTM(ERROR,
"BT: Could not alloc memory for pfile!\n");
ret = BT_STATUS_FAILURE;
goto done;
}
memcpy((u8 *)priv->dev_proc[seq].pfiles, (u8 *)proc_files,
sizeof(proc_files));
priv->dev_proc[seq].num_proc_files = ARRAY_SIZE(proc_files);
for (j = 0; j < priv->dev_proc[seq].num_proc_files; j++)
priv->dev_proc[seq].pfiles[j].pdata = NULL;
for (j = 0; j < priv->dev_proc[seq].num_proc_files; j++) {
priv->dev_proc[seq].pfiles[j].pdata =
kmalloc(priv->dev_proc[seq].pfiles[j].
num_items * sizeof(struct item_data),
GFP_ATOMIC);
if (!priv->dev_proc[seq].pfiles[j].pdata) {
PRINTM(ERROR,
"BT: Could not alloc memory for pdata!\n");
ret = BT_STATUS_FAILURE;
goto done;
}
memcpy((u8 *)priv->dev_proc[seq].pfiles[j].pdata,
(u8 *)proc_files[j].pdata,
priv->dev_proc[seq].pfiles[j].num_items *
sizeof(struct item_data));
for (i = 0; i < priv->dev_proc[seq].pfiles[j].num_items;
i++) {
if (priv->dev_proc[seq].pfiles[j].
pdata[i].flag & OFFSET_BT_DEV)
priv->dev_proc[seq].pfiles[j].pdata[i].
addr =
priv->dev_proc[seq].pfiles[j].
pdata[i].offset +
(t_ptr)&priv->bt_dev;
if (priv->dev_proc[seq].pfiles[j].
pdata[i].flag & OFFSET_BT_ADAPTER)
priv->dev_proc[seq].pfiles[j].pdata[i].
addr =
priv->dev_proc[seq].pfiles[j].
pdata[i].offset +
(t_ptr)priv->adapter;
}
priv->dev_proc[seq].pfiles[j].pbt = priv;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
entry = proc_create_data(proc_files[j].name,
S_IFREG | proc_files[j].
fileflag,
priv->dev_proc[seq].proc_entry,
proc_files[j].fops,
&priv->dev_proc[seq].
pfiles[j]);
if (entry == NULL)
#else
entry = create_proc_entry(proc_files[j].name,
S_IFREG | proc_files[j].
fileflag,
priv->dev_proc[seq].
proc_entry);
if (entry) {
entry->data = &priv->dev_proc[seq].pfiles[j];
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
entry->owner = THIS_MODULE;
#endif
entry->proc_fops = proc_files[j].fops;
} else
#endif
PRINTM(MSG, "BT: Fail to create proc %s\n",
proc_files[j].name);
}
}
done:
if (ret == BT_STATUS_FAILURE) {
if (priv->dev_proc[seq].proc_entry) {
remove_proc_entry(m_dev->name, proc_mbt);
priv->dev_proc[seq].proc_entry = NULL;
}
if (priv->dev_proc[seq].pfiles) {
for (j = 0; j < priv->dev_proc[seq].num_proc_files; j++) {
if (priv->dev_proc[seq].pfiles[j].pdata) {
kfree(priv->dev_proc[seq].pfiles[j].
pdata);
priv->dev_proc[seq].pfiles[j].pdata =
NULL;
}
}
kfree(priv->dev_proc[seq].pfiles);
priv->dev_proc[seq].pfiles = NULL;
}
}
LEAVE();
return ret;
}
/**
* @brief This function removes proc interface
*
* @param priv A pointer to bt_private structure
* @return N/A
*/
void
bt_proc_remove(bt_private *priv)
{
int j, i;
ENTER();
PRINTM(INFO, "BT: Remove Proc Interface\n");
if (proc_mbt) {
for (i = 0; i < MAX_RADIO_FUNC; i++) {
if (!priv->dev_proc[i].proc_entry)
continue;
for (j = 0; j < ARRAY_SIZE(proc_files); j++) {
remove_proc_entry(proc_files[j].name,
priv->dev_proc[i].proc_entry);
}
remove_proc_entry(priv->bt_dev.m_dev[i].name, proc_mbt);
priv->dev_proc[i].proc_entry = NULL;
if (priv->dev_proc[i].pfiles) {
for (j = 0;
j < priv->dev_proc[i].num_proc_files;
j++) {
if (priv->dev_proc[i].pfiles[j].pdata) {
kfree(priv->dev_proc[i].
pfiles[j].pdata);
priv->dev_proc[i].pfiles[j].
pdata = NULL;
}
}
kfree(priv->dev_proc[i].pfiles);
priv->dev_proc[i].pfiles = NULL;
}
}
}
LEAVE();
return;
}
/**
* @brief This function creates proc interface
* directory structure
*
* @return BT_STATUS_SUCCESS or BT_STATUS_FAILURE
*/
int
bt_root_proc_init(void)
{
PRINTM(INFO, "BT: Create Proc Interface\n");
proc_mbt = proc_mkdir("mbt", PROC_DIR);
if (!proc_mbt) {
PRINTM(ERROR, "BT: Cannot create proc interface\n");
return BT_STATUS_FAILURE;
}
return BT_STATUS_SUCCESS;
}
/**
* @brief This function removes proc interface
* directory structure
*
* @return BT_STATUS_SUCCESS
*/
int
bt_root_proc_remove(void)
{
remove_proc_entry("mbt", PROC_DIR);
proc_mbt = NULL;
return BT_STATUS_SUCCESS;
}