关于波波

典型宅男,喜欢编程,喜欢自由,喜欢做梦。

OpenWRT第一次提交补丁

最近在工作中发现了MT7688 OpenWRT的一个看门狗BUG,简单的来说就是MT7688(包括MT7628/MT7621)设备树描述的看门狗寄存器地址与看门狗内核模块的寄存器地址不符导致不能工作。

所以我第一次向OpenWRT github提交了issue, 对方很nice,指出了我的不少提交错误格式。下次递交一定改正:)

最后等了很久终于合并进主仓库了,提交的注释还是对方帮我写的:)

MT7688看门狗模块还有个寄存器可以知道最近一次重启是看门狗导致的还是系统软重启,这个很有用,可以帮助判断是否死机过。

BTW: 最近空余时间在玩arduino,感觉这是单片机里的OpenWRT啊:),大大降低了开发单片机的难度,而且几个大的创客社区都提供现成的模块和软件包,与我之前上学时候玩的单片机是天壤之别啊,概况技术的进步!!! 最后抱怨一下电压是个坑,5V和3.3V模块混杂,这个有点头疼。

AM335X移植OpenWRT 二

上篇说到OK335xD开发板移植到OpenWRT后网口一直不行,装了tcpdump抓包发现数据能接受但不能发送。

最后搜索到这个TI官方论坛的帖子,说明可能是TCLK频率不对,因为RCLK是PHY芯片AR8031提供的,频率是25MHZ,但TCLK是AM335X提供的PLL分频后提供的,如果PLL配置不对TCLK会错误的频率导致发生失败。

enter image description here

AM335X 以太网Clock是通过CORE_CLKOUTM5提供的(250MHZ),一般来说TCLK千兆网口的话需要125MHZ,百兆网口需要25MHZ,十兆需要2.5MHZ, 我现在用的100M网口,TCLK需要达到25MHZ。

检查一下现在系统中的CORE_CLKOUTM5:

root@OpenWrt:/tmp# cat /sys/kernel/debug/clk/clk_summary | grep m5
         dpll_core_m5_ck              2            2   50000000          0 0  

发现只有50MHZ,所以问题出在这里,应该是uboot没配置好PLL参数,换了一个板子自带的就OK了:

root@OpenWrt:/tmp# cat /sys/kernel/debug/clk/clk_summary | grep m5
         dpll_core_m5_ck              2            2   250000000          0 0  

AM335X移植OpenWRT 一

我手上有一个forlinx的AM335X开发板OK335xD,自带的Linux内核是3.2的而且我测试发现一旦网络传输数据稍大kernel就panic,调试了好久都没搞好(我试了TI官方的SDK,也一样)。

最近看到LEDE是支持OMAP的,抱着试试看的精神就下了最新的LEDE 17.1.04试试。编译用了默认的AM33xx target profile,uboot随便找了个之前AM335x的uboot(这是个坑)。编译后bin目录里是内核,文件系统需要到build_dir的linux_omap里去找root.ext4,我嫌麻烦就上LEDE官网下了一个am335x-evm-ext4-sdcard.img.gz(别看是5M的压缩文件,解压后是256M大文件,估计里面都是0XFF或者是0x00,其实真正文件系统用到10M左右,这个以后可以优化缩小一下-_-!!!)烧到SD卡里,然后替换一下uboot和kernel。

接下来是device tree文件了,这个就要花时间调试了。参考TI EVM的am335x-evm.dts文件复制到forlinx.dts,修改dts/Makefile只编译forlinx.dtb。

需要注意的点是:

  1. 根据板子原理图电源管理芯片是tps65217,需要添加tps65217的节点,而且要根据原理图写好各个dcdc和LDO的节点。 TI的evm板子里DCDC2是MPU,DCDC3是core,而OK335xD反了一下,而且DCDC1用来控制DDR3。

    dcdc1_reg: regulator@0 {
        /* DDR3 VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */
        regulator-name = "vdd_ddr3";
        regulator-min-microvolt = <925000>;
        regulator-max-microvolt = <1150000>;
        regulator-boot-on;
        regulator-always-on;
        regulator-always-on;
    };
    
    dcdc2_reg: regulator@1 {
        /* VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */
        regulator-name = "vdd_core";
        regulator-min-microvolt = <925000>;
        regulator-max-microvolt = <1150000>;
        regulator-boot-on;
        regulator-always-on;
    };
    
    dcdc3_reg: regulator@2 {
        /* VDD_MPU voltage limits 0.95V - 1.26V with +/-4% tolerance */
        regulator-name = "vdd_mpu";
        regulator-min-microvolt = <925000>;
        regulator-max-microvolt = <1351500>;
        regulator-boot-on;
        regulator-always-on;
    };
    

    具体电压可以比对官方的datasheet的OPP:

    enter image description here

    上图能看到OPP turbo时能到720M,其实AM335x A8最高能支持到1G,好像最新LEDE主分支里合并了4.9支持1G了,我现在用的4.4.92还不支持,最高只能跑到720M,等下次更新了再说吧。

    root@OpenWrt:~# cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies 
    275000 500000 600000 720000 
    
  2. OK335xD用的是NAND(暂时不用),所以只有添加一个SD卡启动的MMC1就好了:

    &mmc1 {
        bus-width = <4>;
        vmmc-supply = <&vdd33>;
        pinctrl-names = "default";
        pinctrl-0 = <&mmc1_pins>;
        cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
        disable-wp;
        status = "okay";
    };
    
  3. AM335X支持双网口(可以作为路由器),而OK335xD只用了网口1,PHY芯片用了AR8031,而且支持千兆网口(用了rgmii接口),一般rgmii接口信号线需要引入人为的delay,参考这篇有说明。我看了AR8031的datasheet和内核的代码,AR8031是直接支持tx和rx delay的,只要设置一下debug register就行了,内核驱动at803x.c里也是这么干的:

        if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
            ret = phy_write(phydev, AT803X_DEBUG_ADDR,
                    AT803X_DEBUG_SYSTEM_MODE_CTRL);
            if (ret)
                return ret;
            ret = phy_write(phydev, AT803X_DEBUG_DATA,
                AT803X_DEBUG_RGMII_TX_CLK_DLY);
            if (ret)
                return ret;
        }
    

    但我没看到rgmii-rxid和rgmii-id的支持,所以至少对于我手上的4.4.92来说device tree只支持rgmii-txid:

    &mac {
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&cpsw_default>;
        pinctrl-1 = <&cpsw_sleep>;
        slaves = <1>;
        status = "okay";
    };
    
    &davinci_mdio {
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&davinci_mdio_default>;
        pinctrl-1 = <&davinci_mdio_sleep>;
        status = "okay";
    };
    
    &cpsw_emac0 {
        phy_id = <&davinci_mdio>, <0>;
        phy-mode = "rgmii-txid";
    };
    

    mac集成在AM335X里,而且只用了一个网口, phy-mode是rgmii-txid。

好吧,先写到这里吧,还有很多东西没写,完整的dts在这里。下一篇要写写折腾了我很久的网口问题!!!

参考:

http://www.eefocus.com/marianna/blog/14-10/306285_18a90.html

Spectre和Meltdown相关

过完年第一天上班,我抽空看了一下Meltdown漏洞的机制,这里有一篇Raspberry Pi的Blog讲的很好。看完了解了个大概,感叹现代的处理器越来越先进复杂,触发漏洞的是不是单单一个子系统,而是CPU一堆子系统构成的机制(内存缓存、乱序执行、分支预判和Speculative execution),而且都是跟CPU性能相关的核心部件,所以修复起来肯定要伤筋动骨了。

这次的攻击是side-channel attack(翻译成旁路攻击吗?),就是类似观察系统执行指令消耗的时间去推测具体执行了什么(内存缓存对时间影响很大,所以可以判断出来地址时候缓存),这个攻击只能是查看内核或者别的进程的内存数据,还修改不了内存,不过跟之前的DRAM Row hammer结合起来就威力比较大了:)。

最后看了一下ARM受此漏洞的影响,看到A8受到影响,看来A8用了Speculative execution, Raspberry Pi用的是A7不受影响。

最近情绪低落中…

想起了 Lionel Richie 唱的 Say You Say Me :

Say you, say me
Say it for always
That's the way it should be
Say you, say me
Say it together, naturally
I had a dream, I had an awesome dream
People in the park
Playing game in the dark
And what they played
Was a masquerade
But from behind the walls of doubt
A voice was crying out 
。。。。

MT7688 AES硬件加速

最近我无聊翻翻MT7688的datasheet,注意到居然支持AES加速,赶紧搜github找到了一个能用的OpenWRT驱动MTK_AES

赶紧开搞,一路搞好好几天才慢慢摸索出linux的初步加密框架。内核的加密框架的应用层接口是AF_ALG 套接字形式,但是有一个专门的驱动模块crpytodev抽象出一个/dev/crypto设备,采用ioctl接口,而且根据官网数据对比性能更好。

于是选中OpenWRT自带的kmod-cryptodev模块,默认会选中kmod-crypto-core等一大堆依赖,其实最终只依赖aead.ko,编译好后全部加载驱动。由于系统库的加密是openssl提供的libcrypto加密库,所以需要开启openssl库的硬件加密选项 “Crypto acceleration support”(Libraries>SSL>libopenssl) ,顺便选中openssl-util工具进行测试。

  • 测试未开启AES硬件加速

    root@:/# openssl speed -evp aes-128-cbc
    Doing aes-128-cbc for 3s on 16 size blocks: 1242779 aes-128-cbc's in 2.96s
    Doing aes-128-cbc for 3s on 64 size blocks: 359528 aes-128-cbc's in 2.95s
    Doing aes-128-cbc for 3s on 256 size blocks: 93663 aes-128-cbc's in 2.95s
    Doing aes-128-cbc for 3s on 1024 size blocks: 23499 aes-128-cbc's in 2.92s
    Doing aes-128-cbc for 3s on 8192 size blocks: 2953 aes-128-cbc's in 2.96s
    bala...bala...
    The 'numbers' are in 1000s of bytes per second processed.
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-cbc       6717.72k     7799.93k     8128.04k     8240.75k   
    
  • 测试开启AES硬件加速

    root@:/# openssl speed -evp aes-128-cbc
    Doing aes-128-cbc for 3s on 16 size blocks: 276231 aes-128-cbc's in 0.65s
    Doing aes-128-cbc for 3s on 64 size blocks: 270678 aes-128-cbc's in 0.45s
    Doing aes-128-cbc for 3s on 256 size blocks: 243448 aes-128-cbc's in 0.29s
    Doing aes-128-cbc for 3s on 1024 size blocks: 179638 aes-128-cbc's in 0.34s
    Doing aes-128-cbc for 3s on 8192 size blocks: 45879 aes-128-cbc's in 0.11s
    bala...bala...
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-cbc       6799.53k    38496.43k   214905.82k   541027.39k  3416734.25k
    

主要是看最后一行的吞吐量测试,效果惊人啊!!!!

不开启加速时数据吞吐量变化不大,毕竟这里的性能瓶颈是在AES软件实现这块,而开启硬件加速后,数据块越大效果越好,最后的8192字节块效果就提升了414倍。

这里openssl需要开启-evp选项,这样底层的crpyto库会优先选择硬件加速。openssl里的cryptodev引擎是EVP接口形式提供的,所以如果使用openssl库需要加速,必须用EVP接口。不过openssl测试工具默认的加密引擎engine会自动选择cryptodev,不用再手动指定-engine cryptodev了。

这里比较郁闷的是mtk_aes支持AES的ECB和CBC,但是openssl的libcrypto库的enc_cryptodev.c里只支持CBC,所有最终能加速的只能是AES的CBC了,估计ECB不太安全吧,连CBC也不是很安全,现在普遍用CTR吧。CBC只是比ECB多了个IV矩阵,保证每次加密不重样。

static struct {
    int id;
    int nid;
    int ivmax;
    int keylen;
} ciphers[] = {
    {
        CRYPTO_ARC4, NID_rc4, 0, 16,
    },
    {
        CRYPTO_DES_CBC, NID_des_cbc, 8, 8,
    },
    {
        CRYPTO_3DES_CBC, NID_des_ede3_cbc, 8, 24,
    },
    {
        CRYPTO_AES_CBC, NID_aes_128_cbc, 16, 16,
    },
    {
        CRYPTO_AES_CBC, NID_aes_192_cbc, 16, 24,
    },
    {
        CRYPTO_AES_CBC, NID_aes_256_cbc, 16, 32,
    },
# ifdef CRYPTO_AES_CTR
    {
        CRYPTO_AES_CTR, NID_aes_128_ctr, 14, 16,
    },
    {
        CRYPTO_AES_CTR, NID_aes_192_ctr, 14, 24,
    },
    {
        CRYPTO_AES_CTR, NID_aes_256_ctr, 14, 32,
    },
# endif
    {
        CRYPTO_BLF_CBC, NID_bf_cbc, 8, 16,
    },
    {
        CRYPTO_CAST_CBC, NID_cast5_cbc, 8, 16,
    },
    {
        CRYPTO_SKIPJACK_CBC, NID_undef, 0, 0,
    },
    {
        0, NID_undef, 0, 0,
    },
};

接下来用AES CBC加密一个20M的文件:

root@:/tmp# head -c 20m /dev/zero >test

root@:/tmp# time openssl enc -e -aes-128-cbc -in test -out test.out -kfile pgrade_pass 
real 0m 0.74s
user 0m 0.10s 
sys 0m 0.63s

root@:/tmp# rmmod mtk_aes 
root@:/tmp# time openssl enc -e -aes-128-cbc -in test -out test.out -kfile upgrade_pass 
real 0m 3.33s
user 0m 2.93s 
sys 0m 0.38s

提升了5倍。

我这里的openssl的版本是1.0.2e,最新的1.1版本支持AES的ECB的,我没试过,Cryptodev是1.7版本。现在应用就是以后加解密文件有点用,如果是优化ssh和scp估计也不是很好,比较这些应用瓶颈往往是网速,并且如果是小数据加密的话效果差别不大,只在大数据吞吐时才有差别。

MT7688 AP-Client模式

最近我在研究MT7688的闭源WIFI源码,发现默认开启了AP和AP-Client模式,但STA模式默认没选中,估计要支持的话得修改代码。

MT7688我以及在用LEDE了,新的系统很流畅,所有顺便也把WIFI移植了,修改了几个不兼容的地方基本OK了。本来是想公开的,但这个是MTK闭源驱动-_-!!!,不能公开。

涉及到的MT7688.dat里的驱动参数是这么几个:

ApCliEnable
ApCliSsid
ApCliBssid // 这个不是必须,如果不提供,wifi驱动会自己去查找
ApCliAuthMode
ApCliEncrypType
ApCliWPAPSK

但是OpenWRT里默认的uci2dat支持这些参数有BUG,因为uci2dat默认只支持一个wifi-iface,但AP-Client模式必须有两个wifi-iface,一个做AP,一个做Client。修改uci2data:

@@ -1061,23 +1064,69 @@ void hooker(FILE * fp, param * p, const char * devname)
 #endif
    else if (0 == strmatch(p->dat_key, "ApCliEnable"))
    {
-       FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_enable.value);
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_enable.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_enable.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_enable.value);
    }
    else if (0 == strmatch(p->dat_key, "ApCliSsid"))
    {
-       FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_ssid.value);
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_ssid.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_ssid.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_ssid.value);
+   }
+   else if (0 == strmatch(p->dat_key, "ApCliBssid"))
+   {
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_bssid.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_bssid.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_bssid.value);
    }
    else if (0 == strmatch(p->dat_key, "ApCliAuthMode"))
    {
-       FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_authmode.value);
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_authmode.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_authmode.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_authmode.value);
    }
    else if (0 == strmatch(p->dat_key, "ApCliEncrypType"))
    {
-       FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_encryptype.value);
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_encryptype.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_encryptype.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_encryptype.value);
    }
    else if (0 == strmatch(p->dat_key, "ApCliWPAPSK"))
    {
-       FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_password.value);
+        int j;
+       for(j=0; j<wifi_cfg[N].vifnum; j++)
+       {
+           if (strlen(wifi_cfg[N].vifs[j].apcli_password.value) > 0)
+               FPRINT(fp, p, "%s", wifi_cfg[N].vifs[j].apcli_password.value);
+       }
+       if (j < wifi_cfg[N].vifnum)
+           FPRINT(fp, p, "%s", wifi_cfg[N].vifs[i].apcli_password.value);
    }
     /* the rest part is quite simple! */
     else

编辑/etc/config/wireless:

config wifi-device 'mt7628'
    option type 'mt7628'
    option vendor 'ralink'
    option band '2.4G'
    option channel '11'   // 这个必须是你要连接路由器的信道一致,否则会连不上的
    option country 'CN'
    option region '1'

config wifi-iface 'ap'
    option device 'mt7628'
    option ifname 'ra0'
    option network 'lan'
    option mode 'ap'
    option ssid 'AP名'
    option key 'AP密码'
    option encryption 'psk2'
    option wpa_crypto 'TKIP+AES'
    option hidden '1'

config wifi-iface 'sta'
    option device 'mt7628'
    option ifname 'apcli0'
    option network 'wan' // 作为外网接口
    option mode 'sta'   //  这个没用,uci2dat根本不解析
    option ApCliEnable '1'
    option ApCliSsid '路由器名'
    option ApCliAuthMode 'WPA2PSK'
    option encryption 'psk2'     // 这个必须,如果省略这个会导致WIFI驱动错误的认证协议
    option ApCliEncrypType 'TKIP'
    option ApCliWPAPSK '路由器密码'

在/etc/config/network里修改wan:

config interface 'wan'
    option ifname 'apcli0'
    option force_link '1'
    option proto  dhcp

一开始调试死活连不上,逼得只能看WIFI驱动代码发现底层的认证协议需要加上option encryption,还有就是如果是AP-Client模式的话就不能自动选择信道了,必须跟着路由器的信道来,其实把AP和Client做个桥接就是一个信号中继了,不过WIFI驱动底层好像支持MAC层上的中继,这个没空研究了。

在LEDE的iwinfo没法查看WIFI的Link Quality,而且iw也输出为空,原因就是WIFI驱动还没启用CFG80211支持。但是MT7688的WIFI驱动比较老,导致CFG80211的代码跟最新的内核不兼容,所以我疯狂的在github上一遍查找补丁,一遍修改代码。最后总算是能运行iw了,但是iwinfo输出的信息不全,估计是通过ioctl的方式与驱动打交道,驱动不支持,这个后面再修复。

我关于最近爆出的WIFI的WPA2漏洞KRACK,深感震惊!基本上所有WIFI设备没有幸免,那些IoT设备估计永远都没法更新补丁。根据漏洞描述,基本上是可以揭秘客户端或双向通信的数据,但不能破解WIFI密码!

BTW: 这个是几个礼拜前搞的了,放草稿箱里都要长草了,今天晚上抽空填了坑,近年来记忆力衰退,必须多记录!!!

MT7688开发板linux内核补丁记录

我手上的MT7688的开发板用的是Openwrt的系统,内核版本是3.18.23,有点老,调试过程中各种BUG,记录一下。

1. JFFS2 directory hardlinks BUG

在升级软件包重启后(保护删除文件的操作),报目录硬链接问题(JFFS2支持硬链接,目录不允许硬链接)

这个BUG在JFFS2官方的git软件仓库里修复了

2. JFFS2 incorrect i_nlink count after jffs2’s RENAME_EXCHANGE operations

删除文件和目录时报错:

[  291.190000] ------------[ cut here ]------------
[  291.190000] WARNING: CPU: 0 PID: 1218 at fs/inode.c:339 inc_nlink+0x50/0x74()
[  291.200000] Modules linked in: ...balabala...
[  291.290000] CPU: 0 PID: 1218 Comm: rm Not tainted 3.18.23 #25

这个补丁有人很早就提交给OpenWRT了,可惜都没合并进来,要找补丁可以去LEDE找,看人家多勤快。

3. MT7688的第二路SPI全双工模式挂了

这个我调了好久,MT7688有两路SPI,开发板上SPI0被用来连NOR FLASH做系统用了,我好奇找了另外一块NOR FLASH想连在SPI1上。可惜的是死活不行,检查了各种连线(SPI_CS1被MT7688做启动选项引脚了,启动必须拉低,但这样会片选NOR FLASH,不知道启动会不会有影响),无奈上网翻找补丁。

补丁在这里找到了SPI1全双工挂掉补丁

后记

OpenWRT看来老了,补丁更新不勤快,系统臃肿,是时候切换到LEDE了。

Flex和Bison

最近我想在程序中加一个命令行接口,同时可以作为简单的RPC调用协议使用。这个之前在看libiio里的iiod源码时接触到了Flex和Bison写的命令行接口RPC,

简单的例如:

send cmd 
返回SUCCESS或FAIL

这个虽然直接自己写一个也可以,但是效率和复杂度上有点顾虑。Flex的正则表达式能很好的处理命令行字符串,用Bison去处理命令行的组合虽然有点大材小用,不过万一以后写一个带逻辑语句的命令行就比较方便。

有一点麻烦的是,Flex和Bison的C++支持文档不是很多,大多是C的。不过还好Flex和Bison直接输出C++源码,边看源码边改还算方便。这里特别说一下OREILLY有本动物书是《flex&bison》,网上有PDF版,但用的版本老了点。

其他的没啥好写了,我也在学习中…

https://www.gnu.org/software/bison/manual/html_node/index.html#SEC_Contents

USB液晶屏for树莓派

前序

自从入手树莓派一代,一直闲置着。现在树莓派都二代了,再不玩玩就彻底吃灰了。可惜的是树莓派没接显示器总归不好玩。

我手头有一个Atmel A5(SAMA5D3)的ARM板子,自带TFT-LCD电容触摸液晶屏和USB接口,刚好作为树莓派1代的显示器,两者之间用 USB传输图像数据(不求性能,能显示就行)。

准备就绪就缺实现方案了,突然想起RoboPeak有一个树莓派的液晶屏在卖,而且开源树莓派端的驱动和协议,那我就充分发挥开源优势,在这个基础上改。

这就涉及到两个USB驱动:

  1. ARM液晶屏的USB gadget驱动:

    项目在这里

  2. 树莓派的USB HOST驱动:

    首先刷RoboPeak的树莓派固件,重新编译树莓派的Linux kernel, 编译修改原来的RoboPeak的开源树莓派驱动。最后改好的驱动项目在这里

在研究kernel的USB gadget驱动过程中发现了一个USB协议详解网站,当然USB协议文档也必不可少,开发驱动的话单单看第9章就行了。

实现原理

这里只说gadget驱动,host驱动用RoboPeak原来的rpusbdisp,单单改了分辨率(改成液晶屏的分辨率800X480),当然现在驱动刚刚能用,还有很多要完善。

在USB协议层上有一个interface,两个endpoint,一个OUT传输Robopeak液晶屏的图像协议,另一个IN传输触摸屏的输入。

  1. OUT endpoint

根据RoboPeak的rpusbdisp的协议,我只实现图片拷贝的bitblt命令就行了,这个是host驱动检测到画面有更新后拷贝图片给gadget驱动。我只要介绍到图片数据(颜色深度16位,565格式的真彩色)拷贝到当前的framebuffer就可以了,我这里直接霸王硬上弓拷贝的framebuffer的base memory里。

当然一幅800480的图片传输数据就有700多K(800480*2=768000),为了压缩数据,RoboPeak的协议里检测到gadget驱动版本大于1.04会采用新的压缩数据方法。简单来说就是分成128字节的段,每个段有一个head字节,表示当前端是否是重复的颜色数据(一般的桌面背景很多重复颜色数据),这样简单的压缩一下传输的数据一下子就下降了很多。

  1. IN endpoint

标准的中断数据,由于这个ARM板子是pixcir的芯片,支持电容屏和多点触摸。我在pixcir的驱动里做了一个HOOK函数获取触摸位置再发送给host。

好了,具体的细节看代码吧:)。

usb_display