blob: 17ed1faa8aefdf8b6d7ea35b5ea881b2712ac333 [file] [log] [blame]
#include <linux/completion.h>
//#define DISABLE_HOST_OS_DETECTION 1
//#define DISABLE_NCM_ECM_DETECTION 1
#define HAS_USB_CDC_NCM (0x1 << 0)
#define HAS_USB_CDC_ECM (0x1 << 1)
#define USB_FUNCTION_NAME_LEN (256)
#define NCM_GUARD_TIMEOUT_JIFFIES (5 * HZ)
//#define DUMP_OS_DETECT_STRUCT 1
struct os_detect {
/* reconfigure work after detecting the os */
struct work_struct reconfigure_work;
/* apple second stage work */
struct work_struct ncm_ecm_detection_work;
struct work_struct restart_work;
struct delayed_work ncm_guard_work;
int default_os;
int bankup_default_os;
int cur_os;
/* the index of the USB_DT_STRING */
int cmd_idx;
char win7_bankup[USB_FUNCTION_NAME_LEN];
char win8_bankup[USB_FUNCTION_NAME_LEN];
char apple_bankup[USB_FUNCTION_NAME_LEN];
char os_linux_bankup[USB_FUNCTION_NAME_LEN];
char win7[USB_FUNCTION_NAME_LEN];
char win8[USB_FUNCTION_NAME_LEN];
char apple[USB_FUNCTION_NAME_LEN];
char os_linux[USB_FUNCTION_NAME_LEN];
char os[USB_FUNCTION_NAME_LEN];
/* for the second stage functions */
char win7_stage2[USB_FUNCTION_NAME_LEN];
char win8_stage2[USB_FUNCTION_NAME_LEN];
char apple_stage2[USB_FUNCTION_NAME_LEN];
char linux_stage2[USB_FUNCTION_NAME_LEN];
char has_get_bos;
u32 bos_len;
char nr_bos;
char has_set_intf;
char apple_has_ncm_ecm;
char linux_has_ncm_ecm;
char nr_str_before_setcfg;
char nr_str;
u32 str_dt_bitmap;
u32 cfg_dt_bitmap;
u32 has_setconfig;
u32 nr_usb_req;
struct timer_list linux_timer;
struct timer_list apple_timer;
#define NR_RCD_REQLEN (6)
/* the length first 6 requset */
u16 reqlen[NR_RCD_REQLEN];
#define NR_DELAY_REQ_BEFORE_RECONFIG (3)
u16 nr_dreq_before_recfg;
u32 nr_ncm_setdatainf;
u32 nr_ncm_setctrlinf;
#define KTIME_MS_BETWEEN_RSTINF_AND_SUSPEND (10 * 1000)
s64 reset_intf_time_ms;
bool string_0_err;
bool string_n0_err;
};
static struct os_detect os_detect;
static int old_max_speed;
static int os_detect_done;
static char *os_type_name(int os_type)
{
switch (os_type) {
case HOST_OS_TYPE_WIN7_WINXP:
return "WIN7/WINXP";
case HOST_OS_TYPE_APPLE_STAGE1:
return "APPLEs1";
case HOST_OS_TYPE_APPLE:
return "APPLE";
case HOST_OS_TYPE_WIN8:
return "WIN8";
case HOST_OS_TYPE_WIN7_OR_WIN8:
return "WIN7_OR_WIN8";
case HOST_OS_TYPE_LINUX_STAGE1:
return "LINUXs1";
case HOST_OS_TYPE_LINUX:
return "LINUX";
default:
return "UNKNOWN";
}
}
static ssize_t __attr_show(struct device *pdev, char *src, char *dst)
{
struct android_dev *dev = dev_get_drvdata(pdev);
ssize_t size;
mutex_lock(&dev->mutex);
size = sprintf(dst, "%s", src);
mutex_unlock(&dev->mutex);
return size;
}
static ssize_t __attr_store(struct device *pdev, const char *src,
char *dst, int len, size_t size)
{
struct android_dev *dev = dev_get_drvdata(pdev);
mutex_lock(&dev->mutex);
if (dev->enabled) {
mutex_unlock(&dev->mutex);
return -EBUSY;
}
memset(dst, 0, len);
strlcpy(dst, src, len);
mutex_unlock(&dev->mutex);
return size;
}
static void set_ncm_ecm_flag(void)
{
if (strnstr(os_detect.apple, "ncm", USB_FUNCTION_NAME_LEN))
os_detect.apple_has_ncm_ecm |= HAS_USB_CDC_NCM;
else
os_detect.apple_has_ncm_ecm &= ~HAS_USB_CDC_NCM;
if (strnstr(os_detect.apple, "ecm", USB_FUNCTION_NAME_LEN))
os_detect.apple_has_ncm_ecm |= HAS_USB_CDC_ECM;
else
os_detect.apple_has_ncm_ecm &= ~HAS_USB_CDC_ECM;
if (strnstr(os_detect.os_linux, "ncm", USB_FUNCTION_NAME_LEN))
os_detect.linux_has_ncm_ecm |= HAS_USB_CDC_NCM;
else
os_detect.linux_has_ncm_ecm &= ~HAS_USB_CDC_NCM;
if (strnstr(os_detect.os_linux, "ecm", USB_FUNCTION_NAME_LEN))
os_detect.linux_has_ncm_ecm |= HAS_USB_CDC_ECM;
else
os_detect.linux_has_ncm_ecm &= ~HAS_USB_CDC_ECM;
#ifdef DISABLE_NCM_ECM_DETECTION
os_detect.linux_has_ncm_ecm = os_detect.apple_has_ncm_ecm = 0;
#endif
}
static ssize_t
win7_show(struct device *pdev, struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.win7, buf);
}
static ssize_t
win7_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.win7,
sizeof(os_detect.win7), size);
}
static ssize_t
win8_show(struct device *pdev, struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.win8, buf);
}
static ssize_t
win8_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.win8,
sizeof(os_detect.win8), size);
}
static ssize_t
apple_show(struct device *pdev, struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.apple, buf);
}
static ssize_t
apple_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.apple,
sizeof(os_detect.apple), size);
}
static ssize_t
linux_show(struct device *pdev, struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.os_linux, buf);
}
static ssize_t
linux_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.os_linux,
sizeof(os_detect.os_linux), size);
}
static ssize_t
os_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.os, buf);
}
static ssize_t
win7_stage2_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.win7_stage2, buf);
}
static ssize_t
win7_stage2_store(struct device *pdev,
struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.win7_stage2,
sizeof(os_detect.win7_stage2), size);
}
static ssize_t
win8_stage2_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.win8_stage2, buf);
}
static ssize_t
win8_stage2_store(struct device *pdev,
struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.win8_stage2,
sizeof(os_detect.win8_stage2), size);
}
static ssize_t
apple_stage2_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.apple_stage2, buf);
}
static ssize_t
apple_stage2_store(struct device *pdev,
struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.apple_stage2,
sizeof(os_detect.apple_stage2), size);
}
static ssize_t
linux_stage2_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
return __attr_show(pdev, os_detect.linux_stage2, buf);
}
static ssize_t
linux_stage2_store(struct device *pdev,
struct device_attribute *attr,
const char *buff, size_t size)
{
return __attr_store(pdev, buff, os_detect.linux_stage2,
sizeof(os_detect.linux_stage2), size);
}
static ssize_t __functions_store(struct android_dev *dev,
char *buff, size_t size)
{
char *name;
char buf[USB_FUNCTION_NAME_LEN], *b;
char aliases[USB_FUNCTION_NAME_LEN], *a;
int err;
int is_ffs;
int ffs_enabled = 0;
mutex_lock(&dev->mutex);
if (dev->enabled) {
mutex_unlock(&dev->mutex);
return -EBUSY;
}
INIT_LIST_HEAD(&dev->enabled_functions);
strlcpy(buf, buff, sizeof(buf));
b = strim(buf);
#if defined (CONFIG_USB_G_MBIM)
mbim_enabled = 0;
#endif
while (b) {
name = strsep(&b, ",");
if (!name)
continue;
is_ffs = 0;
strlcpy(aliases, dev->ffs_aliases, sizeof(aliases));
a = aliases;
while (a) {
char *alias = strsep(&a, ",");
if (alias && !strcmp(name, alias)) {
is_ffs = 1;
break;
}
}
if (is_ffs) {
if (ffs_enabled)
continue;
err = android_enable_function(dev, "ffs");
if (err)
pr_err("android_usb: Cannot enable ffs (%d)",
err);
else
ffs_enabled = 1;
continue;
}
err = android_enable_function(dev, name);
if (err)
pr_err("android_usb: Cannot enable '%s' (%d)",
name, err);
}
mutex_unlock(&dev->mutex);
return size;
}
static ssize_t __os_store(struct android_dev *dev,
const char *buf, size_t size)
{
char *b;
mutex_lock(&dev->mutex);
memset(os_detect.os, 0, sizeof(os_detect.os));
strlcpy(os_detect.os, buf, sizeof(os_detect.os));
if (strncmp(os_detect.os, "win7", 4) == 0)
b = os_detect.win7;
else if (strncmp(os_detect.os, "win8", 4) == 0)
b = os_detect.win8;
else if (strncmp(os_detect.os, "apple", 5) == 0)
b = os_detect.apple;
else if (strncmp(os_detect.os, "linux", 5) == 0)
b = os_detect.os_linux;
else
b = os_detect.win8;
mutex_unlock(&dev->mutex);
return __functions_store(dev, b, size);
}
static ssize_t
os_store(struct device *pdev, struct device_attribute *attr,
const char *buff, size_t size)
{
struct android_dev *dev = dev_get_drvdata(pdev);
if (strncmp(buff, "win7", 4) == 0)
os_detect.default_os = HOST_OS_TYPE_WIN7_WINXP;
else if (strncmp(buff, "win8", 4) == 0)
os_detect.default_os = HOST_OS_TYPE_WIN8;
else if (strncmp(buff, "apple", 5) == 0)
os_detect.default_os = HOST_OS_TYPE_APPLE;
else if (strncmp(buff, "linux", 5) == 0)
os_detect.default_os = HOST_OS_TYPE_LINUX;
else
os_detect.default_os = HOST_OS_TYPE_WIN7_WINXP;
os_detect.bankup_default_os = os_detect.default_os;
pr_info("### Default OS is %s, %s\n",
os_type_name(os_detect.default_os), buff);
strcpy(os_detect.win7_bankup, os_detect.win7);
strcpy(os_detect.win8_bankup, os_detect.win8);
strcpy(os_detect.os_linux_bankup, os_detect.os_linux);
strcpy(os_detect.apple_bankup, os_detect.apple);
pr_info("win7: %s %s\n", os_detect.win7_bankup, os_detect.win7);
set_ncm_ecm_flag();
return __os_store(dev, buff, size);
}
static DEVICE_ATTR(win7, S_IRUGO | S_IWUSR, win7_show, win7_store);
static DEVICE_ATTR(win8, S_IRUGO | S_IWUSR, win8_show, win8_store);
static DEVICE_ATTR(apple, S_IRUGO | S_IWUSR, apple_show, apple_store);
static DEVICE_ATTR(olinux, S_IRUGO | S_IWUSR, linux_show, linux_store);
static DEVICE_ATTR(os, S_IRUGO | S_IWUSR, os_show, os_store);
static DEVICE_ATTR(win7_s2, S_IRUGO | S_IWUSR,
win7_stage2_show, win7_stage2_store);
static DEVICE_ATTR(win8_s2, S_IRUGO | S_IWUSR,
win8_stage2_show, win8_stage2_store);
static DEVICE_ATTR(apple_s2, S_IRUGO | S_IWUSR,
apple_stage2_show, apple_stage2_store);
static DEVICE_ATTR(olinux_s2, S_IRUGO | S_IWUSR,
linux_stage2_show, linux_stage2_store);
#ifdef DUMP_OS_DETECT_STRUCT
static void dump_os_detect_struct(void)
{
pr_err("os_detect.default_os: %d\n", os_detect.default_os);
pr_err("os_detect.bankup_default_os: %d\n", os_detect.bankup_default_os);
pr_err("os_detect.cur_os: %d\n", os_detect.cur_os);
pr_err("os_detect.cmd_idx: %d\n", os_detect.cmd_idx);
pr_err("os_detect.apple: %s\n", os_detect.apple);
pr_err("os_detect.os_linux: %s\n", os_detect.os_linux);
pr_err("os_detect.has_get_bos: %d\n", os_detect.has_get_bos);
pr_err("os_detect.bos_len: %d\n", os_detect.bos_len);
pr_err("os_detect.nr_bos: %d\n", os_detect.nr_bos);
pr_err("os_detect.has_set_intf: %d\n", os_detect.has_set_intf);
pr_err("os_detect.apple_has_ncm_ecm: 0x%x\n", os_detect.apple_has_ncm_ecm);
pr_err("os_detect.linux_has_ncm_ecm: 0x%x\n", os_detect.linux_has_ncm_ecm);
pr_err("os_detect.nr_str_before_setcfg: %d\n", os_detect.nr_str_before_setcfg);
pr_err("os_detect.nr_str: %d\n", os_detect.nr_str;
pr_err("os_detect.str_dt_bitmap: 0x%x\n", os_detect.str_dt_bitmap);
pr_err("os_detect.cfg_dt_bitmap: 0x%x\n", os_detect.cfg_dt_bitmap);
pr_err("os_detect.has_setconfig: %d\n", os_detect.has_setconfig);
pr_err("os_detect.nr_usb_req: %d\n", os_detect.nr_usb_req);
pr_err("os_detect.nr_dreq_before_recfg: %d\n", os_detect.nr_dreq_before_recfg);
pr_err("os_detect.reset_intf_time_ms: 0x%llx\n", os_detect.reset_intf_time_ms);
pr_err("os_detect.nr_ncm_setdatainf: %d\n", os_detect.nr_ncm_setdatainf);
pr_err("os_detect.nr_ncm_setctrlinf: %d\n", os_detect.nr_ncm_setctrlinf);
pr_err("os_detect.old_max_speed: %d\n", old_max_speed);
pr_err("os_detect.os_detect_done: %d\n", os_detect_done);
}
#else
static void dump_os_detect_struct(void) {}
#endif
static void set_cdc_ncm(char *buf)
{
pr_info("set ncm on %s\n", buf);
strncpy(buf, "ncm", 3);
}
static void set_cdc_ecm(char *buf)
{
pr_info("set ecm on %s\n", buf);
strncpy(buf, "ecm", 3);
}
int os_detect_is_done(void)
{
return !!os_detect_done;
}
static void os_detect_set_done(void)
{
pr_info("%s\n", __func__);
os_detect_done = 1;
}
void os_detect_clear_done(void)
{
pr_info("%s\n", __func__);
os_detect_done = 0;
}
static void os_detect_struct_clear(void)
{
int i;
os_detect.cur_os = HOST_OS_TYPE_UNKNOWN;
os_detect.cmd_idx = 0;
os_detect.has_get_bos = 0;
os_detect.bos_len = 0;
os_detect.nr_bos = 0;
os_detect.has_set_intf = 0;
os_detect.str_dt_bitmap = 0;
os_detect.cfg_dt_bitmap = 0;
os_detect.has_setconfig = 0;
os_detect.nr_usb_req = 0;
os_detect.nr_str_before_setcfg = 0;
os_detect.nr_str = 0;
for (i = 0; i < NR_RCD_REQLEN; i++)
os_detect.reqlen[i] = 0;
os_detect.nr_dreq_before_recfg = 0;
os_detect.nr_ncm_setdatainf = 0;
os_detect.nr_ncm_setctrlinf = 0;
os_detect.reset_intf_time_ms = 0;
os_detect.string_0_err = false;
os_detect.string_n0_err = false;
}
static void android_dev_reconfigure(int os_type)
{
os_detect.cur_os = os_type;
pr_info("Default OS: %s, Current OS: %s\n",
os_type_name(os_detect.default_os),
os_type_name(os_detect.cur_os));
os_detect.has_setconfig = 0;
BUG_ON(os_type == HOST_OS_TYPE_UNKNOWN);
/* We need to give the default os in the script like below:
** echo win8 > /sys/class/android_usb/android0/os
*/
BUG_ON(os_detect.default_os == HOST_OS_TYPE_UNKNOWN);
if (os_type == HOST_OS_TYPE_WIN7_OR_WIN8)
return;
os_detect_set_done();
schedule_work(&os_detect.reconfigure_work);
}
static void android_reconfigure_work(struct work_struct *data)
{
struct android_dev *dev = _android_dev;
char *os;
char *apple = "apple";
char *win7 = "win7";
char *win8 = "win8";
char *os_linux = "linux";
pr_debug("Andoid device reconfigure...\n");
if (old_max_speed != 0) {
dev->cdev->gadget->max_speed = old_max_speed;
/* restore bcdUSB default value to 0x0200 */
dev->cdev->desc.bcdUSB = cpu_to_le16(0x0200);
old_max_speed = 0;
}
os_detect.default_os = os_detect.bankup_default_os;
android_dev_enable(0);
/* give some time to diag app and netifd to finish */
msleep(800);
switch (os_detect.cur_os) {
case HOST_OS_TYPE_LINUX:
os = os_linux;
break;
case HOST_OS_TYPE_APPLE:
os = apple;
break;
case HOST_OS_TYPE_WIN7_WINXP:
os = win7;
break;
case HOST_OS_TYPE_WIN8:
os = win8;
break;
default:
pr_info("Unknown OS type...\n");
if (os_detect.cmd_idx)
WARN_ON(1);
/* set it as the default os type apple */
os = apple;
}
__os_store(dev, os, strlen(os));
dump_os_detect_struct();
android_dev_enable(1);
}
static void android_restart_work(struct work_struct *data)
{
pr_info("%s\n", __func__);
android_dev_enable(0);
/*
* do not turn on usb very early as there need some time for
* host pc to boot up
*/
msleep(5000);
/* usb_os_restore(); */
dump_os_detect_struct();
android_dev_enable(1);
}
static void usb_os_restore_apple(void)
{
struct android_dev *dev = _android_dev;
char *os = "apple";
pr_info("%s apple: %s, apple_bank: %s\n",
__func__, os_detect.apple, os_detect.apple_bankup);
strcpy(os_detect.apple, os_detect.apple_bankup);
strcpy(os_detect.os_linux, os_detect.os_linux_bankup);
/* make sure it's ncm here, otherwise will not got to here */
BUG_ON(!(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM));
BUG_ON(!(os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM));
set_cdc_ncm(os_detect.apple);
__os_store(dev, os, strlen(os));
os_detect_struct_clear();
os_detect.default_os = HOST_OS_TYPE_APPLE;
}
static void ncm_ecm_detection_work(struct work_struct *data)
{
pr_info("##enter %s\n", __func__);
del_timer(&os_detect.apple_timer);
del_timer(&os_detect.linux_timer);
pr_info("%s: del_timer done\n", __func__);
android_dev_enable(0);
/* give some time to diag app and netifd to finish */
msleep(800);
/*set the ncm on apple and clear os detection struct */
usb_os_restore_apple();
dump_os_detect_struct();
android_dev_enable(1);
pr_info("##exit %s\n", __func__);
}
static void ncm_guard_work(struct work_struct *data)
{
pr_info("##########call %s\n", __func__);
del_timer(&os_detect.apple_timer);
pr_info("%s: del_timer done\n", __func__);
android_dev_enable(0);
/* give some time to diag app and netifd to finish */
msleep(800);
usb_os_restore();
dump_os_detect_struct();
android_dev_enable(1);
}
static void usb_force_reenumerate(void)
{
struct android_dev *dev = _android_dev;
char *os;
pr_info("Clear os_detect to re-enumerate\n");
os_detect.default_os = os_detect.bankup_default_os;
os_detect_struct_clear();
os_detect_set_done();
strcpy(os_detect.win7, os_detect.win7_bankup);
strcpy(os_detect.win8, os_detect.win8_bankup);
strcpy(os_detect.os_linux, os_detect.os_linux_bankup);
strcpy(os_detect.apple, os_detect.apple_bankup);
set_ncm_ecm_flag();
old_max_speed = 0;
if (os_detect.default_os == HOST_OS_TYPE_WIN7_WINXP)
os = "win7";
else if (os_detect.default_os == HOST_OS_TYPE_WIN8)
os = "win8";
else if (os_detect.default_os == HOST_OS_TYPE_APPLE)
os = "apple";
else if (os_detect.default_os == HOST_OS_TYPE_LINUX)
os = "linux";
else
BUG();
__os_store(dev, os, strlen(os));
schedule_work(&os_detect.reconfigure_work);
}
static void linux_reconfig_timer(struct timer_list *timer)
{
int os_type = HOST_OS_TYPE_LINUX;
pr_info("\n$$$$$$$$linux re-config timer:%d\n",
os_detect.has_set_intf);
if (unlikely(((os_detect.nr_str_before_setcfg == 0) &&
os_detect.has_setconfig))
|| os_detect.string_0_err
|| os_detect.string_n0_err) {
pr_err("nr_str_before_setcfg %d, string_0_err: %d %d",
os_detect.nr_str_before_setcfg,
os_detect.string_0_err,
os_detect.string_n0_err);
WARN(1, "meet BIOS in linux timer");
os_detect_struct_clear();
os_detect.cur_os = HOST_OS_TYPE_UNKNOWN;
return;
}
os_detect.has_setconfig = 0;
if (((os_detect.default_os == HOST_OS_TYPE_LINUX) &&
(os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM) &&
(!os_detect.has_set_intf)) ||
((os_detect.default_os == HOST_OS_TYPE_APPLE) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM) &&
(!os_detect.has_set_intf) &&
(os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM)))
set_cdc_ecm(os_detect.os_linux);
android_dev_reconfigure(os_type);
os_detect_clear_done();
}
static void apple_reconfig_timer(struct timer_list *timer)
{
int os_type = HOST_OS_TYPE_APPLE;
pr_info("\n$$$$$$$$apple re-config timer:%d\n",
os_detect.has_set_intf);
os_detect.has_setconfig = 0;
if (((os_detect.default_os == HOST_OS_TYPE_LINUX) &&
(os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM)) ||
((os_detect.default_os == HOST_OS_TYPE_APPLE) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM)))
set_cdc_ecm(os_detect.apple);
cancel_delayed_work(&os_detect.ncm_guard_work);
android_dev_reconfigure(os_type);
os_detect_clear_done();
}
void usb_os_detect(struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
int os_type = HOST_OS_TYPE_UNKNOWN;
u16 w_value;
if (ctrl->bRequestType != 0x21 && ctrl->bRequestType != 0xa1) {
pr_info("%2d: 0x%x. 0x%x. 0x%x. 0x%x. 0x%x\n",
os_detect.nr_usb_req,
ctrl->bRequestType, ctrl->bRequest,
ctrl->wValue, ctrl->wIndex, ctrl->wLength);
dump_os_detect_struct();
}
#ifdef DISABLE_HOST_OS_DETECTION
return;
#endif
/* skip os detection for production mode */
if (system_is_prod_mode())
return;
/* record the reset interface time */
if (unlikely((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(0x01 == ctrl->bRequestType) &&
(0x0b == ctrl->bRequest) &&
(0x00 == ctrl->wValue) &&
(0x01 == ctrl->wIndex) &&
(0x00 == ctrl->wLength))) {
os_detect.reset_intf_time_ms = ktime_to_ms(ktime_get());
}
/* NCM: USB_CDC_GET_NTB_PARAMETERS */
if (unlikely((HOST_OS_TYPE_APPLE_STAGE1 == os_detect.cur_os) &&
(0xA1 == ctrl->bRequestType) &&
(0x80 == ctrl->bRequest) &&
(0x00 != ctrl->wLength) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM))) {
pr_info("NCM: del apple_timer\n");
del_timer(&os_detect.apple_timer);
cancel_delayed_work(&os_detect.ncm_guard_work);
os_type = HOST_OS_TYPE_APPLE;
os_detect.has_setconfig = 0;
android_dev_reconfigure(os_type);
return;
}
/* APPLE+NCM */
if (unlikely((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(0xA1 == ctrl->bRequestType) &&
(0x80 == ctrl->bRequest) &&
(0x00 != ctrl->wLength) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM))) {
pr_info("NCM: trig guard_timer\n");
schedule_delayed_work(&os_detect.ncm_guard_work,
NCM_GUARD_TIMEOUT_JIFFIES);
}
/* APPLE+ECM */
if (unlikely((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(0x80 == ctrl->bRequestType) &&
(0x08 == ctrl->bRequest) &&
(0x00 == ctrl->wValue) &&
(0x00 == ctrl->wIndex) &&
(0x01 == ctrl->wLength) &&
(0x0 == strncmp(os_detect.apple, "ecm", 3)))) {
pr_info("ECM: trig guard_timer\n");
schedule_delayed_work(&os_detect.ncm_guard_work,
NCM_GUARD_TIMEOUT_JIFFIES);
}
/* APPLE+NCM: cancel guard timer on set_intf */
if (unlikely((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(0x01 == ctrl->bRequestType) &&
(0x0b == ctrl->bRequest) &&
(0x01 == ctrl->wValue) &&
(0x01 == ctrl->wIndex) &&
(0x00 == ctrl->wLength))) {
pr_info("APPLE: cancel guard_timer\n");
cancel_delayed_work(&os_detect.ncm_guard_work);
}
/* APPLE restart case */
if (unlikely((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(0x80 == ctrl->bRequestType) &&
(0x06 == ctrl->bRequest) &&
(0x0100 == ctrl->wValue) &&
(0x40 != ctrl->wLength) &&
(0x12 != ctrl->wLength))) {
pr_info("APPLE: restart\n");
invoke_os_detect_restore();
}
/* add some more delay for winxp to avoid unkown device */
if ((HOST_OS_TYPE_UNKNOWN != os_detect.cur_os) &&
(HOST_OS_TYPE_WIN7_OR_WIN8 != os_detect.cur_os) &&
(HOST_OS_TYPE_APPLE_STAGE1 != os_detect.cur_os) &&
(HOST_OS_TYPE_LINUX_STAGE1 != os_detect.cur_os)) {
if (0 == os_detect.nr_dreq_before_recfg)
return;
else {
/* delay N request before re-cfg */
pr_info("nr_dreq_before_recfg: %d\n",
os_detect.nr_dreq_before_recfg);
if (--os_detect.nr_dreq_before_recfg)
return;
else {
android_dev_reconfigure(os_detect.cur_os);
return;
}
}
}
/* reset the start point */
if (HOST_OS_TYPE_UNKNOWN == os_detect.cur_os) {
if (ctrl->bRequestType == 0x80 && ctrl->bRequest == 0x06 &&
ctrl->wValue == 0x0100 && ctrl->wLength == 0x40) {
pr_info("###clear os detection structure\n");
os_detect_struct_clear();
}
}
if (os_detect.nr_usb_req >= 0 &&
os_detect.nr_usb_req < NR_RCD_REQLEN)
os_detect.reqlen[os_detect.nr_usb_req] = ctrl->wLength;
os_detect.nr_usb_req++;
if (old_max_speed == 0) {
old_max_speed = cdev->gadget->max_speed;
cdev->gadget->max_speed = USB_SPEED_SUPER;
}
w_value = le16_to_cpu(ctrl->wValue);
switch (ctrl->bRequest) {
/* we handle all standard USB descriptors */
case USB_REQ_GET_DESCRIPTOR:
if (ctrl->bRequestType != USB_DIR_IN)
return;
/*skip if apple/linux is detected except bos_dt */
if ((HOST_OS_TYPE_LINUX_STAGE1 == os_detect.cur_os) ||
(HOST_OS_TYPE_APPLE_STAGE1 == os_detect.cur_os &&
(USB_DT_BOS != (w_value >> 8)))) {
os_detect.nr_str++;
pr_info("os_detect.nr_str: %d\n", os_detect.nr_str);
if ((w_value >> 8) == USB_DT_STRING) {
if ((os_detect.string_0_err == false) && (os_detect.nr_str <= 4)) {
if(((w_value & 0xff) == 0) &&
(ctrl->wLength != 0xff && ctrl->wLength != 0x2))
os_detect.string_0_err = true;
}
if (os_detect.string_n0_err == false) {
if(((w_value & 0xff) != 0) &&
(ctrl->wIndex == 0))
os_detect.string_n0_err = true;
}
}
return;
}
if (HOST_OS_TYPE_UNKNOWN == os_detect.cur_os)
os_detect.cmd_idx++;
switch (w_value >> 8) {
case USB_DT_DEVICE:
/* windows XP does a second get dt_device to confirm
* whether the function is really changed?
*/
if ((5 == os_detect.nr_usb_req) && (0x12 == ctrl->wLength)
&& ((os_detect.cfg_dt_bitmap & 0x1F) == 0x18)
&& (HOST_OS_TYPE_UNKNOWN == os_detect.cur_os)) {
pr_info("WINXP is double checking device\n");
os_type = HOST_OS_TYPE_WIN7_WINXP;
os_detect.has_setconfig = 0;
android_dev_reconfigure(os_type);
return;
}
break;
case USB_DT_STRING:
/* inc nr_str_before_setcfg before scfg is set */
if (!os_detect.has_setconfig)
os_detect.nr_str_before_setcfg++;
else if (os_detect.nr_str_before_setcfg == 0) {
pr_info("return has_scfg =1 & nr_str_before_setcfg == 0\n");
return;
}
if (HOST_OS_TYPE_UNKNOWN == os_detect.cur_os ||
HOST_OS_TYPE_WIN7_OR_WIN8 == os_detect.cur_os) {
/* do nothing if the 2rd dt is string dt */
if (os_detect.cmd_idx == 2) {
return;
/* return if the 3rd dt is string dt */
} else if (os_detect.cmd_idx == 3) {
os_detect.str_dt_bitmap |= (0x1 << 3);
return;
} else if ((os_detect.cmd_idx == 4) &&
((os_detect.str_dt_bitmap & 0x8)== 0x8) &&
(os_detect.nr_usb_req < 8)) {
if ((os_detect.apple_has_ncm_ecm & (HAS_USB_CDC_NCM))
&& (HOST_OS_TYPE_APPLE == os_detect.default_os)) {
pr_info("apple stage1 with NCM, TRIG timer\n");
os_type = HOST_OS_TYPE_APPLE_STAGE1;
os_detect.cur_os = HOST_OS_TYPE_APPLE_STAGE1;
timer_setup(&os_detect.apple_timer, apple_reconfig_timer, 0);
os_detect.apple_timer.expires = jiffies + 15 * HZ;
add_timer(&os_detect.apple_timer);
} else {
pr_info("detected APPLE w/o ncm\n");
os_type = HOST_OS_TYPE_APPLE;
/* default set as none-APPLE and need ncm
* then we do a second stage usb os detection
*/
if (os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM) {
pr_info("Trig NCM/ECM detection on APPLE\n");
os_detect.cur_os = HOST_OS_TYPE_APPLE;
schedule_work(&os_detect.ncm_ecm_detection_work);
os_detect_set_done();
return;
}
}
} else {
if (w_value & 0xFF) {
os_type = HOST_OS_TYPE_WIN7_OR_WIN8;
pr_info("### Host is Win7 Or Win8(w_value = 0x%x)\n", w_value);
/* When connect to a new Win7 or Win8,
** the w_value of the first get string
** will equal to 0xEE.
** We do nothing when w_value = 0xEE(android_dev_reconfigure
** will ignore HOST_OS_TYPE_WIN7_OR_WIN8),
** and wait for the next get string command.
*/
if ((w_value & 0xFF) != 0xEE) {
if (os_detect.has_get_bos && os_detect.nr_bos == 1)
os_type = HOST_OS_TYPE_WIN8;
else
os_type = HOST_OS_TYPE_WIN7_WINXP;
}
} else if ((HOST_OS_TYPE_UNKNOWN == os_detect.cur_os) &&
(((os_detect.cfg_dt_bitmap & 0x1F) == 0x18) ||
(((os_detect.cfg_dt_bitmap & 0x6F) == 0x60) &&
os_detect.has_get_bos))) {
pr_info("##config_map: 0x%x\n", os_detect.cfg_dt_bitmap);
if (((os_detect.reqlen[0] == 0x40 || os_detect.reqlen[0] == 0x08) &&
(os_detect.reqlen[1] == 0x12)) &&
((((os_detect.cfg_dt_bitmap & 0x1F) == 0x18) &&
(os_detect.reqlen[2] == 0x9 || os_detect.reqlen[2] == 0x20) &&
(os_detect.reqlen[3] != 0xff)) ||
(((os_detect.cfg_dt_bitmap & 0x6F) == 0x60) &&
(os_detect.reqlen[4] == 0x9 || os_detect.reqlen[4] == 0x20) &&
(os_detect.reqlen[5] != 0xff)))) {
pr_info("linux stage1\n");
os_type = HOST_OS_TYPE_LINUX_STAGE1;
os_detect.cur_os = HOST_OS_TYPE_LINUX_STAGE1;
timer_setup(&os_detect.linux_timer, linux_reconfig_timer, 0);
os_detect.linux_timer.expires = jiffies + 2 * HZ;
add_timer(&os_detect.linux_timer);
} else {
pr_info("reglen[0...5] = [%2x %2x %2x %2x %2x %2x]\n",
os_detect.reqlen[0],
os_detect.reqlen[1],
os_detect.reqlen[2],
os_detect.reqlen[3],
os_detect.reqlen[4],
os_detect.reqlen[5]);
}
} else {
if (os_detect.reqlen[2] == 0x9)
os_detect.nr_dreq_before_recfg =
NR_DELAY_REQ_BEFORE_RECONFIG;
else
os_detect.nr_dreq_before_recfg = 0;
pr_info("win7/xp: delay %d req\n",
os_detect.nr_dreq_before_recfg);
if (os_detect.has_get_bos && os_detect.nr_bos == 1
&& os_detect.bos_len == 0xff) {
pr_info("WIN8-V2\n");
os_detect.cur_os = HOST_OS_TYPE_WIN8;
os_type = HOST_OS_TYPE_WIN8;
} else {
os_detect.cur_os = HOST_OS_TYPE_WIN7_WINXP;
os_type = HOST_OS_TYPE_WIN7_WINXP;
}
/* do re-config if no extra delay needed */
if (0 == os_detect.nr_dreq_before_recfg)
android_dev_reconfigure(os_detect.cur_os);
return;
}
}
if (os_detect.cur_os != HOST_OS_TYPE_APPLE_STAGE1 &&
os_detect.cur_os != HOST_OS_TYPE_LINUX_STAGE1 &&
os_type != HOST_OS_TYPE_UNKNOWN) {
android_dev_reconfigure(os_type);
return;
}
}
break;
case USB_DT_BOS:
os_detect.has_get_bos = 1;
os_detect.bos_len = ctrl->wLength; /* last one is ok */
os_detect.nr_bos++;
break;
case USB_DT_CONFIG:
if (os_detect.cmd_idx < BITS_PER_LONG) {
os_detect.cfg_dt_bitmap |=
(0x1 << os_detect.cmd_idx);
}
break;
default:
break;
}
break;
case USB_REQ_GET_CONFIGURATION:
pr_info("gcfg:cur_os: %d\n", os_detect.cur_os);
if ((os_detect.cur_os == HOST_OS_TYPE_APPLE_STAGE1) &&
(os_detect.apple_has_ncm_ecm &
(HAS_USB_CDC_NCM | HAS_USB_CDC_ECM)) &&
(!os_detect.has_get_bos)) {
pr_info("mod timer\n");
mod_timer(&os_detect.apple_timer, (jiffies + 3 * HZ));
return;
}
break;
case USB_REQ_SET_INTERFACE:
pr_info("cur_os: %d\n", os_detect.cur_os);
if ((os_detect.cur_os == HOST_OS_TYPE_APPLE_STAGE1) &&
(os_detect.apple_has_ncm_ecm &
(HAS_USB_CDC_NCM | HAS_USB_CDC_ECM))) {
pr_info("%s:del apple_timer\n", __func__);
del_timer(&os_detect.apple_timer);
cancel_delayed_work(&os_detect.ncm_guard_work);
os_type = HOST_OS_TYPE_APPLE;
os_detect.has_setconfig = 0;
android_dev_reconfigure(os_type);
return;
}
if (os_detect.cur_os == HOST_OS_TYPE_LINUX_STAGE1) {
os_detect.has_set_intf = 1;
}
break;
default:
break;
}
if (USB_DIR_OUT == ctrl->bRequestType
&& USB_REQ_SET_CONFIGURATION == ctrl->bRequest) {
pr_info("meet usb set_config in os-detect\n");
os_detect.has_setconfig = 1;
/* trig NCM/ECM detection for linux*/
if ((os_detect.cur_os == HOST_OS_TYPE_LINUX_STAGE1)
&& (0 != os_detect.nr_str_before_setcfg)
&& (HOST_OS_TYPE_APPLE != os_detect.default_os)
&& (os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM)) {
pr_info("Trig NCM/ECM detection on LINUX\n");
del_timer(&os_detect.linux_timer);
os_detect.cur_os = HOST_OS_TYPE_LINUX;
schedule_work(&os_detect.ncm_ecm_detection_work);
os_detect_set_done();
return;
}
/* force a enumeration on windows resume from hibernation */
if ((os_detect.nr_usb_req == 3 || os_detect.nr_usb_req == 4) &&
os_detect.reqlen[0] == 0x40 &&
os_detect.reqlen[1] == 0x12) {
pr_info("####resume from hibernation and re-enumerate\n");
usb_force_reenumerate();
return;
}
}
}
void usb_os_restore(void)
{
struct android_dev *dev = _android_dev;
char *os;
pr_info("%s\n", __func__);
os_detect.default_os = os_detect.bankup_default_os;
strcpy(os_detect.win7, os_detect.win7_bankup);
strcpy(os_detect.win8, os_detect.win8_bankup);
strcpy(os_detect.os_linux, os_detect.os_linux_bankup);
strcpy(os_detect.apple, os_detect.apple_bankup);
set_ncm_ecm_flag();
if (os_detect.default_os == HOST_OS_TYPE_WIN7_WINXP)
os = "win7";
else if (os_detect.default_os == HOST_OS_TYPE_WIN8)
os = "win8";
else if (os_detect.default_os == HOST_OS_TYPE_APPLE)
os = "apple";
else if (os_detect.default_os == HOST_OS_TYPE_LINUX)
os = "linux";
else
BUG();
if (os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM)
set_cdc_ncm(os_detect.apple);
if (os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM)
set_cdc_ncm(os_detect.os_linux);
__os_store(dev, os, strlen(os));
os_detect_struct_clear();
}
/* this should be canceled before the close the usb function */
void cancel_reconfigure_work(void)
{
pr_info("%s:del timer\n", __func__);
del_timer(&os_detect.linux_timer);
del_timer(&os_detect.apple_timer);
pr_info("done\n");
cancel_work_sync(&os_detect.restart_work);
cancel_delayed_work(&os_detect.ncm_guard_work);
cancel_work_sync(&os_detect.reconfigure_work);
cancel_work_sync(&os_detect.ncm_ecm_detection_work);
if (old_max_speed != 0) {
_android_dev->cdev->gadget->max_speed = old_max_speed;
/* restore bcdUSB default value to 0x0200 */
_android_dev->cdev->desc.bcdUSB = cpu_to_le16(0x0200);
old_max_speed = 0;
}
}
void usb_os_detect_init(void)
{
INIT_WORK(&os_detect.reconfigure_work,
android_reconfigure_work);
INIT_WORK(&os_detect.restart_work,
android_restart_work);
INIT_DELAYED_WORK(&os_detect.ncm_guard_work,
ncm_guard_work);
INIT_WORK(&os_detect.ncm_ecm_detection_work,
ncm_ecm_detection_work);
os_detect.default_os = HOST_OS_TYPE_UNKNOWN;
os_detect.bankup_default_os = HOST_OS_TYPE_UNKNOWN;
timer_setup(&os_detect.linux_timer, linux_reconfig_timer, 0);
timer_setup(&os_detect.apple_timer, apple_reconfig_timer, 0);
os_detect_struct_clear();
}
bool host_os_is_win8(void)
{
return (os_detect.cur_os == HOST_OS_TYPE_WIN8);
}
bool host_os_is_win7(void)
{
return (os_detect.cur_os ==
HOST_OS_TYPE_WIN7_WINXP);
}
bool host_os_is_linux(void)
{
return (os_detect.cur_os == HOST_OS_TYPE_LINUX);
}
bool host_os_is_apple(void)
{
return (os_detect.cur_os == HOST_OS_TYPE_APPLE);
}
bool host_os_is_unknown(void)
{
return (os_detect.cur_os == HOST_OS_TYPE_UNKNOWN);
}
void usb_os_detect_reset_state(void)
{
/*
* os detection is not done and at least recvd more than 2
* requests,reset other states receive more than 2 requests
*/
pr_info("RState-OS: %d%s,cmdidx: %d,NrStrNosConfig: %d,"
"has_setconfig %d, reqlen[0]: %d\n",
os_detect.cur_os,
os_type_name(os_detect.cur_os),
os_detect.cmd_idx,
os_detect.nr_str_before_setcfg,
os_detect.has_setconfig,
os_detect.reqlen[0]);
/* bios case */
if ((os_detect.cur_os == HOST_OS_TYPE_UNKNOWN && os_detect.cmd_idx > 2) ||
(os_detect.cur_os != HOST_OS_TYPE_UNKNOWN &&
((os_detect.has_setconfig && os_detect.nr_str_before_setcfg == 0) ||
(os_detect.reqlen[0] != 0x40 && os_detect.reqlen[0] != 0x12)))) {
/* special case for some linux u3 host */
if ((os_detect.cur_os == HOST_OS_TYPE_LINUX) &&
(os_detect.reqlen[0] == 0x8) &&
(os_detect.nr_str_before_setcfg > 0)) {
pr_info("linux usb3 host, don't reset state\n");
return;
}
pr_info("##Clear os_detect\n");
os_detect.default_os = os_detect.bankup_default_os;
os_detect_struct_clear();
}
}
void usb_function_set_stage2(void)
{
struct android_dev *dev = _android_dev;
char *os;
pr_info("Default OS: %s, Current OS: %s\n",
os_type_name(os_detect.default_os),
os_type_name(os_detect.cur_os));
if (in_irq())
os_detect_set_done();
strcpy(os_detect.win7, os_detect.win7_stage2);
strcpy(os_detect.win8, os_detect.win8_stage2);
strcpy(os_detect.apple, os_detect.apple_stage2);
strcpy(os_detect.os_linux, os_detect.linux_stage2);
set_ncm_ecm_flag();
if (((HOST_OS_TYPE_APPLE == os_detect.cur_os) &&
(os_detect.apple_has_ncm_ecm & HAS_USB_CDC_NCM)) ||
((HOST_OS_TYPE_LINUX == os_detect.cur_os) &&
(os_detect.linux_has_ncm_ecm & HAS_USB_CDC_NCM))) {
pr_info("restore os detect\n");
old_max_speed = 0;
if (os_detect.default_os == HOST_OS_TYPE_WIN7_WINXP)
os = "win7";
else if (os_detect.default_os == HOST_OS_TYPE_WIN8)
os = "win8";
else if (os_detect.default_os == HOST_OS_TYPE_APPLE)
os = "apple";
else if (os_detect.default_os == HOST_OS_TYPE_LINUX)
os = "linux";
else
BUG();
__os_store(dev, os, strlen(os));
os_detect.default_os = os_detect.bankup_default_os;
os_detect_struct_clear();
}
schedule_work(&os_detect.reconfigure_work);
}
#if defined (CONFIG_USB_G_MBIM)
bool is_mbim_enabled(void)
{
return mbim_enabled;
}
#endif
static bool should_reset_os_detect(void)
{
s64 ktime_now;
ktime_now = ktime_to_ms(ktime_get());
if (os_detect.reset_intf_time_ms != 0) {
if ((ktime_now - os_detect.reset_intf_time_ms) <
KTIME_MS_BETWEEN_RSTINF_AND_SUSPEND) {
os_detect.reset_intf_time_ms = 0;
return true;
} else {
os_detect.reset_intf_time_ms = 0;
return false;
}
}
return false;
}
/*
* called from interrupt context
*/
void invoke_os_detect_restore(void)
{
if (!should_reset_os_detect()) {
pr_info("no recent reset intf detected\n");
}
if (HOST_OS_TYPE_APPLE == os_detect.cur_os
|| HOST_OS_TYPE_APPLE_STAGE1== os_detect.cur_os) {
pr_info("trigger os detect restore\n");
cancel_delayed_work(&os_detect.ncm_guard_work);
cancel_work_sync(&os_detect.ncm_ecm_detection_work);
del_timer(&os_detect.apple_timer);
pr_info("%s: del timer done\n", __func__);
schedule_work(&os_detect.restart_work);
}
}