Fastboot 是 Bootloader 与 PC 之间用来刷写分区、查询设备状态的轻量级协议。本文深入解析 Fastboot 协议格式、USB 传输层、命令实现细节,并演示如何用 C 实现一个最小化的 Fastboot 客户端。

一、Fastboot 是什么?

当你在终端敲下:

fastboot flash boot boot.img
fastboot reboot

幕后到底发生了什么?

Fastboot 是一个 应用层协议,运行在 USB Bulk Transfer 之上,客户端在 PC,服务端在 Android 设备的 Bootloader(或 userspace fastbootd)中。它的设计极度简洁,适合在资源受限的 Bootloader 环境中运行。


二、协议格式

Fastboot 协议非常简单,所有消息都是文本格式。

客户端 → 设备(命令)

直接发送 ASCII 字符串,最长 64 字节:

"getvar:version"
"download:00100000"
"flash:boot"
"reboot"
"erase:userdata"

设备 → 客户端(响应)

固定 4 字节前缀 + 内容(总长 ≤ 64 字节):

前缀 含义
OKAY 命令成功完成,后续可有附加信息
FAIL 命令失败,后续 60 字节是错误描述
INFO 信息(进度),客户端打印后继续等
DATA 设备准备好接收 N 字节数据(N 是 8 字节十六进制)
TEXT 文本信息(较新协议)

一次完整的交互示例

flash boot boot.img 为例:

[Client]  -> "download:01000000"            (准备发送 16 MB 数据)
[Device]  -> "DATA01000000"                 (Ready,16 MB)
[Client]  -> <16 MB 二进制数据>
[Device]  -> "OKAY"                         (数据接收完毕)
[Client]  -> "flash:boot"
[Device]  -> "INFO writing 'boot' partition" (进度)
[Device]  -> "OKAY"                         (刷写完成)

三、USB 传输层

Fastboot 使用 USB 2.0/3.0 Bulk Endpoint,Interface Class/SubClass/Protocol 固定:

bInterfaceClass     = 0xFF (Vendor Specific)
bInterfaceSubClass  = 0x42 ('B')
bInterfaceProtocol  = 0x03 (Fastboot)

每个 Fastboot Interface 有 2 个 Bulk 端点:

EP1 OUT (Host -> Device)    传输命令和数据
EP1 IN  (Device -> Host)    传输响应

设备通过这一组 Endpoint 提供服务,PC 端通过 libusb 或 WinUSB 与之通信。

在设备端识别

# 设备进入 fastboot 模式后查看 USB
lsusb -d 18d1:4ee0 -v | grep -A4 Interface

输出示例:

bInterfaceClass         255 Vendor Specific Class
bInterfaceSubClass       66
bInterfaceProtocol        3

四、设备端的 Fastboot 实现

设备端的 Fastboot 通常实现在 Bootloader(LK)中。核心代码在 AOSP 的 bootable/bootloader/lk/app/aboot/fastboot.c

主循环框架

static void cmd_handler(const char *cmd, void *arg) {
    char buf[64];

    if (strncmp(cmd, "download:", 9) == 0) {
        unsigned size = strtoul(cmd + 9, NULL, 16);
        if (size > MAX_DOWNLOAD_SIZE) {
            fastboot_fail("size too large");
            return;
        }
        // 通告主机可以发数据
        snprintf(buf, sizeof(buf), "DATA%08x", size);
        usb_write(buf, strlen(buf));
        // 接收数据
        usb_read(download_buffer, size);
        download_size = size;
        fastboot_okay("");
    }
    else if (strncmp(cmd, "flash:", 6) == 0) {
        cmd_flash(cmd + 6);
    }
    else if (strncmp(cmd, "erase:", 6) == 0) {
        cmd_erase(cmd + 6);
    }
    else if (strncmp(cmd, "getvar:", 7) == 0) {
        cmd_getvar(cmd + 7);
    }
    else if (strcmp(cmd, "reboot") == 0) {
        fastboot_okay("");
        msleep(100);
        reboot_device(NORMAL);
    }
    else if (strcmp(cmd, "reboot-bootloader") == 0) {
        fastboot_okay("");
        msleep(100);
        reboot_device(FASTBOOT_MODE);
    }
    else {
        fastboot_fail("unknown command");
    }
}

void fastboot_handler(void) {
    char cmd[64];
    while (1) {
        int n = usb_read(cmd, sizeof(cmd) - 1);
        if (n < 0) continue;
        cmd[n] = 0;
        printf("fastboot: cmd '%s'\n", cmd);
        cmd_handler(cmd, NULL);
    }
}

flash 命令的内部实现

static void cmd_flash(const char *arg) {
    struct partition_info pi;
    if (find_partition(arg, &pi) < 0) {
        fastboot_fail("partition not found");
        return;
    }

    if (download_size == 0) {
        fastboot_fail("no data downloaded");
        return;
    }

    if (download_size > pi.size) {
        fastboot_fail("image too large for partition");
        return;
    }

    // 如果是 sparse image (ext4 大文件),特殊处理
    if (is_sparse_image(download_buffer, download_size)) {
        if (write_sparse_image(&pi, download_buffer, download_size) < 0) {
            fastboot_fail("sparse write failed");
            return;
        }
    } else {
        // 普通块写
        if (mmc_write(pi.offset, download_buffer, download_size) < 0) {
            fastboot_fail("write failed");
            return;
        }
    }

    fastboot_okay("");
}

getvar 的实现

getvar 是 Fastboot 的"读寄存器"命令,客户端用它来探查设备能力。

struct fastboot_var {
    const char *name;
    const char *value;
};

static struct fastboot_var fb_vars[] = {
    { "version",            "0.4" },
    { "version-bootloader", BOOTLOADER_VERSION },
    { "product",            DEVICE_NAME },
    { "secure",             "yes" },
    { "unlocked",           "no" },
    { "slot-count",         "2" },
    { "current-slot",       "a" },
    { "max-download-size",  "0x20000000" },  // 512 MB
    { NULL, NULL }
};

static void cmd_getvar(const char *name) {
    if (strcmp(name, "all") == 0) {
        // 'getvar all' 用 INFO 逐条返回
        for (int i = 0; fb_vars[i].name; i++) {
            char buf[128];
            snprintf(buf, sizeof(buf), "%s:%s",
                     fb_vars[i].name, fb_vars[i].value);
            fastboot_info(buf);
        }
        fastboot_okay("");
        return;
    }

    // 特殊变量:partition-size:<part>
    if (strncmp(name, "partition-size:", 15) == 0) {
        struct partition_info pi;
        if (find_partition(name + 15, &pi) < 0) {
            fastboot_fail("partition not found");
            return;
        }
        char buf[32];
        snprintf(buf, sizeof(buf), "0x%llx", pi.size);
        fastboot_okay(buf);
        return;
    }

    for (int i = 0; fb_vars[i].name; i++) {
        if (strcmp(name, fb_vars[i].name) == 0) {
            fastboot_okay(fb_vars[i].value);
            return;
        }
    }

    fastboot_fail("unknown variable");
}

五、Sparse Image:省带宽的妙招

system.img 这样的 ext4 镜像可能有 3GB,但实际数据只有 1GB,剩下都是空洞。整块发太浪费,Android 设计了 sparse 格式。

Sparse 格式

#define SPARSE_HEADER_MAGIC 0xed26ff3a

struct sparse_header {
    uint32_t magic;             // 0xed26ff3a
    uint16_t major_version;     // 1
    uint16_t minor_version;     // 0
    uint16_t file_hdr_sz;       // 28
    uint16_t chunk_hdr_sz;      // 12
    uint32_t blk_sz;            // 块大小 (通常 4096)
    uint32_t total_blks;        // 总块数 (含空洞)
    uint32_t total_chunks;      // chunk 数
    uint32_t image_checksum;
};

struct chunk_header {
    uint16_t chunk_type;        // RAW=0xCAC1, FILL=0xCAC2, DONT_CARE=0xCAC3, CRC=0xCAC4
    uint16_t reserved;
    uint32_t chunk_sz;          // 此 chunk 占用的块数
    uint32_t total_sz;          // 总字节(含 header)
};

四种 chunk 类型:

类型 用途
RAW (0xCAC1) 原始数据,后面跟 chunk_sz × blk_sz 字节
FILL (0xCAC2) 填充,后面跟 4 字节模式,扩展为 chunk_sz × blk_sz
DONT_CARE 不关心(空洞),无 payload
CRC (0xCAC4) 校验信息

设备端解析

int write_sparse_image(struct partition_info *pi,
                       uint8_t *data, size_t size) {
    struct sparse_header *sh = (struct sparse_header *)data;
    if (sh->magic != SPARSE_HEADER_MAGIC)
        return -1;

    uint8_t *p = data + sh->file_hdr_sz;
    uint64_t cur_offset = pi->offset;

    for (uint32_t i = 0; i < sh->total_chunks; i++) {
        struct chunk_header *ch = (struct chunk_header *)p;
        uint64_t bytes = (uint64_t)ch->chunk_sz * sh->blk_sz;

        switch (ch->chunk_type) {
            case 0xCAC1:  // RAW
                mmc_write(cur_offset,
                          p + sh->chunk_hdr_sz, bytes);
                break;

            case 0xCAC2: { // FILL
                uint32_t fill = *(uint32_t*)(p + sh->chunk_hdr_sz);
                fill_and_write(cur_offset, fill, bytes);
                break;
            }

            case 0xCAC3:  // DONT_CARE - 跳过
                break;
        }

        cur_offset += bytes;
        p += ch->total_sz;
    }

    return 0;
}

六、用 C 实现一个 minimal Fastboot 客户端

下面是一个用 libusb 实现的迷你 Fastboot 客户端,支持 getvarreboot:

// minifastboot.c
// 编译: gcc minifastboot.c -lusb-1.0 -o minifastboot
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>

#define FB_INTERFACE_CLASS    0xFF
#define FB_INTERFACE_SUBCLASS 0x42
#define FB_INTERFACE_PROTOCOL 0x03

static int ep_in = -1, ep_out = -1, iface = -1;

static int find_fastboot_interface(libusb_device *dev,
                                   struct libusb_device_handle *h) {
    struct libusb_config_descriptor *cfg;
    if (libusb_get_active_config_descriptor(dev, &cfg) < 0)
        return -1;

    for (int i = 0; i < cfg->bNumInterfaces; i++) {
        const struct libusb_interface_descriptor *id =
            &cfg->interface[i].altsetting[0];

        if (id->bInterfaceClass    == FB_INTERFACE_CLASS &&
            id->bInterfaceSubClass == FB_INTERFACE_SUBCLASS &&
            id->bInterfaceProtocol == FB_INTERFACE_PROTOCOL) {

            for (int j = 0; j < id->bNumEndpoints; j++) {
                const struct libusb_endpoint_descriptor *ep = &id->endpoint[j];
                if ((ep->bmAttributes & 0x3) != LIBUSB_TRANSFER_TYPE_BULK)
                    continue;
                if (ep->bEndpointAddress & 0x80) ep_in = ep->bEndpointAddress;
                else                              ep_out = ep->bEndpointAddress;
            }
            iface = id->bInterfaceNumber;
            libusb_free_config_descriptor(cfg);
            return 0;
        }
    }
    libusb_free_config_descriptor(cfg);
    return -1;
}

static struct libusb_device_handle *open_fastboot_device(void) {
    libusb_device **list;
    ssize_t n = libusb_get_device_list(NULL, &list);

    for (ssize_t i = 0; i < n; i++) {
        struct libusb_device_handle *h;
        if (libusb_open(list[i], &h) < 0) continue;
        if (find_fastboot_interface(list[i], h) == 0) {
            libusb_claim_interface(h, iface);
            libusb_free_device_list(list, 1);
            return h;
        }
        libusb_close(h);
    }
    libusb_free_device_list(list, 1);
    return NULL;
}

static int send_command(struct libusb_device_handle *h, const char *cmd) {
    int transferred;
    return libusb_bulk_transfer(h, ep_out,
                                 (unsigned char *)cmd, strlen(cmd),
                                 &transferred, 5000);
}

static int read_response(struct libusb_device_handle *h, char *buf, int max) {
    int transferred;
    int ret = libusb_bulk_transfer(h, ep_in,
                                    (unsigned char *)buf, max - 1,
                                    &transferred, 5000);
    if (ret == 0) buf[transferred] = 0;
    return ret;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <command>\n", argv[0]);
        return 1;
    }

    libusb_init(NULL);
    struct libusb_device_handle *h = open_fastboot_device();
    if (!h) {
        fprintf(stderr, "No fastboot device found\n");
        return 1;
    }

    if (send_command(h, argv[1]) < 0) {
        fprintf(stderr, "send failed\n");
        return 1;
    }

    // 循环读响应直到 OKAY 或 FAIL
    char resp[65];
    while (read_response(h, resp, sizeof(resp)) == 0) {
        if (strncmp(resp, "OKAY", 4) == 0) {
            printf("[OK] %s\n", resp + 4);
            break;
        }
        if (strncmp(resp, "FAIL", 4) == 0) {
            printf("[FAIL] %s\n", resp + 4);
            break;
        }
        if (strncmp(resp, "INFO", 4) == 0) {
            printf("[INFO] %s\n", resp + 4);
        }
    }

    libusb_release_interface(h, iface);
    libusb_close(h);
    libusb_exit(NULL);
    return 0;
}

使用:

sudo ./minifastboot getvar:version
# [OK] 0.4

sudo ./minifastboot getvar:product
# [OK] sm8550

sudo ./minifastboot reboot
# [OK]

七、Fastboot 命令对照表

命令 作用
getvar:<name> 读取设备变量
getvar:all 读取所有变量
download:<size> 准备接收 size 字节数据
upload 上传数据到 PC (oem unlock 等用)
flash:<part> 把已下载数据写入分区
erase:<part> 擦除分区
boot 临时用已下载数据启动(不写入)
continue 继续正常启动
reboot 正常重启
reboot-bootloader 重启回 fastboot
reboot fastboot 重启到 userspace fastboot (fastbootd)
set_active:a/b 切换 active slot
oem unlock 解锁 Bootloader (需 OEM 流程)
flashing unlock 现代解锁命令

八、userspace fastbootd

Android 10+ 把一部分 Fastboot 功能移到了用户态(super 分区相关操作),叫 fastbootd,运行在 recovery ramdisk 中。

# 重启进 fastbootd (需要 recovery 内核 + ramdisk)
adb reboot fastboot

# 这时候才能刷 dynamic partition
fastboot flash system system.img
fastboot flash vendor vendor.img

fastbootd 的协议和 Bootloader 的 Fastboot 完全相同,只是运行环境从 LK 换成了 Linux 用户态,可以处理更复杂的逻辑(比如 super 分区里的 LVM 操作)。


九、踩坑经验

  1. Windows 找不到设备:需要安装 Google USB Driver,或者用 Zadig 替换为 WinUSB
  2. download 超过 max-download-size:设备会拒绝,要分批下载(getvar:max-download-size 查询)
  3. flash dynamic partition 失败:必须在 fastbootd 下,不是普通 fastboot
  4. erase userdata 后不能直接刷小镜像:userdata 分区大小巨大,但镜像只有几 MB,会被识别为损坏。需要 fastboot format userdata
  5. 某些机型加固 Bootloader,不响应 oem unlock:被 OEM 替换为 flashing unlock,并且要先在开发者选项打开 OEM 解锁开关

十、总结

Fastboot 协议是一个工程典范:

  • 极简:文本命令 + 4 字节前缀响应,几百行代码就能实现
  • 灵活:download + flash 解耦,支持任意数据传输
  • 可扩展:getvar 机制让客户端能动态发现设备能力
  • 跨阶段:既能在 Bootloader 跑,也能在 Linux 用户态跑

理解 Fastboot 协议对做 OEM 工具、定制刷机流程、自动化测试非常有帮助。下篇我们继续讲 Recovery 模式 — Android 的另一套救命系统。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐