我买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。
-
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上。
-
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。
-
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,之前大概看了一些,了解了些原理,理解有误也说不定。要写的太多了写不过来了,就写这些吧。