原厂MT7620N转为OpenWrt随笔

#@波王科技MT7620N原厂固件路由器折腾全过程

前几天,向@波王科技要5个路由器样品,配置是MT7620N, 8M的Flash, 64M的Memory,前天(9月1日)路由器到货了,由于忙这忙那,一直昨晚(9月2日)21点才开始鼓捣这些路由器.


第一天, 拆箱,拿出一个路由器,上电,连接WiFi,发现不知道WiFi密码; 遂只好连接有线以太网,本本成功DHCP获取IP地址,然后赶紧的打开浏览器,输入路由器的地址,进入到路由器的后台管理登陆界面,只可惜还是同样的,不知道用户名密码.路由器后盖标签上写的username/password:admin,进而我尝试了用户名和密码都是admin,失败,登不进去.猜测试过root/root也不行. 便电询@波王科技, 要我直接进Uboot界面刷适合Openwrt的Uboot,然后再移动那64K的所谓的产测数据,网卡和RF的一些Mac,参数等.得知原厂的MTD布局和OpenWrt的布局不一致. 原厂的factory数据在0xbc030000--0xbc040000之间.得知这些信息后,我拆开路由器后盖,意外发现木有那TTL的接线柱,对于我一个不怎么摸电子硬件的人来说,又要多增一份动作,需要自己找排针自己焊接.于是找来了排针自己在板子上TTL预留的孔里焊接好.然后连接RX/TX/GND三根线到USB转TTL的适配器上,插入MacBook,打开终端,运行minicom -s 设置好对应的串口设备:/dev/tty.SLAB_USBtoUART, 波特率:57600, 8N1, 关闭硬件流控模式. 设置好后进入minicom. 给路由器上电,键盘按4,进入uboot命令界面,敲入help,发现有cp命令可以用,遂尝试用cp命令来进行数据的拷贝移动,cp.b 0xbc030000 0xbc040000 0x10000, 本想用这个来把factory分区的数据往后移动到Openwrt固件正确的位置.但是一回车就报错了,不支持此命令.原来被忽悠了.uboot应该是经过简化的,已经没有这个功能了.此路不通.另寻它法.于是到群共享里下载了lintel上传192K的Uboot文件,TTL+TFTP刷机,下载完文件后报错: bootloader size 19668 to big! ,然后又尝试了manfeel为wrtnode提高的uboot,170K+大小..同样的错误. 第一次尝试就这样失败结束.第二天早上再尝试.向@波王科技要了个lintel的适用128K大小的Uboot.感谢好几位朋友的热心帮助, 暖暖的.


第二天早上,继续尝试TTL + TFTP刷写Uboot,这回的Uboot是@波王科技给的Uboot,还是老步骤, 链接TTL线,打开minicom(57600 8N1),给路由器上电,minicom里敲入9进入TFTP刷写Uboot模式. 按Y回车,接着输入设备的IP,和TFTP服务器的IP(就是有线连接的PC的IP), 然后输入固件名字.回车就开始下载了.刷写完了之后,重启正常!!!! 一点欣喜,Uboot的大小错误解决了.
Uboot的问题没有了,下面需要刷OpenWrt的固件,步骤类似,不过是上电之后在串口终端minicom界面按2,进入TFTP刷写固件的模式.刷写完之后也是预料之中的问题,那就是网络功能不行,因为factory分区的数据不对,这个时候我想用把正确的数据从错误的位置(刚好是op下的mtd1)dd出来到一个文件.这个没问题,但是dd写入mtd2分区或者mtd write写入factory分区都失败,没有写的权限.虽然dev下设备文件的权限位有可写.
遇到这样的现象,让我想到了,在openwrt代码中的target/linux/ramips/dts目录下的众多板子的dts文件定义, mtd分区定义的时候默认除了firmware分区外,其他都是read-only的.所以问题所在和解决方法也找到了.
于是迅速点亮我的1U Debian Sid编译服务器,编辑对应的dts文件.找到mtd的factory分区定义的地方:

factory: partition@40000 {
        label = "factory";
        reg = <0x40000 0x10000>;
        read-only;
    };

去掉其中的read-only这一行,打开可写:

factory: partition@40000 {
        label = "factory";
        reg = <0x40000 0x10000>;
    };

当然,如果想要所有分区都可以在OpenWrt里刷写,那方法也都知道了.

我在这里也顺便把OpenWrt里的串口波特率也修改为Uboot的波特率一致了.dts中关于波特率定义在这:

chosen {
    bootargs = "console=ttyS0,115200";
};

因为Uboot的波特率是57600,OpenWrt默认波特率为115200,这样就会出现一个繁琐的问题,uboot刷机后,引导内核进入系统之后,终端会乱码,因为前后的TTL输出的波特率不一致.老是需要在2个模式下去切换.所以,我就修改OpenWrt的波特率和Uboot一致为57600,如下:

chosen {
    bootargs = "console=ttyS0,57600";
};

修改完板子对应的DTS文件后,重新编译固件.分分钟后,重新刷写新固件.这个时候就在op里提取正确的factory数据后就可以通过mtd刷写factory分区了.
因为@波王科技给的数据说原厂的factory的数据在Uboot下的地址是0xbc030000到0xbc040000之间这个64K数据,而这一段在Openwrt里刚刚好对应mtd1分区,位置大小都一致,哈哈,一见此状我就用如下命令搞定了factory数据的移动:

#cd /tmp
#dd if=/dev/mtd1 of=./factory.bin bs=1k count=64
#dd if=./factory.bin of=/dev/mtd2

#上面这个方法虽然可以写mtd2了.但是写完后再hexdump出来一对比,数据不对,可能和设备文件类型有关,没试mtdblock2.所以正确的方法是用mtd命令刷写.

#mtd write ./factory.bin  factory

至此,总结几点:

  1. Uboot的大小问题要注意.不然有的Uboot可能会因为超过大小而不能刷写Uboot.
  2. Openwrt trunk中对MTD的分区读写权限定义是在板子对应的dts文件中.加read-only就是只读,去掉就是读写.
  3. dd命令可以从mtd分区中提取数据,但是dd回写factory分区有问题,只能用mtd命令刷写factory.原因暂时没调查.

第二天中午,由于已经把5台设备都转换好了.于是想尝试下那大于128K的Uboot会是什么样.便用已经可以正常工作的Uboot,在上电后按9,进入TFTP刷写模式,下载了@ManFeel为WrtNode编译的uboot,当时没过多的思考是否兼容之类,好奇和冒险精神使我很快输入文件名后回了车. 一瞬间, 板子重启了, 结局非常的不错, Uboot挂了,识别内存64K,打印@Manfeel的logo后就死机了, 这个时候突然才意识到是内存类型不同了. 好奇害死猫啊.哈哈.
没办法,只能拆flash,上编程器,隐约记得之前有买过一个编程器; 我拙劣的手艺,费了九牛二虎之力终于把flash给弄下来了,针脚也擦干净了. 放到烧录座里后,插入编程器的孔里, 第一次竟然识别不出,原来是位置不对,切换位置后自动检测出了正确的flash芯片的型号:

类型: 25 SPI FLASH
厂商: WINBOND
型号: W25Q64

这个是一颗8M bytes的flash芯片.
由于我没有完整的编程器固件,我想,我只烧录uboot应该也是可以的.于是按照所谓的一般方法,先擦除,再烧写,再校验的步骤,我好不犹豫的就点击了一下擦除flash芯片,然后编程器烧写了@波王科技给的那个uboot文件,自认为这样就Okey了,有网友也帮我试过这样烧写后可以启动.于是就继续把Flash芯片焊接回去了.

焊接好后,继续连接后串口线,进uboot选择2,开始刷新Openwrt固件,固件刷写一切正常, 重启系统期待启动正常,甚是欣慰,第一次动刀子拆flash,上编程器烧录,感觉好顺利的样子.

但是,直到我去检查网络功能的时候才发现,我编程器烧写的时候傻逼了,因为我第一步全部擦除了flash,导致原有的正确的factory分区数据也没了,然而后面烧写的时候却只烧写了最前面的Uboot,忘记备份了. 这个时候虽然可以运行Openwrt,但是想从别的机器dump出factory分区过来刷写上去再改mac地址,已经没有办法了,有线无线都不能正常工作,没办法传数据进去,想到板子上有个预留的usb接口,于是迅速焊接一个usb座子上去,然后插一个U盘试试,以为我估计有usb storage驱动,不幸的是,usb灯都不亮,咨询@波王科技得知,预留的那usb需要额外接5V电源,只预留了数据线.故此路不通了. 不过得知了@lintel的Uboot中集成的httpd功能可以刷factory分区. 简直雪中送碳啊. 以太网在Uboot里的是可以用的. 于是迅速用16进制编辑器打开别的设备里导出的fatory分区的bin文件,找到对应的2个Mac地址的位置,把路由器后盖拿过来,对着Mac地址输入进去(有大小端问题,稍微注意).然后在Uboot的httpd web恢复模式下上传factory.bin刷写了正确的factory分区.重启之后,检查网络,一切正常. 哦耶~. 由衷地感谢@lintel,如此神器.

至此,又几点总结:

  1. 对于没有硬件动手经验的软件人士,预备功课要做好,别好奇冒险贸然出手,一不小心就变砖.
  2. 备有一套基本工具还是有必要的,电烙铁之类,编程器之类.以防万一.
  3. 编程器烧写固件,如果有有用数据,一定要先备份,不然后悔莫及.
  4. 多和社区的朋友请教,交流,他们能让你很快涨硬功姿势.

之前一直隐隐约约有种不想去动烙铁这样的想法,怕麻烦,怕把板子搞坏更麻烦,但是这一次真搞坏了,还不得不霸王硬上弓,该面对的还是要面对.以后更细心,希望别出这样的叉子.


排名不分先后的衷心的感谢: @波王科技, @奈何col, @lintel,@manfeel,@被遗忘的时光, @勇往直前


Unix-Like系统C语言标准I/O缓冲

Unix-Like系统C语言标准输入输出缓冲模式

####以及系统调用与C库函数混合编码需注意的Buffer问题


最近群里有同学讨论到一些关于缓冲的问题,便写几行超简单的代码简单解说一下:

/*buffer-test.c*/
#include <stdio.h>
#include <unistd.h>

int main(int a, char **b)
{
    printf("printf line break output:\t\n");    /*有换行符*/
    printf("printf output:\t");              /*无换行符*/
    write(STDOUT_FILENO, "write output:\t", 14);/*无换行符*/

    return 0;
}

编译:

wuqiong:tmp apple$ cc buffer-test.c -o buffer-test

运行:

wuqiong:tmp apple$ ./buffer-test
printf line break output:
write output:   printf output:  wuqiong:tmp apple$

此时我们可以看到第二条printf语句的输出跑到最后面去了,初学者看来程序没有按照预期的方式执行;其实这是没有任何问题而且是很正确的行为,因为有个稍微底层一点的关于标准输入输出的缓冲机制我们应该了解,那就是C库函数中的标准输出函数在终端(Terminal)默认是基于行缓冲的,C库运行时内部行缓冲之后才进入到操作系统内核缓冲中去,而系统调用write函数不属于库函数的一部分,不依赖C语言运行时,故他不需要经过这样一个行缓冲的过程,而直接进入内核缓冲。这样就导致了第二个不包含换行符号的printf函数的输出到看write系统调用的输出之后。

这样是不是稍微有点理解清楚了?下面我们再进行一次别样的测试:来个通过管道重定向输出看看是什么现象。执行命令如下:


wuqiong:tmp apple$ ./buffer-test | cat
write output:   printf line break output:
printf output:  wuqiong:tmp apple$

噢,又有变化了。write系统调用的输出更提前了。这是什么原因呢。不应该啊?呵呵。事情是这样的。重定向是通过一个内核里的一个管道来实现的。这一命令会创建2个进程。第一个进程的输出会写入到管道的输入端,管道的输出端的输出会写入第二个进程的输入端。这样一来printf数据到第二进程(cat)处理时就经过 3个缓冲区。第一个进程的C库缓冲→内核的管道缓冲→第二个进程的C库缓冲。而系统调用write的数据就只经过一个内核管道的缓冲就到了第二进程的read直接读出来了。示意图:


管道缓冲示意图


默认缓冲模式:

  1. stdin总是全缓冲模式
  2. stderr总是无缓冲模式
  3. stdout如果是输出到终端的话就会自动设置为行缓冲,否则就是全缓冲

引用:http://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html


假如管道重定向有级联的情况又是什么样呢。比如这样的一条日志分析shell命令:

$ tail -f access.log | cut -d' ' -f1 | uniq

我们还是再看个示意图吧,有图有真相嘛:

管道重定向级联的缓冲示意图


那么,我们可不可以把C语言运行时里的缓冲机制给关闭掉呢?答案是肯定的!C库函数中有一系列操作缓冲机制的函数,我们可以根据我们的实际情况来关闭或者设置合适的缓冲模式。读者可以在Unix-like操作系统中的终端执行如下命令就可以看到各个函数的原型以及说明文档:


wuqiong:tmp apple$ man 3 setbuf

回车会输出如下结果:

SETBUF(3)                BSD Library Functions Manual                SETBUF(3)

NAME
     setbuf, setbuffer, setlinebuf, setvbuf -- stream buffering operations

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <stdio.h>

     void
     setbuf(FILE *restrict stream, char *restrict buf);

     void
     setbuffer(FILE *stream, char *buf, int size);

     int
     setlinebuf(FILE *stream);

     int
     setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size);

DESCRIPTION
     Three types of buffering are available: unbuffered, block buffered, and line buffered.  When an output stream is unbuffered, information
     appears on the destination file or terminal as soon as written; when it is block buffered, many characters are saved up and written as a
     block; when it is line buffered, characters are saved up until a newline is output or input is read from any stream attached to a termi-
     nal device (typically stdin).  The function fflush(3) may be used to force the block out early.  (See fclose(3).)

     Normally, all files are block buffered.  When the first I/O operation occurs on a file, malloc(3) is called and an optimally-sized buffer
     is obtained.  If a stream refers to a terminal (as stdout normally does), it is line buffered.  The standard error stream stderr is
     always unbuffered.

这几个设置缓冲的函数我就不多说了,大家自己阅读文档和亲手实验吧。Unix下man命令是manual的简写,3是库函数的手册,2是系统调用的手册。


介绍了这么些关于C语言标准输入输出的缓冲机制,C语言标准输入输出也几乎是每一个程序员初次学C语言最先接触的,希望本文对读者有或多或少的帮助。

Swift语言Array扩展Shuffle方法

#Playground代码

// Playground - noun: a place where people can play
// 戴维营教育  Swift Array数组 简单洗牌函数实现
// demonstrated by 大茶园丁.


import Cocoa


//全局函数形式
func ç<T : Comparable>(arr:T[]) -> Void{
    sort(arr, { (a1: T , a2: T) -> Bool in
        var a: UInt32 = arc4random()%3
        return a >= 1
        })

}
var array = [1,2,3,4,5,6,7,8,9,10,11,12,13]
shuffle(array)

array


//数组的扩展方法形式
extension Array {
    func shuffle(){
        self.sort({(a1, a2) -> Bool in
            var a: UInt32 = arc4random()%3
            return a >= 1
            })
    }
}
var aa = ["~!@~","qwe","sdf","132","354345", "bvnccnbvncvn"]

aa.shuffle()