[PATCH 2/3] usb: gadget: legacy: add 9pfs multi gadget

From: Michael Grzeschik
Date: Mon Jan 15 2024 - 20:56:08 EST


Add the newly introduced 9pfs transport gadget interface with an new
multi composed gadget together with acm and eem.

The usb forwarding can be started as follows:

Start the Network export via diod to localhost:

$ diod -f -n -d 0 -S -l 0.0.0.0:9999 -e $PWD

Start the python transport:

$ python p9_fwd.py

The gadget can then mount the exported filesystem:

$ mount -t 9p -o trans=usbg,aname=$PWD usb9pfs0 /mnt/9p

When using this legacy module, it is also possible to
mount the 9PFS usb dir as root filesystem. The kernel
commandline looks like this:

root=usb9pfs0 rootfstype=9p rootflags=trans=usbg,cache=loose,uname=root,access=0,dfltuid=0,dfltgid=0,aname=/path/to/rootfs

Signed-off-by: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx>
---
drivers/usb/gadget/legacy/9pfs.c | 260 +++++++++++++++++++++++++++++++++++++
drivers/usb/gadget/legacy/Kconfig | 15 +++
drivers/usb/gadget/legacy/Makefile | 2 +
3 files changed, 277 insertions(+)

diff --git a/drivers/usb/gadget/legacy/9pfs.c b/drivers/usb/gadget/legacy/9pfs.c
new file mode 100644
index 0000000000000..e38fc89c89ded
--- /dev/null
+++ b/drivers/usb/gadget/legacy/9pfs.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * usb9pfs.c -- Gadget usb9pfs
+ *
+ * Copyright (C) 2023 Michael Grzeschik
+ */
+
+/*
+ * Gadget usb9pfs only needs two bulk endpoints, and will use the usb9pfs usb
+ * transport to mount host filesystem via usb gadget.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/usb/composite.h>
+#include <linux/netdevice.h>
+
+#include "u_eem.h"
+#include "u_ether.h"
+
+/*-------------------------------------------------------------------------*/
+USB_GADGET_COMPOSITE_OPTIONS();
+
+USB_ETHERNET_MODULE_PARAMETERS();
+
+/* Defines */
+
+#define DRIVER_VERSION_STR "v1.0"
+#define DRIVER_VERSION_NUM 0x1000
+
+#define DRIVER_DESC "Composite Gadget (9P + ACM + NCM)"
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_VENDOR_NUM 0x1d6b /* Linux Foundation */
+#define DRIVER_PRODUCT_NUM 0x0109 /* Linux-USB 9PFS Gadget */
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof(device_desc),
+ .bDescriptorType = USB_DT_DEVICE,
+
+ /* .bcdUSB = DYNAMIC */
+
+ .bDeviceClass = USB_CLASS_MISC,
+ .bDeviceSubClass = 2,
+ .bDeviceProtocol = 1,
+
+ /* .bMaxPacketSize0 = f(hardware) */
+
+ /* Vendor and product id can be overridden by module parameters. */
+ .idVendor = cpu_to_le16(DRIVER_VENDOR_NUM),
+ .idProduct = cpu_to_le16(DRIVER_PRODUCT_NUM),
+ /* .bcdDevice = f(hardware) */
+ /* .iManufacturer = DYNAMIC */
+ /* .iProduct = DYNAMIC */
+ /* NO SERIAL NUMBER */
+ /*.bNumConfigurations = DYNAMIC*/
+};
+
+static const struct usb_descriptor_header *otg_desc[2];
+
+static struct usb_string strings_dev[] = {
+ [USB_GADGET_MANUFACTURER_IDX].s = "",
+ [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC,
+ [USB_GADGET_SERIAL_IDX].s = "",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+ &stringtab_dev,
+ NULL,
+};
+
+static struct usb_configuration cdc_driver_conf = {
+ .label = DRIVER_DESC,
+ .bConfigurationValue = 1,
+ /* .iConfiguration = DYNAMIC */
+ .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+static struct usb_function *f_9pfs;
+static struct usb_function_instance *fi_9pfs;
+
+static struct usb_function *f_acm;
+static struct usb_function_instance *fi_acm;
+
+static struct usb_function *f_eem;
+static struct usb_function_instance *fi_eem;
+
+static int cdc_do_config(struct usb_configuration *c)
+{
+ int ret;
+
+ if (gadget_is_otg(c->cdev->gadget)) {
+ c->descriptors = otg_desc;
+ c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+ }
+
+ f_9pfs = usb_get_function(fi_9pfs);
+ if (IS_ERR(f_9pfs))
+ return PTR_ERR(f_9pfs);
+
+ ret = usb_add_function(c, f_9pfs);
+ if (ret < 0)
+ goto err_func_9pfs;
+
+ f_acm = usb_get_function(fi_acm);
+ if (IS_ERR(f_acm)) {
+ ret = PTR_ERR(f_acm);
+ goto err_func_acm;
+ }
+
+ ret = usb_add_function(c, f_acm);
+ if (ret)
+ goto err_conf;
+
+ f_eem = usb_get_function(fi_eem);
+ if (IS_ERR(f_eem)) {
+ ret = PTR_ERR(f_eem);
+ goto err_eem;
+ }
+
+ ret = usb_add_function(c, f_eem);
+ if (ret)
+ goto err_run;
+
+ return 0;
+err_run:
+ usb_put_function(f_eem);
+err_eem:
+ usb_remove_function(c, f_acm);
+err_conf:
+ usb_put_function(f_acm);
+err_func_acm:
+ usb_remove_function(c, f_9pfs);
+err_func_9pfs:
+ usb_put_function(f_9pfs);
+ return ret;
+}
+
+static int usb9pfs_bind(struct usb_composite_dev *cdev)
+{
+ struct f_eem_opts *eem_opts = NULL;
+ int status;
+
+ fi_9pfs = usb_get_function_instance("usb9pfs");
+ if (IS_ERR(fi_9pfs))
+ return PTR_ERR(fi_9pfs);
+
+ /* set up serial link layer */
+ fi_acm = usb_get_function_instance("acm");
+ if (IS_ERR(fi_acm)) {
+ status = PTR_ERR(fi_acm);
+ goto err_conf_acm;
+ }
+
+ fi_eem = usb_get_function_instance("eem");
+ if (IS_ERR(fi_eem)) {
+ status = PTR_ERR(fi_eem);
+ goto err_conf_eem;
+ }
+
+ eem_opts = container_of(fi_eem, struct f_eem_opts, func_inst);
+
+ gether_set_qmult(eem_opts->net, qmult);
+ if (!gether_set_host_addr(eem_opts->net, host_addr))
+ pr_info("using host ethernet address: %s", host_addr);
+ if (!gether_set_dev_addr(eem_opts->net, dev_addr))
+ pr_info("using self ethernet address: %s", dev_addr);
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+ status = usb_string_ids_tab(cdev, strings_dev);
+ if (status < 0)
+ return status;
+
+ device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;
+ device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
+ device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;
+
+ /* support OTG systems */
+ if (gadget_is_otg(cdev->gadget)) {
+ if (!otg_desc[0]) {
+ struct usb_descriptor_header *usb_desc;
+
+ usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
+ if (!usb_desc) {
+ status = -ENOMEM;
+ goto err_conf_otg;
+ }
+ usb_otg_descriptor_init(cdev->gadget, usb_desc);
+ otg_desc[0] = usb_desc;
+ otg_desc[1] = NULL;
+ }
+ }
+
+ status = usb_add_config(cdev, &cdc_driver_conf, cdc_do_config);
+ if (status)
+ goto err_free_otg_desc;
+
+ usb_ep_autoconfig_reset(cdev->gadget);
+ usb_composite_overwrite_options(cdev, &coverwrite);
+
+ dev_info(&cdev->gadget->dev, DRIVER_DESC " version: " DRIVER_VERSION_STR "\n");
+
+ return 0;
+
+err_free_otg_desc:
+ kfree(otg_desc[0]);
+ otg_desc[0] = NULL;
+err_conf_otg:
+ usb_put_function_instance(fi_eem);
+err_conf_eem:
+ usb_put_function_instance(fi_acm);
+err_conf_acm:
+ usb_put_function_instance(fi_9pfs);
+ return status;
+}
+
+static int usb9pfs_unbind(struct usb_composite_dev *cdev)
+{
+ if (!IS_ERR_OR_NULL(f_eem))
+ usb_put_function(f_eem);
+ usb_put_function_instance(fi_eem);
+ if (!IS_ERR_OR_NULL(f_acm))
+ usb_put_function(f_acm);
+ usb_put_function_instance(fi_acm);
+ if (!IS_ERR_OR_NULL(f_9pfs))
+ usb_put_function(f_9pfs);
+ usb_put_function_instance(fi_9pfs);
+ kfree(otg_desc[0]);
+ otg_desc[0] = NULL;
+
+ return 0;
+}
+
+static struct usb_composite_driver usb9pfs_driver = {
+ .name = "usb9pfs",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .max_speed = USB_SPEED_SUPER,
+ .bind = usb9pfs_bind,
+ .unbind = usb9pfs_unbind,
+};
+
+module_usb_composite_driver(usb9pfs_driver);
+
+MODULE_AUTHOR("Michael Grzeschik");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig
index 0a7b382fbe27c..8569aecc1487b 100644
--- a/drivers/usb/gadget/legacy/Kconfig
+++ b/drivers/usb/gadget/legacy/Kconfig
@@ -62,6 +62,21 @@ config USB_ZERO
Say "y" to link the driver statically, or "m" to build a
dynamically linked module called "g_zero".

+config USB_9PFS
+ tristate "Gadget 9PFS"
+ select USB_LIBCOMPOSITE
+ select USB_U_SERIAL
+ select USB_U_ETHER
+ select USB_F_ACM
+ select USB_F_EEM
+ select USB_F_9PFS
+ select NET_9P
+ help
+ 9PFS Transport Layer
+
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "g_9pfs".
+
config USB_ZERO_HNPTEST
bool "HNP Test Device"
depends on USB_ZERO && USB_OTG
diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile
index 4d864bf82799d..99c098800d898 100644
--- a/drivers/usb/gadget/legacy/Makefile
+++ b/drivers/usb/gadget/legacy/Makefile
@@ -8,6 +8,7 @@ ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
ccflags-y += -I$(srctree)/drivers/usb/gadget/function/

g_zero-y := zero.o
+g_9pfs-y := 9pfs.o
g_audio-y := audio.o
g_ether-y := ether.o
g_serial-y := serial.o
@@ -26,6 +27,7 @@ g_acm_ms-y := acm_ms.o
g_tcm_usb_gadget-y := tcm_usb_gadget.o

obj-$(CONFIG_USB_ZERO) += g_zero.o
+obj-$(CONFIG_USB_9PFS) += g_9pfs.o
obj-$(CONFIG_USB_AUDIO) += g_audio.o
obj-$(CONFIG_USB_ETH) += g_ether.o
obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o

--
2.39.2