Design and Implementation of Linux Gadget Configfs

Design and Implementation of Linux Gadget Configfs

This article briefly explains the implementation ideas of Gadget Configfs.

1. Configfs Implementation Design

The following describes the working principle of configfs. In configfs, there are items and groups, both presented in the form of directories.

The difference between items and groups is that a group can contain other groups.

Users can create and delete directories, but cannot delete files; files can be read-only or read-write, depending on the content they represent.

The filesystem part of configfs operates on (1) subsystem (2) config_items (3) groups and (4) configfs_attributes, these structures are generic and the same for all configured element types. However, they are embedded into larger structures for specific purposes. In the diagram below, there is a structure named “cs” that contains a config_item; there is also a structure named “sa” that contains a configfs_attribute.

Both items and groups can have attributes, which are represented as files.

The diagram below shows only one item, and the view of the filesystem is as follows:

  ./
  ./cs        (directory)
     |
     +--sa    (file)
     |

Whenever a user reads/writes the “sa” file, a function is called that takes a struct config_item and a struct configfs_attribute. In that function, the well-known container_of technique is used to obtain “cs” and “sa”.

And calls the corresponding function of sa (show or store), while passing “cs” and a character buffer.

The “show” function is used to display the file content (copying data from cs to the buffer),

and the “store” function is used to modify the file content (copying data from the buffer to cs).

  typedef struct configured_structure cs;
  typedef struct specific_attribute sa;
                                         sa
                         +----------------------------------+
          cs             |  (*show)(cs *, buffer);          |
  +-----------------+    |  (*store)(cs *, buffer, length); |
  |                 |    |                                  |
  | +-------------+ |    |       +------------------+       |
  | | struct      |-|----|------>|struct            |       |
  | | config_item | |    |       |configfs_attribute|       |
  | +-------------+ |    |       +------------------+       |
  |                 |    +----------------------------------+
  | data to be set  |                .
  |                 |                .
  +-----------------+                .

The filenames are determined by the designers of the config items/groups, while directories can usually be freely named. A group can automatically create multiple default subgroups.

 mount none /sys/kernel/config -t configfs

After mounting, the usb_gadget subsystem configfs can be seen mounted; refer to <span>Documentation/filesystems/configfs.rst</span>.

2. Corresponding Implementation in USB Gadget

(1) A gadget has its configuration group, which contains some attributes (such as idVendor, idProduct, etc.) and default subgroups (configs, functions, strings).

Writing data to these attributes will store the information in the appropriate locations; in the configs, functions, and strings subgroups, users can create their own subgroups to represent configurations, functions, and string groups for specific languages.

(2) Users create configurations and functions, and create symbolic links to functions within the configuration; when data is written to the gadget’s UDC attribute (which means binding the gadget to the UDC), this information is used.

The code in drivers/usb/gadget/configfs.c traverses all configurations and binds all functions within each configuration, thereby binding the entire gadget.

(3) The file drivers/usb/gadget/configfs.c contains code for the following functions:

- config_group of the gadget
- default groups of the gadget (configs, functions, strings)
- associating functions with configurations (symbolic links)

(4) Each USB function naturally has its own unique configuration requirements, so the config_groups for specific functions are defined in the function implementation files drivers/usb/gadget/f_*.c.

(5) The way the function code is written causes it to call usb_get_function_instance(), which is the specific underlying operation for the soft link, and this function will call request_module. Therefore, as long as modprobe works normally, the module for the specific function will be automatically loaded.

Note that the reverse is not true; after the gadget is disabled and removed, these modules will still remain loaded.

3 Examples

3.1 Correspondence of Design Concepts

  • • usb_gadget corresponds to subsystem
    $ /sys/kernel/config/usb_gadget                                  
  • • g1 corresponds to groups
$ /sys/kernel/config/usb_gadget/g1                                 
UDC              bDeviceSubClass  bcdUSB     idProduct  os_desc
bDeviceClass     bMaxPacketSize0  configs    idVendor   strings
bDeviceProtocol  bcdDevice        functions  max_speed
  • • strings correspond to config item
$ /sys/kernel/config/usb_gadget/g1/configs/b.1/strings
 total 0
drwxrwx--- 2 root root    0 Jan  1 12:00 .
drwxr-xr-x 3 root root    0 Jan  1 12:00 ..
-rw-r--r-- 1 root root 4096 Jan  1 12:00 configuration   
  • • configuration file corresponds to configfs_attributes
-rw-r--r-- 1 root root 4096 Jan  1 12:00 configuration  

3.2 How Group g1 is Created and Applied

The implementation of groups g1 is called when mkdir is executed under usb_gadget after mounting, mainly in the gadgets_make function, where the group is created and checks the rationality of setting attributes.

static struct config_group *gadgets_make(                                       
        struct config_group *group,                                             
        const char *name)                                                       
{                                                                               
    struct gadget_info *gi;                                                     
                                                                                
    gi = kzalloc(sizeof(*gi), GFP_KERNEL);                                      
    if (!gi)                                                                    
        return ERR_PTR(-ENOMEM);                                                
                                                                                
    config_group_init_type_name(&gi->group, name, &gadget_root_type);           
                                                                                
    config_group_init_type_name(&gi->functions_group, "functions",              
            &functions_type);                                                    
    configfs_add_default_group(&gi->functions_group, &gi->group);               
                                                                                
    config_group_init_type_name(&gi->configs_group, "configs",                  
            &config_desc_type);                                                 
    configfs_add_default_group(&gi->configs_group, &gi->group);                 
                                                                                
    config_group_init_type_name(&gi->strings_group, "strings",                  
            &gadget_strings_strings_type);                                      
    configfs_add_default_group(&gi->strings_group, &gi->group);                 
                                                                                
    config_group_init_type_name(&gi->os_desc_group, "os_desc",                  
            &os_desc_type);                                                      
    configfs_add_default_group(&gi->os_desc_group, &gi->group);                 
                                                                                
    gi->composite.bind = configfs_do_nothing;                                   
    gi->composite.unbind = configfs_do_nothing;                                 
    gi->composite.suspend = NULL;                                               
    gi->composite.resume = NULL;                                                
    gi->composite.max_speed = USB_SPEED_SUPER_PLUS;                             
                                                                                
    spin_lock_init(&gi->spinlock);                                              
    mutex_init(&gi->lock);                                                      
    INIT_LIST_HEAD(&gi->string_list);                                           
    INIT_LIST_HEAD(&gi->available_func);                                        
                                                                                
    composite_init_dev(&gi->cdev);                                              
    gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE;                                 
    gi->cdev.desc.bDescriptorType = USB_DT_DEVICE;                              
    gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice());              
                                                                                
    gi->cdev.desc.bcdUSB = cpu_to_le16(get_default_bcdUSB());                   
    gi->cdev.desc.bDeviceClass = USB_CLASS_COMM;                               
    gi->cdev.desc.bDeviceSubClass = USB_SUBCLASS_COMM_DIRECT;                   
    gi->cdev.desc.bDeviceProtocol = USB_PROTOCOL_VENDOR;                        
                                                                                
    gi->composite.gadget_driver = configfs_driver_template;                     
                                                                                
    gi->composite.gadget_driver.driver.name = kasprintf(GFP_KERNEL,              
                                "configfs-gadget.%s", name);                    
    if (!gi->composite.gadget_driver.driver.name)                               
        goto err;                                                               
                                                                                
    gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);           
    gi->composite.name = gi->composite.gadget_driver.function;                  
                                                                                
    if (!gi->composite.gadget_driver.function)                                  
        goto out_free_driver_name;                                              
                                                                                
    if (android_device_create(gi) < 0)                                          
        goto out_free_driver_name;                                              
                                                                                
    return &gi->group;                                                           
                                                                                
out_free_driver_name:                                                            
    kfree(gi->composite.gadget_driver.driver.name);                             
err:                                                                            
    kfree(gi);                                                                 
    return ERR_PTR(-ENOMEM);                                                    
}                                      
static struct configfs_group_operations gadgets_ops = {                         
    .make_group     = &gadgets_make,                                            
    .drop_item      = &gadgets_drop,                                            
};                                                                                  
static const struct config_item_type gadgets_type = {                                
    .ct_group_ops   = &gadgets_ops,                                                 
    .ct_owner       = THIS_MODULE,                                                  
};      
static struct configfs_subsystem gadget_subsys = {                                  
    .su_group = {                                                                    
        .cg_item = {                                                                 
            .ci_namebuf = "usb_gadget",                                              
            .ci_type = &gadgets_type,                                                
        },                                                                          
    },                                                                              
    .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),                        
};                                                                                  
void unregister_gadget_item(struct config_item *item)                                
{                                                                                   
    struct gadget_info *gi = to_gadget_info(item);                                  
                                                                                   
    mutex_lock(&gi->lock);                                                           
    unregister_gadget(gi);                                                           
    mutex_unlock(&gi->lock);                                                        
}                                                                                   
EXPORT_SYMBOL_GPL(unregister_gadget_item);                                          
static int __init gadget_cfs_init(void)                                              
{                                                                                   
    int ret;                                                                        
                                                                                   
    config_group_init(&gadget_subsys.su_group);                                      
                                                                                   
    ret = configfs_register_subsystem(&gadget_subsys);                               
                                                                                   
#ifdef CONFIG_USB_CONFIGFS_UEVENT                                                    
    android_class = class_create(THIS_MODULE, "android_usb");                       
    if (IS_ERR(android_class))                                                       
        return PTR_ERR(android_class);                                               
#endif                                                                                  
    return ret;                                                                     
}                               

For example, the attributes under g1 correspond to the actions of the gadgets_make interface one by one.

$ /sys/kernel/config/usb_gadget/g1# ls -al
 total 0
drwxrwx--- 6 root root    0 Jan  1 12:00 .
drwxr-xr-x 4 root root    0 Jan  1 12:00 ..
-rw-r--r-- 1 root root 4096 Jan  1 12:00 UDC
-rw-r--r-- 1 root root 4096 Jan  1 14:04 bDeviceClass
-rw-r--r-- 1 root root 4096 Jan  1 14:04 bDeviceProtocol
-rw-r--r-- 1 root root 4096 Jan  1 14:04 bDeviceSubClass
-rw-r--r-- 1 root root 4096 Jan  1 14:04 bMaxPacketSize0
-rw-r--r-- 1 root root 4096 Jan  1 12:00 bcdDevice
-rw-r--r-- 1 root root 4096 Jan  1 12:00 bcdUSB
 drwxr-xr-x 3 root root    0 Jan  1 12:00 configs
 drwxr-xr-x 7 root root    0 Jan  1 12:00 functions
-rw-r--r-- 1 root root 4096 Jan  1 12:00 idProduct
-rw-r--r-- 1 root root 4096 Jan  1 12:00 idVendor
-rw-r--r-- 1 root root 4096 Jan  1 14:04 max_speed
 drwxr-xr-x 2 root root    0 Jan  1 12:00 os_desc
 drwxr-xr-x 3 root root    0 Jan  1 12:00 strings

The above regular files correspond to the attributes of gadget make’s gadget_root_attrs.

static struct configfs_attribute *gadget_root_attrs[] = {                       
    &gadget_dev_desc_attr_bDeviceClass,                                         
    &gadget_dev_desc_attr_bDeviceSubClass,                                      
    &gadget_dev_desc_attr_bDeviceProtocol,                                      
    &gadget_dev_desc_attr_bMaxPacketSize0,                                      
    &gadget_dev_desc_attr_idVendor,                                             
    &gadget_dev_desc_attr_idProduct,                                            
    &gadget_dev_desc_attr_bcdDevice,                                            
    &gadget_dev_desc_attr_bcdUSB,                                               
    &gadget_dev_desc_attr_UDC,                                                 
    &gadget_dev_desc_attr_max_speed,                                            
    NULL,                                                                       
};              

Then the corresponding store and show interfaces.

#define GI_DEVICE_DESC_SIMPLE_R_u8(__name)  \                                   
static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \      
            char *page) \                                                       
{   \                                                                           
    return sprintf(page, "0x%02x\n", \                                          
        to_gadget_info(item)->cdev.desc.__name); \                              
}                                                                                
#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \                                   
static ssize_t gadget_dev_desc_##__name##_show(struct config_item *item, \      
            char *page) \                                                       
{   \                                                                           
    return sprintf(page, "0x%04x\n", \                                          
        le16_to_cpup(&to_gadget_info(item)->cdev.desc.__name)); \                
}                                                                                
#define GI_DEVICE_DESC_SIMPLE_W_u8(_name)       \                               
static ssize_t gadget_dev_desc_##_name##_store(struct config_item *item, \      
        const char *page, size_t len)       \                                   
{                           \                                                   
    u8 val;                     \                                               
    int ret;                    \                                               
    ret = kstrtou8(page, 0, &val);          \                                   
    if (ret)                    \                                               
        return ret;             \                                               
    to_gadget_info(item)->cdev.desc._name = val;    \                           
    return len;                 \                                               
}                                                                                
#define GI_DEVICE_DESC_SIMPLE_W_u16(_name)  \                                   
static ssize_t gadget_dev_desc_##_name##_store(struct config_item *item, \      
        const char *page, size_t len)       \                                   
{                           \                                                   
    u16 val;                    \                                               
    int ret;                    \                                               
    ret = kstrtou16(page, 0, &val);         \                                   
    if (ret)                    \                                               
        return ret;             \                                               
    to_gadget_info(item)->cdev.desc._name = cpu_to_le16p(&val); \               
    return len;                 \                                               
}                                                                                
#define GI_DEVICE_DESC_SIMPLE_RW(_name, _type)  \                               
    GI_DEVICE_DESC_SIMPLE_R_##_type(_name)  \                                   
    GI_DEVICE_DESC_SIMPLE_W_##_type(_name)                                      
GI_DEVICE_DESC_SIMPLE_R_u16(bcdUSB);                                             
GI_DEVICE_DESC_SIMPLE_RW(bDeviceClass, u8);                                      
GI_DEVICE_DESC_SIMPLE_RW(bDeviceSubClass, u8);                                  
GI_DEVICE_DESC_SIMPLE_RW(bDeviceProtocol, u8); 
...
struct configfs_attribute {                                                      
    const char      *ca_name;                                                    
    struct module       *ca_owner;                                               
    umode_t         ca_mode;                                                    
    ssize_t (*show)(struct config_item *, char *);                               
    ssize_t (*store)(struct config_item *, const char *, size_t);               
};                   

3.3 Other Directory Creation and Application

Similarly, there are function_make (functions) and config_desc_make (configs), which are used to create Gadget configurations and functions, and then link them through config_usb_cfg_link, and finally apply them through the UDC interface udc_bind_to_driver.

Leave a Comment