kernel_samsung_a34x-permissive/drivers/misc/mediatek/c2k_usb/rawbulk.c
2024-04-28 15:51:13 +02:00

759 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2017 MediaTek Inc.
*/
/* #define DEBUG */
/* #define VERBOSE_DEBUG */
#define DRIVER_AUTHOR "Juelun Guo <jlguo@via-telecom.com>"
#define DRIVER_DESC "Rawbulk Gadget - transport data from CP to Gadget"
#define DRIVER_VERSION "1.0.2"
#define DRIVER_NAME "usb_rawbulk"
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/version.h>
#include <linux/usb/composite.h>
#include "viatel_rawbulk.h"
#define C2K_TTY_USB_SKIP
#ifdef C2K_USB_UT
#include <linux/random.h>
#include <linux/sched/signal.h>
#define UT_CMD 3
#define UT_CLR_ERR 4
#define SZ 4096
int ut_err;
#endif
/* sysfs attr idx assignment */
enum _attr_idx {
ATTR_ENABLE = 0, /** enable switch for Rawbulk */
#ifdef SUPPORT_LEGACY_CONTROL
ATTR_ENABLE_C, /** enable switch too, but for legacy */
#endif
ATTR_AUTORECONN, /** enable to rebind cp when it reconnect */
ATTR_STATISTICS, /** Rawbulk summary/statistics for one pipe */
ATTR_NUPS, /** upstream transfers count */
ATTR_NDOWNS, /** downstram transfers count */
ATTR_UPSIZE, /** upstream buffer for each transaction */
ATTR_DOWNSIZE, /** downstram buffer for each transaction */
ATTR_DTR, /** DTR control, only for Data-Call port */
};
struct rawbulk_function *prealloced_functions[_MAX_TID] = { NULL };
struct rawbulk_function *rawbulk_lookup_function(int transfer_id)
{
C2K_DBG("%s\n", __func__);
if (transfer_id >= 0 && transfer_id < _MAX_TID)
return prealloced_functions[transfer_id];
return NULL;
}
EXPORT_SYMBOL_GPL(rawbulk_lookup_function);
static inline int check_enable_state(struct rawbulk_function *fn)
{
int enab;
unsigned long flags;
C2K_DBG("%s\n", __func__);
spin_lock_irqsave(&fn->lock, flags);
enab = fn->enable ? 1 : 0;
C2K_DBG("enab(%d)\n", enab);
spin_unlock_irqrestore(&fn->lock, flags);
return enab;
}
int rawbulk_check_enable(struct rawbulk_function *fn)
{
C2K_DBG("%s\n", __func__);
return check_enable_state(fn);
}
EXPORT_SYMBOL_GPL(rawbulk_check_enable);
static inline void set_enable_state(struct rawbulk_function *fn, int enab)
{
unsigned long flags;
spin_lock_irqsave(&fn->lock, flags);
fn->enable = !!enab;
spin_unlock_irqrestore(&fn->lock, flags);
}
void rawbulk_disable_function(struct rawbulk_function *fn)
{
C2K_NOTE("enable to 0\n");
set_enable_state(fn, 0);
}
EXPORT_SYMBOL_GPL(rawbulk_disable_function);
#define port_to_rawbulk(p) container_of(p, struct rawbulk_function, port)
#define function_to_rawbulk(f) container_of(f, struct rawbulk_function,\
function)
static void init_endpoint_desc(struct usb_endpoint_descriptor *epdesc, int in,
int maxpacksize)
{
struct usb_endpoint_descriptor template = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
*epdesc = template;
if (in)
epdesc->bEndpointAddress = USB_DIR_IN;
else
epdesc->bEndpointAddress = USB_DIR_OUT;
epdesc->wMaxPacketSize = cpu_to_le16(maxpacksize);
}
static void init_interface_desc(struct usb_interface_descriptor *ifdesc)
{
struct usb_interface_descriptor template = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = 0xff, /* USB_CLASS_VENDOR_SPEC, */
.bInterfaceSubClass = 0xff,
.bInterfaceProtocol = 0xff,
.iInterface = 0,
};
*ifdesc = template;
}
static ssize_t rawbulk_attr_show(struct device *dev, struct device_attribute
*attr, char *buf);
static ssize_t rawbulk_attr_store(struct device *dev, struct device_attribute
*attr, const char *buf, size_t count);
static inline void add_device_attr(struct rawbulk_function *fn, unsigned int n,
const char *name, int mode)
{
if (n < MAX_ATTRIBUTES) {
sysfs_attr_init(&fn->attr[n].attr);
fn->attr[n].attr.name = name;
fn->attr[n].attr.mode = mode;
fn->attr[n].show = rawbulk_attr_show;
fn->attr[n].store = rawbulk_attr_store;
}
}
static int which_attr(struct rawbulk_function *fn, struct device_attribute
*attr)
{
int n;
for (n = 0; n < fn->max_attrs; n++) {
if (attr == &fn->attr[n])
return n;
}
return -1;
}
static ssize_t rawbulk_attr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int n;
int idx;
int enab;
struct rawbulk_function *fn;
ssize_t count = 0;
for (n = 0; n < _MAX_TID; n++) {
fn = rawbulk_lookup_function(n);
if (IS_ERR_OR_NULL(fn)) {
C2K_ERR("Null or error rawbulk_function! (n = %d)\n", n);
return 0;
}
if (fn->dev == dev) {
idx = which_attr(fn, attr);
break;
}
#ifdef SUPPORT_LEGACY_CONTROL
if (!strcmp(attr->attr.name, fn->shortname)) {
idx = ATTR_ENABLE_C;
break;
}
#endif
}
if (n == _MAX_TID)
return 0;
enab = check_enable_state(fn);
switch (idx) {
case ATTR_ENABLE:
count = snprintf(buf, PAGE_SIZE, "%d", enab);
break;
#ifdef SUPPORT_LEGACY_CONTROL
case ATTR_ENABLE_C:
count = snprintf(buf, PAGE_SIZE, "%s", en ? "gadget" : "tty");
break;
#endif
case ATTR_AUTORECONN:
count = snprintf(buf, PAGE_SIZE, "%d", fn->autoreconn);
break;
case ATTR_STATISTICS:
count = rawbulk_transfer_statistics(fn->transfer_id, buf);
break;
case ATTR_NUPS:
count = snprintf(buf, PAGE_SIZE,
"nups<%d>,udata<%d>,ucnt<%d>,drop<%d>,aloc_fai<%d>,tran<%d>\n",
enab ? fn->nups : -1,
upstream_data[fn->transfer_id],
upstream_cnt[fn->transfer_id],
total_drop[fn->transfer_id],
alloc_fail[fn->transfer_id],
total_tran[fn->transfer_id]);
break;
case ATTR_NDOWNS:
count = snprintf(buf, PAGE_SIZE, "%d", enab ? fn->ndowns : -1);
break;
case ATTR_UPSIZE:
count = snprintf(buf, PAGE_SIZE, "%d", enab ? fn->upsz : -1);
break;
case ATTR_DOWNSIZE:
count = snprintf(buf, PAGE_SIZE, "%d", enab ? fn->downsz : -1);
break;
default:
break;
}
return count;
}
#ifdef C2K_USB_UT
int total_cnt;
void do_push_upstream(int transfer_id, char *buf, int len)
{
int ret;
while (1) {
ret = rawbulk_push_upstream_buffer(transfer_id, buf, len);
if (ret == len || ret == 0) {
C2K_DBG("push ret(%d)\n", ret);
#ifdef C2K_USB_PERF
total_cnt += 1;
if (total_cnt == 1024) {
C2K_NOTE("4MB got\n");
total_cnt = 0;
}
#endif
break;
}
C2K_DBG("push ret(%d)\n", ret);
#ifndef C2K_USB_PERF
udelay(1000);
#endif
}
}
#endif
static ssize_t rawbulk_attr_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
int n;
int rc = 0;
int idx = -1;
int nups;
int ndowns;
int upsz;
int downsz;
struct rawbulk_function *fn;
for (n = 0; n < _MAX_TID; n++) {
fn = rawbulk_lookup_function(n);
if (IS_ERR_OR_NULL(fn))
break;
if (fn->dev == dev) {
idx = which_attr(fn, attr);
break;
}
#ifdef SUPPORT_LEGACY_CONTROL
if (!strcmp(attr->attr.name, fn->shortname)) {
idx = ATTR_ENABLE_C;
break;
}
#endif
}
if (idx < 0) {
C2K_ERR("sorry, I cannot find rawbulk fn '%s'\n",
attr->attr.name);
goto exit;
}
nups = fn->nups;
ndowns = fn->ndowns;
upsz = fn->upsz;
downsz = fn->downsz;
/* find out which attr(file) we write */
#ifdef SUPPORT_LEGACY_CONTROL
if (idx <= ATTR_ENABLE_C) {
#else
if (idx == ATTR_ENABLE) {
#endif
int enable;
long tmp = 0;
#ifdef SUPPORT_LEGACY_CONTROL
if (idx == ATTR_ENABLE) {
#else
{
#endif
int ret;
ret = kstrtol(buf, 0, &tmp);
enable = (int)tmp;
C2K_NOTE("enable:%d\n", enable);
#ifdef C2K_USB_UT
if (enable == UT_CMD) {
int i;
unsigned int len;
int transfer_id = n;
static char last_end;
char *buf = kmalloc(SZ, GFP_KERNEL);
C2K_NOTE("buf(%p)\n", buf);
/* #define C2K_USB_PERF */
#ifdef C2K_USB_PERF
total_cnt = 0;
memset(buf, 0x41, SZ);
#endif
while (!signal_pending(current)) {
#ifdef C2K_USB_PERF
len = SZ;
#else
if (ut_err) {
C2K_NOTE("errrrrrrrrrrrrrr\n");
break;
}
get_random_bytes(&len, sizeof(len));
C2K_NOTE("len before(%d)\n", len);
len %= SZ;
len += 1;
C2K_NOTE("len after(%d)\n", len);
buf[0] = last_end + 1;
for (i = 1; i < len; i++)
buf[i] = buf[i - 1] + 1;
last_end = buf[i - 1];
#endif
do_push_upstream(transfer_id, buf, len)
;
#ifdef C2K_USB_PERF
/* simulate 4MB per sec */
udelay(delay_set);
#endif
}
if (buf) {
C2K_NOTE("free buf\n");
kfree(buf);
}
ut_err = 0;
C2K_NOTE("exit UT\n");
goto exit;
} else if (enable == UT_CLR_ERR) {
ut_err = 0;
goto exit;
}
#endif
#ifdef SUPPORT_LEGACY_CONTROL
} else if (idx == ATTR_ENABLE_C) {
if (!strncmp(buf, "tty", 3))
enable = 0;
else if (!strncmp(buf, "gadget", 6))
enable = 1;
else {
C2K_ERR("invalid option(%s) for bypas\n", buf);
goto exit;
}
} else
goto exit;
#else
}
#endif /* SUPPORT_LEGACY_CONTROL */
if ((check_enable_state(fn) != (!!enable))) {
C2K_NOTE("enable:%d\n", enable);
set_enable_state(fn, enable);
if (!!enable && fn->activated) {
C2K_NOTE("enable rb %s channel,activated %s\n",
fn->shortname, fn->activated ?
"yes" : "no");
if (fn->transfer_id == RAWBULK_TID_MODEM) {
/* clear DTR to endup last session
* between AP and CP
*/
modem_dtr_set(1, 1);
modem_dtr_set(0, 1);
modem_dtr_set(1, 1);
modem_dcd_state();
}
/* Start rawbulk transfer */
__pm_stay_awake(fn->keep_awake);
rc = rawbulk_start_transactions(
fn->transfer_id, nups, ndowns, upsz,
downsz);
if (rc < 0)
C2K_ERR("%s rc = %d bypass failed\n",
__func__, rc);
} else {
/* Stop rawbulk transfer */
C2K_NOTE("D.S rb %s chan,been activated %s\n",
fn->shortname,
fn->activated ? "yes" : "no");
rawbulk_stop_transactions(fn->transfer_id);
if (fn->transfer_id == RAWBULK_TID_MODEM) {
/* clear DTR automatically when disable
* modem rawbulk
*/
modem_dtr_set(1, 1);
modem_dtr_set(0, 1);
modem_dtr_set(1, 1);
modem_dcd_state();
}
__pm_relax(fn->keep_awake);
}
}
} else if (idx == ATTR_DTR) {
if (fn->transfer_id == RAWBULK_TID_MODEM) {
if (check_enable_state(fn)) {
int val, ret;
long tmp = 0;
ret = kstrtol(buf, 0, &tmp);
val = (int)tmp;
modem_dtr_set(val, 1);
}
}
} else if (idx == ATTR_AUTORECONN) {
int val, ret;
long tmp = 0;
ret = kstrtol(buf, 0, &tmp);
val = (int)tmp;
fn->autoreconn = !!val;
} else {
int val, ret;
long tmp = 0;
ret = kstrtol(buf, 0, &tmp);
val = (int)tmp;
switch (idx) {
case ATTR_NUPS:
nups = val;
break;
case ATTR_NDOWNS:
ndowns = val;
break;
case ATTR_UPSIZE:
upsz = val;
break;
case ATTR_DOWNSIZE:
downsz = val;
break;
default:
goto exit;
}
if (!check_enable_state(fn))
goto exit;
if (!fn->activated)
goto exit;
rawbulk_stop_transactions(fn->transfer_id);
rc = rawbulk_start_transactions(fn->transfer_id, nups, ndowns,
upsz, downsz);
if (rc >= 0) {
fn->nups = nups;
fn->ndowns = ndowns;
fn->upsz = upsz;
fn->downsz = downsz;
} else {
rawbulk_stop_transactions(fn->transfer_id);
__pm_relax(fn->keep_awake);
C2K_NOTE("enable to 0\n");
set_enable_state(fn, 0);
}
}
exit:
return count;
}
static int rawbulk_create_files(struct rawbulk_function *fn)
{
int n, rc;
for (n = 0; n < fn->max_attrs; n++) {
#ifdef SUPPORT_LEGACY_CONTROL
if (n == ATTR_ENABLE_C)
continue;
#endif
rc = device_create_file(fn->dev, &fn->attr[n]);
if (rc < 0) {
while (--n >= 0) {
#ifdef SUPPORT_LEGACY_CONTROL
if (n == ATTR_ENABLE_C)
continue;
#endif
device_remove_file(fn->dev, &fn->attr[n]);
}
return rc;
}
}
return 0;
}
static void rawbulk_remove_files(struct rawbulk_function *fn)
{
int n = fn->max_attrs;
while (--n >= 0) {
#ifdef SUPPORT_LEGACY_CONTROL
if (n == ATTR_ENABLE_C)
continue;
#endif
device_remove_file(fn->dev, &fn->attr[n]);
}
}
/*****************************************************************************/
static struct class *rawbulk_class;
static struct _function_init_stuff {
const char *longname;
const char *shortname;
const char *iString;
unsigned int nups;
unsigned int ndowns;
unsigned int upsz;
unsigned int downsz;
bool autoreconn;
bool pushable;
} _init_params[] = {
#ifdef CONFIG_EVDO_DT_VIA_SUPPORT
{
"rawbulk-modem", "data", "Modem Port", 32, 32, 4096, 4096, true, false}
, {
"rawbulk-ets", "ets", "ETS Port", 32, 32, 4096, 4096, true, false}, {
"rawbulk-at", "atc", "AT Channel", 3, 3, 4096, 4096, true, false}, {
"rawbulk-pcv", "pcv", "PCM Voice", 1, 1, 4096, 4096, true, false}, {
"rawbulk-gps", "gps", "LBS GPS Port", 1, 1, 4096, 4096, true, false}, {
}, /* End of configurations */
#else
{
"rawbulk-pcv", "pcv", "PCM Voice", 1, 1, 4096, 4096, true, false}, {
"rawbulk-modem", "data", "Modem Port", 32, 32, 4096, 4096, true, false}
, {
"rawbulk-dummy0", "dummy0", "DUMMY0", 1, 1, 4096, 4096, true, false}, {
"rawbulk-at", "atc", "AT Channel", 3, 3, 4096, 4096, true, false}, {
"rawbulk-gps", "gps", "LBS GPS Port", 1, 1, 4096, 4096, true, false}, {
"rawbulk-dummy1", "dummy1", "DUMMY1", 1, 1, 4096, 4096, true, false}, {
"rawbulk-dummy2", "dummy2", "DUMMY2", 1, 1, 4096, 4096, true, false}, {
"rawbulk-ets", "ets", "ETS Port", 32, 32, 4096, 4096, true, false}, {
}, /* End of configurations */
#endif
};
static __init struct rawbulk_function *rawbulk_alloc_function(int transfer_id)
{
int rc;
struct rawbulk_function *fn;
C2K_NOTE("%s\n", __func__);
if (transfer_id == _MAX_TID)
return NULL;
fn = kzalloc(sizeof(*fn), GFP_KERNEL);
if (IS_ERR_OR_NULL(fn))
return NULL;
/* init default features of rawbulk functions */
fn->longname = _init_params[transfer_id].longname;
fn->shortname = _init_params[transfer_id].shortname;
fn->string_defs[0].s = _init_params[transfer_id].iString;
fn->nups = _init_params[transfer_id].nups;
fn->ndowns = _init_params[transfer_id].ndowns;
fn->upsz = _init_params[transfer_id].upsz;
fn->downsz = _init_params[transfer_id].downsz;
fn->autoreconn = _init_params[transfer_id].autoreconn;
fn->tty_minor = -1;
/* init descriptors */
init_interface_desc(&fn->interface);
init_endpoint_desc(&fn->fs_bulkin_endpoint, 1, 512);
init_endpoint_desc(&fn->hs_bulkin_endpoint, 1, 512);
init_endpoint_desc(&fn->fs_bulkout_endpoint, 0, 512);
init_endpoint_desc(&fn->hs_bulkout_endpoint, 0, 512);
fn->fs_descs[INTF_DESC] = (struct usb_descriptor_header *)
&fn->interface;
fn->fs_descs[BULKIN_DESC] = (struct usb_descriptor_header *)
&fn->fs_bulkin_endpoint;
fn->fs_descs[BULKOUT_DESC] = (struct usb_descriptor_header *)
&fn->fs_bulkout_endpoint;
fn->hs_descs[INTF_DESC] = (struct usb_descriptor_header *)
&fn->interface;
fn->hs_descs[BULKIN_DESC] = (struct usb_descriptor_header *)
&fn->hs_bulkin_endpoint;
fn->hs_descs[BULKOUT_DESC] = (struct usb_descriptor_header *)
&fn->hs_bulkout_endpoint;
fn->string_table.language = 0x0409;
fn->string_table.strings = fn->string_defs;
fn->strings[0] = &fn->string_table;
fn->strings[1] = NULL;
fn->transfer_id = transfer_id;
fn->tty_throttled = 0;
/* init function callbacks */
fn->function.strings = fn->strings;
fn->function.fs_descriptors = fn->fs_descs;
fn->function.hs_descriptors = fn->hs_descs;
/* init device attributes */
add_device_attr(fn, ATTR_ENABLE, "enable", 0660);
#ifdef SUPPORT_LEGACY_CONTROL
add_device_attr(fn, ATTR_ENABLE_C, fn->shortname, 0660);
#endif
add_device_attr(fn, ATTR_AUTORECONN, "autoreconn", 0660);
add_device_attr(fn, ATTR_STATISTICS, "statistics", 0660);
add_device_attr(fn, ATTR_NUPS, "nups", 0660);
add_device_attr(fn, ATTR_NDOWNS, "ndowns", 0660);
add_device_attr(fn, ATTR_UPSIZE, "ups_size", 0660);
add_device_attr(fn, ATTR_DOWNSIZE, "downs_size", 0660);
fn->max_attrs = ATTR_DOWNSIZE + 1;
if (transfer_id == RAWBULK_TID_MODEM) {
add_device_attr(fn, ATTR_DTR, "dtr", 0220);
fn->max_attrs++;
}
fn->dev = device_create(rawbulk_class, NULL, MKDEV(0,
fn->transfer_id), NULL, fn->shortname);
if (IS_ERR(fn->dev)) {
kfree(fn);
return NULL;
}
rc = rawbulk_create_files(fn);
if (rc < 0) {
device_destroy(rawbulk_class, fn->dev->devt);
kfree(fn);
return NULL;
}
spin_lock_init(&fn->lock);
fn->keep_awake = wakeup_source_register(fn->dev, fn->longname);
return fn;
}
static void rawbulk_destroy_function(struct rawbulk_function *fn)
{
C2K_DBG("%s\n", __func__);
if (!fn)
return;
wakeup_source_remove(fn->keep_awake);
rawbulk_remove_files(fn);
device_destroy(rawbulk_class, fn->dev->devt);
kfree(fn);
}
#ifdef SUPPORT_LEGACY_CONTROL
static struct attribute *legacy_sysfs[_MAX_TID + 1] = { NULL };
static struct attribute_group legacy_sysfs_group = {
.attrs = legacy_sysfs,
};
struct kobject *legacy_sysfs_stuff;
#endif /* SUPPORT_LEGACY_CONTROL */
static int __init init(void)
{
int n;
int rc = 0;
C2K_NOTE("rawbulk functions go!!!\n");
rawbulk_class = class_create(THIS_MODULE, "usb_rawbulk");
if (IS_ERR(rawbulk_class))
return PTR_ERR(rawbulk_class);
for (n = 0; n < _MAX_TID; n++) {
struct rawbulk_function *fn = rawbulk_alloc_function(n);
if (IS_ERR(fn)) {
while (n--)
rawbulk_destroy_function
(prealloced_functions[n]);
rc = PTR_ERR(fn);
break;
}
prealloced_functions[n] = fn;
#ifdef SUPPORT_LEGACY_CONTROL
legacy_sysfs[n] = &fn->attr[ATTR_ENABLE_C].attr;
#endif
}
if (rc < 0) {
class_destroy(rawbulk_class);
return rc;
}
#ifdef SUPPORT_LEGACY_CONTROL
/* make compatiable with old bypass sysfs access */
legacy_sysfs_stuff = kobject_create_and_add("usb_bypass", NULL);
if (legacy_sysfs_stuff) {
rc = sysfs_create_group(legacy_sysfs_stuff,
&legacy_sysfs_group);
if (rc < 0)
C2K_ERR("failed to create legacy bypass,continue\n");
}
#endif /* SUPPORT_LEGACY_CONTROL */
return 0;
}
module_init(init);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");