在Android的SDK中自带了fastboot,路径为
${SDK_HOME}/platform-tools/fastboot
查看help:
usage: fastboot [ <option> ] <command>
commands:
update <filename> Reflash device from update.zip.
Sets the flashed slot as active.
flashall Flash boot, system, vendor, and --
if found -- recovery. If the device
supports slots, the slot that has
been flashed to is set as active.
Secondary images may be flashed to
an inactive slot.
flash <partition> [ <filename> ] Write a file to a flash partition.
flashing lock Locks the device. Prevents flashing.
flashing unlock Unlocks the device. Allows flashing
any partition except
bootloader-related partitions.
flashing lock_critical Prevents flashing bootloader-related
partitions.
flashing unlock_critical Enables flashing bootloader-related
partitions.
flashing get_unlock_ability Queries bootloader to see if the
device is unlocked.
flashing get_unlock_bootloader_nonce Queries the bootloader to get the
unlock nonce.
flashing unlock_bootloader <request> Issue unlock bootloader using request.
flashing lock_bootloader Locks the bootloader to prevent
bootloader version rollback.
erase <partition> Erase a flash partition.
format[:[<fs type>][:[<size>]] <partition>
Format a flash partition. Can
override the fs type and/or size
the bootloader reports.
getvar <variable> Display a bootloader variable.
set_active <slot> Sets the active slot. If slots are
not supported, this does nothing.
boot <kernel> [ <ramdisk> [ <second> ] ] Download and boot kernel.
flash:raw <bootable-partition> <kernel> [ <ramdisk> [ <second> ] ]
Create bootimage and flash it.
devices [-l] List all connected devices [with
device paths].
continue Continue with autoboot.
reboot [bootloader|emergency] Reboot device [into bootloader or emergency mode].
reboot-bootloader Reboot device into bootloader.
oem <parameter1> ... <parameterN> Executes oem specific command.
stage <infile> Sends contents of <infile> to stage for
the next command. Supported only on
Android Things devices.
get_staged <outfile> Receives data to <outfile> staged by the
last command. Supported only on Android
Things devices.
help Show this help message.
options:
-w Erase userdata and cache (and format
if supported by partition type).
-u Do not erase partition before
formatting.
-s <specific device> Specify a device. For USB, provide either
a serial number or path to device port.
For ethernet, provide an address in the
form <protocol>:<hostname>[:port] where
<protocol> is either tcp or udp.
-c <cmdline> Override kernel commandline.
-i <vendor id> Specify a custom USB vendor id.
-b, --base <base_addr> Specify a custom kernel base
address (default: 0x10000000).
--kernel-offset Specify a custom kernel offset.
(default: 0x00008000)
--ramdisk-offset Specify a custom ramdisk offset.
(default: 0x01000000)
--tags-offset Specify a custom tags offset.
(default: 0x00000100)
-n, --page-size <page size> Specify the nand page size
(default: 2048).
-S <size>[K|M|G] Automatically sparse files greater
than 'size'. 0 to disable.
--slot <slot> Specify slot name to be used if the
device supports slots. All operations
on partitions that support slots will
be done on the slot specified.
'all' can be given to refer to all slots.
'other' can be given to refer to a
non-current slot. If this flag is not
used, slotted partitions will default
to the current active slot.
-a, --set-active[=<slot>] Sets the active slot. If no slot is
provided, this will default to the value
given by --slot. If slots are not
supported, this does nothing. This will
run after all non-reboot commands.
--skip-secondary Will not flash secondary slots when
performing a flashall or update. This
will preserve data on other slots.
--skip-reboot Will not reboot the device when
performing commands that normally
trigger a reboot.
--disable-verity Set the disable-verity flag in the
the vbmeta image being flashed.
--disable-verification Set the disable-verification flag in the vbmeta image being flashed.
--wipe-and-use-fbe On devices which support it,
erase userdata and cache, and
enable file-based encryption
--unbuffered Do not buffer input or output.
--version Display version.
-h, --help show this message.
够长的,好多参数解释看得也不是很懂。
自己编译Android源码也会产生fastboot,路径为:
${OUT}/host/darwin-x86/bin/fastboot //Mac的编译结果在darwin-x86下
查看help:
usage: fastboot [ <option> ] <command>
commands:
update <filename> reflash device from update.zip
flashall flash boot + recovery + system
flash <partition> [ <filename> ] write a file to a flash partition
erase <partition> erase a flash partition
format <partition> format a flash partition
getvar <variable> display a bootloader variable
boot <kernel> [ <ramdisk> ] download and boot kernel
flash:raw boot <kernel> [ <ramdisk> ] create bootimage and flash it
devices list all connected devices
continue continue with autoboot
reboot reboot device normally
reboot-bootloader reboot device into bootloader
help show this help message
options:
-w erase userdata and cache (and format
if supported by partition type)
-u do not first erase partition before
formatting
-s <specific device> specify device serial number
or path to device port
-l with "devices", lists device paths
-p <product> specify product name
-c <cmdline> override kernel commandline
-i <vendor id> specify a custom USB vendor id
-b <base_addr> specify a custom kernel base address. default: 0x10000000
-n <page size> specify the nand page size. default: 2048
-S <size>[K|M|G] automatically sparse files greater than
size. 0 to disable
两者还不太一样,好像自己编译的在功能上是SDK自带的子集。在源码中有fastboot相关的代码,正好研究一下。
情景一
我们使用fastboot的第一个有效命令(不算 fastboot -h)通常是fastboot devices
,我们来跟踪一下:
if (argc > 0 && !strcmp(*argv, "devices")) {
skip(1);
list_devices();
return 0;
}
list_devices:
void list_devices(void) {
// We don't actually open a USB device here,
// just getting our callback called so we can
// list all the connected devices.
usb_open(list_devices_callback);
}
usb_open,不同操作系统有不同的实现,为了方便理解,我们分析Linux版本的实现(system/core/fastboot/usb_linux.c):
usb_handle *usb_open(ifc_match_func callback)
{
return find_usb_device("/dev/bus/usb", callback);
}
find_usb_device:
static usb_handle *find_usb_device(const char *base, ifc_match_func callback)
{
usb_handle *usb = 0;
char busname[64], devname[64];
char desc[1024];
int n, in, out, ifc;
DIR *busdir, *devdir;
struct dirent *de;
int fd;
int writable;
busdir = opendir(base);
if(busdir == 0) return 0;
while((de = readdir(busdir)) && (usb == 0)) {
if(badname(de->d_name)) continue;
sprintf(busname, "%s/%s", base, de->d_name);
devdir = opendir(busname);
if(devdir == 0) continue;
// DBG("[ scanning %s ]\n", busname);
while((de = readdir(devdir)) && (usb == 0)) {
if(badname(de->d_name)) continue;
sprintf(devname, "%s/%s", busname, de->d_name);
// DBG("[ scanning %s ]\n", devname);
writable = 1;
if((fd = open(devname, O_RDWR)) < 0) {
// Check if we have read-only access, so we can give a helpful
// diagnostic like "adb devices" does.
writable = 0;
if((fd = open(devname, O_RDONLY)) < 0) {
continue;
}
}
n = read(fd, desc, sizeof(desc));
if(filter_usb_device(fd, desc, n, writable, callback,
&in, &out, &ifc) == 0) {
usb = calloc(1, sizeof(usb_handle));
strcpy(usb->fname, devname);
usb->ep_in = in;
usb->ep_out = out;
usb->desc = fd;
n = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifc);
if(n != 0) {
close(fd);
free(usb);
usb = 0;
continue;
}
} else {
close(fd);
}
}
closedir(devdir);
}
closedir(busdir);
return usb;
}
循环读取/dev/bus/usb
目录下及子目录中的信息,解析并使用filter_usb_device
过滤,然后对fastboot模式的usb进行callback调用,至于如何过滤fastboot模式的usb,这里面涉及到usb相关的知识,我也不是很了解,应该是通过usb信息中的某个标识来识别的,屌大的同学可以给我讲讲。
对callback的调用:
if(callback(&info) == 0) {
*ept_in_id = in;
*ept_out_id = out;
*ifc_id = ifc->bInterfaceNumber;
return 0;
}
回到list_devices_callback
:
int list_devices_callback(usb_ifc_info *info)
{
if (match_fastboot_with_serial(info, NULL) == 0) {
char* serial = info->serial_number;
if (!info->writable) {
serial = "no permissions"; // like "adb devices"
}
if (!serial[0]) {
serial = "????????????";
}
// output compatible with "adb devices"
if (!long_listing) {
printf("%s\tfastboot\n", serial);
} else if (!info->device_path) {
printf("%-22s fastboot\n", serial);
} else {
printf("%-22s fastboot %s\n", serial, info->device_path);
}
}
return -1;
}
其实就是输出连接的设备信息,假如是long_listing,会把device_path也输出来,long_listing通过 -l
指定:
$ fastboot devices -l
01d977445292ca8c fastboot usb:337838080X
情景二
在刷机的时候,通常使用fastboot -w flashall
,先看看-w
:
case 'w':
wants_wipe = 1;
break;
...
if (wants_wipe) {
fb_queue_erase("userdata");
fb_queue_format("userdata", 1);
fb_queue_erase("cache");
fb_queue_format("cache", 1);
}
带上这个选项,会清除userdata
和cache
中的内容。
再看看flashall
:
else if(!strcmp(*argv, "flashall")) {
skip(1);
do_flashall(usb, erase_first);
wants_reboot = 1;
}
do_flashall:
void do_flashall(usb_handle *usb, int erase_first)
{
char *fname;
void *data;
unsigned sz;
struct fastboot_buffer buf;
int i;
queue_info_dump();
fb_queue_query_save("product", cur_product, sizeof(cur_product));
fname = find_item("info", product);
if (fname == 0) die("cannot find android-info.txt");
data = load_file(fname, &sz);
if (data == 0) die("could not load android-info.txt: %s", strerror(errno));
setup_requirements(data, sz);
for (i = 0; i < ARRAY_SIZE(images); i++) {
fname = find_item(images[i].part_name, product);
if (load_buf(usb, fname, &buf)) {
if (images[i].is_optional)
continue;
die("could not load %s\n", images[i].img_name);
}
do_send_signature(fname);
if (erase_first && needs_erase(images[i].part_name)) {
fb_queue_erase(images[i].part_name);
}
flash_buf(images[i].part_name, &buf);
}
}
find_item:
char *find_item(const char *item, const char *product)
{
char *dir;
char *fn;
char path[PATH_MAX + 128];
if(!strcmp(item,"boot")) {
fn = "boot.img";
} else if(!strcmp(item,"recovery")) {
fn = "recovery.img";
} else if(!strcmp(item,"system")) {
fn = "system.img";
} else if(!strcmp(item,"userdata")) {
fn = "userdata.img";
} else if(!strcmp(item,"cache")) {
fn = "cache.img";
} else if(!strcmp(item,"info")) {
fn = "android-info.txt";
} else {
fprintf(stderr,"unknown partition '%s'\n", item);
return 0;
}
if(product) {
get_my_path(path);
sprintf(path + strlen(path),
"../../../target/product/%s/%s", product, fn);
return strdup(path);
}
dir = getenv("ANDROID_PRODUCT_OUT");
if((dir == 0) || (dir[0] == 0)) {
die("neither -p product specified nor ANDROID_PRODUCT_OUT set");
return 0;
}
sprintf(path, "%s/%s", dir, fn);
return strdup(path);
}
会根据环境变量ANDROID_PRODUCT_OUT
指定的目录下去找相关的文件,这里是android-info.txt
。我们在刷机的时候,如果报找不到img文件时,需要设置ANDROID_PRODUCT_OUT就是这个原因。
接下来就是找相关的img文件,然后刷:
static struct {
char img_name[13];
char sig_name[13];
char part_name[9];
bool is_optional;
} images[3] = {
{"boot.img", "boot.sig", "boot", false},
{"recovery.img", "recovery.sig", "recovery", true},
{"system.img", "system.sig", "system", false},
};
...
for (i = 0; i < ARRAY_SIZE(images); i++) {
fd = unzip_to_file(zip, images[i].img_name);
if (fd < 0) {
if (images[i].is_optional)
continue;
die("update package missing %s", images[i].img_name);
}
rc = load_buf_fd(usb, fd, &buf);
if (rc) die("cannot load %s from flash", images[i].img_name);
do_update_signature(zip, images[i].sig_name);
if (erase_first && needs_erase(images[i].part_name)) {
fb_queue_erase(images[i].part_name);
}
flash_buf(images[i].part_name, &buf);
/* not closing the fd here since the sparse code keeps the fd around
* but hasn't mmaped data yet. The tmpfile will get cleaned up when the
* program exits.
*/
}
可以看到,flashall的时候,会批量刷入boot.img、recovery.img、system.img,其中recovery.img是可选刷入的。
其中load_buf_fd
将解压的文件load到buf中。
flash_buf:
static void flash_buf(const char *pname, struct fastboot_buffer *buf)
{
struct sparse_file **s;
switch (buf->type) {
case FB_BUFFER_SPARSE:
s = buf->data;
while (*s) {
int64_t sz64 = sparse_file_len(*s, true, false);
fb_queue_flash_sparse(pname, *s++, sz64);
}
break;
case FB_BUFFER:
fb_queue_flash(pname, buf->data, buf->sz);
break;
default:
die("unknown buffer type: %d", buf->type);
}
}
fb_queue_flash和fb_queue_flash_sparse:
void fb_queue_flash(const char *ptn, void *data, unsigned sz)
{
Action *a;
a = queue_action(OP_DOWNLOAD, "");
a->data = data;
a->size = sz;
a->msg = mkmsg("sending '%s' (%d KB)", ptn, sz / 1024);
a = queue_action(OP_COMMAND, "flash:%s", ptn);
a->msg = mkmsg("writing '%s'", ptn);
}
void fb_queue_flash_sparse(const char *ptn, struct sparse_file *s, unsigned sz)
{
Action *a;
a = queue_action(OP_DOWNLOAD_SPARSE, "");
a->data = s;
a->size = 0;
a->msg = mkmsg("sending sparse '%s' (%d KB)", ptn, sz / 1024);
a = queue_action(OP_COMMAND, "flash:%s", ptn);
a->msg = mkmsg("writing '%s'", ptn);
}
queue_action:
static Action *queue_action(unsigned op, const char *fmt, ...)
{
Action *a;
va_list ap;
size_t cmdsize;
a = calloc(1, sizeof(Action));
if (a == 0) die("out of memory");
va_start(ap, fmt);
cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap);
va_end(ap);
if (cmdsize >= sizeof(a->cmd)) {
free(a);
die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd));
}
if (action_last) {
action_last->next = a;
} else {
action_list = a;
}
action_last = a;
a->op = op;
a->func = cb_default;
a->start = -1;
return a;
}
其实就是将对应命令和数据放入到action_last
链表中。那肯定有另外一个地方对这个链表进行真正的操作。
fb_execute_queue:
int fb_execute_queue(usb_handle *usb)
{
Action *a;
char resp[FB_RESPONSE_SZ+1];
int status = 0;
a = action_list;
if (!a)
return status;
resp[FB_RESPONSE_SZ] = 0;
double start = -1;
for (a = action_list; a; a = a->next) {
a->start = now();
if (start < 0) start = a->start;
if (a->msg) {
// fprintf(stderr,"%30s... ",a->msg);
fprintf(stderr,"%s...\n",a->msg);
}
if (a->op == OP_DOWNLOAD) {
status = fb_download_data(usb, a->data, a->size);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_COMMAND) {
status = fb_command(usb, a->cmd);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_QUERY) {
status = fb_command_response(usb, a->cmd, resp);
status = a->func(a, status, status ? fb_get_error() : resp);
if (status) break;
} else if (a->op == OP_NOTICE) {
fprintf(stderr,"%s\n",(char*)a->data);
} else if (a->op == OP_FORMAT) {
status = fb_format(a, usb, (int)a->data);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_DOWNLOAD_SPARSE) {
status = fb_download_data_sparse(usb, a->data);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else {
die("bogus action");
}
}
fprintf(stderr,"finished. total time: %.3fs\n", (now() - start));
return status;
}
fb_download_data:
int fb_download_data(usb_handle *usb, const void *data, unsigned size)
{
char cmd[64];
int r;
sprintf(cmd, "download:%08x", size);
r = _command_send(usb, cmd, data, size, 0);
if(r < 0) {
return -1;
} else {
return 0;
}
}
_command_send:
static int _command_send(usb_handle *usb, const char *cmd,
const void *data, unsigned size,
char *response)
{
int r;
if (size == 0) {
return -1;
}
r = _command_start(usb, cmd, size, response);
if (r < 0) {
return -1;
}
r = _command_data(usb, data, size);
if (r < 0) {
return -1;
}
r = _command_end(usb);
if(r < 0) {
return -1;
}
return size;
}
_command_start:
static int _command_start(usb_handle *usb, const char *cmd, unsigned size,
char *response)
{
int cmdsize = strlen(cmd);
int r;
if(response) {
response[0] = 0;
}
if(cmdsize > 64) {
sprintf(ERROR,"command too large");
return -1;
}
if(usb_write(usb, cmd, cmdsize) != cmdsize) {
sprintf(ERROR,"command write failed (%s)", strerror(errno));
usb_close(usb);
return -1;
}
return check_response(usb, size, response);
}
usb_write(system/core/fastboot/usb_linux.c):
int usb_write(usb_handle *h, const void *_data, int len)
{
unsigned char *data = (unsigned char*) _data;
unsigned count = 0;
struct usbdevfs_bulktransfer bulk;
int n;
if(h->ep_out == 0) {
return -1;
}
if(len == 0) {
bulk.ep = h->ep_out;
bulk.len = 0;
bulk.data = data;
bulk.timeout = 0;
n = ioctl(h->desc, USBDEVFS_BULK, &bulk);
if(n != 0) {
fprintf(stderr,"ERROR: n = %d, errno = %d (%s)\n",
n, errno, strerror(errno));
return -1;
}
return 0;
}
while(len > 0) {
int xfer;
xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;
bulk.ep = h->ep_out;
bulk.len = xfer;
bulk.data = data;
bulk.timeout = 0;
n = ioctl(h->desc, USBDEVFS_BULK, &bulk);
if(n != xfer) {
DBG("ERROR: n = %d, errno = %d (%s)\n",
n, errno, strerror(errno));
return -1;
}
count += xfer;
len -= xfer;
data += xfer;
}
return count;
}
这里就是将内容写入到usb_handle
对应的usb设备中。这样我们的数据就通过usb写入到设备中了,而在设备中,有对应的usb驱动程序来处理写过去的数据。对于设备如何处理写过去的数据,我并没有搜索到相关的代码,也许在厂商的驱动程序里面,同样,屌大的同学可以给我讲讲。
情景三
有时候我们只刷单个的img,使用的命令是fastboot flash xxx xxx.img
,也来看看吧:
else if(!strcmp(*argv, "flash")) {
char *pname = argv[1];
char *fname = 0;
require(2);
if (argc > 2) {
fname = argv[2];
skip(3);
} else {
fname = find_item(pname, product);
skip(2);
}
if (fname == 0) die("cannot determine image filename for '%s'", pname);
if (erase_first && needs_erase(pname)) {
fb_queue_erase(pname);
}
do_flash(usb, pname, fname);
}
do_flash:
void do_flash(usb_handle *usb, const char *pname, const char *fname)
{
struct fastboot_buffer buf;
if (load_buf(usb, fname, &buf)) {
die("cannot load '%s'", fname);
}
flash_buf(pname, &buf);
}
同样的是将数据load到buf,然后调用flash_buf
,flash_buf
前面已经分析过了,就不再复述。
情景四:
在help中,我们看到这个:
boot <kernel> [ <ramdisk> ] download and boot kernel
貌似对boot镜像有特别的处理,跟踪一下:
else if(!strcmp(*argv, "boot")) {
char *kname = 0;
char *rname = 0;
skip(1);
if (argc > 0) {
kname = argv[0];
skip(1);
}
if (argc > 0) {
rname = argv[0];
skip(1);
}
data = load_bootable_image(kname, rname, &sz, cmdline);
if (data == 0) return 1;
fb_queue_download("boot.img", data, sz);
fb_queue_command("boot", "booting");
}
其中,第一个参数为kernel文件名,第二个参数为ramdisk文件名(可选的)。
load_bootable_image:
void *load_bootable_image(const char *kernel, const char *ramdisk,
unsigned *sz, const char *cmdline)
{
void *kdata = 0, *rdata = 0;
unsigned ksize = 0, rsize = 0;
void *bdata;
unsigned bsize;
if(kernel == 0) {
fprintf(stderr, "no image specified\n");
return 0;
}
kdata = load_file(kernel, &ksize);
if(kdata == 0) {
fprintf(stderr, "cannot load '%s': %s\n", kernel, strerror(errno));
return 0;
}
/* is this actually a boot image? */
if(!memcmp(kdata, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
if(cmdline) bootimg_set_cmdline((boot_img_hdr*) kdata, cmdline);
if(ramdisk) {
fprintf(stderr, "cannot boot a boot.img *and* ramdisk\n");
return 0;
}
*sz = ksize;
return kdata;
}
if(ramdisk) {
rdata = load_file(ramdisk, &rsize);
if(rdata == 0) {
fprintf(stderr,"cannot load '%s': %s\n", ramdisk, strerror(errno));
return 0;
}
}
fprintf(stderr,"creating boot image...\n");
bdata = mkbootimg(kdata, ksize, kernel_offset,
rdata, rsize, ramdisk_offset,
0, 0, second_offset,
page_size, base_addr, tags_offset, &bsize);
if(bdata == 0) {
fprintf(stderr,"failed to create boot.img\n");
return 0;
}
if(cmdline) bootimg_set_cmdline((boot_img_hdr*) bdata, cmdline);
fprintf(stderr,"creating boot image - %d bytes\n", bsize);
*sz = bsize;
return bdata;
}
先通过load_file
将kernel加载到内存中,如果发现其魔数是ANDROID!
,代表其已经是打包好的boot.img文件(包含了kernel和ramdisk),这种情况下就忽略对ramdisk的处理。
不然的话,就将ramdisk也加载到内存,并使用mkbootimg
将二者打包成boot.img格式的数据。
然后将这个打包好的数据,写入的usb中,指定要刷的目标为boot.img
。
所以,这个命令可以有两种用法:
1.
fastboot boot boot.img
2.
fastboot boot kernel ramdisk //会先打包成boot.img
另外,可以看到,在使用mkbootimg
时,用到了很多参数,很多参数是可以通过特定选项指定的,比如kernel_offset
用-k
:
case 'k':
kernel_offset = strtoul(optarg, 0, 16);
情景五
类似的flash:raw
:
else if(!strcmp(*argv, "flash:raw")) {
char *pname = argv[1];
char *kname = argv[2];
char *rname = 0;
require(3);
if(argc > 3) {
rname = argv[3];
skip(4);
} else {
skip(3);
}
data = load_bootable_image(kname, rname, &sz, cmdline);
if (data == 0) die("cannot load bootable image");
fb_queue_flash(pname, data, sz);
}
和fastboot boot
类似,先将kernel和ramdisk打包,在刷入,这里的不同是,你需要指定pname,即boot
,使用方式如下:
fastboot flash:raw boot kernel ramdisk
总结
至此,重要的几个命令分析清楚了,即通过fastboot协议,将数据写入到usb中。感觉完整的整个过程,需要再分析一下设备上的usb驱动接收到数据后的处理过程。但暂时没有相关的代码可以分析,先到此为止。
网友评论