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。

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

修复WIFI驱动

最近我阴差阳错升级了Ubuntu14.04 Linux内核到4.4.0,升级后发现Broadcom的私有WIFI驱动 wl挂掉了。切换到开源驱动信号又不是很好,万般无奈只能调试找原因了。

dmesg下发现了原因,原来是内核更新后dkms阶段重新编译bcmwl驱动失败了,找不到rdtscl,估计API改了-_-!!!。

Using CFG80211 API
  CC [M]  /var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.o
/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.c: In function ‘osl_getcycles’:
/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.c:938:5: error: implicit declaration of function ‘rdtscl’ [-Werror=implicit-function-declaration]
     rdtscl(cycles);
     ^

电脑上安装的Broadcom的私有驱动是bcmwl, 版本是6.30.223.248。马上上网搜索bcmwl, 找到了Ubuntu的代码仓库。挨个找BUG修复记录,在这里找到了一点端倪

+diff --git a/src/shared/linux_osl.c b/src/shared/linux_osl.c
+index d540636..e622c5c 100644
+--- a/src/shared/linux_osl.c
++++ b/src/shared/linux_osl.c
+@@ -932,6 +932,10 @@ osl_getcycles(void)
+   uint cycles;
+ 
+ #if defined(__i386__)
++#ifndef rdtscl
++#define rdtscl(low) \
++ ((low) = (u32)rdtsc())
++#endif
+   rdtscl(cycles);
+ #else
+   cycles = 0;
+-- 
+1.9.1
+

OK,马上修改,安装bcmwl的代码在/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build下,sudo make , sudo make install后,depmod一下就满血复活。

但这有个缺点,默认仓库里的bcmwl代码还是没改过来,下次升级内核,dkms机制下编译的还是有BUG的代码,每次升级都要编译一下WIFI驱动,有点麻烦,下次再找方法吧。

第一次在Github上合并Pull Request

我用Github也差不多两年了吧,大多上传一些自己无聊的代码而已。今天打开Github发现有个老外居然向我提交代码,没错是full_text_rss仓库-_-!!!。

full_text_rss最初是因为用Google Reader读到的RSS很多都没有全文输出,所以自己就在当时的VPS做了一个(详细见这里),后来忘了啥原因就把网站关了,把代码丢到Github上就没管了,到今天合并代码才想起来,让我这些老代码又焕发活力了:)。Github用到现在为止感觉不错,以后也要多多提交代码咯。

工作中发现的交流问题(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,之前大概看了一些,了解了些原理,理解有误也说不定。要写的太多了写不过来了,就写这些吧。

春节过年中

越来越就得过年好无聊,各种烦恼不开心:(

Eric Carmen 的 All by Myself

When I was young
I never needed anyone
And makin' love was just for fun
Those days are gone

Livin' alone
I think of all the friends I've known
But when I dial the telephone
Nobody's home

All by myself
Don't wanna be, all by myself anymore
All by myself
Don't wanna live, all by myself anymore

Hard to be sure
Some times I feel so insecure
And love so distant and obscure
Remains the cure

gnu make有关建立目录

最近写代码想Makefile同时支持交叉编译器和主机编译器,所以要把不同编译器生成的目标文件放到不同的目录中。首先想到的是目录里要保留原来的源文件目录结构,这样就可以用隐式规则或静态规则推导出源文件,否则根本无法得到依赖关系。最后用了
隐式规则扣出源文件路径才勉强可用,照这个思路花了点时间写了个测试例子,准备把现有需要交叉编译的程序都转到这种模式:

ifdef HOST
    objdir:=obj_$(word 1, $(subst -, ,$(HOST)))
else
    objdir:=obj
endif

SRC_DIR:=. src
CFILES:=$(shell find $(SRC_DIR) -maxdepth 1 -name "*.c")
OBJS:=$(addprefix $(objdir)/,$(CFILES:%.c=%.o))
OBJDIRS:=$(addprefix $(objdir)/, $(SRC_DIR))
TARGET=main

all:$(OBJDIRS) $(TARGET)

$(TARGET):$(OBJS)
    gcc -o $@ $(OBJS)

$(objdir)/%.o:%.c
    gcc -c $< -o $@

$(OBJDIRS):
    @mkdir -p $@

clean:
    -rm -r -f $(objdir) $(TARGET)

.PHONY: clean all

还有就是查资料时发现了这个东东(order-only prerequisites),可惜没法用在我这里,因为用隐式规则我没法弄出目录依赖-_-!!!,下一步是把所有*.d文件也搞进目录中去。


gnu make的两种依赖关系(Prerequisites):

  1. 正常依赖(normal prerequisites)

    1)检查和重建依赖

    2)如果依赖更新,则要重建目标

  2. 顺序依赖(order-only prerequisites)

    1)检查和重建依赖

顺序依赖不会影响最终的目标的重建,这个适合与在makefile里建目录,因为目录作为目标有一个特性是目录里的文件改了,目录就更新了,导致一系列不必要的重建,用顺序依赖方式的话,就不回影响最终的目标了

Gnu make手册上给了个例子:

 OBJDIR := objdir
 OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

 $(OBJDIR)/%.o : %.c
         $(COMPILE.c) $(OUTPUT_OPTION) $<

 all: $(OBJS)

 $(OBJS): | $(OBJDIR)

 $(OBJDIR):
         mkdir $(OBJDIR)

http://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html http://cm.techwell.com/articles/weekly/making-directories-gnu-make