问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

U-Boot中Fastboot UDP协议分析

创作时间:
作者:
@小白创作中心

U-Boot中Fastboot UDP协议分析

引用
CSDN
1.
https://blog.csdn.net/qq_56733950/article/details/141429979

本文详细分析了U-Boot中的Fastboot UDP协议,从基本概念到源码实现,再到具体的应用场景,为读者提供了一个全面的技术指南。

一、什么是Fastboot

Fastboot是Android系统常用的一种刷机方法。Android系统设计了两种刷机方式:Fastboot和Recovery。不论是哪一种,都是用来给设备刷写固件的。而刷机其实就是镜像传输+烧录,Fastboot刷机时使用USB来传输镜像文件,然后写入对应分区。

Fastboot是一种通信协议。基本工作原理就是上位机(PC)通过USB或以太网(UDP)与引导加载程序(U-Boot)通信。它的设计兼容性很好,可以在各种设备上运行,如Linux、Windows的主机上使用。

Fastboot是U-Boot中的一个命令。在U-Boot控制台中执行Fastboot命令就可以让U-Boot进入Fastboot模式。要实现Fastboot刷机,只有开发板端U-Boot是不行的,还需要在主机有Fastboot.exe配合。

二、Fastboot原理

U-Boot的Fastboot命令会将开发板伪装成一个USB设备。因为开发板本身并不是一个USB设备,所以开发板直接插到电脑是没有反应的。伪装之后开发板就被主机Windows识别成一个USB设备。

Fastboot在开发板和主机间定义了一套协议(Fastboot协议),这套协议以USB为底层传输物理层,协议规定了主机Fastboot软件和开发板Fastboot软件之间的信息传输规则。在该规则下,消息传递可以实现的功能有:主机可以向开发板发送命令、开发板可以向主机发送回复、主机可以向开发板发送文件(download)。

Fastboot实现方式

主机的Fastboot软件和开发板的Fastboot程序是怎样通信来工作的呢。平时工作时,开发板端只要执行了Fastboot命令进入Fastboot模式即可,开发板这一侧剩下的就不用管了。之后主机端通过运行Fastboot命令,传递不同的参数来实现主机端和开发板端的通信。譬如主机端执行Fastboot devices,则这个命令通过USB线被传递到开发板中被开发板的Fastboot程序接收,接收后去处理向主机端发送发送信息,主机端接收到反馈信息后显示出来。

Fastboot命令执行过程

三、Fastboot UDP 源码分析

Fastboot代码的分析思路:

主机端:Fastboot.exe的源代码是没有的,所以主机端不是分析的重点。

开发板端:主要分析点就是U-Boot如何进入Fastboot模式,Fastboot模式下如何响应主机发送的各种命令。

U-Boot控制台输入Fastboot命令之后会执行do_fastboot函数

在do_fastboot函数中有两个分支,如果终端输入Fastboot usb会使用USB进行更新,如果输入Fastboot udp会使用网口来进行更新。

如果走UDP会调用do_fastboot_udp()函数,来看一下

在do_fastboot_udp()函数中又调用了网络循环发送函数net_loop(),在这个函数中会匹配我们使用的网络协议,从而执行相应的函数。

可以看到这里调用了fastboot_start_server()函数,定位该函数,发现在neet/fastboot.c中,打开fastboot.c会发现,该文件是对Fastboot UDP方式的核心实现

在fastboot_start_server 函数中,首先会打印出使用的网络设备名称,以及正在监听的 IP 地址。

fastboot_our_port 被设置为预定义的常量 CONFIG_UDP_FUNCTION_FASTBOOT_PORT,这个端口用于接收控制台输入的 Fastboot 命令。

如果配置了 CONFIG_IS_ENABLED(FASTBOOT_FLASH),那么会设置一个进度回调函数 fastboot_set_progress_callback(fastboot_timed_send_info)。会在 Fastboot 操作过程中定时向主机发送信息。

接着设置 UDP 包的处理函数,使用 net_set_udp_handler(fastboot_handler) 设置了一个 UDP 数据包处理函数为fastboot_handler()。到时所有接收到的 UDP 数据包都会被 fastboot_handler 函数处理。

最后,清空服务器 MAC 地址。使用 memset 函数将 net_server_ethaddr 数组中的内容全部置零。这是为了在服务器 IP 地址发生变化时清除旧的 MAC 地址信息。

可以看到在fastboot_handler中,前半段都是一些必要的初始化和拆包处理,注意这里把数据包头部的信息复制到 header 结构体中就行。重点看switch中的执行逻辑,这里通过检查数据包的头部信息,然后会根据不同的请求类型采取相应的动作。如果是查询请求 (FASTBOOT_QUERY),则直接响应;如果是初始化 (FASTBOOT_INIT) 或者执行 (FASTBOOT_FASTBOOT) 请求,则根据序列号来确定是否需要发送响应或者重发数据包。如果接收到的数据包类型不被识别,则会反馈一个错误信息给对方。

这里面都调用了fastboot_send()函数,来看看这个函数

在fastboot_send()中首先进行了一些初始化准备工作,让packet指针偏移到填充数据的地方,以及判断是否要重传上一次数据包。接着为待发送的数据做准备工作,先将本地字节序转换为网络字节序,将response_header(回复包的头部信息)拷贝到待发送的packet指针所指向的空间中,指针继续向后偏移

接着会根据接收到的数据包的头部信息,去构造不同的响应数据包,如果是查询请求 (FASTBOOT_QUERY),则构造一个包含sequence_number的响应包;如果是初始化请求 (FASTBOOT_INIT) ,则构造一个包含udp_version和其长度信息的响应包;如果是出错请求(FASTBOOT_ERROR)则会构造一个包含错误信息的响应包;

如果是 (FASTBOOT_FASTBOOT) 请求,先判断当前命令是否是FASTBOOT_COMMAND_DOWNLOAD,是的话再看当前有没有剩余数据,没有数据的话调用fastboot_data_complete()函数,来处理数据下载完成的情况,包括发送响应、输出日志、设置环境变量以及重置状态等操作。如果还有数据的话会调用fastboot_data_download()函数,来接收主机传过来的数据。

如果当前没有待处理的命令,则复制命令到 command 并设置 pending_command 为真。

否则,调用 fastboot_handle_command 来处理命令,并更新 pending_command

如果接收到的数据包类型不被识别,则会打印出错误信息。

将响应字符串response拷贝到响应包pkcket中,最后调用uboot自带的一个底层udp包发送函数net_send_udp_packet()将构建好的响应包发送出去。

如果响应字符串的前四位是“OKAY“会匹配当前cmd去执行对应操作。如果响应字符串以 "OKAY" 或 "FAIL" 开头,则重置 cmd。

四、Fastboot UDP中的帧格式

其中id是一个枚举,

FASTBOOT_QUERY代表了是一个快速请求类型的数据包,(告诉我你当前待接收的包序号)

FASTBOOT_INIT 代表了是一个获得初始化信息类型的数据包,(告诉我你的udp版本,以及你一包数据是接多大)

FASTBOOT_FASTBOOT代表了是一个命令或者文件类型的数据包,

flags在发送端发出的包中为1,在接收端发出的包中为0

seq代表当前包的一个序号

包的结构大概是这样的

五、Fastboot UDP中的重传机制

接收端的udp包处理函数fastboot_handler()在接收到udp包后,会先对比包头中的seq序号,和我本地这边等待接收的包序号seq_num做一个对比,如果二者一致,说明是我想要的包,调用fastboot_send(,,,0),在send函数中做数据的一个处理,并构建相应的反馈包发送给发送端;如果seq=seq_num – 1,则认为发送端没有收到我的反馈包,会调用fastboot(,,,1),在send函数中重发上一次发送的反馈包。在fastboot接收端中从头到尾,不论是命令包还是数据包都有包序号,都是根据包序号是否匹配来唯一判断是否接收正确(虽然拿不到上位机侧的协议源码,但可以推测发送端也一定是这样,只看序列号对不对)。

六、Fastboot UDP 命令底层怎么执行

在这个函数中,会循环对比预先定义的命令结构体数组中是否有传进来的命令相同的,如果有则会调用对应结构体中的命令处理函数。

七、Fastboot UDP中如何扩展指令

可以看到到,这是厂商自己添加的两个命令,要实现添加自定义指令,首先需要在最开始定义指令的结构体数组中,添加字节定义的指令名称,并填好这个指令的处理函数的入口地址

接着在下面按照厂商的实现形式,仿写一个命令处理函数即可。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号