kernel_samsung_a34x-permissive/drivers/misc/mediatek/mddp/ctrl/mddp_dev.c

1004 lines
25 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* mddp_dev.c - MDDP device node API.
*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/rtc.h>
#include "mddp_ctrl.h"
#include "mddp_debug.h"
#include "mddp_dev.h"
#include "mddp_if.h"
#include "mddp_sm.h"
//------------------------------------------------------------------------------
// Struct definition.
// -----------------------------------------------------------------------------
#define MDDP_DEV_NAME "mddp"
struct mddp_dev_rb_t {
struct mddp_dev_rb_t *next;
struct mddp_dev_rb_t *prev;
uint32_t rb_len;
void *rb_data;
};
struct mddp_dev_rb_head_t {
struct mddp_dev_rb_t *next;
struct mddp_dev_rb_t *prev;
uint32_t cnt;
spinlock_t locker;
wait_queue_head_t read_wq;
};
//------------------------------------------------------------------------------
// Private helper macro.
//------------------------------------------------------------------------------
#define MDDP_DEV_CLONE_COMM_HDR(_rsp, _req, _status) \
do { \
(_rsp)->mcode = (_req)->mcode; \
(_rsp)->status = _status; \
(_rsp)->app_type = (_req)->app_type; \
(_rsp)->msg = (_req)->msg; \
} while (0)
#define MDDP_SET_BUF_TERMIN(_buf, _len) \
do { \
_len = strlen(_buf); \
if (_len > 1 && _buf[_len-1] == '\n') \
_buf[_len-1] = '\0'; \
} while (0)
#define MDDP_DSTATE_IS_VALID_ID(_id) (_id >= 0 && _id < MDDP_DSTATE_ID_NUM)
#define MDDP_DSTATE_IS_ACTIVATED() (mddp_dstate_activated_s)
//------------------------------------------------------------------------------
// Private prototype.
// -----------------------------------------------------------------------------
static int32_t mddp_dev_open(struct inode *inode,
struct file *file);
static int32_t mddp_dev_close(struct inode *inode,
struct file *file);
static ssize_t mddp_dev_read(struct file *file,
char *buf, size_t count, loff_t *ppos);
static ssize_t mddp_dev_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos);
static long mddp_dev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg);
#ifdef CONFIG_COMPAT
static long mddp_dev_compat_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg);
#endif
static unsigned int mddp_dev_poll(struct file *fp,
struct poll_table_struct *poll);
//------------------------------------------------------------------------------
// Private variables.
//------------------------------------------------------------------------------
static const struct file_operations mddp_dev_fops = {
.owner = THIS_MODULE,
.open = &mddp_dev_open,
.read = &mddp_dev_read,
.write = &mddp_dev_write,
.release = &mddp_dev_close,
.unlocked_ioctl = &mddp_dev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = &mddp_dev_compat_ioctl,
#endif
.poll = &mddp_dev_poll,
};
static atomic_t mddp_dev_open_ref_cnt_s;
static struct mddp_dev_rb_head_t mddp_hidl_rb_head_s;
static struct mddp_dev_rb_head_t mddp_dstate_rb_head_s;
#define MDDP_CMCMD_RSP_CNT (MDDP_CMCMD_RSP_END - MDDP_CMCMD_RSP_BEGIN)
static enum mddp_dev_evt_type_e
mddp_dev_rsp_status_mapping_s[MDDP_CMCMD_RSP_CNT][2] = {
/* FAIL SUCCESS */
{MDDP_DEV_EVT_STOPPED_ERROR, MDDP_DEV_EVT_SUPPORT_AVAILABLE},//ENABLE
{MDDP_DEV_EVT_NONE, MDDP_DEV_EVT_NONE},//DISABLE
{MDDP_DEV_EVT_STOPPED_ERROR, MDDP_DEV_EVT_STARTED},//ACT
{MDDP_DEV_EVT_STOPPED_UNSUPPORTED, MDDP_DEV_EVT_STOPPED_UNSUPPORTED},//DEACT
{MDDP_DEV_EVT_STOPPED_LIMIT_REACHED, MDDP_DEV_EVT_STOPPED_LIMIT_REACHED},//LIMIT
{MDDP_DEV_EVT_CONNECT_UPDATE, MDDP_DEV_EVT_CONNECT_UPDATE},//CT_IND
{MDDP_DEV_EVT_WARNING_REACHED, MDDP_DEV_EVT_WARNING_REACHED},
};
#undef MDDP_CMCMD_RSP_CNT
uint32_t mddp_debug_log_class_s = MDDP_LC_ALL;
uint32_t mddp_debug_log_level_s = MDDP_LL_DEFAULT;
static bool mddp_dstate_activated_s;
//------------------------------------------------------------------------------
// Function Prototype.
// -----------------------------------------------------------------------------
static struct mddp_dev_rb_t *mddp_query_dstate(
struct mddp_dev_rb_head_t *list, uint32_t seq);
static struct mddp_dev_rb_t *mddp_dequeue_dstate(
struct mddp_dev_rb_head_t *list);
static void mddp_clear_dstate(struct mddp_dev_rb_head_t *list);
//------------------------------------------------------------------------------
// Sysfs APIs
//------------------------------------------------------------------------------
static ssize_t
debug_log_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE,
"debug_log_class(%x), debug_log_level(%x)\n",
mddp_debug_log_class_s, mddp_debug_log_level_s);
}
static ssize_t
debug_log_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
uint32_t lv;
uint32_t class;
unsigned long value;
if (!kstrtoul(buf, 0, &value)) {
class = (value & MDDP_DEBUG_LOG_CLASS_MASK) >> 4;
if (MDDP_IS_VALID_LOG_CLASS(class))
mddp_debug_log_class_s = class;
lv = value & MDDP_DEBUG_LOG_LV_MASK;
if (MDDP_IS_VALID_LOG_LEVEL(lv))
mddp_debug_log_level_s = lv;
}
return count;
}
static DEVICE_ATTR_RW(debug_log);
static ssize_t
version_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "MDDP version(%d)\n",
__MDDP_VERSION__);
}
static DEVICE_ATTR_RO(version);
static ssize_t
state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct mddp_app_t *app;
uint32_t type;
uint8_t idx;
uint32_t ret_num = 0;
uint32_t seq = 0;
struct mddp_dev_rb_head_t *list = &mddp_dstate_rb_head_s;
struct mddp_dev_rb_t *entry;
for (idx = 0; idx < MDDP_MOD_CNT; idx++) {
type = mddp_sm_module_list_s[idx];
app = mddp_get_app_inst(type);
ret_num += scnprintf(buf + ret_num, PAGE_SIZE - ret_num,
"type(%d), state(%d)\n",
app->type, app->state);
ret_num += scnprintf(buf + ret_num, PAGE_SIZE - ret_num,
"drv_reg(%d), feature(%d)\n",
app->drv_reg, atomic_read(&app->feature));
ret_num += scnprintf(buf + ret_num, PAGE_SIZE - ret_num,
"abnormal(%x), reset_cnt(%d)\n",
app->abnormal_flags, app->reset_cnt);
// NG. Failed to fill-in data!
if (ret_num <= 0)
return scnprintf(buf, PAGE_SIZE,
"%s: Failed to fill-in data!\n", __func__);
}
/*
* Detailed state.
*/
entry = mddp_query_dstate(list, seq);
while (entry) {
ret_num += scnprintf(buf + ret_num, PAGE_SIZE - ret_num,
"%s\n",
((struct mddp_dstate_t *)entry->rb_data)->str);
seq += 1;
entry = mddp_query_dstate(list, seq);
}
// OK.
return ret_num;
}
static ssize_t
state_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
unsigned long value;
if (!kstrtoul(buf, 0, &value)) {
if (value == MDDP_DETAILED_STATE_ENABLE) {
mddp_dstate_activated_s = true;
mddp_enqueue_dstate(MDDP_DSTATE_ID_START);
} else if (value == MDDP_DETAILED_STATE_DISABLE) {
mddp_enqueue_dstate(MDDP_DSTATE_ID_STOP);
mddp_dstate_activated_s = false;
}
}
return count;
}
static DEVICE_ATTR_RW(state);
static ssize_t
wh_statistic_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct mddp_app_t *app;
app = mddp_get_app_inst(MDDP_APP_TYPE_WH);
if (app->sysfs_callback)
return app->sysfs_callback(app,
MDDP_SYSFS_CMD_STATISTIC_READ, buf, 0);
return scnprintf(buf, PAGE_SIZE,
"Cannot change WH mode, mddp-wh config(%d)\n",
app->is_config);
}
static DEVICE_ATTR_RO(wh_statistic);
static ssize_t
wh_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct mddp_app_t *app;
app = mddp_get_app_inst(MDDP_APP_TYPE_WH);
if (app->sysfs_callback)
return app->sysfs_callback(app,
MDDP_SYSFS_CMD_ENABLE_READ, buf, 0);
return scnprintf(buf, PAGE_SIZE,
"Cannot change WH mode, mddp-wh config(%d)\n",
app->is_config);
}
static ssize_t
wh_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct mddp_app_t *app;
app = mddp_get_app_inst(MDDP_APP_TYPE_WH);
if (app->sysfs_callback) {
// OK.
app->sysfs_callback(app, MDDP_SYSFS_CMD_ENABLE_WRITE,
(char *)buf, count);
return count;
}
// NG. Failed to configure!
return count;
}
static DEVICE_ATTR_RW(wh_enable);
#ifdef MDDP_EM_SUPPORT
#define EM_CMD_BUF_SZ 32
static uint8_t em_cmd_buf[EM_CMD_BUF_SZ];
static int32_t em_cmd_app = -1;
static int32_t em_cmd_status;
#define EM_CMD_RESET() (em_cmd_app = -1)
static ssize_t
em_test_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "staus:%d, cmd_buf:%s\n",
em_cmd_status, em_cmd_buf);
}
static ssize_t
em_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct mddp_app_t *app;
const char *delim = " ";
char *token;
char *strsep_buf_p;
unsigned int str_len;
str_len = strlen(buf);
snprintf(em_cmd_buf, EM_CMD_BUF_SZ, "%.*s",
(int)min(count, sizeof(em_cmd_buf) - 1), buf);
strsep_buf_p = em_cmd_buf;
MDDP_SET_BUF_TERMIN(em_cmd_buf, str_len);
token = strsep(&strsep_buf_p, delim);
if (token == NULL) {
em_cmd_status = -EINVAL;
goto input_param_error;
}
if (kstrtoint(token, 10, &em_cmd_app))
return -EINVAL;
if (em_cmd_app != MDDP_APP_TYPE_WH) {
em_cmd_status = -EPERM;
goto not_support_error;
}
app = mddp_get_app_inst(em_cmd_app);
if (app->sysfs_callback) {
// OK.
snprintf(em_cmd_buf, EM_CMD_BUF_SZ, "%.*s",
(int)min(count, sizeof(em_cmd_buf) - 1), buf);
MDDP_SET_BUF_TERMIN(em_cmd_buf, str_len);
em_cmd_status = app->sysfs_callback(app,
MDDP_SYSFS_EM_CMD_TEST_WRITE,
em_cmd_buf, strlen(em_cmd_buf));
return count;
}
// NG. Failed to configure!
em_cmd_status = -ERANGE;
snprintf(em_cmd_buf, EM_CMD_BUF_SZ, "%.*s",
(int)min(count, sizeof(em_cmd_buf) - 1), buf);
return count;
input_param_error:
not_support_error:
EM_CMD_RESET();
snprintf(em_cmd_buf, EM_CMD_BUF_SZ, "%.*s",
(int)min(count, sizeof(em_cmd_buf) - 1), buf);
return count;
}
static DEVICE_ATTR_RW(em_test);
#endif /* MDDP_EM_SUPPORT */
static struct attribute *mddp_attrs[] = {
&dev_attr_version.attr,
&dev_attr_state.attr,
&dev_attr_wh_statistic.attr,
&dev_attr_debug_log.attr,
&dev_attr_wh_enable.attr,
#ifdef MDDP_EM_SUPPORT
&dev_attr_em_test.attr,
#endif
NULL,
};
ATTRIBUTE_GROUPS(mddp);
//------------------------------------------------------------------------------
// Private functions.
//------------------------------------------------------------------------------
static inline void __mddp_dev_insert(struct mddp_dev_rb_t *new,
struct mddp_dev_rb_t *prev,
struct mddp_dev_rb_t *next,
struct mddp_dev_rb_head_t *list)
{
new->next = next;
new->prev = prev;
next->prev = prev->next = new;
list->cnt++;
}
static inline void __mddp_dev_rb_unlink(struct mddp_dev_rb_t *entry,
struct mddp_dev_rb_head_t *list)
{
struct mddp_dev_rb_t *next;
struct mddp_dev_rb_t *prev;
list->cnt--;
next = entry->next;
prev = entry->prev;
entry->next = entry->prev = NULL;
next->prev = prev;
prev->next = next;
}
static void mddp_dev_rb_enqueue_tail(struct mddp_dev_rb_head_t *list,
struct mddp_dev_rb_t *new)
{
unsigned long flags;
spin_lock(&list->locker);
__mddp_dev_insert(new, list->prev, (struct mddp_dev_rb_t *) list, list);
spin_unlock(&list->locker);
spin_lock_irqsave(&list->read_wq.lock, flags);
wake_up_all_locked(&list->read_wq);
spin_unlock_irqrestore(&list->read_wq.lock, flags);
}
static struct mddp_dev_rb_t *mddp_dev_rb_peek(
struct mddp_dev_rb_head_t *list)
{
struct mddp_dev_rb_t *entry;
entry = list->next;
if (entry == (struct mddp_dev_rb_t *)list)
entry = NULL;
return entry;
}
static struct mddp_dev_rb_t *mddp_dev_rb_query(
struct mddp_dev_rb_head_t *list, uint32_t seq)
{
struct mddp_dev_rb_t *entry = NULL;
uint32_t cnt = 0;
spin_lock(&list->locker);
entry = mddp_dev_rb_peek(list);
while (entry) {
if (seq == cnt)
break;
entry = entry->next;
if (entry == (struct mddp_dev_rb_t *)list) {
entry = NULL;
break;
}
cnt += 1;
}
spin_unlock(&list->locker);
return entry;
}
static struct mddp_dev_rb_t *mddp_dev_rb_dequeue(
struct mddp_dev_rb_head_t *list)
{
struct mddp_dev_rb_t *entry = NULL;
spin_lock(&list->locker);
entry = mddp_dev_rb_peek(list);
if (entry)
__mddp_dev_rb_unlink(entry, list);
spin_unlock(&list->locker);
return entry;
}
static bool mddp_dev_rb_queue_empty(struct mddp_dev_rb_head_t *list)
{
return list->next == (struct mddp_dev_rb_t *)list;
}
static struct mddp_dev_rb_t *mddp_query_dstate(
struct mddp_dev_rb_head_t *list, uint32_t seq)
{
return mddp_dev_rb_query(list, seq);
}
static struct mddp_dev_rb_t *mddp_dequeue_dstate(
struct mddp_dev_rb_head_t *list)
{
return mddp_dev_rb_dequeue(list);
}
static void mddp_clear_dstate(
struct mddp_dev_rb_head_t *list)
{
struct mddp_dev_rb_t *entry;
entry = mddp_dequeue_dstate(list);
while (entry) {
kfree(entry->rb_data);
kfree(entry);
entry = mddp_dequeue_dstate(list);
}
}
//------------------------------------------------------------------------------
// Public functions.
//------------------------------------------------------------------------------
void mddp_dev_list_init(struct mddp_dev_rb_head_t *list)
{
spin_lock_init(&list->locker);
list->cnt = 0;
list->prev = list->next = (struct mddp_dev_rb_t *)list;
init_waitqueue_head(&list->read_wq);
}
struct miscdevice mddp_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = MDDP_DEV_NAME,
.fops = &mddp_dev_fops,
.groups = mddp_groups,
};
int32_t mddp_dev_init(void)
{
atomic_set(&mddp_dev_open_ref_cnt_s, 0);
/*
* Ring buffer init.
*/
mddp_dev_list_init(&mddp_hidl_rb_head_s);
mddp_dev_list_init(&mddp_dstate_rb_head_s);
/*
* Create device node.
*/
if (misc_register(&mddp_dev) < 0)
return -1;
/*
* Detailed state init.
*/
mddp_dstate_activated_s = false;
return 0;
}
void mddp_dev_uninit(void)
{
/*
* Release CHAR device node.
*/
misc_deregister(&mddp_dev);
mddp_clear_dstate(&mddp_hidl_rb_head_s);
mddp_clear_dstate(&mddp_dstate_rb_head_s);
}
void mddp_dev_response(enum mddp_app_type_e type,
enum mddp_ctrl_msg_e msg, bool is_success,
uint8_t *data, uint32_t data_len)
{
struct mddp_dev_rb_head_t *list = &mddp_hidl_rb_head_s;
struct mddp_dev_rb_t *entry;
struct mddp_dev_rsp_common_t *dev_rsp;
uint16_t status;
uint32_t rsp_idx;
if (msg < MDDP_CMCMD_RSP_BEGIN || msg >= MDDP_CMCMD_RSP_END) {
MDDP_C_LOG(MDDP_LL_NOTICE,
"%s: invalid rsp msg(%d) in type(%d)!\n",
__func__, msg, type);
return;
}
rsp_idx = (msg - MDDP_CMCMD_RSP_BEGIN);
status = mddp_dev_rsp_status_mapping_s[rsp_idx][is_success];
if (status == MDDP_DEV_EVT_NONE) {
// No response to upper module.
MDDP_C_LOG(MDDP_LL_NOTICE,
"%s: No RSP, type(%d), msg(%d), is_success(%d).\n",
__func__, type, msg, is_success);
return;
}
dev_rsp = kmalloc(sizeof(struct mddp_dev_rsp_common_t) + data_len,
GFP_ATOMIC);
if (unlikely(!dev_rsp))
return;
dev_rsp->mcode = MDDP_CTRL_MSG_MCODE;
dev_rsp->status = status;
dev_rsp->app_type = type;
dev_rsp->msg = msg;
dev_rsp->data_len = data_len;
if (data_len > 0)
memcpy(dev_rsp->data, data, data_len);
entry = kmalloc(sizeof(struct mddp_dev_rb_t), GFP_ATOMIC);
if (unlikely(!entry)) {
kfree(dev_rsp);
return;
}
entry->rb_len = sizeof(struct mddp_dev_rsp_common_t) + data_len;
entry->rb_data = dev_rsp;
mddp_dev_rb_enqueue_tail(list, entry);
}
#define MDDP_CURR_TIME_STR_SZ 32
void mddp_enqueue_dstate(enum mddp_dstate_id_e id, ...)
{
struct mddp_dev_rb_head_t *list = &mddp_dstate_rb_head_s;
struct mddp_dev_rb_t *entry;
struct mddp_dstate_t *dstat;
struct rtc_time rt;
struct timespec ts;
char curr_time_str[MDDP_CURR_TIME_STR_SZ];
va_list ap;
int ip;
int port;
unsigned long long rx;
unsigned long long tx;
if (!MDDP_DSTATE_IS_VALID_ID(id) || !MDDP_DSTATE_IS_ACTIVATED())
return;
dstat = kzalloc(sizeof(struct mddp_dstate_t), GFP_ATOMIC);
if (unlikely(!dstat))
return;
entry = kzalloc(sizeof(struct mddp_dev_rb_t), GFP_ATOMIC);
if (unlikely(!entry)) {
kfree(dstat);
return;
}
// Generate current time string.
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &rt);
snprintf(curr_time_str, MDDP_CURR_TIME_STR_SZ,
"%d%02d%02d %02d:%02d:%02d.%09ld UTC",
rt.tm_year + 1900, rt.tm_mon + 1, rt.tm_mday,
rt.tm_hour, rt.tm_min, rt.tm_sec, ts.tv_nsec);
// Generate detailed state message.
dstat->id = id;
va_start(ap, id);
switch (id) {
case MDDP_DSTATE_ID_START:
mddp_clear_dstate(list);
snprintf(dstat->str, MDDP_DSTATE_STR_SZ,
mddp_dstate_temp_s[id].str, curr_time_str);
break;
case MDDP_DSTATE_ID_STOP:
case MDDP_DSTATE_ID_SUSPEND_TAG:
case MDDP_DSTATE_ID_RESUME_TAG:
snprintf(dstat->str, MDDP_DSTATE_STR_SZ,
mddp_dstate_temp_s[id].str, curr_time_str);
break;
case MDDP_DSTATE_ID_NEW_TAG:
ip = va_arg(ap, int);
port = va_arg(ap, int);
snprintf(dstat->str, MDDP_DSTATE_STR_SZ,
mddp_dstate_temp_s[id].str, curr_time_str,
ip, port);
break;
case MDDP_DSTATE_ID_GET_OFFLOAD_STATS:
rx = va_arg(ap, unsigned long long);
tx = va_arg(ap, unsigned long long);
snprintf(dstat->str, MDDP_DSTATE_STR_SZ,
mddp_dstate_temp_s[id].str, curr_time_str,
rx, tx);
break;
default:
break;
}
va_end(ap);
entry->rb_len = sizeof(struct mddp_dstate_t);
entry->rb_data = dstat;
mddp_dev_rb_enqueue_tail(list, entry);
}
//------------------------------------------------------------------------------
// Device node functins.
//------------------------------------------------------------------------------
static int32_t mddp_dev_open(struct inode *inode, struct file *file)
{
MDDP_C_LOG(MDDP_LL_INFO, "%s: IOCTL dev open.\n", __func__);
if (atomic_read(&mddp_dev_open_ref_cnt_s))
return -EBUSY;
atomic_inc(&mddp_dev_open_ref_cnt_s);
return 0;
}
static int32_t mddp_dev_close(struct inode *inode, struct file *file)
{
MDDP_C_LOG(MDDP_LL_INFO, "%s: IOCTL dev close.\n", __func__);
atomic_dec(&mddp_dev_open_ref_cnt_s);
return 0;
}
static ssize_t mddp_dev_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
int32_t ret = 0;
uint32_t len = 0;
struct mddp_dev_rb_head_t *list = &mddp_hidl_rb_head_s;
struct mddp_dev_rb_t *entry;
/*
* READ: MDDP send data to upper module.
*/
if (mddp_dev_rb_queue_empty(list)) {
if (!(file->f_flags & O_NONBLOCK)) {
spin_lock_irq(&list->read_wq.lock);
ret = wait_event_interruptible_locked_irq(
list->read_wq,
!mddp_dev_rb_queue_empty(list));
spin_unlock_irq(&list->read_wq.lock);
if (ret == -ERESTARTSYS) {
ret = -EINTR;
goto exit;
}
} else {
ret = -EAGAIN;
goto exit;
}
}
MDDP_C_LOG(MDDP_LL_DEBUG,
"%s: IOCTL dev read, count(%zu).\n", __func__, count);
entry = mddp_dev_rb_peek(list);
if (!entry) {
len = 0;
goto exit;
}
len = entry->rb_len;
if (count >= entry->rb_len) {
if (copy_to_user(buf, entry->rb_data, entry->rb_len)) {
MDDP_C_LOG(MDDP_LL_WARN, "%s: copy_to_user fail!\n",
__func__);
ret = -EFAULT;
}
entry = mddp_dev_rb_dequeue(list);
if (entry == NULL) {
MDDP_C_LOG(MDDP_LL_WARN,
"%s: unexpected dequeue fail!\n",
__func__);
ret = -EFAULT;
goto exit;
}
kfree(entry->rb_data);
kfree(entry);
} else {
ret = -ENOBUFS;
goto exit;
}
exit:
return ret ? ret : len;
}
static ssize_t mddp_dev_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
/*
* Not support WRITE.
*/
MDDP_C_LOG(MDDP_LL_DEBUG, "%s: Receive\n", __func__);
return count;
}
static long mddp_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct mddp_dev_req_common_t dev_req;
struct mddp_dev_rsp_common_t *dev_rsp;
long ret = 0;
uint32_t data_len;
uint8_t buf[MDDP_MAX_GET_BUF_SZ] = {0};
uint32_t buf_len = MDDP_MAX_GET_BUF_SZ;
struct mddp_dev_req_set_ct_value_t *ct_req;
/*
* NG. copy_from_user fail!
*/
if (copy_from_user(&dev_req, (void __user *)arg,
sizeof(struct mddp_dev_req_common_t))) {
MDDP_C_LOG(MDDP_LL_WARN,
"%s: copy_from_user failed!\n", __func__);
ret = -EFAULT;
goto ioctl_error;
}
/*
* NG. MCODE check fail!
*/
if (dev_req.mcode != MDDP_CTRL_MSG_MCODE) {
MDDP_C_LOG(MDDP_LL_WARN,
"%s: MCODE(%d) wrong!\n",
__func__, dev_req.mcode);
ret = -EINVAL;
goto ioctl_error;
}
data_len = dev_req.data_len;
/*
* OK. IOCTL command dispatch.
*/
switch (dev_req.msg) {
case MDDP_CMCMD_ENABLE_REQ:
ret = mddp_on_enable(dev_req.app_type);
break;
case MDDP_CMCMD_DISABLE_REQ:
ret = mddp_on_disable(dev_req.app_type);
break;
case MDDP_CMCMD_ACT_REQ:
if (data_len == sizeof(struct mddp_dev_req_act_t)) {
struct mddp_dev_req_act_t *from, *to;
to = (struct mddp_dev_req_act_t *) &buf;
from = (struct mddp_dev_req_act_t *)
&(((struct mddp_dev_req_common_t *)arg)->data);
ret = copy_from_user(to, from, data_len);
if (ret == 0) {
/* OK */
to->ul_dev_name[IFNAMSIZ - 1] = 0;
to->dl_dev_name[IFNAMSIZ - 1] = 0;
ret = mddp_on_activate(dev_req.app_type,
to->ul_dev_name,
to->dl_dev_name);
break;
}
}
/* NG */
MDDP_C_LOG(MDDP_LL_ERR,
"%s: ACT fail, data_len(%d), ret(%ld)!\n",
__func__, data_len, ret);
break;
case MDDP_CMCMD_DEACT_REQ:
ret = mddp_on_deactivate(dev_req.app_type);
break;
case MDDP_CMCMD_GET_OFFLOAD_STATS_REQ:
ret = mddp_on_get_offload_stats(dev_req.app_type,
buf, &buf_len);
MDDP_C_LOG(MDDP_LL_DEBUG,
"%s: ret(%ld), type(%d), buf(%p), len(%u)\n",
__func__, ret, dev_req.app_type, buf, buf_len);
MDDP_C_LOG(MDDP_LL_NOTICE,
"%s: get_offload_stats, rx(%llu), tx(%llu).\n",
__func__,
((struct mddp_u_data_stats_t *)buf)->total_rx_bytes,
((struct mddp_u_data_stats_t *)buf)->total_tx_bytes);
if (!ret) {
dev_rsp = kmalloc(
sizeof(struct mddp_dev_rsp_common_t) + buf_len,
GFP_ATOMIC);
if (dev_rsp == NULL) {
ret = -ENOMEM;
goto ioctl_error;
}
MDDP_DEV_CLONE_COMM_HDR(dev_rsp, &dev_req, 0);
dev_rsp->data_len = buf_len;
memcpy(dev_rsp->data, &buf, buf_len);
ret = (copy_to_user((void *)arg, dev_rsp,
sizeof(struct mddp_dev_rsp_common_t) +
buf_len))
? -EFAULT : 0;
kfree(dev_rsp);
mddp_enqueue_dstate(MDDP_DSTATE_ID_GET_OFFLOAD_STATS,
((struct mddp_u_data_stats_t *)buf)->
total_rx_bytes,
((struct mddp_u_data_stats_t *)buf)->
total_tx_bytes);
}
break;
case MDDP_CMCMD_SET_DATA_LIMIT_REQ:
if (data_len == sizeof(struct mddp_dev_req_set_data_limit_t)) {
struct mddp_dev_req_set_data_limit_t *from, *to;
to = (struct mddp_dev_req_set_data_limit_t *) &buf;
from = (struct mddp_dev_req_set_data_limit_t *)
&(((struct mddp_dev_req_common_t *)arg)->data);
ret = copy_from_user(to, from, data_len);
if (ret == 0) {
to->ul_dev_name[IFNAMSIZ - 1] = 0;
ret = mddp_on_set_data_limit(dev_req.app_type, buf, data_len);
}
}
break;
case MDDP_CMCMD_SET_CT_VALUE_REQ:
if (data_len !=
sizeof(struct mddp_dev_req_set_ct_value_t)) {
MDDP_C_LOG(MDDP_LL_WARN,
"%s: arg_len(%u) of command(%u) is not expected!\n",
__func__,
dev_req.data_len, dev_req.msg);
ret = -EINVAL;
break;
}
ct_req = (struct mddp_dev_req_set_ct_value_t *)
&(((struct mddp_dev_req_common_t *)arg)->data);
buf_len = sizeof(struct mddp_dev_req_set_ct_value_t);
ret = copy_from_user((char *)&buf, (char *)ct_req, buf_len);
if (ret == 0)
ret = mddp_on_set_ct_value(dev_req.app_type,
buf, buf_len);
else
MDDP_C_LOG(MDDP_LL_WARN,
"%s: failed to copy_from_user, buf_len(%u), ret(%ld)!\n",
__func__, buf_len, ret);
break;
case MDDP_CMCMD_SET_WARNING_AND_DATA_LIMIT_REQ:
if (data_len == sizeof(struct mddp_dev_req_set_warning_and_data_limit_t)) {
struct mddp_dev_req_set_warning_and_data_limit_t *from, *to;
to = (struct mddp_dev_req_set_warning_and_data_limit_t *) &buf;
from = (struct mddp_dev_req_set_warning_and_data_limit_t *)
&(((struct mddp_dev_req_common_t *)arg)->data);
ret = copy_from_user(to, from, data_len);
if (ret == 0) {
to->ul_dev_name[IFNAMSIZ - 1] = 0;
ret = mddp_on_set_warning_and_data_limit(dev_req.app_type, buf,
data_len);
}
}
break;
default:
MDDP_C_LOG(MDDP_LL_WARN, "%s: Invalid command(%d)!\n",
__func__, dev_req.msg);
ret = -EINVAL;
break;
}
ioctl_error:
MDDP_C_LOG(MDDP_LL_INFO,
"%s: cmd(%d) app_type(%d), ret (%ld).\n",
__func__, dev_req.msg, dev_req.app_type, ret);
return ret;
}
#ifdef CONFIG_COMPAT
static long mddp_dev_compat_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg)
{
return 0;
}
#endif
static unsigned int mddp_dev_poll(struct file *fp, struct poll_table_struct *poll)
{
return 0;
}