背景
最近2个多月主要在忙USBTMC
设备端驱动的重构,原来的驱动是参考gtzhai的github工程linux-driver-usbtmc-gadget,然后根据公司需求做了一堆单片机风格的魔改,可读性惨不忍睹,BUG不多但都很棘手,于是决定重构。
重构后所有BUG都解决了,没解决的也知道为啥不能解决。为了对自己这段时间死掉的脑细胞聊表纪念(压力也有点大),决定将心得分享一下。
重构思路
首先让驱动能实现建链
旧版本的驱动充斥着关中断操作,这使得设备不出错则已,一出错就是整机卡死的严重故障。于是我首先将所有开关中断的操作都删除,spinlock也都替换成无irq后缀的版本,然后加上充足的打印,这样能让错误更早暴露出来。
第一个暴露的问题是状态管理的混乱,导致的建链失败。老版驱动用一堆全局flag来管理状态,而且将transfer(UDC跟EHCI的一次BULK传输,长度一般不超过512)跟report(一个USBTMC用户报文,长度不限)的状态混淆在一起,于是抽象出transfer状态机
和report状态机,并定义好各自的相应状态值,这样建链就成功了。
然后让驱动稳定传输
设备建链后传输测量数据2分钟左右就会断链,分析发现是上位机收到MSG2
的应答后会立马发送下一个MSG2,间隔短到状态机无法正常维护。老版驱动是通过关中断
加定期轮询UDC中断
来规避的,我的解决方案是引入队列
(对,你没看错,老版驱动没有使用队列,收发都没使用,仅在接收时将MSG1
的内容拷贝到自己实现的一个蹩脚队列里)和workqueue
,给队列里的每个usb_request
关联一个结构体,里面有负责收发的work
函数、transfer的状态等,收到MSG1或MSG2直接从相应队列里取出一个usb_request
然后将其关联的work函数丢给默认workqueue运行即可,这就解决了中断间隔太短问题。
最后解决插拔USB线等重同步问题
插拔USB线或上位机休眠,都会导致VISA驱动下发ABORT_BULK_IN
消息,老版驱动直接死给客户看(关EP),我的解决办法是按规范来,先发setup应答,再发NAK包(实测NAK包存在后发而先至的情况,于是将发NAK消息的操作也封装成work函数,延迟10ms再发)这样重同步的问题也解决了。
驱动框架
结构图和时序图
资源组织结构:
- 一开始rx_idle里全是usb_request,当收到MSG1时,将含MSG1的req挪到rx_pend供应用进程读取
- 一开始tx_idle里全是usb_request,当收到MSG2时,从tx_idle里摘取一个req到tx_pend供应用写入
MSG收发时序
MSG2收发时序
数据结构
记录transfer的状态和相应work(tx和rx的work函数不一样),关联到usb_request
的context
字段
struct TMCTransferInfo
{
uint8_t bTag; /**< contains the bTag value of the currently active transfer */
uint8_t transfer_state; /**< which stage is the current transfer in? */
uint8_t last_pkt; /**< last packet? */
uint8_t resv;
uint32_t len; /* TMC payload length, excluding 12 byte header */
struct work_struct handler; /* TMC specific rx/tx process */
struct delayed_work aborter; /* send NAK to abort TMC specific rx/tx */
struct usb_request *req; /* which req it belongs */
};
记录report的状态,全局唯一
typedef struct
{
uint8_t report_state; /**< which stage is the reporting in? */
uint8_t reserved[3];
uint32_t report_cnt; /**< num of viRead/viWrite received */
uint32_t bytes_requested; /**< appear in 1st MSG2 */
uint32_t bytes_to_send;
uint32_t bytes_sent;
uint32_t bytes_to_recv;
uint32_t bytes_received;
}report_info;
汇总到private_data
struct tmc_dev
{
struct usb_function function;
struct usb_composite_dev *cdev;
spinlock_t lock_rx;
spinlock_t lock_tx;
spinlock_t lock_report;
struct usb_ep *ep_in;
struct usb_ep *ep_out;
struct usb_request *rx_req;
atomic_t read_excl;
atomic_t write_excl;
atomic_t open_excl;
struct list_head tx_idle;
struct list_head rx_idle;
struct list_head tx_pend;
struct list_head rx_pend;
wait_queue_head_t read_wq;
wait_queue_head_t write_wq;
char buf[512]; // for re-synchronization
report_info rinfo;
uint8_t term_char_enabled;
uint8_t term_char;
/* char driver specific members */
int minor;
struct cdev tmc_cdev;
u8 tmc_cdev_open;
};
总结
这是我第一次重构有一定规模(2000多行)的代码,幸好一开始的策略就是
看懂一部分,重构一部分,验证一部分,通过了再看下一部分
这样的方式,所以一路走来虽然也有挫折反复,但总体是越来越好的。
ps.感谢公司提供了相对富裕的重构时间。