2018/11/07

自製 make menuconfig

linux kernel 提供的 kconfig 功能十分好用,openwrt 和 buildroot 也都拿來使用,但是內容太龐大,整包拿來用並不輕鬆,所幸找到一包已經從 kconfig 抽出來的 standalong 版本 (github 連結),可以好好的利用,以下就是利用這個 github 當基礎來建立一個簡單的 menuconfig template。

資料夾結構

名稱(Name) 描述(Descript)
~/build/ 編譯空間
~/defconfig/ 放置 defconfig 的地方
~/host-tools/ 集中放置 host 工具的地方,可再擴充,像是 toolchain
~/host-tools/menuconfig/ standalong 的原始碼,內層不再贅述

~/host-tools/menuconfig/Makefile
原始的專案採用 cmake,因為會產生 cmake 相關的檔案,所以自己建一個簡單的 Makefile 來用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
CC ?= gcc

lxdialog := lxdialog/checklist.o
lxdialog += lxdialog/util.o
lxdialog += lxdialog/inputbox.o
lxdialog += lxdialog/textbox.o
lxdialog += lxdialog/yesno.o
lxdialog += lxdialog/menubox.o

mconf-objs := mconf.o
mconf-objs += zconf.tab.o
mconf-objs += $(lxdialog)

conf-objs := conf.o
conf-objs += zconf.tab.o

clean-files	:= mconf conf
clean-files += $(mconf-objs)
clean-files += $(conf-objs)

all: mconf conf
	
$(obj)/%.o: %.c
	$(CC) -c $< -o $@

mconf: $(mconf-objs)
	$(CC) $(mconf-objs) -o mconf -lncurses

conf: $(conf-objs)
	$(CC) $(conf-objs) -o conf

clean:
	@rm -f $(clean-files)
這個 Makefile 預設使用 gcc 進行編譯,如果需要更改,需要 make 的時候予以告知,例如 make CC=your_compiler
make 完成後會產生 mconf 和 conf 兩個執行檔

~/Makefile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ifneq ("$(wildcard .config)","")
	include .config
endif

all: preconfig

preconfig:
ifeq ("$(wildcard .config)","")
	$(error "Please run make menuconfig first.")
endif

menuconfig:
	@mkdir -p build
	@rsync -ur host-tools/menuconfig build
	@make -C build/menuconfig
	@./build/menuconfig/mconf Config.in

list-defconfigs:
	@echo 'Built-in configs:'
	@$(foreach b, $(sort $(notdir $(wildcard defconfig/*_defconfig))), \
	  printf "  %-35s - Build for %s\\n" $(b) $(b:_defconfig=);)
	@echo

%_defconfig:
	@./build/menuconfig/conf --defconfig=defconfig/$@ Config.in~
1~3: 如果已經存在 .config 就將內容 include 進來
5: all 之前要先跑過 preconfig
7~10: preconfig 會檢查 .config 是不是存在,如果不存在就顯示要求執行 menuconfig 並且報錯跳出
12~16: menuconfig 主體,make 的機制是已經編譯過且沒有更改內容,就不會重新編譯
18~22: list-defconfigs 可以列出在 ~/defconfig 裡面的所有 *_defconfig
24~25: 當執行 make xxxx_defconfig 就會依據 xxxx_defconfig 來產生 .config

使用這個 template 可以建立一個基本的 menuconfig 功能,通常會根據不同的 target board 或是個別的客製化來製作 xxxx_defconfig,要編譯的時候就可以直接 make xxxx_defconfig 然後再 make all。

一般來說,build 放置的是 target 的編譯空間,砍掉 build 就會非常乾淨,但是 host-tools 也得重編,所以如果確定 host-tools 跟 target 無依存關係,也可以另外開 host-build 來用。

2018/08/09

執行時期控制 linux kernel 印出的訊息

(based on linux kernel version 3.10.104)

在 embedded linux 開發過程中,常常會為了對 kernel space 進行追蹤而使用 printk,但是每次編譯重燒是很耗費時間的,所以直接在執行時期控制 printk 的訊息要不要顯示出來,會是追蹤的一門重要技巧。

參考

printk 提供了一個在執行時期的控制介面,/proc/sys/kernel/printk

#cat /proc/sys/kernel/printk
7 4 1 7

第一個數字代表要印出來的最低等級,也就是等級高於或等於7 (KERN_DEBUG) 都會被印出來
第二個數字代表 printk 沒有指定等級的預設等級 4 (KERN_WARNING)

因為 KERN_WARNING 等級高於 KERN_DEBUG,所以目前不指定等級的 printk() 都會被印出來。

如果只想看到嚴重等級以上的訊息,可以直接

#echo 2 > /proc/sys/kernel/printk
#cat /proc/sys/kernel/printk
2 4 1 7

這樣未指定等級的訊息就不會顯示出來,甚至等級為 KERN_ERR 的訊息也都忍住不噴出了。

以下列出各等級資料:

Name String Meaning alias function
KERN_EMERG "0" Emergency messages, system is about to crash or is unstable pr_emerg
KERN_ALERT "1" Something bad happened and action must be taken immediately pr_alert
KERN_CRIT "2" A critical condition occurred like a serious hardware/software failure pr_crit
KERN_ERR "3" An error condition, often used by drivers to indicate difficulties with the hardware pr_err
KERN_WARNING "4" A warning, meaning nothing serious by itself but might indicate problems pr_warning
KERN_NOTICE "5" Nothing serious, but notably nevertheless. Often used to report security events. pr_notice
KERN_INFO "6" Informational message e.g. startup information at driver initialization pr_info
KERN_DEBUG "7" Debug messages pr_debug, pr_devel if DEBUG is defined
KERN_DEFAULT "d" The default kernel loglevel
KERN_CONT "" "continued" line of log printout (only done after a line that had no enclosing \n) [1] pr_cont

2018/07/20

[C] 如何反查 function 被誰呼叫

除錯時常常需要逆向追蹤程式流程,如果被呼叫的地方很少,可以直接在呼叫前 printf,但是當很多地方呼叫的時候,一個一個放 printf 似乎不是ㄧ個高效率的作法。

#define FuncName(a, b, c) \
do { \
    printf("[%s %d] called FuncName\n",__func__, __LINE__); \
    FuncNameReal (a, b, c); \
} while (0)

例如原本有個 function:
int max(int aaa, int bbb) {...}

因為不想改變呼叫的地方,所以要先把 function 名稱改成 _max

然後在 header file 裡面追加

#define max(a, b) printf("[%s %d] called max"); _max(a, b)

這樣呼叫 max 的程式就會自動報上自己是哪個 func,而且同一 func 有多的地方呼叫的話,是在哪一行呼叫也會報出來。

帶回傳值的時候
#define FuncName(a, b, c) ({ \
        int retval; \
        printf("[%s %d] called FuncName\n",__func__, __LINE__); \
        retval = FuncNameReal (a, b, c); \
        retval;})

參考來源:

2018/04/08

買書同時在 mooink 跟 kindle paperwhite 上閱讀 (進度同步就別想了)

雖然 Kindle 有家庭共享機制,但是不容易買到正體中文書。而在讀墨上面買的書只能在 mooink 閱讀,無法下載轉到 kindle 上閱讀。最終只能在 Kobo 上面買書,檔案下載回來,再經過幾道程序,就能同時在 kindle 和 mooink 上面閱讀。

所需工具:



處理過程:


上 Kobo 買書,購買時可以留意電子書詳細資料裡面的下載選項,如果是 EPUB 3 (Adobe DRM) 就要經過解鎖。

將書籍下載。Adobe DRM 的書籍下載回來是一個描述檔,副檔名是acsm (在 Google 圖書購買的也是這個格式)。

使用 Adobe Digital Editions 打開 *.acsm,就會自動下載該書籍。

執行 Epubor Ultimate,點選 Adobe 頁籤,就可以看到 ADE 管理的書籍清單,依照畫面指示,將書籍拖曳到右邊,看到已解密就完成了。

執行 calibre
-> 點選偏好設定
-> 點選外掛
-> 勾選址顯示使用者已安裝的外掛 
-> 展開檔案類型外掛 
-> 雙擊 DeDRM
-> 點擊 Adobe Digital Editions ebooks

-> 點擊 Import Existing Keyfiles
-> 開啟 user/.Epubor_Keys,選取要匯入的檔案 *.der,然後按下開啟
這時已經匯入所需的DRM金鑰,退回 calibre 主畫面
回到 ADE,在要處理的書籍典籍滑鼠右鍵,點選項目資訊

點擊位置右邊的箭頭(以檔案總管顯示)
從檔案總管將書籍拖曳到 calibre 主畫面,由於 DeDRM 已經匯入相對應的 Key,所以可以開始轉換至所需的格式。要注意的是放進去的書籍是有 DRM 的,無法直接從 mooink 讀取,所以仍然需要從 EPUB 轉 EPUB。

2018/04/02

git 筆記

只是筆記而已,用到才備忘。找工作很重要,從 svn 要再回到 git 還是有點燒腦。

檢查改了哪些檔案
    #git status

恢復各別回未修改前的狀態,其中 filename 可以是 * 來恢復所有檔案,概念就是重新 checkout 檔案
    #git checkout -- <filename>

看看這次改了什麼
    #git diff -- <filename>

本地提交
    #git commit 

單行列出紀錄
    #git log --oneline

顯示 commit 的詳細內容
    #git show commit_id

只顯示本地工作副本的提交紀錄
    #git reflog

增加追蹤檔案
    #git add filename

停止追蹤
    #git update-index --assume-unchanged filename

繼續追蹤
    #git update-index --no-assume-unchanged filename

Merge 其他分支的某個 commit
    #git cherry-pick commit-hash

++++++++++ 分支相關 ++++++++++
列出分支
    本地
    #git branch --list
    遠端
    #git branch --remotes
    全部
    #git branch --all

切換分支
    #git checkout branch

建立分支
    #git branch branch

在 branch_name_father 建立分支 branch_name_son 並直接切過去
    #git checkout -b branch_name_son branch_name_father

將本地分支 push 成遠端分支
    #git push remote_name branch_name

將本地目前分支掛勾到遠端分支
    #git push --set-upstream-to=remote/branch_r branch_l

直接 Checkout 遠端分支到本地分支
    #git checkout -b branch_l remote/branch_r

將分支 branch 併入 master
    #git checkout master
    #git merge branch

如果有衝突 (conflict) 可以使用 mergetool 進行排除
    #git mergetool

設定 mergetool (使用 kdiff3 會有更好的介面)
    #git config merge.tool vimdiff
    #git config merge.conflictstyle diff3
    #git config mergetool.prompt false

刪除已 merge 過的 branch 分支
    #git branch -d branch

捨棄並刪除 branch 分支
    #git branch -D branch

刪除 remote_name 遠端 branch 分支
    #git push --delete remote_name branch

同步遠端分支清單
    #git fetch

++++++++++ 分支相關 ++++++++++

捨棄前一次提交 (限定未 push,發現前次提交還有問題,可以捨棄前一次提交,working tree 仍會保留修改過後的內容,其中 ^ 表示前一版)
    #git reset HEAD^

現有的專案建立新的 git 管理,然後 push 到空的 repository.
    #git init
    #git add .
    #git commit -m "First commit"
    #git remote add remote_name remote_repository_URL
    #git remote -v
    #git push remote_name master

本地建立分支,將分支推到遠端新分支,並建立連結
    #git checkout -b branch_name_son branch_name_father
    #git push remote_name branch_name
    #git push --set-upstream remote_name branch_name

在 sdk 分支進行修改,然後併入分支 develop
    先確保分支 develop 已經乾淨(完成 commit)且同步
        #git status
        #git fetch --all
        #git pull
    切換到分支 sdk
        #git checkout sdk
    修改後 commit 並 push
        #git commit -a -e
        #git push
    切回分支 develop
        #git checkout develop
    合併分支 sdk 的修改 (如果有衝突需要手動修正)
        #git merge sdk
        #git commit -a -e
        #git push

要存取 GitLab 建立的repository 會需要 ssh key 跟 password,所以請記得去設定 password,產生 ssh key 的指令如下:
    #ssh-keygen -t rsa -b 4096 -C "email address"

merge 衝突處理
    #git checkout br2
    #git merge br1
        Auto-merging file
        CONFLICT (content): Merge conflict in index.html
        Automatic merge failed; fix conflicts and then commit the result.
    #git add file
    #git commit -m "conflict fixed"

修復 .gitignore 有列出卻還出現在 modified 清單上的問題
    #git rm --cached filename


++++++++++ Repository 相關 ++++++++++

建立完整的 repository 鏡像
    一個 repository 在運行一段時間之後,可能會有很多分支。遇到需要移植到新的伺服器的狀況時,直接鏡像整個 repository 是比較單純的做法。
    #git clone --mirror src_git_url target_folder
    #cd target_folder
    #git push --mirror dst_git_url


更新 remote url
    #git remote set-url remote_name new_url

    更新後檢查 url 有沒有改成功
    #git remote -v

建立 tag 並推送到 remote
    #git tag -a tag_name -m 'tag_comment'
    #git push remote_name tag_name

++++++++++ 修改剛剛提交而且已經 push 的 message ++++++++++

先修改本地的提交
    #git commit --amend

重新 push 內容
    #git push --force-with-lease remote branch

例如使用 git bracnh --all 顯示為 remotes/origin/develop 的分支, 就執行 git push --force-with-lease origin develop

如果在重新 push 之前就已經 pull 回來, 就需要 hard reset,但是本地端的修改會消失, 執行前最好先透過 git status 找出修改過的內容並且備份
    #git fetch origin
    #git reset --hard remote/branch


2018/03/29

GNU Makefile 一些筆記

筆記而已

-   開頭,表示該行不報錯
@ 開頭,表示該行只執行,不回顯該行指令

:= 強制改變
+= 追加內容
?= 如果變數已經存在就不改變,如果變數不存在才設定

顯示變數內容(也可以用 error 方便除錯,用完就刪掉)
  $(info $$var is [${var}])

觸發錯誤
  $(error "message")

從 AOSP 看到的整合方式,動態將自己的項目加進編譯流程
  #預設目標指定為 droid
  DEFAULT_GOAL := droid
  $(DEFAULT_GOAL):

  .PHONY: mod1
  mod1:
    @echo "mod1"
  #將 mod1 加入預設目標的依賴,這裡可以透過 if 控制
  $(DEFAULT_GOAL): mod1

  .PHONY: mod2
  mod2:
    @echo "mod2"
  $(DEFAULT_GOAL): mod2

2018/01/31

如何在 PC 編譯 open source project 給 raspberry pi,以 NTP 為例

Why

很多 open source project 都很大,放在 pi 編譯有兩個缺點,第一很費時,第二很浪費空間。所以在 PC 編譯好之後再 install 到 pi 會是一個很好的選擇。如果 open source 專案只有提供 Makefile,那就直接把編譯相關的指向到 pi 的 tools。很多大型專案會提供 configure 來進行編譯設定,那就需要透過 configure 來設定 cross 了。以下就透過 ntp 這個專案來說明,其他專案也大同小異。

How

首先要下載原始碼,如果有更新的版本,請自行調整。
wget http://archive.ntp.org/ntp4/ntp-4.2/ntp-4.2.8p10.tar.gz

參考 Cross-compilingNTP host 是目標機器,build 是編譯機器,所以我們要分別在 PC 跟 pi 上面執行 config.guess
PC: x86_64-unknown-linux-gnu
pi: armv7l-unknown-linux-gnueabihf
知道 host 跟 build 該怎麼填之後,還要設定一些編譯要用的環境變數
export RPI_BASE=${HOME}/rpi
export CROSS=${RPI_BASE}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
export CC=${CROSS}gcc
export AR=${CROSS}ar
export LD=${CROSS}ld
export AS=${CROSS}as
再來就要準備編譯了
mkdir target_pi
cd target_pi
../configure --host=armv7l-unknown-linux-gnueabihf --build=x86_64-unknown-linux-gnu --with-yielding-select=yes
make
make DESTDIR=`pwd`/Built install

跑完之後,就會在 Built 裡面建立需要安裝的檔案,tar 起來到 pi 的跟目錄下解 tar 就完成安裝了


2018/01/30

在 PC 編譯 wiringPi

參考了 raspberry pi 操控 gpio 的幾個方法
原本最想要的是直接存取,不知道哪裡沒弄好,gpio8 以上的行為怪怪的,只能 out,設定成 in 時,不管是拉 3.3v 還是 gnd 都是 low,所以改用 wiringpi 來處理。基於上篇,還是在 PC 上編譯 wiringPi 再拿去 raspberry pi 執行。

下載 wiringPi 原始碼

參考官網
git clone git://git.drogon.net/wiringPi

編譯主元件

需要編譯的內容是 wiringPi 和 devLib 這兩支,因為原先的 Makefile 內容是放在 raspberry pi 上編譯用的,所以需要做一些小小的調整,以便符合在 PC 上進行 cross compile。

wiringPi/Makefile 在 LIBS = 後面追加下列內容來蓋掉前面的設定
RPI_BASE := $(HOME)/rpi
CROSS := $(RPI_BASE)/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC := $(CROSS)gcc
AR := $(CROSS)ar
RANLIB := $(CROSS)ranlib

devLib/Makefile 一樣在 LIBS = 後面追加
RPI_BASE := $(HOME)/rpi
CROSS := $(RPI_BASE)/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC := $(CROSS)gcc
AR := $(CROSS)ar
RANLIB := $(CROSS)ranlib
INCLUDE += -I../wiringPi

原先的 Makefile 只編出 shared object,如果想要編出 static object,就在 all: 追加 $(STATIC) 就可以了。

編譯 gpio

因為是要先測試功能,所以使用 static object 的 wiringPi 元件,這樣就只需要把 gpio 程式放到 nfs 就能執行。

gpio/Makefile 還是在 LIBS = 後面追加
RPI_BASE := $(HOME)/rpi
CROSS := $(RPI_BASE)/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC := $(CROSS)gcc
AR := $(CROSS)ar
RANLIB := $(CROSS)ranlib
INCLUDE += -I../wiringPi -I../devLib
LDFLAGS += -L../wiringPi -L../devLib

把 gpio 放到 nfs 上面,把 raspberrypi 的 v33 接到 gpio26 進行測試,改接gnd 再測,再改回 v33 再測,終於可以正常當 input 了。

安裝 shared object

如果有多支程式需要用到 wiringPi,做成 shared object 會比較理想。先把編譯好的 libwiringPi.so.2.44 和 libwiringPiDev.so.2.44 複製到 host 的 nfs 上,然後從 raspberrypi 上複製到 /lib 同時處理 symbolic link。
sudo cp /mnt/nfs/libwiringPi.so.2.44 /lib
sudo ln -s /lib/libwiringPi.so.2.44 /lib/libwiringPi.so
sudo cp /mnt/nfs/libwiringPiDev.so.2.44 /lib
sudo ln -s /lib/libwiringPiDev.so.2.44 /lib/libwiringPiDev.so



完成

基本上到此已經能完全操控 gpio,如果為了效能跟空間,可以參考 wiringPi 的 gpio,把不需要的拿掉,放到自己的程式內部。由於 gpio 需要 sudo 執行,基於安全上的考量,可能要改寫成 gpiod,再透過 ipc 跟 shared memory 去要求或進行控制。

2018/01/27

Raspberry Pi 開發環境建置

raspberry pi 可以直接使用 debian linux 發行套件,安裝 makefile 跟 gcc 就可以拿 source code 去編譯,但是編譯速度絕對不比高速 x86 桌機,遇到需要除錯的程式,還是在 x86 linux 上面編譯好再給 raspberry pi 跑會是一個比較省時間的做法。想法很簡單,就是在 x86 linux 上安裝 raspberry pi cross tools,然後透過 nfs 讓 raspberry pi 直接 mount 去執行。

我目前使用的 x86 linux 是 debian 9 amd64,所以以下內容將以此為基準,使用 debian based 的發行套件應該都能直接套用,其他發行套件可能要做些修改。

安裝 cross tools


編譯環境不該使用 root,所以請用一般 user 登入,把 toolchain 放在 home

aimwang@debian:~$ mkdir rpi
aimwang$debian:~$ cd rpi
aimwang@debian:~/rpi$ git clone https://github.com/raspberrypi/tools.git

安裝並設定 nfs server


aimwang@debian:~$ sudo apt-get install nfs-kernel-server nfs-common
aimwang@debian:~$ sudo mkdir /srv/nfs
aimwang@debian:~$ sudo chmod 777 /srv/nfs

/etc/exports 最後增加 (IP 根據自己的環境改)
/srv/nfs 192.168.22.*(rw,sync,no_subtree_check,no_root_squash)

重新啟動 nfs server
aimwang@debian:~$ sudo /etc/init.d/nfs-kernel-server restart

準備編譯 hello world 等一下要透過 nfs 餵給 raspberry pi 執行
Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ROOT_PATH := $(PWD)
RPI_BASE := $(HOME)/rpi
NFS_PATH := /srv/nfs
CROSS := $(RPI_BASE)/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC := $(CROSS)gcc

all: hello nfs

nfs:
 @cp -f hello $(NFS_PATH)/.

hello:
 @$(CC) $(CFLAGS) -o hello hello.c

clean:
 @rm -f hello
hello.c
1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>

void main (void)
{
 printf ("Hello world!\n");
 exit (0);
}

編譯啦
aimwang@debian:~/project/rpi/hello$ make

Raspberry Pi mount nfs 及執行

pi@raspberrypi:~ $ sudo mount -t nfs 192.168.22.200:/srv/nfs /mnt/nfs
pi@raspberrypi:~ $ /mnt/nfs/gpio
Hello world!

完成