lineage_kernel_xcoverpro/drivers/usb/notify/usb_notify_sysfs.c

803 lines
20 KiB
C
Executable File

/*
* drivers/usb/notify/usb_notify_sysfs.c
*
* Copyright (C) 2015-2017 Samsung, Inc.
* Author: Dongrak Shin <dongrak.shin@samsung.com>
*
*/
/* usb notify layer v3.3 */
#define pr_fmt(fmt) "usb_notify: " fmt
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/usb.h>
#include <linux/usb_notify.h>
#include <linux/string.h>
#include "usb_notify_sysfs.h"
#define MAX_STRING_LEN 20
#if defined(CONFIG_USB_HW_PARAM)
const char
usb_hw_param_print[USB_CCIC_HW_PARAM_MAX][MAX_HWPARAM_STRING] = {
{"CC_WATER"},
{"CC_DRY"},
{"CC_I2C"},
{"CC_OVC"},
{"CC_OTG"},
{"CC_DP"},
{"CC_VR"},
{"H_SUPER"},
{"H_HIGH"},
{"H_FULL"},
{"H_LOW"},
{"C_SUPER"},
{"C_HIGH"},
{"H_AUDIO"},
{"H_COMM"},
{"H_HID"},
{"H_PHYSIC"},
{"H_IMAGE"},
{"H_PRINTER"},
{"H_STORAGE"},
{"H_STO_S"},
{"H_STO_H"},
{"H_STO_F"},
{"H_HUB"},
{"H_CDC"},
{"H_CSCID"},
{"H_CONTENT"},
{"H_VIDEO"},
{"H_WIRE"},
{"H_MISC"},
{"H_APP"},
{"H_VENDOR"},
{"CC_DEX"},
{"CC_WTIME"},
{"CC_WVBUS"},
{"CC_WVTIME"},
{"CC_WLVBS"},
{"CC_WLVTM"},
{"CC_CSHORT"},
{"CC_SVSHT"},
{"CC_SGSHT"},
{"M_AFCNAK"},
{"M_AFCERR"},
{"M_DCDTMO"},
{"F_CNT"},
{"CC_KILLER"},
{"CC_FWERR"},
{"C_ARP"},
{"CC_VER"},
};
#endif
struct notify_data {
struct class *usb_notify_class;
atomic_t device_count;
};
static struct notify_data usb_notify_data;
static int is_valid_cmd(char *cur_cmd, char *prev_cmd)
{
pr_info("%s : current state=%s, previous state=%s\n",
__func__, cur_cmd, prev_cmd);
if (!strcmp(cur_cmd, "ON") ||
!strncmp(cur_cmd, "ON_ALL_", 7)) {
if (!strcmp(prev_cmd, "ON") ||
!strncmp(prev_cmd, "ON_ALL_", 7)) {
goto ignore;
} else if (!strncmp(prev_cmd, "ON_HOST_", 8)) {
goto all;
} else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) {
goto all;
} else if (!strcmp(prev_cmd, "OFF")) {
goto all;
} else {
goto invalid;
}
} else if (!strcmp(cur_cmd, "OFF")) {
if (!strcmp(prev_cmd, "ON") ||
!strncmp(prev_cmd, "ON_ALL_", 7)) {
goto off;
} else if (!strncmp(prev_cmd, "ON_HOST_", 8)) {
goto off;
} else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) {
goto off;
} else if (!strcmp(prev_cmd, "OFF")) {
goto ignore;
} else {
goto invalid;
}
} else if (!strncmp(cur_cmd, "ON_HOST_", 8)) {
if (!strcmp(prev_cmd, "ON") ||
!strncmp(prev_cmd, "ON_ALL_", 7)) {
goto host;
} else if (!strncmp(prev_cmd, "ON_HOST_", 8)) {
goto ignore;
} else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) {
goto host;
} else if (!strcmp(prev_cmd, "OFF")) {
goto host;
} else {
goto invalid;
}
} else if (!strncmp(cur_cmd, "ON_CLIENT_", 10)) {
if (!strcmp(prev_cmd, "ON") ||
!strncmp(prev_cmd, "ON_ALL_", 7)) {
goto client;
} else if (!strncmp(prev_cmd, "ON_HOST_", 8)) {
goto client;
} else if (!strncmp(prev_cmd, "ON_CLIENT_", 10)) {
goto ignore;
} else if (!strcmp(prev_cmd, "OFF")) {
goto client;
} else {
goto invalid;
}
} else {
goto invalid;
}
host:
pr_info("%s cmd=%s is accepted.\n", __func__, cur_cmd);
return NOTIFY_BLOCK_TYPE_HOST;
client:
pr_info("%s cmd=%s is accepted.\n", __func__, cur_cmd);
return NOTIFY_BLOCK_TYPE_CLIENT;
all:
pr_info("%s cmd=%s is accepted.\n", __func__, cur_cmd);
return NOTIFY_BLOCK_TYPE_ALL;
off:
pr_info("%s cmd=%s is accepted.\n", __func__, cur_cmd);
return NOTIFY_BLOCK_TYPE_NONE;
ignore:
pr_err("%s cmd=%s is ignored but saved.\n", __func__, cur_cmd);
return -EEXIST;
invalid:
pr_err("%s cmd=%s is invalid.\n", __func__, cur_cmd);
return -EINVAL;
}
static ssize_t disable_show(
struct device *dev, struct device_attribute *attr,
char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
pr_info("read disable_state %s\n", udev->disable_state_cmd);
return sprintf(buf, "%s\n", udev->disable_state_cmd);
}
static ssize_t disable_store(
struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
char *disable;
int sret, param = -EINVAL;
size_t ret = -ENOMEM;
if (size > MAX_DISABLE_STR_LEN) {
pr_err("%s size(%zu) is too long.\n", __func__, size);
goto error;
}
if (size < strlen(buf))
goto error;
disable = kzalloc(size+1, GFP_KERNEL);
if (!disable)
goto error;
sret = sscanf(buf, "%s", disable);
if (sret != 1)
goto error1;
if (udev->set_disable) {
param = is_valid_cmd(disable, udev->disable_state_cmd);
if (param == -EINVAL) {
ret = param;
} else {
if (param != -EEXIST)
udev->set_disable(udev, param);
strncpy(udev->disable_state_cmd,
disable, sizeof(udev->disable_state_cmd)-1);
ret = size;
}
} else
pr_err("set_disable func is NULL\n");
error1:
kfree(disable);
error:
return ret;
}
static ssize_t support_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
char *support;
if (n->unsupport_host || !IS_ENABLED(CONFIG_USB_HOST_NOTIFY))
support = "CLIENT";
else
support = "ALL";
pr_info("read support %s\n", support);
return snprintf(buf, sizeof(support)+1, "%s\n", support);
}
static ssize_t otg_speed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
char *speed;
switch (n->speed) {
case USB_SPEED_SUPER:
speed = "SUPER";
break;
case USB_SPEED_HIGH:
speed = "HIGH";
break;
case USB_SPEED_FULL:
speed = "FULL";
break;
case USB_SPEED_LOW:
speed = "LOW";
break;
default:
speed = "UNKNOWN";
break;
}
pr_info("%s : read otg speed %s\n", __func__, speed);
return snprintf(buf, sizeof(speed)+1, "%s\n", speed);
}
static ssize_t gadget_speed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
const char *speed;
if (n->get_gadget_speed)
speed = usb_speed_string(n->get_gadget_speed());
else
speed = "UNKNOWN";
pr_info("%s : read gadget speed %s\n", __func__, speed);
return snprintf(buf, MAX_STRING_LEN, "%s\n", speed);
}
#if defined(CONFIG_USB_HW_PARAM)
static unsigned long long strtoull(char *ptr, char **end, int base)
{
unsigned long long ret = 0;
if (base > 36)
goto out;
while (*ptr) {
int digit;
if (*ptr >= '0' && *ptr <= '9' && *ptr < '0' + base)
digit = *ptr - '0';
else if (*ptr >= 'A' && *ptr < 'A' + base - 10)
digit = *ptr - 'A' + 10;
else if (*ptr >= 'a' && *ptr < 'a' + base - 10)
digit = *ptr - 'a' + 10;
else
break;
ret *= base;
ret += digit;
ptr++;
}
out:
if (end)
*end = (char *)ptr;
return ret;
}
static ssize_t usb_hw_param_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
int index, ret = 0;
unsigned long long *p_param = NULL;
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
p_param = get_hw_param(n, USB_CCIC_WATER_INT_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_INT_COUNT);
p_param = get_hw_param(n, USB_CCIC_DRY_INT_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_DRY_INT_COUNT);
p_param = get_hw_param(n, USB_CLIENT_SUPER_SPEED_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CLIENT_SUPER_SPEED_COUNT);
p_param = get_hw_param(n, USB_CLIENT_HIGH_SPEED_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CLIENT_HIGH_SPEED_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_VBUS_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_LPM_VBUS_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_VBUS_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_LPM_VBUS_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_VERSION);
#endif
if (p_param)
*p_param = show_ccic_version();
for (index = 0; index < USB_CCIC_HW_PARAM_MAX - 1; index++) {
p_param = get_hw_param(n, index);
if (p_param)
ret += sprintf(buf + ret, "%llu ", *p_param);
else
ret += sprintf(buf + ret, "0 ");
}
p_param = get_hw_param(n, index);
if (p_param)
ret += sprintf(buf + ret, "%llu\n", *p_param);
else
ret += sprintf(buf + ret, "0\n");
pr_info("%s - ret : %d\n", __func__, ret);
return ret;
}
static ssize_t usb_hw_param_store(
struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
unsigned long long prev_hw_param[USB_CCIC_HW_PARAM_MAX] = {0, };
unsigned long long *p_param = NULL;
int index = 0;
size_t ret = -ENOMEM;
char *token, *str = (char *)buf;
if (size > MAX_HWPARAM_STR_LEN) {
pr_err("%s size(%zu) is too long.\n", __func__, size);
goto error;
}
ret = size;
if (size < USB_CCIC_HW_PARAM_MAX) {
pr_err("%s efs file is not created correctly.\n", __func__);
goto error;
}
for (index = 0; index < (USB_CCIC_HW_PARAM_MAX - 1); index++) {
token = strsep(&str, " ");
if (token)
prev_hw_param[index] = strtoull(token, NULL, 10);
if (!token || (prev_hw_param[index] > HWPARAM_DATA_LIMIT))
goto error;
}
for (index = 0; index < (USB_CCIC_HW_PARAM_MAX - 1); index++) {
p_param = get_hw_param(n, index);
if (p_param)
*p_param += prev_hw_param[index];
}
pr_info("%s - ret : %zu\n", __func__, ret);
error:
return ret;
}
static ssize_t hw_param_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
int index, ret = 0;
unsigned long long *p_param = NULL;
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
p_param = get_hw_param(n, USB_CCIC_WATER_INT_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_INT_COUNT);
p_param = get_hw_param(n, USB_CCIC_DRY_INT_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_DRY_INT_COUNT);
p_param = get_hw_param(n, USB_CLIENT_SUPER_SPEED_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CLIENT_SUPER_SPEED_COUNT);
p_param = get_hw_param(n, USB_CLIENT_HIGH_SPEED_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CLIENT_HIGH_SPEED_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_VBUS_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_COUNT);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_LPM_VBUS_COUNT);
p_param = get_hw_param(n, USB_CCIC_WATER_VBUS_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_VBUS_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_WATER_LPM_VBUS_TIME_DURATION);
if (p_param)
*p_param += manager_hw_param_update
(USB_CCIC_WATER_LPM_VBUS_TIME_DURATION);
p_param = get_hw_param(n, USB_CCIC_VERSION);
#endif
if (p_param)
*p_param = show_ccic_version();
for (index = 0; index < USB_CCIC_HW_PARAM_MAX - 1; index++) {
p_param = get_hw_param(n, index);
if (p_param)
ret += sprintf(buf + ret, "\"%s\":\"%llu\",",
usb_hw_param_print[index], *p_param);
else
ret += sprintf(buf + ret, "\"%s\":\"0\",",
usb_hw_param_print[index]);
}
/* CCIC FW version */
ret += sprintf(buf + ret, "\"%s\":\"",
usb_hw_param_print[index]);
p_param = get_hw_param(n, index);
if (p_param) {
/* HW Version */
ret += sprintf(buf + ret, "%02X%02X%02X%02X",
*((unsigned char *)p_param + 3),
*((unsigned char *)p_param + 2),
*((unsigned char *)p_param + 1),
*((unsigned char *)p_param));
/* SW Main Version */
ret += sprintf(buf + ret, "%02X%02X%02X",
*((unsigned char *)p_param + 6),
*((unsigned char *)p_param + 5),
*((unsigned char *)p_param + 4));
/* SW Boot Version */
ret += sprintf(buf + ret, "%02X",
*((unsigned char *)p_param + 7));
ret += sprintf(buf + ret, "\"\n");
} else
ret += sprintf(buf + ret, "0000000000000000\"\n");
pr_info("%s - ret : %d\n", __func__, ret);
return ret;
}
static ssize_t hw_param_store(
struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
struct otg_notify *n = udev->o_notify;
int index = 0;
size_t ret = -ENOMEM;
char *str = (char *)buf;
unsigned long long *p_param = NULL;
if (size > 2) {
pr_err("%s size(%zu) is too long.\n", __func__, size);
goto error;
}
ret = size;
pr_info("%s : %s\n", __func__, str);
if (!strncmp(str, "c", 1))
for (index = 0; index < USB_CCIC_HW_PARAM_MAX; index++) {
p_param = get_hw_param(n, index);
if (p_param)
*p_param = 0;
}
error:
return ret;
}
#endif
char interface_class_name[USB_CLASS_VENDOR_SPEC][4] = {
[U_CLASS_PER_INTERFACE] = {"PER"},
[U_CLASS_AUDIO] = {"AUD"},
[U_CLASS_COMM] = {"COM"},
[U_CLASS_HID] = {"HID"},
[U_CLASS_PHYSICAL] = {"PHY"},
[U_CLASS_STILL_IMAGE] = {"STI"},
[U_CLASS_PRINTER] = {"PRI"},
[U_CLASS_MASS_STORAGE] = {"MAS"},
[U_CLASS_HUB] = {"HUB"},
[U_CLASS_CDC_DATA] = {"CDC"},
[U_CLASS_CSCID] = {"CSC"},
[U_CLASS_CONTENT_SEC] = {"CON"},
[U_CLASS_VIDEO] = {"VID"},
[U_CLASS_WIRELESS_CONTROLLER] = {"WIR"},
[U_CLASS_MISC] = {"MIS"},
[U_CLASS_APP_SPEC] = {"APP"},
[U_CLASS_VENDOR_SPEC] = {"VEN"}
};
void init_usb_whitelist_array(int *whitelist_array)
{
int i;
for (i = 1; i <= MAX_CLASS_TYPE_NUM; i++)
whitelist_array[i] = 0;
}
int set_usb_whitelist_array(const char *buf, int *whitelist_array)
{
int valid_class_count = 0;
char *ptr = NULL;
int i;
char *source;
source = (char *)buf;
while ((ptr = strsep(&source, ":")) != NULL) {
pr_info("%s token = %c%c%c!\n", __func__,
ptr[0], ptr[1], ptr[2]);
for (i = U_CLASS_PER_INTERFACE; i <= U_CLASS_VENDOR_SPEC; i++) {
if (!strncmp(ptr, interface_class_name[i], 3))
whitelist_array[i] = 1;
}
}
for (i = U_CLASS_PER_INTERFACE; i <= U_CLASS_VENDOR_SPEC; i++) {
if (whitelist_array[i])
valid_class_count++;
}
pr_info("%s valid_class_count = %d!\n", __func__, valid_class_count);
return valid_class_count;
}
static ssize_t whitelist_for_mdm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
if (udev == NULL) {
pr_err("udev is NULL\n");
return -EINVAL;
}
pr_info("%s read whitelist_classes %s\n",
__func__, udev->whitelist_str);
return sprintf(buf, "%s\n", udev->whitelist_str);
}
static ssize_t whitelist_for_mdm_store(
struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct usb_notify_dev *udev = (struct usb_notify_dev *)
dev_get_drvdata(dev);
char *disable;
int sret;
size_t ret = -ENOMEM;
int mdm_disable;
int valid_whilelist_count;
if (udev == NULL) {
pr_err("udev is NULL\n");
ret = -EINVAL;
goto error;
}
if (size > MAX_WHITELIST_STR_LEN) {
pr_err("%s size(%zu) is too long.\n", __func__, size);
goto error;
}
if (size < strlen(buf))
goto error;
disable = kzalloc(size+1, GFP_KERNEL);
if (!disable)
goto error;
sret = sscanf(buf, "%s", disable);
if (sret != 1)
goto error1;
pr_info("%s buf=%s\n", __func__, disable);
init_usb_whitelist_array(udev->whitelist_array_for_mdm);
/* To active displayport, hub class must be enabled */
if (!strncmp(buf, "ABL", 3)) {
udev->whitelist_array_for_mdm[U_CLASS_HUB] = 1;
mdm_disable = NOTIFY_MDM_TYPE_ON;
} else if (!strncmp(buf, "OFF", 3))
mdm_disable = NOTIFY_MDM_TYPE_OFF;
else {
valid_whilelist_count = set_usb_whitelist_array
(buf, udev->whitelist_array_for_mdm);
if (valid_whilelist_count > 0) {
udev->whitelist_array_for_mdm[U_CLASS_HUB] = 1;
mdm_disable = NOTIFY_MDM_TYPE_ON;
} else
mdm_disable = NOTIFY_MDM_TYPE_OFF;
}
strncpy(udev->whitelist_str,
disable, sizeof(udev->whitelist_str)-1);
if (udev->set_mdm) {
udev->set_mdm(udev, mdm_disable);
ret = size;
} else {
pr_err("set_mdm func is NULL\n");
ret = -EINVAL;
}
error1:
kfree(disable);
error:
return ret;
}
int usb_notify_dev_uevent(struct usb_notify_dev *udev, char *envp_ext[])
{
int ret = 0;
if (!udev || !udev->dev) {
pr_err("%s udev or udev->dev NULL\n", __func__);
ret = -EINVAL;
goto err;
}
if (strncmp("TYPE", envp_ext[0], 4)) {
pr_err("%s error.first array must be filled TYPE\n",
__func__);
ret = -EINVAL;
goto err;
}
if (strncmp("STATE", envp_ext[1], 5)) {
pr_err("%s error.second array must be filled STATE\n",
__func__);
ret = -EINVAL;
goto err;
}
kobject_uevent_env(&udev->dev->kobj, KOBJ_CHANGE, envp_ext);
pr_info("%s\n", __func__);
err:
return ret;
}
EXPORT_SYMBOL_GPL(usb_notify_dev_uevent);
static DEVICE_ATTR(disable, 0664, disable_show, disable_store);
static DEVICE_ATTR(support, 0444, support_show, NULL);
static DEVICE_ATTR(otg_speed, 0444, otg_speed_show, NULL);
static DEVICE_ATTR(gadget_speed, 0444, gadget_speed_show, NULL);
static DEVICE_ATTR(whitelist_for_mdm, 0664,
whitelist_for_mdm_show, whitelist_for_mdm_store);
#if defined(CONFIG_USB_HW_PARAM)
static DEVICE_ATTR(usb_hw_param, 0664, usb_hw_param_show, usb_hw_param_store);
static DEVICE_ATTR(hw_param, 0664, hw_param_show, hw_param_store);
#endif
static struct attribute *usb_notify_attrs[] = {
&dev_attr_disable.attr,
&dev_attr_support.attr,
&dev_attr_otg_speed.attr,
&dev_attr_gadget_speed.attr,
&dev_attr_whitelist_for_mdm.attr,
#if defined(CONFIG_USB_HW_PARAM)
&dev_attr_usb_hw_param.attr,
&dev_attr_hw_param.attr,
#endif
NULL,
};
static struct attribute_group usb_notify_attr_grp = {
.attrs = usb_notify_attrs,
};
static int create_usb_notify_class(void)
{
if (!usb_notify_data.usb_notify_class) {
usb_notify_data.usb_notify_class
= class_create(THIS_MODULE, "usb_notify");
if (IS_ERR(usb_notify_data.usb_notify_class))
return PTR_ERR(usb_notify_data.usb_notify_class);
atomic_set(&usb_notify_data.device_count, 0);
}
return 0;
}
int usb_notify_dev_register(struct usb_notify_dev *udev)
{
int ret;
if (!usb_notify_data.usb_notify_class) {
ret = create_usb_notify_class();
if (ret < 0)
return ret;
}
udev->index = atomic_inc_return(&usb_notify_data.device_count);
udev->dev = device_create(usb_notify_data.usb_notify_class, NULL,
MKDEV(0, udev->index), NULL, udev->name);
if (IS_ERR(udev->dev))
return PTR_ERR(udev->dev);
udev->disable_state = 0;
strncpy(udev->disable_state_cmd, "OFF",
sizeof(udev->disable_state_cmd)-1);
ret = sysfs_create_group(&udev->dev->kobj, &usb_notify_attr_grp);
if (ret < 0) {
device_destroy(usb_notify_data.usb_notify_class,
MKDEV(0, udev->index));
return ret;
}
dev_set_drvdata(udev->dev, udev);
return 0;
}
EXPORT_SYMBOL_GPL(usb_notify_dev_register);
void usb_notify_dev_unregister(struct usb_notify_dev *udev)
{
sysfs_remove_group(&udev->dev->kobj, &usb_notify_attr_grp);
device_destroy(usb_notify_data.usb_notify_class, MKDEV(0, udev->index));
dev_set_drvdata(udev->dev, NULL);
}
EXPORT_SYMBOL_GPL(usb_notify_dev_unregister);
int usb_notify_class_init(void)
{
return create_usb_notify_class();
}
EXPORT_SYMBOL_GPL(usb_notify_class_init);
void usb_notify_class_exit(void)
{
class_destroy(usb_notify_data.usb_notify_class);
}
EXPORT_SYMBOL_GPL(usb_notify_class_exit);