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会错误的频率导致发生失败。

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:

    上图能看到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

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了。

修复RG100A的按键

我买RG100A路由器很久了,刷了OpenWRT后作为公司工作的主力路由器至今。因为我平时要调试很多不同网段的设备,全靠了OpenWRT的VLAN功能从而轻松解决。为了消除记忆IP地址麻烦就使用了mDNS,为了能把mDNS广播到各个子网,专门在OpenWRT上跑了个程序进行监听和转发mDNS(不过后来发现太占资源就去掉了,最主要是设备不多,Windows安装mDNS一直有问题,剩下的只有Ubuntu笔记本和虚拟机上跑的Ubuntu了)。但我一直不太满意的是RG100A后面的按键,三个按键(WPS、Wifi、Reset)OpenWRT不知为何不支持。最近几天闲来无事就想彻底解决这个问题。

首先就是找问题的根源了,这个除了看代码外别无他途了。从OpenWRT官网下载代码,我装的是老版的Backfire 10.03,内核是linux-2.6.32.27(之前试过编译最新的版本,但一烧进去要么网络有问题,要么就直接崩溃-_-!!!),编译了大半天,主要是下载各种软件包,幸亏网络给力(电信宽带马上要升到12M了:))。

接下来是找相关的驱动代码,这里主要是三个驱动:input_gpio_button、input_polldev、button-hotplug。

  1. input_gpio_button负责低层读取按键的GPIO口状态转换成input event(我这里把把Wifi键映射成BTN_0, WPS键映射成BTN_1,没用Reset键)。这个代码在build_dir/linux-brcm63xx/linux-2.6.32.27/drivers/input/misc/gpio_buttons.c上。

  2. input_polldev是input system的input_dev,因为这三个按键的GPIO都不支持中断,所以只能采取轮询方式,这个input_polldev就是干这个事的。它不断通过input_gpio_button查询GPIO状态,然后发送input event。源码在build_dir/linux-brcm63xx/linux-2.6.32.27/drivers/input/input-polldev.c。

  3. button-hotplug是面向应用层接口的,把input_event转换成hotplug消息。这个主要是内核的hotplug机制(通过内核netlink技术广播对象消息,从而支持热插拔之类的)。OpenWRT用的是hotplug2,具体配置在/etc/hotplug.d下。如果要在应用层处理按键事件,就新建/etc/hotplug.d/button目录,写个测试脚本,用按键点亮前面板的WPS LED灯,参考这里。源码在build_dir/linux-brcm63xx/button-hotplug/button-hotplug.c。

    #!/bin/sh
    LED_NUM=24
    [ -e "/sys/class/gpio/gpio${LED_NUM}/value" ] || echo $LED_NUM > /sys/class/gpio/export
    [ -e "/sys/class/gpio/gpio${LED_NUM}/direction" ] && echo out > /sys/class/gpio/gpio${LED_NUM}/direction
    
    turn_on()
    {
        echo 0 > /sys/class/gpio/gpio${LED_NUM}/value
    }
    
    turn_off()
    {
        echo 1 > /sys/class/gpio/gpio${LED_NUM}/value
    }
    
    if [ "$ACTION" = "pressed" ]; then
        turn_on
    else
        turn_off
    fi
    

而出问题的驱动是input_gpio_button,它依赖平台board的GPIO按键配置,而我调试后发现board初始化给的button GPIO参数不对。这样要么改board初始化代码,要么在input_gpio_button 代码里手动指定GPIO参数。我不想重新编译整个内核,所以选择后者,下面是补丁:

--- ../linux-brcm63xx/linux-2.6.32.27/drivers/input/misc/gpio_buttons.c 2014-02-27 16:58:19.502642430 +0800
+++ gpio_buttons.c  2014-03-06 11:05:50.939708245 +0800
@@ -29,6 +29,31 @@

 #define DRV_NAME   "gpio-buttons"

+static struct gpio_button my_all_buttons[] = {
+        {
+            .gpio = 34,
+            .active_low = 1,
+            .desc = "wifi",
+            .type = EV_KEY,
+            .code = BTN_0,
+            .threshold = 3,
+        },
+        {
+            .gpio = 37,
+            .active_low = 1,
+            .desc = "wps",
+            .type = EV_KEY,
+            .code = BTN_1,
+            .threshold = 3,
+        },
+};
+
+static struct gpio_buttons_platform_data my_buttons_datas = {
+    .buttons = my_all_buttons,
+    .nbuttons = 2,
+    .poll_interval = 100,
+};
+
 struct gpio_button_data {
    int last_state;
    int count;
@@ -55,6 +80,9 @@
    if (state != bdata->last_state) {
        unsigned int type = button->type ?: EV_KEY;

+        // I add
+        printk(KERN_DEBUG "type %d, code %d, active %d", type, button->code, state ^ button->active_low);
+
        input_event(input, type, button->code,
                !!(state ^ button->active_low));
        input_sync(input);
@@ -84,7 +112,8 @@

 static int __devinit gpio_buttons_probe(struct platform_device *pdev)
 {
-   struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data;
+   //struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data;
+   struct gpio_buttons_platform_data *pdata = &my_buttons_datas;
    struct device *dev = &pdev->dev;
    struct gpio_buttons_dev *bdev;
    struct input_polled_dev *poll_dev;
@@ -95,6 +124,15 @@
    if (!pdata)
        return -ENXIO;

+    // I add
+    printk(KERN_DEBUG "platform_data buttons: %d poll_interval: %d ", pdata->nbuttons, pdata->poll_interval);
+   for (i = 0; i < pdata->nbuttons; i++) {
+        printk(KERN_DEBUG "button gpio %d %s type:%d code:%d", pdata->buttons[i].gpio, pdata->buttons[i].desc, 
+                                                               pdata->buttons[i].type, pdata->buttons[i].code);
+    }
+
+    printk(KERN_DEBUG "platform_device name %s", pdev->name);
+
    bdev = kzalloc(sizeof(struct gpio_buttons_dev) +
               pdata->nbuttons * sizeof(struct gpio_button_data),
               GFP_KERNEL);
@@ -189,7 +227,8 @@
 static int __devexit gpio_buttons_remove(struct platform_device *pdev)
 {
    struct gpio_buttons_dev *bdev = platform_get_drvdata(pdev);
-   struct gpio_buttons_platform_data *pdata = bdev->pdata;
+   //struct gpio_buttons_platform_data *pdata = bdev->pdata;
+   struct gpio_buttons_platform_data *pdata = &my_buttons_datas;
    int i;

input_unregister_polled_device(bdev->poll_dev);

中间加了些调试信息,主要是那个gpio_buttons_platform_data my_buttons_datas,默认是platform_device *pdev指定的。这个在board init里指定,有兴趣的可以看看arch/mips/bcm63xx/board_bcm963xx.c,里面有很多板子,RG100A对应的板子是96358VW2,这个可以通过命令行dmesg | grep board看出来。

有关RG100A的GPIO参数,网上有公布,我只摘抄一些:

button gpio:
    gpio34 wifi
    gpio36 reset
    gpio37 wps

led gpio:
    gpio0 usb
    gpio4 power
    gpio8 net
    gpio22 dsl
    gpio24 wps

这样修改好后,载入驱动就可以通过按键控制RG100A前面板WPS灯了,不过暂时还想不出来可以用来干什么。我现在用RG100A的USB口可以驱动摄像头了,就搞了个远程拍照的东西,还不错,就是不太稳定-_-!!!。下一步细看linux的 input system,之前大概看了一些,了解了些原理,理解有误也说不定。要写的太多了写不过来了,就写这些吧。

打造OpenWrt网络监听器

前言

写了上一篇后我偶然想到网桥也可以用来监听数据:),只要在目标PC或路由器出口上连接一个网桥,监听网桥网卡就可以了。

实现

话说原理其实很简单啦,网桥连接两个子网,因为是二层设备所以对上层的TCP/IP是透明的,而且所有通信都要经过网桥中转,用普通的tcpdump就可以监听了。

在OpenWrt上需要开启VLAN来模拟两个虚拟子网vlan0和vlan1

config switch eth1
        option reset 1
        option enable_vlan 1

config switch_vlan
        option vlan 0
        option device eth1
        option ports '0 1 2 5t'

config switch_vlan
        option vlan 1
        option device eth1
        option ports '3 5t'

两个vlan网络接口分别为eth1.0和eth1.1,0、1、2口为一个子网,3口单独一个子网。现在把eth1.0和eth1.1两个网络接口组成一个网桥br0:

config interface br0
        option type bridge
        option ifname 'eth1.0 eth1.1'
        option proto none

br0默认不设置ip,因为我直接用TTL线获得Shell所以用不着。如果你想远程访问OpenWrt,则必须按情况设置static或dhcp,当然也可以用前一篇说到底Wifi客户端,这样你就可以舒舒服服的坐在电脑边远程登录查看抓包了:)。

最后一步是关闭防火墙,因为网桥用不着:

/etc/init.d/firewall disable

重启网络后可以用ifconfig看到很多网络接口,只要监听br-br0就行了:

br-br0    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth1      Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
          Interrupt:14

eth1.0    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth1.1    Link encap:Ethernet  HWaddr 00:74:04:07:4B:CC
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

测试

  1. 首先在OpenWrt里安装Tcpdump:
    opkg install tcpdump
    
  2. 用网线连接电脑到路由器3口(eth1.1),路由器2口(或0、1口)连接原本电脑的出口。
  3. 运行Tcpdump监听POP邮箱密码:
    tcpdump -X -i br-br0 port 110
    

如果你的邮箱没加SSL的话,应该能看到密码明文啦:)。

OpenWrt无线Wifi客户端模式

前言

前一篇讲到了OpenWrt的网络结构,这一篇讲讲OpenWrt的Wifi客户端模式,这主要是满足那些懒得接网线的人:)。

Wifi一般用的是AP(Access Point)模式:无线路由器作为一个Wifi热点为其他的Wifi客户端提供连接服务。

这里要用到的是Wifi的STA(Station)模式:作为客户端连接AP模式下的Wifi,这个一般笔记本的无线网卡都是这种模式(详细的Linux下支持的Wifi模式看这里),网络结构图:

上图中左边STA客户端模式下的无线网卡作为WAN把192.168.2.0/24划为内网,用NAT方式连接外网的Wifi AP节点(192.168.1.1)。这个很像经典的OpenWrt WAN和LAN网络结构,只不过把WAN的以太网接口换成了Wifi。

OpenWrt设置

在OpenWrt下主要是设置/etc/config/network、/etc/config/wireless这两个文件,其他的都与默认的LAN和WAN模式相同。

/etc/config/network下,关闭VLAN(enable_valn=0),lan接口设置成静态并去掉网桥(默认为’option type bridge’,Wifi通常自动桥接到lan接口),wan去掉’option ifname ‘选项(无线Wifi接口会自动加入wan作为ifname)。

config switch eth1
    option reset 0
    option enable_vlan 0

config interface loopback
    option ifname lo
    option proto static
    option ipaddr 127.0.0.1
    option netmask 255.0.0.0

config interface lan
    option ifname eth1
    option proto static
    option ipaddr 192.168.2.1
    option netmask 255.255.255.0

config interface wan
    option proto dhcp  

配置以后所有交换机上的接口都变为内部LAN,而无线Wifi作为WAN连接外网。LAN和WAN之间用NAT方式进行地址转换(具体在firewall的WAN设置masq=1,默认已经设置好了),firewall的NAT选项叫Masquerade(伪装),就是WAN接口把内网的数据包源地址伪装成自己的,很形象:)。

我一开始连接失败用Tcpdump查看wlan0,发现数据包还没有NAT伪装,最后发现是firewall没启动,所以确保firewall开机启动:

/etc/init.d/firewall enable

查看firewall是否启动:

/etc/init.d/firewall enabled && echo on

/etc/config/wireless下,设置Wifi参数:

config wifi-device radio0
    option type mac80211
    option channel 0
    option hwmode 11g
    option txpower 0

config wifi-iface
    option device     radio0
    option network    wan
    option mode       sta
    option ssid       yourAPssid
    option encryption psk2
    option key        yourkey

主要是设置mode为sta,network选择要自动加入wan,填上要连接Wifi AP的ssid、加密方式encryption和密钥key,全部完成后重启网络,Wifi连接成功后WLAN LED灯会亮起。

/etc/init.d/network restart

把你的台式电脑网线随便插入LAN口,这样你就成为了一台有无线网卡的台式机了,省去了用网线想方设法连接其他房间路由器的烦恼:)。

参考

http://wiki.openwrt.org/doc/howto/clientmode

http://wiki.openwrt.org/doc/recipes/routedclient#using.masquerade

OpenWrt网络结构

OpenWrt的网络配置很丰富,在我看来几乎可以完成任何网络结构。下图为一个支持OpenWrt的路由器网络结构:这个路由器内部交换机有6个口,其中1个WAN口、4个LAN口、Port5默认连接内部网卡eth0,还有连接Wifi的无线网卡接eth2,eth3保留。

从中我们可以看到这个路由器最多支持3个物理网络接口eth0、eth1、eth2。其实一般路由器有两张以太网卡和一张无线网卡,但更多的路由器里只有一张以太网卡和一张无线网卡。

最让我惊奇的是OpenWrt网络的灵活性,它主要靠VLAN和(Bridging)网桥等实现。

  • VLAN一般路由器为节约成本只有一张网卡(我的理解是以太网控制芯片),但如何接入多个网络呢?(路由器按常理至少要两张网卡吧),这就是VLAN的功劳啦。VLAN(Virtual Local Area Network)是虚拟局域网缩写,是把局域网中的同一工作组的主机连在一起,隔绝不同的子网(在一个或多个交换机上),这种局限增强了工作组间的保密性,也减少了全面广播的副作用。用了VLAN也就把连接子网用的路由器给省了,省钱而且效率还很高。

    VLAN需要路由器内置的交换机支持(一般都支持啦),最多可以划分4096个VLAN。一般使用只划分两个VLAN:WAN和LAN,分别对应外网和内网。如果是纯粹的VLAN,则WAN和LAN之间是隔离的,互相不能通信,但在自己的VLAN里可以自由通信。

  • Trunking用一张网卡怎么连接两个网络呢?不知你是否留意到上图中的Port5,Port5一边连接路由器,一边连接网卡eth0,它的作用就是连接vlan0和vlan1。这就是采用VLAN的Trunking技术,在Trunk Port(port5)经过的以太网帧上打标签(Tagging)用以区分来自那个VLAN,然后路由器用网卡eth0接收并处理标签从而处理(在CPU中处理),这就是所谓的单臂路由器,很形象啊:)。
  • Bridging网桥说穿了就是交换机,它就是用软件模拟以太网交换机,连接多个以太网网卡(虚拟的或物理的)分享同一个IP子网。在OpenWrt中,网桥主要用来连接无线WIFI网络和LAN,组成一个统一的内部局域网

最后为了方便理解,我画了一张示意图用普通的交换机和路由器解释上面提到的VLAN、Trunking和Bridging:

hand

参考

VLAN动画详解

OpenWrt network interface

Linux下的网桥