Analysis of Linux USB Gadget ADB Driver

Analysis of Linux USB Gadget ADB Driver

This article briefly explains how to configure the USB ADB device through Configfs ffs. The typical block diagram of a working Linux ADB setup is shown below, primarily illustrating how the USB ADB device side operates.

Host Side (Host):
+-----------------------------------------------+
|                 Host Computer                 |
|  +---------------------+  +----------------+  |
|  |     ADB Client      |  |   ADB Server   |  |
|  |  (adb command line) |  | (adbd hostside)|  |
|  +---------------------+  +----------------+  |
|          |                  |                 |
|          | TCP/IP (e.g.,    | USB Host        |
|          | localhost:5037)  | Protocol        |
|          |                  v                 |
|          |          +----------------+        |
|          |          |  USB Host      |        |
|          |          | Controller     |        |
|          |          | Driver         |        |
|          |          +----------------+        |
+-----------------------------------------------+
                         | USB Cable
Device Side (Device):          |
+-----------------------------------------------+
|                 Target Device                 |
|  +-----------------------------------------+  |
|  |               User Space                |  |
|  |  +----------------+                     |  |
|  |  |     adbd       |                     |  |
|  |  | (daemon)       |                     |  |
|  |  +----------------+                     |  |
|  |        | (e.g., UNIX socket or TCP)     |  |
|  |        v                                |  |
|  +-----------------------------------------+  |
|  |         Kernel Space                    |  |
|  |  +----------------+                     |  |
|  |  |   Gadget FFS   |                     |  |
|  |  |   ( ffs.adb)   |                     |  |
|  |  +----------------+                     |  |
|  |        |                                |  |
|  |  +----------------+                     |  |
|  |  |   UDC Driver   |                     |  |
|  |  | (gadget driver)|                     |  |
|  |  +----------------+                     |  |
|  +-----------------------------------------+  |
|         Hardware                              |
+-----------------------------------------------+

1. Configuring Gadget Configfs

First, create the ffs filesystem for enumerating ADB. As long as the controller driver is configured correctly, executing the following commands will enumerate the USB ADB device.

$ mkdir -p /sys/kernel/config/usb_gadget/g1/
$ mkdir /dev/usb-ffs/adb -m 0770  
$ mount -t functionfs adb /dev/usb-ffs/adb  
$ mkdir -p /sys/kernel/config/usb_gadget/g1/functions/ffs.adb
$ mkdir /sys/kernel/config/usb_gadget/g1/configs/ -m 0770  
$ ln -s /sys/kernel/config/usb_gadget/g1/functions/ffs.adb /sys/kernel/config/usb_gadget/g1/configs/b.1/f1
$ start-stop-daemon --start --quiet --background --exec /usr/bin/adbd  
$ echo fc000000.usb > UDC 

2. Configuration Process Analysis

Analyze the Configfs script used to configure ADB. When the functionfs ffs.adb is registered, it triggers the ffs filesystem registration log via functionfs_init.

file system registered

Below is the prototype function for registering the functionfs filesystem.

static int functionfs_init(void)                                                    
{                                                                                   
    int ret;                                                                        
                                                                                    
    ENTER();                                                                        
                                                                                    
    ret = register_filesystem(&ffs_fs_type);                                        
    if (!ret)                                                                       
        pr_info("file system registered\n");                                        
    else                                                                            
        pr_err("failed registering file system (%d)\n", ret);                       
                                                                                    
    return ret;                                                                     
}

Using the command cat, you can see the currently loaded filesystems (nodev functionfs).

$    cat /proc/filesystems
nodev   sysfs
nodev   tmpfs
nodev   bdev
nodev   proc
nodev   cgroup
nodev   cgroup2
nodev   cpuset
nodev   devtmpfs
nodev   configfs
nodev   debugfs
nodev   tracefs
nodev   securityfs
nodev   sockfs
nodev   pipefs
nodev   ramfs
nodev   rpc_pipefs
nodev   devpts
        ext3
        ext2
        ext4
        squashfs
        vfat
        iso9660
nodev   nfs
nodev   nfs4
        ntfs
nodev   jffs2
        fuseblk
nodev   fuse
nodev   fusectl
        xfs
nodev   ubifs
nodev   pstore
nodev   functionfs

Mount the filesystem

$ mkdir adb
$ mount -t functionfs adb /dev/usb-ffs/adb

Typically, this will call ffs_fs_init_fs_context->ffs_fs_get_tree->ffs_sb_fill->ffs_sb_create_file to create ep0.

static const struct fs_context_operations ffs_fs_context_ops = {                    
    .free       = ffs_fs_free_fc,                                                    
    .parse_param    = ffs_fs_parse_param,                                            
    .get_tree   = ffs_fs_get_tree,                                                  
};                                                                                  
                                                                                    
static int ffs_fs_init_fs_context(struct fs_context *fc)                            
{                                                                                   
    struct ffs_sb_fill_data *ctx;                                                    
                                                                                    
    ctx = kzalloc(sizeof(struct ffs_sb_fill_data), GFP_KERNEL);                     
    if (!ctx)                                                                        
        return -ENOMEM;                                                              
                                                                                    
    ctx->perms.mode = S_IFREG | 0600;                                                
    ctx->perms.uid = GLOBAL_ROOT_UID;                                                
    ctx->perms.gid = GLOBAL_ROOT_GID;                                                
    ctx->root_mode = S_IFDIR | 0500;                                                
    ctx->no_disconnect = false;                                                      
                                                                                    
    fc->fs_private = ctx;                                                            
    fc->ops = &ffs_fs_context_ops;                                                 
    return 0;                                                                        
}
static int ffs_fs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{                                                                                
    struct ffs_sb_fill_data *data = fc->fs_private;                              
    struct fs_parse_result result;                                               
    int opt;                                                                 
                                                                                
    ENTER();                                                                 
                                                                                
    opt = fs_parse(fc, ffs_fs_fs_parameters, param, &result);                   
    if (opt < 0)                                                                
        return opt;                                                              
                                                                                
    switch (opt) {                                                               
    case Opt_no_disconnect:                                                      
        data->no_disconnect = result.boolean;                                    
        break;                                                                 
    case Opt_rmode:                                                              
        data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;                   
        break;                                                                 
    case Opt_fmode:                                                              
        data->perms.mode = (result.uint_32 & 0666) | S_IFREG;                   
        break;                                                                 
    case Opt_mode:                                                               
        data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;                   
        data->perms.mode = (result.uint_32 & 0666) | S_IFREG;                   
        break;                                                                 
                                                                                
    case Opt_uid:                                                                
        data->perms.uid = make_kuid(current_user_ns(), result.uint_32);         
        if (!uid_valid(data->perms.uid))                                        
            goto unmapped_value;                                                
        break;                                                                 
    case Opt_gid:                                                                
        data->perms.gid = make_kgid(current_user_ns(), result.uint_32);         
        if (!gid_valid(data->perms.gid))                                        
            goto unmapped_value;                                                
        break;                                                                 
                                                                                
    default:                                                                 
        return -ENOPARAM;                                                        
    }                                                                            
                                                                                
    return 0;                                                                    
                                                                                
unmapped_value:                                                                 
    return invalf(fc, "%s: unmapped value: %u", param->key, result.uint_32);    
}
static int ffs_fs_get_tree(struct fs_context *fc)                               
{                                                                                
    struct ffs_sb_fill_data *ctx = fc->fs_private;                              
    struct ffs_data *ffs;                                                        
    int ret;                                                                 
                                                                                
    ENTER();                                                                 
                                                                                
    if (!fc->source)                                                             
        return invalf(fc, "No source specified");                               
                                                                                
    ffs = ffs_data_new(fc->source);                                              
    if (unlikely(!ffs))                                                         
        return -ENOMEM;                                                         
    ffs->file_perms = ctx->perms;                                                
    ffs->no_disconnect = ctx->no_disconnect;                                    
                                                                                
    ffs->dev_name = kstrdup(fc->source, GFP_KERNEL);                            
    if (unlikely(!ffs->dev_name)) {                                              
        ffs_data_put(ffs);                                                       
        return -ENOMEM;                                                         
    }                                                                            
                                                                                
    ret = ffs_acquire_dev(ffs->dev_name, ffs);                                 
    if (ret) {                                                                 
        ffs_data_put(ffs);                                                       
        return ret;                                                              
    }                                                                            
                                                                                
    ctx->ffs_data = ffs;                                                        
    return get_tree_nodev(fc, ffs_sb_fill);                                      
}
static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc)           
{                                                                                
    struct ffs_sb_fill_data *data = fc->fs_private;                              
    struct inode    *inode;                                                      
    struct ffs_data *ffs = data->ffs_data;                                      
                                                                                
    ENTER();                                                                 
                                                                                
    ffs->sb              = sb;                                                  
    data->ffs_data       = NULL;                                                
    sb->s_fs_info        = ffs;                                                 
    sb->s_blocksize      = PAGE_SIZE;                                            
    sb->s_blocksize_bits = PAGE_SHIFT;                                           
    sb->s_magic          = FUNCTIONFS_MAGIC;                                    
    sb->s_op             = &ffs_sb_operations;                                 
    sb->s_time_gran      = 1;                                                    
                                                                                
    /* Root inode */                                                            
    data->perms.mode = data->root_mode;                                         
    inode = ffs_sb_make_inode(sb, NULL,                                         
                  &simple_dir_operations,                                        
                  &simple_dir_inode_operations,                                 
                  &data->perms);                                                
    sb->s_root = d_make_root(inode);                                            
    if (unlikely(!sb->s_root))                                                  
        return -ENOMEM;                                                         
                                                                                
    /* EP0 file */                                                              
    if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs,                            
                     &ffs_ep0_operations)))                                      
        return -ENOMEM;                                                         
                                                                                
    return 0;                                                                    
}

Accessing the ep0 driver will invoke ffs_sb_create_file(sb, “ep0”, ffs, &ffs_ep0_operations) while passing ffs_ep0_operations. When user space writes or reads, it calls the corresponding ffs_ep0_write and ffs_ep0_read.

static const struct file_operations ffs_ep0_operations = {                      
    .llseek =   no_llseek,                                                       
                                                                                
    .open =     ffs_ep0_open,                                                    
    .write =    ffs_ep0_write,                                                  
    .read =     ffs_ep0_read,                                                   
    .release =  ffs_ep0_release,                                                
    .unlocked_ioctl =   ffs_ep0_ioctl,                                          
    .poll =     ffs_ep0_poll,                                                    
};     
  • • Note: The filling of this descriptor and the creation of ep1IN and EP1OUT are implemented in the adbd code, so the enumeration of adb must first execute the adbd application to load the descriptors and strings.

When executing the adbd application

$ start-stop-daemon --start --quiet --background --exec /usr/bin/adbd 

The log will prompt as follows

read descriptors
read strings

3. Analysis of adbd Source Code

Based on buildroot to compile adbd, normally configuring the corresponding project will download the adbd source code from the internet for compilation. The main function of adbd is in main.cpp.

android-adbd-8.1.0+r23-8/adb/daemon$ 

Modifying main.cpp, you can see the version has been updated.

int main(int argc, char** argv) {                                                  
    while (true) {                                                                 
        static struct option opts[] = {                                            
#ifdef __ANDROID__                                                                 
            {"root_seclabel", required_argument, nullptr, 's'},                    
#endif                                                                             
            {"device_banner", required_argument, nullptr, 'b'},                    
            {"version", no_argument, nullptr, 'v'},                                
        };                                                                         
                                                                                   
        int option_index = 0;                                                       
        int c = getopt_long(argc, argv, "", opts, &option_index);                  
        if (c == -1) {                                                              
            break;                                                                 
        }                                                                          
                                                                                   
        switch (c) {                                                                
#ifdef __ANDROID__                                                                 
        case 's':                                                                 
            root_seclabel = optarg;                                                
            break;                                                                 
#endif                                                                             
        case 'b':                                                                 
            adb_device_banner = optarg;                                            
            break;                                                                 
        case 'v':                                                                 
-            printf("Android Debug Bridge Daemon version  %d.%d.%d\n", ADB_VERSION_MAJOR,                       ADB_VERSION_MINOR, ADB_SERVER_VERSION); 
+            printf("Android Debug Bridge Daemon version  ---------------------test----------------- %d.%d.%d\n", ADB_VERSION_MAJOR,
                   ADB_VERSION_MINOR, ADB_SERVER_VERSION);       
            return 0;                                                               
        default:                                                                    
            // getopt already prints "adbd: invalid option -- %c" for us.          
            return 1;                                                               
        }                                                                          
    }                                                                              
                                                                                   
    close_stdin();                                                                 
                                                                                   
#ifdef __ANDROID__                                                                 
    debuggerd_init(nullptr);                                                        
#endif                                                                             
                                                                                   
    adb_trace_init(argv);                                                           
                                                                                   
    D("Handling main()");                                                           
    return adbd_main(DEFAULT_ADB_PORT);                                            
}                                 

Executing prints as follows.

adbd --version
Android Debug Bridge Daemon version  ---------------------test----------------- 1.0.39

The workflow of adbd.

static void usb_ffs_open_thread(void* x) {                                                                 
    struct usb_handle* usb = (struct usb_handle*)x;                                                               
                                                                                                                 
    adb_thread_setname("usb ffs open");                                                                            
                                                                                                                 
    while (true) {                                                                                                 
        // wait until the USB device needs opening                                                                 
        std::unique_lock<std::mutex> lock(usb->lock);                                                              
        while (!usb->open_new_connection) {                                                                        
            usb->notify.wait(lock);                                                                                
        }                                                                                                         
        usb->open_new_connection = false;                                                                         
        lock.unlock();                                                                                             
                                                                                                                 
        while (true) {                                                                                             
            if (init_functionfs(usb)) {                                                                            
                LOG(INFO) << "functionfs successfully initialized";                                                
                break;                                                                                             
            }                                                                                                      
            std::this_thread::sleep_for(1s);                                                                      
        }                                                                                                         
                                                                                                                 
        LOG(INFO) << "registering usb transport";                                                                 
        register_usb_transport(usb, 0, 0, 1);                                                                     
    }                                                                                                              
                                                                                                                 
    // never gets here                                                                                             
    abort();                                                                                                      
}           

Normally, main will call adbd_main, checking if there is an ep0 path. If there is, it will call usb_init and then usb_ffs_init, and start another thread usb_ffs_open_thread (as above), which will initialize the USB descriptors and EP through init_functionfs.

bool init_functionfs(struct usb_handle* h){                                       
    LOG(INFO) << "initializing functionfs";                                        
                                                                                   
    ssize_t ret;                                                                    
    struct desc_v1 v1_descriptor;                                                  
    struct desc_v2 v2_descriptor;                                                  
    size_t retries = 0;                                                            
                                                                                   
    v2_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2);  
    v2_descriptor.header.length = cpu_to_le32(sizeof(v2_descriptor));              
    v2_descriptor.header.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
                                 FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC;
    v2_descriptor.fs_count = 3;                                                    
    v2_descriptor.hs_count = 3;                                                    
    v2_descriptor.ss_count = 5;                                                    
    v2_descriptor.os_count = 1;                                                    
    v2_descriptor.fs_descs = fs_descriptors;                                       
    v2_descriptor.hs_descs = hs_descriptors;                                       
    v2_descriptor.ss_descs = ss_descriptors;                                       
    v2_descriptor.os_header = os_desc_header;                                      
    v2_descriptor.os_desc = os_desc_compat;                                        
                                                                                   
    if (h->control < 0) { // might have already done this before                   
        LOG(INFO) << "opening control endpoint " << USB_FFS_ADB_EP0;               
        h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR);                            
        if (h->control < 0) {                                                       
            PLOG(ERROR) << "cannot open control endpoint " << USB_FFS_ADB_EP0;  
            goto err;                                                               
        }                                                                          
                                                                                   
        ret = adb_write(h->control, &v2_descriptor, sizeof(v2_descriptor));        
        if (ret < 0) {                                                              
            v1_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC);
            v1_descriptor.header.length = cpu_to_le32(sizeof(v1_descriptor));   
            v1_descriptor.header.fs_count = 3;                                      
            v1_descriptor.header.hs_count = 3;                                      
            D("[ %s: Switching to V1_descriptor format errno=%d ]", USB_FFS_ADB_EP0, errno);
            ret = adb_write(h->control, &v1_descriptor, sizeof(v1_descriptor)); 
            if (ret < 0) {                                                         
                D("[ %s: write descriptors failed: errno=%d ]", USB_FFS_ADB_EP0, errno);
                goto err;                                                          
            }                                                                      
        }                                                                          
                                                                                   
        ret = adb_write(h->control, &strings, sizeof(strings));                    
        if (ret < 0) {                                                              
            D("[ %s: writing strings failed: errno=%d]", USB_FFS_ADB_EP0, errno);
            goto err;                                                               
        }                                                                          
        //Signal only when writing the descriptors to ffs                          
        android::base::SetProperty("sys.usb.ffs.ready", "1");                      
    }                                                                              
                                                                                   
    h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR);                                
    if (h->bulk_out < 0) {                                                         
        PLOG(ERROR) << "cannot open bulk-out endpoint " << USB_FFS_ADB_OUT;        
        goto err;                                                                  
    }                                                                              
                                                                                   
    h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR);                                 
    if (h->bulk_in < 0) {                                                           
        PLOG(ERROR) << "cannot open bulk-in endpoint " << USB_FFS_ADB_IN;          
        goto err;                                                                  
    }                                                                              
                                                                                   
    h->max_rw = MAX_PAYLOAD;                                                        
    while (h->max_rw >= USB_FFS_BULK_SIZE && retries < ENDPOINT_ALLOC_RETRIES) {
        int ret_in = ioctl(h->bulk_in, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(h->max_rw));
        int errno_in = errno;                                                       
        int ret_out = ioctl(h->bulk_out, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(h->max_rw));
        int errno_out = errno;                                                      
                                                                                   
        if (ret_in || ret_out) {                                                    
            if (errno_in == ENODEV || errno_out == ENODEV) {                       
                std::this_thread::sleep_for(100ms);                                
                retries += 1;                                                       
                continue;                                                           
            }                                                                      
            h->max_rw /= 2;                                                        
        } else {                                                                    
            return true;                                                            
        }                                                                          
    }                                                                              
                                                                                   
    D("[ adb: cannot call endpoint alloc: errno=%d ]", errno);                     
    // Kernel pre-allocation could have failed for recoverable reasons.            
    // Continue running with a safe max rw size.                                    
    h->max_rw = USB_FFS_BULK_SIZE;                                                 
    return true;                                                                    
                                                                                   
err:                                                                                
    if (h->bulk_in > 0) {                                                           
        adb_close(h->bulk_in);                                                      
        h->bulk_in = -1;                                                            
    }                                                                              
    if (h->bulk_out > 0) {                                                         
        adb_close(h->bulk_out);                                                    
        h->bulk_out = -1;                                                           
    }                                                                              
    if (h->control > 0) {                                                           
        adb_close(h->control);                                                      
        h->control = -1;                                                            
    }                                                                              
    return false;                     

The code first writes the descriptor with ret = adb_write(h->control, &v2_descriptor, sizeof(v2_descriptor)); then writes the string descriptor with ret = adb_write(h->control, &strings, sizeof(strings)); which triggers the log.

read descriptors
read strings

The print occurs in the ffs_ep0_write interface.

static ssize_t ffs_ep0_write(struct file *file, const char __user *buf,             
                 size_t len, loff_t *ptr)                                            
{                                                                                   
    struct ffs_data *ffs = file->private_data;                                      
    ssize_t ret;                                                                 
    char *data;                                                                 
                                                                                    
    ENTER();                                                                     
                                                                                    
    /* Fast check if setup was canceled */                                           
    if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)                
        return -EIDRM;                                                               
                                                                                    
    /* Acquire mutex */                                                              
    ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);                  
    if (unlikely(ret < 0))                                                           
        return ret;                                                                 
                                                                                    
    /* Check state */                                                                
    switch (ffs->state) {                                                            
    case FFS_READ_DESCRIPTORS:                                                       
    case FFS_READ_STRINGS:                                                           
        /* Copy data */                                                              
        if (unlikely(len < 16)) {                                                    
            ret = -EINVAL;                                                           
            break;                                                                 
        }                                                                            
                                                                                    
        data = ffs_prepare_buffer(buf, len);                                        
        if (IS_ERR(data)) {                                                         
            ret = PTR_ERR(data);                                                    
            break;                                                                 
        }                                                                            
                                                                                    
        /* Handle data */                                                            
        if (ffs->state == FFS_READ_DESCRIPTORS) {                                    
            pr_info("read descriptors\n");                                        
            ret = __ffs_data_got_descs(ffs, data, len);                           
            if (unlikely(ret < 0))                                                  
                break;                                                               
                                                                                    
            ffs->state = FFS_READ_STRINGS;                                          
            ret = len;                                                              
        } else {                                                                 
            pr_info("read strings\n");                                            
            ret = __ffs_data_got_strings(ffs, data, len);                          
            if (unlikely(ret < 0))                                                  
                break;                                                               
                                                                                    
            ret = ffs_epfiles_create(ffs);                                          
            if (unlikely(ret)) {                                                    
                ffs->state = FFS_CLOSING;                                          
                break;                                                               
            }                                                                        
                                                                                    
            ffs->state = FFS_ACTIVE;                                                
            mutex_unlock(&ffs->mutex);                                              
                                                                                    
            ret = ffs_ready(ffs);                                                   
            if (unlikely(ret < 0)) {                                                
                ffs->state = FFS_CLOSING;                                          
                return ret;                                                         
            }                                                                        
                                                                                    
            return len;                                                              
        }                                                                            
        break;                                                                 
    case FFS_ACTIVE:                                                                 
        data = NULL;                                                                 
        /*                                                                          
         * We're called from user space, we can use _irq                            
         * rather than _irqsave                                                      
         */                                                                         
        spin_lock_irq(&ffs->ev.waitq.lock);                                         
        switch (ffs_setup_state_clear_cancelled(ffs)) {                              
        case FFS_SETUP_CANCELLED:                                                    
            ret = -EIDRM;                                                            
            goto done_spin;                                                         
                                                                                    
        case FFS_NO_SETUP:                                                           
            ret = -ESRCH;                                                            
            goto done_spin;                                                         
                                                                                    
        case FFS_SETUP_PENDING:                                                      
            break;                                                                 
        }                                                                            
                                                                                    
        /* FFS_SETUP_PENDING */                                                      
        if (!(ffs->ev.setup.bRequestType & USB_DIR_IN)) {                           
            spin_unlock_irq(&ffs->ev.waitq.lock);                                   
            ret = __ffs_ep0_stall(ffs);                                             
            break;                                                                 
        }                                                                            
                                                                                    
        /* FFS_SETUP_PENDING and not stall */                                        
        len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength));                 
                                                                                    
        spin_unlock_irq(&ffs->ev.waitq.lock);                                        
                                                                                    
        data = ffs_prepare_buffer(buf, len);                                        
        if (IS_ERR(data)) {                                                         
            ret = PTR_ERR(data);                                                    
            break;                                                                 
        }                                                                            
                                                                                    
        spin_lock_irq(&ffs->ev.waitq.lock);                                         
                                                                                    
        /*                                                                                  
         * We are guaranteed to be still in FFS_ACTIVE state                        
         * but the state of setup could have changed from                           
         * FFS_SETUP_PENDING to FFS_SETUP_CANCELLED so we need                      
         * to check for that.  If that happened we copied data                      
         * from user space in vain but it's unlikely.                                
         *                                                                                  
         * For sure we are not in FFS_NO_SETUP since this is                        
         * the only place FFS_SETUP_PENDING -> FFS_NO_SETUP                         
         * transition can be performed and it's protected by                        
         * mutex.                                                                    
         */                                                                         
        if (ffs_setup_state_clear_cancelled(ffs) ==                                 
            FFS_SETUP_CANCELLED) {                                                  
            ret = -EIDRM;                                                            
done_spin:                                                                          
            spin_unlock_irq(&ffs->ev.waitq.lock);                                   
        } else {                                                                 
            /* unlocks spinlock */                                                  
            ret = __ffs_ep0_queue_wait(ffs, data, len);                             
        }                                                                            
        kfree(data);                                                                 
        break;                                                                 
    default:                                                                 
        ret = -EBADFD;                                                               
        break;                                                                 
    }                                                                                
                                                                                    
    mutex_unlock(&ffs->mutex);                                                       
    return ret;                                                                 
}

When writing the string descriptor, two bulk EPs will be created, ret = ffs_epfiles_create(ffs).

static int ffs_epfiles_create(struct ffs_data *ffs)                             
{                                                                                
    struct ffs_epfile *epfile, *epfiles;                                        
    unsigned i, count;                                                          
                                                                                
    ENTER();                                                                    
                                                                                
    count = ffs->eps_count;                                                      
    epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL);                    
    if (!epfiles)                                                               
        return -ENOMEM;                                                         
                                                                                
    epfile = epfiles;                                                           
    for (i = 1; i <= count; ++i, ++epfile) {                                     
        epfile->ffs = ffs;                                                      
        mutex_init(&epfile->mutex);                                             
        if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)                          
            sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);               
        else                                                                    
            sprintf(epfile->name, "ep%u", i);                                   
        epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name,              
                         epfile,                                                
                         &ffs_epfile_operations);                                
        if (unlikely(!epfile->dentry)) {                                        
            ffs_epfiles_destroy(epfiles, i - 1);                               
            return -ENOMEM;                                                    
        }                                                                        
    }                                                                            
                                                                                
    ffs->epfiles = epfiles;                                                      
    return 0;            
}

Thus, adbd can open and write to other EPs through h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR) and h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR); allowing for read and write operations.

static const struct file_operations ffs_epfile_operations = {                   
    .llseek =   no_llseek,                                                       
                                                                                
    .open =     ffs_epfile_open,                                                
    .write_iter =   ffs_epfile_write_iter,                                      
    .read_iter =    ffs_epfile_read_iter,                                       
    .release =  ffs_epfile_release,                                             
    .unlocked_ioctl =   ffs_epfile_ioctl,                                       
    .compat_ioctl = compat_ptr_ioctl,                                           
};                             

Conclusion

This is also an example of using Configfs, representing a typical implementation approach for porting ADB with buildroot.

Leave a Comment