root/drivers/usb/usbip/stub_main.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2003-2008 Takahiro Hirofuchi
 */

#include <linux/string.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/scatterlist.h>

#include "usbip_common.h"
#include "stub.h"

#define DRIVER_AUTHOR "Takahiro Hirofuchi"
#define DRIVER_DESC "USB/IP Host Driver"

struct kmem_cache *stub_priv_cache;

/*
 * busid_tables defines matching busids that usbip can grab. A user can change
 * dynamically what device is locally used and what device is exported to a
 * remote host.
 */
#define MAX_BUSID 16
static struct bus_id_priv busid_table[MAX_BUSID];
static DEFINE_SPINLOCK(busid_table_lock);

static void init_busid_table(void)
{
        int i;

        /*
         * This also sets the bus_table[i].status to
         * STUB_BUSID_OTHER, which is 0.
         */
        memset(busid_table, 0, sizeof(busid_table));

        for (i = 0; i < MAX_BUSID; i++)
                spin_lock_init(&busid_table[i].busid_lock);
}

/*
 * Find the index of the busid by name.
 * Must be called with busid_table_lock held.
 */
static int get_busid_idx(const char *busid)
{
        int i;
        int idx = -1;

        for (i = 0; i < MAX_BUSID; i++) {
                spin_lock(&busid_table[i].busid_lock);
                if (busid_table[i].name[0])
                        if (!strncmp(busid_table[i].name, busid, BUSID_SIZE)) {
                                idx = i;
                                spin_unlock(&busid_table[i].busid_lock);
                                break;
                        }
                spin_unlock(&busid_table[i].busid_lock);
        }
        return idx;
}

/* Returns holding busid_lock. Should call put_busid_priv() to unlock */
struct bus_id_priv *get_busid_priv(const char *busid)
{
        int idx;
        struct bus_id_priv *bid = NULL;

        spin_lock(&busid_table_lock);
        idx = get_busid_idx(busid);
        if (idx >= 0) {
                bid = &(busid_table[idx]);
                /* get busid_lock before returning */
                spin_lock(&bid->busid_lock);
        }
        spin_unlock(&busid_table_lock);

        return bid;
}

void put_busid_priv(struct bus_id_priv *bid)
{
        if (bid)
                spin_unlock(&bid->busid_lock);
}

static int add_match_busid(char *busid)
{
        int i;
        int ret = -1;

        spin_lock(&busid_table_lock);
        /* already registered? */
        if (get_busid_idx(busid) >= 0) {
                ret = 0;
                goto out;
        }

        for (i = 0; i < MAX_BUSID; i++) {
                spin_lock(&busid_table[i].busid_lock);
                if (!busid_table[i].name[0]) {
                        strscpy(busid_table[i].name, busid, BUSID_SIZE);
                        if ((busid_table[i].status != STUB_BUSID_ALLOC) &&
                            (busid_table[i].status != STUB_BUSID_REMOV))
                                busid_table[i].status = STUB_BUSID_ADDED;
                        ret = 0;
                        spin_unlock(&busid_table[i].busid_lock);
                        break;
                }
                spin_unlock(&busid_table[i].busid_lock);
        }

out:
        spin_unlock(&busid_table_lock);

        return ret;
}

int del_match_busid(char *busid)
{
        int idx;
        int ret = -1;

        spin_lock(&busid_table_lock);
        idx = get_busid_idx(busid);
        if (idx < 0)
                goto out;

        /* found */
        ret = 0;

        spin_lock(&busid_table[idx].busid_lock);

        if (busid_table[idx].status == STUB_BUSID_OTHER)
                memset(busid_table[idx].name, 0, BUSID_SIZE);

        if ((busid_table[idx].status != STUB_BUSID_OTHER) &&
            (busid_table[idx].status != STUB_BUSID_ADDED))
                busid_table[idx].status = STUB_BUSID_REMOV;

        spin_unlock(&busid_table[idx].busid_lock);
out:
        spin_unlock(&busid_table_lock);

        return ret;
}

static ssize_t match_busid_show(struct device_driver *drv, char *buf)
{
        int i;
        char *out = buf;

        spin_lock(&busid_table_lock);
        for (i = 0; i < MAX_BUSID; i++) {
                spin_lock(&busid_table[i].busid_lock);
                if (busid_table[i].name[0])
                        out += sprintf(out, "%s ", busid_table[i].name);
                spin_unlock(&busid_table[i].busid_lock);
        }
        spin_unlock(&busid_table_lock);
        out += sprintf(out, "\n");

        return out - buf;
}

static ssize_t match_busid_store(struct device_driver *dev, const char *buf,
                                 size_t count)
{
        char busid[BUSID_SIZE];

        if (count < 5)
                return -EINVAL;

        /* busid needs to include \0 termination */
        if (strscpy(busid, buf + 4, BUSID_SIZE) < 0)
                return -EINVAL;

        if (!strncmp(buf, "add ", 4)) {
                if (add_match_busid(busid) < 0)
                        return -ENOMEM;

                pr_debug("add busid %s\n", busid);
                return count;
        }

        if (!strncmp(buf, "del ", 4)) {
                if (del_match_busid(busid) < 0)
                        return -ENODEV;

                pr_debug("del busid %s\n", busid);
                return count;
        }

        return -EINVAL;
}
static DRIVER_ATTR_RW(match_busid);

static int do_rebind(char *busid, struct bus_id_priv *busid_priv)
{
        int ret = 0;

        /* device_attach() callers should hold parent lock for USB */
        if (busid_priv->udev->dev.parent)
                device_lock(busid_priv->udev->dev.parent);
        ret = device_attach(&busid_priv->udev->dev);
        if (busid_priv->udev->dev.parent)
                device_unlock(busid_priv->udev->dev.parent);
        if (ret < 0)
                dev_err(&busid_priv->udev->dev, "rebind failed\n");
        return ret;
}

static void stub_device_rebind(void)
{
#if IS_MODULE(CONFIG_USBIP_HOST)
        struct bus_id_priv *busid_priv;
        int i;

        /* update status to STUB_BUSID_OTHER so probe ignores the device */
        spin_lock(&busid_table_lock);
        for (i = 0; i < MAX_BUSID; i++) {
                if (busid_table[i].name[0] &&
                    busid_table[i].shutdown_busid) {
                        busid_priv = &(busid_table[i]);
                        busid_priv->status = STUB_BUSID_OTHER;
                }
        }
        spin_unlock(&busid_table_lock);

        /* now run rebind - no need to hold locks. driver files are removed */
        for (i = 0; i < MAX_BUSID; i++) {
                if (busid_table[i].name[0] &&
                    busid_table[i].shutdown_busid) {
                        busid_priv = &(busid_table[i]);
                        do_rebind(busid_table[i].name, busid_priv);
                }
        }
#endif
}

static ssize_t rebind_store(struct device_driver *dev, const char *buf,
                                 size_t count)
{
        int ret;
        int len;
        struct bus_id_priv *bid;

        /* buf length should be less that BUSID_SIZE */
        len = strnlen(buf, BUSID_SIZE);

        if (!(len < BUSID_SIZE))
                return -EINVAL;

        bid = get_busid_priv(buf);
        if (!bid)
                return -ENODEV;

        /* mark the device for deletion so probe ignores it during rescan */
        bid->status = STUB_BUSID_OTHER;
        /* release the busid lock */
        put_busid_priv(bid);

        ret = do_rebind((char *) buf, bid);
        if (ret < 0)
                return ret;

        /* delete device from busid_table */
        del_match_busid((char *) buf);

        return count;
}

static DRIVER_ATTR_WO(rebind);

static struct stub_priv *stub_priv_pop_from_listhead(struct list_head *listhead)
{
        struct stub_priv *priv, *tmp;

        list_for_each_entry_safe(priv, tmp, listhead, list) {
                list_del_init(&priv->list);
                return priv;
        }

        return NULL;
}

void stub_free_priv_and_urb(struct stub_priv *priv)
{
        struct urb *urb;
        int i;

        for (i = 0; i < priv->num_urbs; i++) {
                urb = priv->urbs[i];

                if (!urb)
                        return;

                kfree(urb->setup_packet);
                urb->setup_packet = NULL;


                if (urb->transfer_buffer && !priv->sgl) {
                        kfree(urb->transfer_buffer);
                        urb->transfer_buffer = NULL;
                }

                if (urb->num_sgs) {
                        sgl_free(urb->sg);
                        urb->sg = NULL;
                        urb->num_sgs = 0;
                }

                usb_free_urb(urb);
        }
        if (!list_empty(&priv->list))
                list_del(&priv->list);
        if (priv->sgl)
                sgl_free(priv->sgl);
        kfree(priv->urbs);
        kmem_cache_free(stub_priv_cache, priv);
}

static struct stub_priv *stub_priv_pop(struct stub_device *sdev)
{
        unsigned long flags;
        struct stub_priv *priv;

        spin_lock_irqsave(&sdev->priv_lock, flags);

        priv = stub_priv_pop_from_listhead(&sdev->priv_init);
        if (priv)
                goto done;

        priv = stub_priv_pop_from_listhead(&sdev->priv_tx);
        if (priv)
                goto done;

        priv = stub_priv_pop_from_listhead(&sdev->priv_free);

done:
        spin_unlock_irqrestore(&sdev->priv_lock, flags);

        return priv;
}

void stub_device_cleanup_urbs(struct stub_device *sdev)
{
        struct stub_priv *priv;
        int i;

        dev_dbg(&sdev->udev->dev, "Stub device cleaning up urbs\n");

        while ((priv = stub_priv_pop(sdev))) {
                for (i = 0; i < priv->num_urbs; i++)
                        usb_kill_urb(priv->urbs[i]);

                stub_free_priv_and_urb(priv);
        }
}

static int __init usbip_host_init(void)
{
        int ret;

        init_busid_table();

        stub_priv_cache = KMEM_CACHE(stub_priv, SLAB_HWCACHE_ALIGN);
        if (!stub_priv_cache) {
                pr_err("kmem_cache_create failed\n");
                return -ENOMEM;
        }

        ret = usb_register_device_driver(&stub_driver, THIS_MODULE);
        if (ret) {
                pr_err("usb_register failed %d\n", ret);
                goto err_usb_register;
        }

        ret = driver_create_file(&stub_driver.driver,
                                 &driver_attr_match_busid);
        if (ret) {
                pr_err("driver_create_file failed\n");
                goto err_create_file;
        }

        ret = driver_create_file(&stub_driver.driver,
                                 &driver_attr_rebind);
        if (ret) {
                pr_err("driver_create_file failed\n");
                goto err_create_file;
        }

        return ret;

err_create_file:
        usb_deregister_device_driver(&stub_driver);
err_usb_register:
        kmem_cache_destroy(stub_priv_cache);
        return ret;
}

static void __exit usbip_host_exit(void)
{
        driver_remove_file(&stub_driver.driver,
                           &driver_attr_match_busid);

        driver_remove_file(&stub_driver.driver,
                           &driver_attr_rebind);

        /*
         * deregister() calls stub_disconnect() for all devices. Device
         * specific data is cleared in stub_disconnect().
         */
        usb_deregister_device_driver(&stub_driver);

        /* initiate scan to attach devices */
        stub_device_rebind();

        kmem_cache_destroy(stub_priv_cache);
}

module_init(usbip_host_init);
module_exit(usbip_host_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");