在Android的SDK中自带了fastboot,路径为
${SDK_HOME}/platform-tools/fastboot
查看help:
usage: fastboot [
够长的,好多参数解释看得也不是很懂。
自己编译Android源码也会产生fastboot,路径为:
${OUT}/host/darwin-x86/bin/fastboot //Mac的编译结果在darwin-x86下
查看help:
usage: fastboot [
两者还不太一样,好像自己编译的在功能上是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 [ ] 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驱动接收到数据后的处理过程。但暂时没有相关的代码可以分析,先到此为止。