USBTMC设备端驱动的一些开发心得

背景

最近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供应用写入
    USBTMC设备端驱动的一些开发心得_第1张图片
    MSG收发时序
    USBTMC设备端驱动的一些开发心得_第2张图片
    MSG2收发时序
    USBTMC设备端驱动的一些开发心得_第3张图片

数据结构

记录transfer的状态和相应work(tx和rx的work函数不一样),关联到usb_requestcontext字段

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.感谢公司提供了相对富裕的重构时间。

你可能感兴趣的:(驱动开发,C/C++,USBTMC,USB,驱动,重构)