6db4831e98
Android 14
478 lines
12 KiB
C
478 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2015-2022 Samsung Electronics Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
/* usb notify layer v3.7 */
|
|
|
|
#define pr_fmt(fmt) "usb_notify: " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/err.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/version.h>
|
|
#include <linux/usb_notify.h>
|
|
#include <linux/usb/hcd.h>
|
|
|
|
#define SMARTDOCK_INDEX 1
|
|
#define MMDOCK_INDEX 2
|
|
|
|
struct dev_table {
|
|
struct usb_device_id dev;
|
|
int index;
|
|
};
|
|
|
|
static struct dev_table enable_notify_hub_table[] = {
|
|
{ .dev = { USB_DEVICE(0x0424, 0x2514), },
|
|
.index = SMARTDOCK_INDEX,
|
|
}, /* SMART DOCK HUB 1 */
|
|
{ .dev = { USB_DEVICE(0x1a40, 0x0101), },
|
|
.index = SMARTDOCK_INDEX,
|
|
}, /* SMART DOCK HUB 2 */
|
|
{ .dev = { USB_DEVICE(0x0424, 0x9512), },
|
|
.index = MMDOCK_INDEX,
|
|
}, /* SMSC USB LAN HUB 9512 */
|
|
{}
|
|
};
|
|
|
|
static struct dev_table essential_device_table[] = {
|
|
{ .dev = { USB_DEVICE(0x08bb, 0x2704), },
|
|
.index = SMARTDOCK_INDEX,
|
|
}, /* TI USB Audio DAC 1 */
|
|
{ .dev = { USB_DEVICE(0x08bb, 0x27c4), },
|
|
.index = SMARTDOCK_INDEX,
|
|
}, /* TI USB Audio DAC 2 */
|
|
{ .dev = { USB_DEVICE(0x0424, 0xec00), },
|
|
.index = MMDOCK_INDEX,
|
|
}, /* SMSC LAN Driver */
|
|
{}
|
|
};
|
|
|
|
static struct dev_table unsupport_device_table[] = {
|
|
{ .dev = { USB_DEVICE(0x1a0a, 0x0201), },
|
|
}, /* The device for usb certification */
|
|
{}
|
|
};
|
|
|
|
static int check_essential_device(struct usb_device *dev, int index)
|
|
{
|
|
struct dev_table *id;
|
|
int ret = 0;
|
|
|
|
/* check VID, PID */
|
|
for (id = essential_device_table; id->dev.match_flags; id++) {
|
|
if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
|
|
(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
|
|
id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
|
|
id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct) &&
|
|
id->index == index) {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int check_gamepad_device(struct usb_device *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
pr_info("%s : product=%s\n", __func__, dev->product);
|
|
|
|
if (!dev->product)
|
|
return ret;
|
|
|
|
if (!strncmp(dev->product, "Gamepad for SAMSUNG", 19))
|
|
ret = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int check_lanhub_device(struct usb_device *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
pr_info("%s : product=%s\n", __func__, dev->product);
|
|
|
|
if (!dev->product)
|
|
return ret;
|
|
|
|
if (!strncmp(dev->product, "LAN9512", 8))
|
|
ret = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int is_notify_hub(struct usb_device *dev)
|
|
{
|
|
struct dev_table *id;
|
|
struct usb_device *hdev;
|
|
int ret = 0;
|
|
|
|
hdev = dev->parent;
|
|
if (!hdev)
|
|
goto skip;
|
|
/* check VID, PID */
|
|
for (id = enable_notify_hub_table; id->dev.match_flags; id++) {
|
|
if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
|
|
(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
|
|
id->dev.idVendor == le16_to_cpu(hdev->descriptor.idVendor) &&
|
|
id->dev.idProduct == le16_to_cpu(hdev->descriptor.idProduct)) {
|
|
ret = (hdev->parent &&
|
|
(hdev->parent == dev->bus->root_hub)) ? id->index : 0;
|
|
break;
|
|
}
|
|
}
|
|
skip:
|
|
return ret;
|
|
}
|
|
|
|
static int call_battery_notify(struct usb_device *dev, bool on)
|
|
{
|
|
struct usb_device *hdev;
|
|
struct usb_device *udev;
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
int index = 0;
|
|
int count = 0;
|
|
int port;
|
|
|
|
index = is_notify_hub(dev);
|
|
if (!index)
|
|
goto skip;
|
|
if (!check_essential_device(dev, index))
|
|
goto skip;
|
|
|
|
hdev = dev->parent;
|
|
if (!hdev)
|
|
goto skip;
|
|
|
|
usb_hub_for_each_child(hdev, port, udev) {
|
|
if (check_essential_device(udev, index)) {
|
|
if (!on && (udev == dev))
|
|
continue;
|
|
else
|
|
count++;
|
|
}
|
|
}
|
|
|
|
pr_info("%s : VID : 0x%x, PID : 0x%x, on=%d, count=%d\n", __func__,
|
|
dev->descriptor.idVendor, dev->descriptor.idProduct,
|
|
on, count);
|
|
if (on) {
|
|
if (count == 1) {
|
|
if (index == SMARTDOCK_INDEX)
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_SMTD_EXT_CURRENT, 1);
|
|
else if (index == MMDOCK_INDEX)
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_MMD_EXT_CURRENT, 1);
|
|
}
|
|
} else {
|
|
if (!count) {
|
|
if (index == SMARTDOCK_INDEX)
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_SMTD_EXT_CURRENT, 0);
|
|
else if (index == MMDOCK_INDEX)
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_MMD_EXT_CURRENT, 0);
|
|
}
|
|
}
|
|
skip:
|
|
return 0;
|
|
}
|
|
|
|
static void seek_usb_interface(struct usb_device *dev)
|
|
{
|
|
struct usb_interface *intf;
|
|
int i;
|
|
|
|
if (!dev) {
|
|
pr_err("%s no dev\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
if (!dev->actconfig) {
|
|
pr_info("%s no set config\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
|
|
intf = dev->actconfig->interface[i];
|
|
/* You can use this function for various purposes */
|
|
store_usblog_notify(NOTIFY_PORT_CLASS,
|
|
(void *)&dev->descriptor.bDeviceClass,
|
|
(void *)&intf->cur_altsetting->desc.bInterfaceClass);
|
|
}
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static void disconnect_usb_driver(struct usb_device *dev)
|
|
{
|
|
struct usb_interface *intf = NULL;
|
|
struct usb_driver *driver = NULL;
|
|
int i;
|
|
|
|
if (!dev) {
|
|
pr_err("%s no dev\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
if (!dev->actconfig) {
|
|
pr_err("%s no set config\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
|
|
intf = dev->actconfig->interface[i];
|
|
if (intf->dev.driver) {
|
|
driver = to_usb_driver(intf->dev.driver);
|
|
usb_driver_release_interface(driver, intf);
|
|
}
|
|
}
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static int call_device_notify(struct usb_device *dev, int connect)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
|
|
if (dev->bus->root_hub != dev) {
|
|
if (connect) {
|
|
pr_info("%s device\n", __func__);
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_DEVICE_CONNECT, 1);
|
|
|
|
if (check_gamepad_device(dev))
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_GAMEPAD_CONNECT, 1);
|
|
else if (check_lanhub_device(dev))
|
|
send_otg_notify(o_notify,
|
|
NOTIFY_EVENT_LANHUB_CONNECT, 1);
|
|
else
|
|
;
|
|
store_usblog_notify(NOTIFY_PORT_CONNECT,
|
|
(void *)&dev->descriptor.idVendor,
|
|
(void *)&dev->descriptor.idProduct);
|
|
|
|
seek_usb_interface(dev);
|
|
|
|
if (!usb_check_whitelist_for_mdm(dev)) {
|
|
pr_info("This device will be noattached state.\n");
|
|
disconnect_usb_driver(dev);
|
|
usb_set_device_state(dev, USB_STATE_NOTATTACHED);
|
|
}
|
|
} else
|
|
store_usblog_notify(NOTIFY_PORT_DISCONNECT,
|
|
(void *)&dev->descriptor.idVendor,
|
|
(void *)&dev->descriptor.idProduct);
|
|
} else {
|
|
if (connect)
|
|
pr_info("%s root hub\n", __func__);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void check_device_speed(struct usb_device *dev, bool on)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
struct usb_device *hdev;
|
|
struct usb_device *udev;
|
|
int port = 0;
|
|
int speed = USB_SPEED_UNKNOWN;
|
|
int pr_speed = USB_SPEED_UNKNOWN;
|
|
static int hs_hub;
|
|
static int ss_hub;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return;
|
|
}
|
|
|
|
pr_speed = get_con_dev_max_speed(o_notify);
|
|
|
|
hdev = dev->parent;
|
|
if (!hdev)
|
|
return;
|
|
|
|
hdev = dev->bus->root_hub;
|
|
if (!hdev)
|
|
return;
|
|
|
|
usb_hub_for_each_child(hdev, port, udev) {
|
|
if (!on && (udev == dev))
|
|
continue;
|
|
if (udev->speed > speed)
|
|
speed = udev->speed;
|
|
}
|
|
|
|
if (hdev->speed >= USB_SPEED_SUPER) {
|
|
if (speed > USB_SPEED_UNKNOWN)
|
|
ss_hub = 1;
|
|
else
|
|
ss_hub = 0;
|
|
} else if (hdev->speed > USB_SPEED_UNKNOWN
|
|
&& hdev->speed != USB_SPEED_WIRELESS) {
|
|
if (speed > USB_SPEED_UNKNOWN)
|
|
hs_hub = 1;
|
|
else
|
|
hs_hub = 0;
|
|
} else
|
|
;
|
|
|
|
if (ss_hub || hs_hub) {
|
|
if (speed > pr_speed)
|
|
set_con_dev_max_speed(o_notify, speed);
|
|
} else
|
|
set_con_dev_max_speed(o_notify, USB_SPEED_UNKNOWN);
|
|
|
|
pr_info("%s : dev->speed %s %s\n", __func__,
|
|
usb_speed_string(dev->speed), on ? "on" : "off");
|
|
|
|
pr_info("%s : o_notify->speed %s\n", __func__,
|
|
usb_speed_string(get_con_dev_max_speed(o_notify)));
|
|
}
|
|
|
|
#if defined(CONFIG_USB_HW_PARAM)
|
|
static int set_hw_param(struct usb_device *dev)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
int ret = 0;
|
|
int bInterfaceClass = 0, speed = 0;
|
|
|
|
if (o_notify == NULL || dev->config->interface[0] == NULL) {
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
if (dev->bus->root_hub != dev) {
|
|
bInterfaceClass
|
|
= dev->config->interface[0]
|
|
->cur_altsetting->desc.bInterfaceClass;
|
|
speed = dev->speed;
|
|
|
|
pr_info("%s USB device connected - Class : 0x%x, speed : 0x%x\n",
|
|
__func__, bInterfaceClass, speed);
|
|
|
|
if (bInterfaceClass == USB_CLASS_AUDIO)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_AUDIO_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_COMM)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_COMM_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_HID)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_HID_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_PHYSICAL)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_PHYSICAL_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_STILL_IMAGE)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_IMAGE_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_PRINTER)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_PRINTER_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_MASS_STORAGE) {
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_STORAGE_COUNT);
|
|
if (speed == USB_SPEED_SUPER)
|
|
inc_hw_param(o_notify,
|
|
USB_HOST_STORAGE_SUPER_COUNT);
|
|
else if (speed == USB_SPEED_HIGH)
|
|
inc_hw_param(o_notify,
|
|
USB_HOST_STORAGE_HIGH_COUNT);
|
|
else if (speed == USB_SPEED_FULL)
|
|
inc_hw_param(o_notify,
|
|
USB_HOST_STORAGE_FULL_COUNT);
|
|
} else if (bInterfaceClass == USB_CLASS_HUB)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_HUB_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_CDC_DATA)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_CDC_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_CSCID)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_CSCID_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_CONTENT_SEC)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_CONTENT_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_VIDEO)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_VIDEO_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_WIRELESS_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_MISC)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_MISC_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_APP_SPEC)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_APP_COUNT);
|
|
else if (bInterfaceClass == USB_CLASS_VENDOR_SPEC)
|
|
inc_hw_param(o_notify, USB_HOST_CLASS_VENDOR_COUNT);
|
|
|
|
if (speed == USB_SPEED_SUPER)
|
|
inc_hw_param(o_notify, USB_HOST_SUPER_SPEED_COUNT);
|
|
else if (speed == USB_SPEED_HIGH)
|
|
inc_hw_param(o_notify, USB_HOST_HIGH_SPEED_COUNT);
|
|
else if (speed == USB_SPEED_FULL)
|
|
inc_hw_param(o_notify, USB_HOST_FULL_SPEED_COUNT);
|
|
else if (speed == USB_SPEED_LOW)
|
|
inc_hw_param(o_notify, USB_HOST_LOW_SPEED_COUNT);
|
|
}
|
|
err:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static void check_unsupport_device(struct usb_device *dev)
|
|
{
|
|
struct dev_table *id;
|
|
|
|
/* check VID, PID */
|
|
for (id = unsupport_device_table; id->dev.match_flags; id++) {
|
|
if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
|
|
(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
|
|
id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
|
|
id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) {
|
|
#if defined(CONFIG_USB_HOST_CERTI)
|
|
send_usb_certi_uevent(USB_CERTI_UNSUPPORT_ACCESSORY);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int dev_notify(struct notifier_block *self,
|
|
unsigned long action, void *dev)
|
|
{
|
|
switch (action) {
|
|
case USB_DEVICE_ADD:
|
|
call_device_notify(dev, 1);
|
|
call_battery_notify(dev, 1);
|
|
check_device_speed(dev, 1);
|
|
#if defined(CONFIG_USB_HW_PARAM)
|
|
set_hw_param(dev);
|
|
#endif
|
|
check_unsupport_device(dev);
|
|
break;
|
|
case USB_DEVICE_REMOVE:
|
|
call_device_notify(dev, 0);
|
|
call_battery_notify(dev, 0);
|
|
check_device_speed(dev, 0);
|
|
break;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block dev_nb = {
|
|
.notifier_call = dev_notify,
|
|
};
|
|
|
|
void register_usbdev_notify(void)
|
|
{
|
|
usb_register_notify(&dev_nb);
|
|
}
|
|
EXPORT_SYMBOL(register_usbdev_notify);
|
|
|
|
void unregister_usbdev_notify(void)
|
|
{
|
|
usb_unregister_notify(&dev_nb);
|
|
}
|
|
EXPORT_SYMBOL(unregister_usbdev_notify);
|
|
|