工作中发现的交流问题(X-Y Problem)

我很早就读过酷壳的这篇文章X-Y Problem,在工作中同事时常犯这种问题-_-!!!。

虽然我自己也不擅长沟通,但我在沟通时还是有意识的说的全面点,如果还不行的话就画图呗,一图胜千言啊。有时候说清楚一件事情是很难的。

按酷壳博主的意思X-Y问题如下:

1) 有人想解决问题X

2) 他觉得Y可能是解决X问题的方法

3)但是他不知道Y应该怎么做

4)于是他去问别人Y应该怎么做?

我遇到的X-Y Problem都是同事试图向我沟通不是他们专业的问题,在这种情况下人很容易的按自己的理解提出问题,但其实如果没有扎实的基础知识和经验,问题的假设前提往往是错的,更何况问题本来就很复杂,可能更本就没了解清楚问题。

说了这么多,举个典型例子吧:

某天同事A打电话来问我一个工作问题:

同事A:xxx设备如果能xxx相比xxx能不能提高速度?

…然后是一大堆解释和讨论…

然后我精疲力竭的弱弱问了一句:你为什么想这么搞,现在xxx设备速度还可以嘛,没必要搞啊?

然后同事A惊讶的喊:xxx设备不是很慢很慢吗,xxx指标都xxx了。

然后我-_-!!!说:xxx设备本来就没这个问题啊,你瞎想了…

然后就没然后了-_-!!!

如果事前能先说明一下原因,也不至于费这么多口舌和我的脑细胞啊,不说了,总之在说明问题前先说清楚自己的思路,你为什么这么想的理由,这样人家才能帮你吧。

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