diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c index 02aaa3e24b32..8df424bd1683 100644 --- a/drivers/usb/gadget/function/f_gsi.c +++ b/drivers/usb/gadget/function/f_gsi.c @@ -1741,6 +1741,15 @@ static unsigned int gsi_xfer_bitrate(struct usb_gadget *g) return 19 * 64 * 1 * 1000 * 8; } +static void gsi_uevent_work(struct work_struct *w) +{ + struct gsi_ctrl_port *c_port = + container_of(w, struct gsi_ctrl_port, uevent_work); + + if (c_port->dev) + kobject_uevent(&c_port->dev->kobj, KOBJ_CHANGE); +} + static int gsi_function_ctrl_port_init(struct f_gsi *gsi) { int ret; @@ -1760,6 +1769,9 @@ static int gsi_function_ctrl_port_init(struct f_gsi *gsi) spin_lock_init(&gsi->c_port.lock); init_waitqueue_head(&gsi->c_port.read_wq); + INIT_WORK(&gsi->c_port.uevent_work, gsi_uevent_work); + gsi->c_port.dev = NULL; + gsi->c_port.uevent_wq = NULL; if (gsi->prot_id == IPA_USB_RMNET) strlcat(gsi->c_port.name, GSI_RMNET_CTRL_NAME, sz); @@ -1775,6 +1787,10 @@ static int gsi_function_ctrl_port_init(struct f_gsi *gsi) if (!ctrl_dev_create) return 0; + gsi->c_port.uevent_wq = alloc_workqueue(gsi->c_port.name, + WQ_UNBOUND | WQ_MEM_RECLAIM | + WQ_FREEZABLE, 1); + minor = ida_simple_get(&gsi_ida, 0, MAX_CDEV_INSTANCES, GFP_KERNEL); if (minor < 0) { pr_err("%s: No more minor numbers left! rc:%d\n", __func__, @@ -1790,7 +1806,7 @@ static int gsi_function_ctrl_port_init(struct f_gsi *gsi) goto err_cdev_add; } - dev = device_create(gsi_class, NULL, MKDEV(major, minor), NULL, + dev = device_create(gsi_class, NULL, MKDEV(major, minor), &gsi->c_port, gsi->c_port.name); if (IS_ERR(dev)) { log_event_err("%s: device_create failed for (%s)\n", __func__, @@ -1799,6 +1815,8 @@ static int gsi_function_ctrl_port_init(struct f_gsi *gsi) goto err_create_dev; } + gsi->c_port.dev = dev; + return 0; err_create_dev: @@ -2325,6 +2343,10 @@ gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) gsi->rmnet_dtr_status = line_state; log_event_dbg("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE DTR:%d\n", __func__, line_state); + if (gsi->c_port.uevent_wq) + queue_work(gsi->c_port.uevent_wq, + &gsi->c_port.uevent_work); + gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); value = 0; break; @@ -2557,6 +2579,9 @@ static int gsi_set_alt(struct usb_function *f, unsigned int intf, gsi->prot_id == IPA_USB_MBIM) gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); + if (gsi->c_port.uevent_wq) + queue_work(gsi->c_port.uevent_wq, &gsi->c_port.uevent_work); + return ret; notify_ep_disable: @@ -2589,6 +2614,10 @@ static void gsi_disable(struct usb_function *f) } gsi_ctrl_clear_cpkt_queues(gsi, false); + + if (gsi->c_port.uevent_wq) + queue_work(gsi->c_port.uevent_wq, &gsi->c_port.uevent_work); + /* send 0 len pkt to qti/qbi/gps to notify state change */ gsi_ctrl_send_cpkt_tomodem(gsi, NULL, 0); gsi->c_port.notify_req_queued = false; @@ -3748,11 +3777,22 @@ static void gsi_inst_clean(struct gsi_opts *opts) { if (opts->gsi->c_port.cdev.dev) { struct cdev *cdev = &opts->gsi->c_port.cdev; + struct f_gsi *gsi = opts->gsi; int minor = MINOR(cdev->dev); + if (gsi->c_port.dev) + dev_set_drvdata(gsi->c_port.dev, NULL); + + if (gsi->c_port.uevent_wq) { + cancel_work_sync(&gsi->c_port.uevent_work); + destroy_workqueue(gsi->c_port.uevent_wq); + gsi->c_port.uevent_wq = NULL; + } + device_destroy(gsi_class, cdev->dev); cdev_del(cdev); cdev->dev = 0; + gsi->c_port.dev = NULL; ida_simple_remove(&gsi_ida, minor); } @@ -3904,6 +3944,41 @@ DECLARE_USB_FUNCTION(gsi, gsi_alloc_inst, gsi_alloc); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("GSI function driver"); +static int usb_gsi_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct gsi_ctrl_port *c_port = dev_get_drvdata(dev); + struct f_gsi *gsi; + char *str = "undefined"; + + if (!c_port) { + dev_dbg(dev, "%s: gsi is not initialized\n", __func__); + add_uevent_var(env, "STATE=%s", str); + return 0; + } + + gsi = c_port_to_gsi(c_port); + + switch (gsi->prot_id) { + case IPA_USB_RMNET: + str = gsi->rmnet_dtr_status ? "connected" : "disconnected"; + break; + case IPA_USB_MBIM: + case IPA_USB_DIAG: + case IPA_USB_GPS: + str = atomic_read(&gsi->connected) ? + "connected" : "disconnected"; + break; + default: + return 0; + } + + add_uevent_var(env, "STATE=%s", str); + + log_event_dbg("%s:STATE=%s\n", c_port->name, str); + + return 0; +} + static int fgsi_init(void) { int i; @@ -3927,6 +4002,7 @@ static int fgsi_init(void) pr_err("%s: class_create() failed:%d\n", __func__, ret); return ret; } + gsi_class->dev_uevent = usb_gsi_uevent; ret = alloc_chrdev_region(&dev, 0, MAX_CDEV_INSTANCES, "gsi_usb"); if (ret) { diff --git a/drivers/usb/gadget/function/f_gsi.h b/drivers/usb/gadget/function/f_gsi.h index 5ba7b5088db9..a9993cfdfb8a 100644 --- a/drivers/usb/gadget/function/f_gsi.h +++ b/drivers/usb/gadget/function/f_gsi.h @@ -240,6 +240,10 @@ struct gsi_ctrl_port { unsigned int modem_to_host; unsigned int cpkt_drop_cnt; unsigned int get_encap_cnt; + + struct device *dev; + struct work_struct uevent_work; + struct workqueue_struct *uevent_wq; }; struct gsi_data_port {