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.