summaryrefslogtreecommitdiff
path: root/drivers/usb/usbip/vhci_hcd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/usbip/vhci_hcd.c')
-rw-r--r--drivers/usb/usbip/vhci_hcd.c285
1 files changed, 191 insertions, 94 deletions
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 2e0450bec1b1..96f2dacb27fa 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2003-2008 Takahiro Hirofuchi
+ * Copyright (C) 2015-2016 Nobuo Iwata
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,7 +57,9 @@ static int vhci_get_frame_number(struct usb_hcd *hcd);
static const char driver_name[] = "vhci_hcd";
static const char driver_desc[] = "USB/IP Virtual Host Controller";
-struct vhci_hcd *the_controller;
+int vhci_num_controllers = VHCI_NR_HCS;
+
+struct platform_device **vhci_pdevs;
static const char * const bit_desc[] = {
"CONNECTION", /*0*/
@@ -119,47 +122,59 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status)
pr_debug("\n");
}
-void rh_port_connect(int rhport, enum usb_device_speed speed)
+void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed)
{
+ struct vhci_hcd *vhci = vdev_to_vhci(vdev);
+ int rhport = vdev->rhport;
+ u32 status;
unsigned long flags;
usbip_dbg_vhci_rh("rh_port_connect %d\n", rhport);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
+
+ status = vhci->port_status[rhport];
- the_controller->port_status[rhport] |= USB_PORT_STAT_CONNECTION
- | (1 << USB_PORT_FEAT_C_CONNECTION);
+ status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION);
switch (speed) {
case USB_SPEED_HIGH:
- the_controller->port_status[rhport] |= USB_PORT_STAT_HIGH_SPEED;
+ status |= USB_PORT_STAT_HIGH_SPEED;
break;
case USB_SPEED_LOW:
- the_controller->port_status[rhport] |= USB_PORT_STAT_LOW_SPEED;
+ status |= USB_PORT_STAT_LOW_SPEED;
break;
default:
break;
}
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ vhci->port_status[rhport] = status;
+
+ spin_unlock_irqrestore(&vhci->lock, flags);
- usb_hcd_poll_rh_status(vhci_to_hcd(the_controller));
+ usb_hcd_poll_rh_status(vhci_to_hcd(vhci));
}
-static void rh_port_disconnect(int rhport)
+static void rh_port_disconnect(struct vhci_device *vdev)
{
+ struct vhci_hcd *vhci = vdev_to_vhci(vdev);
+ int rhport = vdev->rhport;
+ u32 status;
unsigned long flags;
usbip_dbg_vhci_rh("rh_port_disconnect %d\n", rhport);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
+
+ status = vhci->port_status[rhport];
+
+ status &= ~USB_PORT_STAT_CONNECTION;
+ status |= (1 << USB_PORT_FEAT_C_CONNECTION);
- the_controller->port_status[rhport] &= ~USB_PORT_STAT_CONNECTION;
- the_controller->port_status[rhport] |=
- (1 << USB_PORT_FEAT_C_CONNECTION);
+ vhci->port_status[rhport] = status;
- spin_unlock_irqrestore(&the_controller->lock, flags);
- usb_hcd_poll_rh_status(vhci_to_hcd(the_controller));
+ spin_unlock_irqrestore(&vhci->lock, flags);
+ usb_hcd_poll_rh_status(vhci_to_hcd(vhci));
}
#define PORT_C_MASK \
@@ -188,7 +203,7 @@ static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
int changed = 0;
unsigned long flags;
- retval = DIV_ROUND_UP(VHCI_NPORTS + 1, 8);
+ retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8);
memset(buf, 0, retval);
vhci = hcd_to_vhci(hcd);
@@ -200,7 +215,7 @@ static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
}
/* check pseudo status register for each port */
- for (rhport = 0; rhport < VHCI_NPORTS; rhport++) {
+ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
if ((vhci->port_status[rhport] & PORT_C_MASK)) {
/* The status of a port has been changed, */
usbip_dbg_vhci_rh("port %d status changed\n", rhport);
@@ -225,7 +240,7 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc)
desc->bDescLength = 9;
desc->wHubCharacteristics = cpu_to_le16(
HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM);
- desc->bNbrPorts = VHCI_NPORTS;
+ desc->bNbrPorts = VHCI_HC_PORTS;
desc->u.hs.DeviceRemovable[0] = 0xff;
desc->u.hs.DeviceRemovable[1] = 0xff;
}
@@ -238,7 +253,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
int rhport;
unsigned long flags;
- u32 prev_port_status[VHCI_NPORTS];
+ u32 prev_port_status[VHCI_HC_PORTS];
if (!HCD_HW_ACCESSIBLE(hcd))
return -ETIMEDOUT;
@@ -249,7 +264,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
*/
usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue,
wIndex);
- if (wIndex > VHCI_NPORTS)
+ if (wIndex > VHCI_HC_PORTS)
pr_err("invalid port number %d\n", wIndex);
rhport = ((__u8)(wIndex & 0x00ff)) - 1;
@@ -315,7 +330,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
break;
case GetPortStatus:
usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex);
- if (wIndex > VHCI_NPORTS || wIndex < 1) {
+ if (wIndex > VHCI_HC_PORTS || wIndex < 1) {
pr_err("invalid port number %d\n", wIndex);
retval = -EPIPE;
}
@@ -416,14 +431,27 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
static struct vhci_device *get_vdev(struct usb_device *udev)
{
- int i;
+ struct platform_device *pdev;
+ struct usb_hcd *hcd;
+ struct vhci_hcd *vhci;
+ int pdev_nr, rhport;
if (!udev)
return NULL;
- for (i = 0; i < VHCI_NPORTS; i++)
- if (the_controller->vdev[i].udev == udev)
- return port_to_vdev(i);
+ for (pdev_nr = 0; pdev_nr < vhci_num_controllers; pdev_nr++) {
+ pdev = *(vhci_pdevs + pdev_nr);
+ if (pdev == NULL)
+ continue;
+ hcd = platform_get_drvdata(pdev);
+ if (hcd == NULL)
+ continue;
+ vhci = hcd_to_vhci(hcd);
+ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
+ if (vhci->vdev[rhport].udev == udev)
+ return &vhci->vdev[rhport];
+ }
+ }
return NULL;
}
@@ -432,6 +460,7 @@ static void vhci_tx_urb(struct urb *urb)
{
struct vhci_device *vdev = get_vdev(urb->dev);
struct vhci_priv *priv;
+ struct vhci_hcd *vhci = vdev_to_vhci(vdev);
unsigned long flags;
if (!vdev) {
@@ -447,7 +476,7 @@ static void vhci_tx_urb(struct urb *urb)
spin_lock_irqsave(&vdev->priv_lock, flags);
- priv->seqnum = atomic_inc_return(&the_controller->seqnum);
+ priv->seqnum = atomic_inc_return(&vhci->seqnum);
if (priv->seqnum == 0xffff)
dev_info(&urb->dev->dev, "seqnum max\n");
@@ -465,7 +494,9 @@ static void vhci_tx_urb(struct urb *urb)
static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
gfp_t mem_flags)
{
+ struct vhci_hcd *vhci = hcd_to_vhci(hcd);
struct device *dev = &urb->dev->dev;
+ u8 portnum = urb->dev->portnum;
int ret = 0;
struct vhci_device *vdev;
unsigned long flags;
@@ -473,26 +504,30 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
usbip_dbg_vhci_hc("enter, usb_hcd %p urb %p mem_flags %d\n",
hcd, urb, mem_flags);
+ if (portnum > VHCI_HC_PORTS) {
+ pr_err("invalid port number %d\n", portnum);
+ return -ENODEV;
+ }
+ vdev = &vhci->vdev[portnum-1];
+
/* patch to usb_sg_init() is in 2.5.60 */
BUG_ON(!urb->transfer_buffer && urb->transfer_buffer_length);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
if (urb->status != -EINPROGRESS) {
dev_err(dev, "URB already unlinked!, status %d\n", urb->status);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
return urb->status;
}
- vdev = port_to_vdev(urb->dev->portnum-1);
-
/* refuse enqueue for dead connection */
spin_lock(&vdev->ud.lock);
if (vdev->ud.status == VDEV_ST_NULL ||
vdev->ud.status == VDEV_ST_ERROR) {
dev_err(dev, "enqueue for inactive port %d\n", vdev->rhport);
spin_unlock(&vdev->ud.lock);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
return -ENODEV;
}
spin_unlock(&vdev->ud.lock);
@@ -565,17 +600,16 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
out:
vhci_tx_urb(urb);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
return 0;
no_need_xmit:
usb_hcd_unlink_urb_from_ep(hcd, urb);
no_need_unlink:
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
if (!ret)
- usb_hcd_giveback_urb(vhci_to_hcd(the_controller),
- urb, urb->status);
+ usb_hcd_giveback_urb(hcd, urb, urb->status);
return ret;
}
@@ -627,19 +661,20 @@ no_need_unlink:
*/
static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
+ struct vhci_hcd *vhci = hcd_to_vhci(hcd);
struct vhci_priv *priv;
struct vhci_device *vdev;
unsigned long flags;
pr_info("dequeue a urb %p\n", urb);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
priv = urb->hcpriv;
if (!priv) {
/* URB was never linked! or will be soon given back by
* vhci_rx. */
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
return -EIDRM;
}
@@ -648,7 +683,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
ret = usb_hcd_check_unlink_urb(hcd, urb, status);
if (ret) {
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
return ret;
}
}
@@ -676,10 +711,9 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
usb_hcd_unlink_urb_from_ep(hcd, urb);
- spin_unlock_irqrestore(&the_controller->lock, flags);
- usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb,
- urb->status);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
+ usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status);
+ spin_lock_irqsave(&vhci->lock, flags);
} else {
/* tcp connection is alive */
@@ -691,12 +725,12 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
unlink = kzalloc(sizeof(struct vhci_unlink), GFP_ATOMIC);
if (!unlink) {
spin_unlock(&vdev->priv_lock);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC);
return -ENOMEM;
}
- unlink->seqnum = atomic_inc_return(&the_controller->seqnum);
+ unlink->seqnum = atomic_inc_return(&vhci->seqnum);
if (unlink->seqnum == 0xffff)
pr_info("seqnum max\n");
@@ -712,7 +746,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
spin_unlock(&vdev->priv_lock);
}
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
usbip_dbg_vhci_hc("leave\n");
return 0;
@@ -720,10 +754,12 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
{
+ struct vhci_hcd *vhci = vdev_to_vhci(vdev);
+ struct usb_hcd *hcd = vhci_to_hcd(vhci);
struct vhci_unlink *unlink, *tmp;
unsigned long flags;
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
spin_lock(&vdev->priv_lock);
list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {
@@ -752,24 +788,23 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
urb->status = -ENODEV;
- usb_hcd_unlink_urb_from_ep(vhci_to_hcd(the_controller), urb);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
list_del(&unlink->list);
spin_unlock(&vdev->priv_lock);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
- usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb,
- urb->status);
+ usb_hcd_giveback_urb(hcd, urb, urb->status);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
spin_lock(&vdev->priv_lock);
kfree(unlink);
}
spin_unlock(&vdev->priv_lock);
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
}
/*
@@ -827,7 +862,7 @@ static void vhci_shutdown_connection(struct usbip_device *ud)
* is actually given back by vhci_rx after receiving its return pdu.
*
*/
- rh_port_disconnect(vdev->rhport);
+ rh_port_disconnect(vdev);
pr_info("disconnect device\n");
}
@@ -866,7 +901,7 @@ static void vhci_device_unusable(struct usbip_device *ud)
static void vhci_device_init(struct vhci_device *vdev)
{
- memset(vdev, 0, sizeof(*vdev));
+ memset(vdev, 0, sizeof(struct vhci_device));
vdev->ud.side = USBIP_VHCI;
vdev->ud.status = VDEV_ST_NULL;
@@ -887,17 +922,34 @@ static void vhci_device_init(struct vhci_device *vdev)
usbip_start_eh(&vdev->ud);
}
+static int hcd_name_to_id(const char *name)
+{
+ char *c;
+ long val;
+ int ret;
+
+ c = strchr(name, '.');
+ if (c == NULL)
+ return 0;
+
+ ret = kstrtol(c+1, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
static int vhci_start(struct usb_hcd *hcd)
{
struct vhci_hcd *vhci = hcd_to_vhci(hcd);
- int rhport;
+ int id, rhport;
int err = 0;
usbip_dbg_vhci_hc("enter vhci_start\n");
/* initialize private data of usb_hcd */
- for (rhport = 0; rhport < VHCI_NPORTS; rhport++) {
+ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
struct vhci_device *vdev = &vhci->vdev[rhport];
vhci_device_init(vdev);
@@ -910,11 +962,26 @@ static int vhci_start(struct usb_hcd *hcd)
hcd->power_budget = 0; /* no limit */
hcd->uses_new_polling = 1;
+ id = hcd_name_to_id(hcd_name(hcd));
+ if (id < 0) {
+ pr_err("invalid vhci name %s\n", hcd_name(hcd));
+ return -EINVAL;
+ }
+
/* vhci_hcd is now ready to be controlled through sysfs */
- err = sysfs_create_group(&vhci_dev(vhci)->kobj, &dev_attr_group);
- if (err) {
- pr_err("create sysfs files\n");
- return err;
+ if (id == 0) {
+ err = vhci_init_attr_group();
+ if (err) {
+ pr_err("init attr group\n");
+ return err;
+ }
+ err = sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group);
+ if (err) {
+ pr_err("create sysfs files\n");
+ vhci_finish_attr_group();
+ return err;
+ }
+ pr_info("created sysfs %s\n", hcd_name(hcd));
}
return 0;
@@ -923,15 +990,19 @@ static int vhci_start(struct usb_hcd *hcd)
static void vhci_stop(struct usb_hcd *hcd)
{
struct vhci_hcd *vhci = hcd_to_vhci(hcd);
- int rhport = 0;
+ int id, rhport;
usbip_dbg_vhci_hc("stop VHCI controller\n");
/* 1. remove the userland interface of vhci_hcd */
- sysfs_remove_group(&vhci_dev(vhci)->kobj, &dev_attr_group);
+ id = hcd_name_to_id(hcd_name(hcd));
+ if (id == 0) {
+ sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group);
+ vhci_finish_attr_group();
+ }
/* 2. shutdown all the ports of vhci_hcd */
- for (rhport = 0; rhport < VHCI_NPORTS; rhport++) {
+ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
struct vhci_device *vdev = &vhci->vdev[rhport];
usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED);
@@ -1025,9 +1096,6 @@ static int vhci_hcd_probe(struct platform_device *pdev)
}
hcd->has_tt = 1;
- /* this is private data for vhci_hcd */
- the_controller = hcd_to_vhci(hcd);
-
/*
* Finish generic HCD structure initialization and register.
* Call the driver's reset() and start() routines.
@@ -1036,7 +1104,6 @@ static int vhci_hcd_probe(struct platform_device *pdev)
if (ret != 0) {
pr_err("usb_add_hcd failed %d\n", ret);
usb_put_hcd(hcd);
- the_controller = NULL;
return ret;
}
@@ -1059,7 +1126,6 @@ static int vhci_hcd_remove(struct platform_device *pdev)
*/
usb_remove_hcd(hcd);
usb_put_hcd(hcd);
- the_controller = NULL;
return 0;
}
@@ -1070,21 +1136,24 @@ static int vhci_hcd_remove(struct platform_device *pdev)
static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state)
{
struct usb_hcd *hcd;
- int rhport = 0;
+ struct vhci_hcd *vhci;
+ int rhport;
int connected = 0;
int ret = 0;
unsigned long flags;
hcd = platform_get_drvdata(pdev);
+ if (!hcd)
+ return 0;
+ vhci = hcd_to_vhci(hcd);
- spin_lock_irqsave(&the_controller->lock, flags);
+ spin_lock_irqsave(&vhci->lock, flags);
- for (rhport = 0; rhport < VHCI_NPORTS; rhport++)
- if (the_controller->port_status[rhport] &
- USB_PORT_STAT_CONNECTION)
+ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++)
+ if (vhci->port_status[rhport] & USB_PORT_STAT_CONNECTION)
connected += 1;
- spin_unlock_irqrestore(&the_controller->lock, flags);
+ spin_unlock_irqrestore(&vhci->lock, flags);
if (connected > 0) {
dev_info(&pdev->dev,
@@ -1106,6 +1175,8 @@ static int vhci_hcd_resume(struct platform_device *pdev)
dev_dbg(&pdev->dev, "%s\n", __func__);
hcd = platform_get_drvdata(pdev);
+ if (!hcd)
+ return 0;
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
usb_hcd_poll_rh_status(hcd);
@@ -1129,52 +1200,78 @@ static struct platform_driver vhci_driver = {
},
};
-/*
- * The VHCI 'device' is 'virtual'; not a real plug&play hardware.
- * We need to add this virtual device as a platform device arbitrarily:
- * 1. platform_device_register()
- */
-static void the_pdev_release(struct device *dev)
+static int add_platform_device(int id)
{
+ struct platform_device *pdev;
+ int dev_nr;
+
+ if (id == 0)
+ dev_nr = -1;
+ else
+ dev_nr = id;
+
+ pdev = platform_device_register_simple(driver_name, dev_nr, NULL, 0);
+ if (pdev == NULL)
+ return -ENODEV;
+
+ *(vhci_pdevs + id) = pdev;
+ return 0;
}
-static struct platform_device the_pdev = {
- /* should be the same name as driver_name */
- .name = driver_name,
- .id = -1,
- .dev = {
- .release = the_pdev_release,
- },
-};
+static void del_platform_devices(void)
+{
+ struct platform_device *pdev;
+ int i;
+
+ for (i = 0; i < vhci_num_controllers; i++) {
+ pdev = *(vhci_pdevs + i);
+ if (pdev != NULL)
+ platform_device_unregister(pdev);
+ *(vhci_pdevs + i) = NULL;
+ }
+ sysfs_remove_link(&platform_bus.kobj, driver_name);
+}
static int __init vhci_hcd_init(void)
{
- int ret;
+ int i, ret;
if (usb_disabled())
return -ENODEV;
+ if (vhci_num_controllers < 1)
+ vhci_num_controllers = 1;
+
+ vhci_pdevs = kcalloc(vhci_num_controllers, sizeof(void *), GFP_KERNEL);
+ if (vhci_pdevs == NULL)
+ return -ENOMEM;
+
ret = platform_driver_register(&vhci_driver);
if (ret)
goto err_driver_register;
- ret = platform_device_register(&the_pdev);
- if (ret)
- goto err_platform_device_register;
+ for (i = 0; i < vhci_num_controllers; i++) {
+ ret = add_platform_device(i);
+ if (ret)
+ goto err_platform_device_register;
+ }
pr_info(DRIVER_DESC " v" USBIP_VERSION "\n");
return ret;
err_platform_device_register:
+ del_platform_devices();
platform_driver_unregister(&vhci_driver);
err_driver_register:
+ kfree(vhci_pdevs);
return ret;
}
static void __exit vhci_hcd_exit(void)
{
- platform_device_unregister(&the_pdev);
+ del_platform_devices();
platform_driver_unregister(&vhci_driver);
+ kfree(vhci_pdevs);
}
module_init(vhci_hcd_init);