اختاپوس خسته

یادداشت‌هایی پیرامون کد، زندگی و دوستان

این بورد لعنتی

مدتی‌یه که برای انجام یه پروژهٔ صنعتی یه بورد Smart 210 به دستم رسیده. این بورد ساخت شرکت FriendlyARM هست که یه کمپانی چینی‌یه که سخت‌افزارهای ارزون‌قیمت صنعتی می‌سازه. مشخصات ظاهری‌ش خوب به نظر می‌رسه. با این وجود از لحاظ نرم‌افزاری یک فاجعه‌ست! این پست توضیحاتی در مورد طرز کار و بیشتر توضیح معایب این بورده. امیدوارم در آینده برای کسایی که می‌خوان باهاش کار کنن مفید باشه یا لااقل باعث باشه از خریدش منصرف بشن (:

سخت‌افزار

این بورد یه سیستم-روی-چیپ مدل Samsung S5PV210 داره که پردازندهٔ صنعتی و مالتی‌مدیا به‌حساب میاد و ویژگی‌های خوبی هم داره از جمله یک هستهٔ ARM Cortex-A8 که با فرکانس یک گیگاهرتز کار می‌کنه و کلی امکانات مالتی‌مدیای دیگه که در کنار هستهٔ آرم گذاشته شده. در کنار این اینترفیس‌ها یک پردازندهٔ گرافیکی PowerVR SGX540 هم روی سیستم وجود داره که خیلی به‌درد می‌خوره. حافظهٔ اصلی بورد ۵۱۲ مگابایته که خیلی خوبه. اینترفیس‌های زیادی هم برای کار با بورد در نظر گرفته شده از جمله: Serial, SPI, I2C, I2S, SD/MMC, USB, LCD, Camera.

کیفیت ساخت بورد در حد قابل قبوله. استانداردهاش در ردهٔ صنعتی هستن و خیلی تمیز کار شده. خوب تا این‌جا همه‌چی خیلی خوبه. یه بورد داریم که سخت‌افزارش خوبه و خوب مونتاژ شده. دیگه چی می‌خوایم؟ خوب معلومه! نرم‌افزار…

ادعا: هر چقدر که سخت‌افزارهای بورد اسمارت خوب طراحی شدن، نرم‌افزارهای ارائه شده براش بد هستن. به نظر من این آدم‌ها کوچکترین توانایی در کار با نرم‌افزار نداشتن.

کرنل

اولین چیزی که برای کار کردن با یه بورد صنعتی لازمه یه کرنل لینوکس سبک هست که خوب و مینیمال کانفیگ شده باشه. معمولا من دوست دارم خودم کرنل‌مو بسازم و کانفیگ‌های چیپست یا ماژول‌ها (که معمولا به صورت پچ یا رپوزیتوری گیت ارائه شدن) رو بعداً روش بریزم. این کار چند تا مزیت داره. از جمله این که می‌تونیم در هر لحظه نسخهٔ جدید کرنل رو جایگزین قبلی بکنیم. در واقع ما روی دو تا مجموعهٔ جدا از هم کار می‌کنیم: ۱- سورس کرنل (سورس mainline) و ۲- تغییراتی که تولیدکنندهٔ بورد منتشر کرده. معمولا این تغییرات برای SoCها انقدر اساسی هستند که تولیدکننده یه مخزن سورس مستقل رو نگهداری می‌کنه. البته شما اگه به هر دلیلی نتونید از نسخهٔ کرنل ارائه‌شده توسط تولیدکننده استفاده کنید، می‌تونید خیلی راحت از سورس اون‌ها و کرنل اصلی diff بگیرید و پچ‌های به‌دست آمده رو روی نسخهٔ جدیدتری از کرنل سوار کنید. (تجربه: فی‌الواقع با مقادیر بسیار زیادی دردسر)

تقریباً همهٔ کسایی که تو این زمینه کار می‌کنن از همین روند پیروی کردن. مثلا Texas Instruments برای سیستم‌های OMAP اومده یه رپوزیتوری توی آدرس git.omapzoom.org در اختیار همه گذاشته. هر کسی می‌تونه هر کرنلی خواست رو بسازه. همین‌طور Allwinner برای سیستم-روی-چیپ‌های فوق‌العادهٔ سری A کرنل رو به‌صورت مخزن گیت در اختیار عموم گذاشته. اصلا هر عقل سلیمی این کار رو می‌کنه (:

کانفیگ کرنل Smart210

اما رفقای چینی‌مون چی‌کار کردن؟ رپوزیتوری گیت؟ یک سری پچ؟ اصلا سخت‌افزاری ساختن که با کرنل mainline به‌خوبی کار می‌کنه؟ (آخری خیلی تخیلی بود). نه! هیچکدوم… اون‌ها اومدن یه سری تاربال گذاشتن توی دراپ‌باکس! خوب تا این‌جاش رو میشه تحمل کرد. سورس‌ها رو دانلود کردیم اما خیلی عجیب بود. آخه حجم سورس کرنل باید بشه ۱۰۰ مگابایت؟! مگه چقدر سورس می‌تونن نوشته باشن؟! چند تا ماژول؟ چند تا هک روی خود کرنل؟ بعد از اکسترکت کردن تاربال متوجه شدم که حضرات باینری‌هایی که کامپایل کردن رو هم گذاشتن بمونه. کلی فایل آبجکت به همراه خود کرنل کامپایل شده[!!] کنار سورس‌ها بود…

بهرحال. از این هم گذشتیم. بریم سروقت کانفیگ کردن کرنل. معمولاً کسی که بوردی می‌سازه، میاد یه سری defconfig همراهش منتشر می‌کنه که به شکل اصولی با وارد کردن دستور make zahremar_defconfig بشه کانفیگ پیش‌فرض رو روی سورس کرنل اعمال کرد. دوستامون همچین کاری نکردن. اصلا اعتقادی به سیستم‌های استاندارد ندارن. به‌جاش اومدن یه اسکریپت داغون بش گذاشتن بغل سورس‌ها که یه سری فایل رو به‌جای ‪.config‬ کپی می‌کنه! این‌جوری:

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
34
#!/bin/bash

DESTDIR=/tmp/FriendlyARM/mini210
ADSTDIR=${DESTDIR}/android
LDSTDIR=${DESTDIR}/linux

mkdir -p ${ADSTDIR} ${LDSTDIR}

CPU_JOB_NUM=$(grep processor /proc/cpuinfo | awk '{field=$NF};END{print 
field+1}')

make distclean
touch .scmversion

# build zImage for android
cp mini210_android_defconfig .config && \
   make -j${CPU_JOB_NUM} &&
   cp -vf arch/arm/boot/zImage ${ADSTDIR}/zImage || exit 1

cp mini210-tvp5150_android_defconfig .config && \
  make -j${CPU_JOB_NUM} &&
  cp -vf arch/arm/boot/zImage ${ADSTDIR}/zImage_tvp5150 || exit 1

make distclean
touch .scmversion

# build zImage for linux
cp mini210_linux_defconfig .config && \
  make -j${CPU_JOB_NUM} &&
  cp -vf arch/arm/boot/zImage ${LDSTDIR}/zImage || exit 1

cp mini210-tvp5150_linux_defconfig .config && \
  make -j${CPU_JOB_NUM} &&
  cp -vf arch/arm/boot/zImage ${LDSTDIR}/zImage_tvp5150 || exit 1

باید این فایل رو باز کنیم، براساس کاری که می‌خوایم انجام بدیم (کرنل لینوکس یا اندرویید) قسمت‌هایی از سورس رو حذف کنیم، و بعد بزنیم اجراش کنیم تا بره از مسیرهای موردنظر فایل‌های موردنظر رو به‌جای فایل کانفیگ کپی کنه. بماند که انجام این کار به‌جای استفاده از defconfig ها چقدر غیراصولی و احمقانه‌ست. حضرات حتا به خودشون اجازه دادن مشخص کنن که من چند هسته‌ای باید کامپایل کنم! (تعداد هسته‌های پردازنده‌م رو شمردن).

کامپایل کردن کرنل

خوب بعد از کانفیگ میریم سروقت کامپایل کرنل. این که یه کرنل بار اول کامپایل نشه خیلی جای تعجب نداره. معمولاً باید خیلی باهاش سر و کله زد. این کرنل هم بار اول کامپایل نشد. بعد از کمی سر و کله زدن متوجه شدم اصلا با فلگ ‪-Wall‬ امکان کامپایل این سورس وجود نداره! این یه فاجعه‌ست چون کرنل باید حتماً بدون اخطار کامپایل بشه… اون‌هم با فلگ‌های ‪-Wall -Werror --pedantic‬ که کوچکترین جای خطایی باقی نمونه. مثلا در مورد strict aliasing و لی‌آوت‌های حافظه، کرنل جایی نیست که بخوایم اخطارهای کامپایلر رو نادیده بگیریم! اصلا شما با زیرساخت buildroot نمی‌تونید این فلگ‌ها رو خاموش کنید (: اما دوست‌های چینی‌مون یه کدهایی نوشتن که کامپایل نمیشه. از اون بدتر این که نشستن برای کرنل لینوکس (دقت کنید کرنل لینوکس) یه سری میک‌فایل دستی نوشتن که فلگ‌های مربوطه از توش برداشته شده. آدم دلش می‌خواد سرشو بکوبه به دیوار…

بوت‌لودر

بعد از تلاش‌های متعدد و اصلاح چندین خط کد و حذف کامل چندین ماژول (که همه‌شونو FriendlyARMای‌ها نوشته بودن) بالأخره کرنل کامپایل شد. حالا باید بریم بریزیمش روی بورد و یه بوت‌لودر بسازیم که سخت‌افزارهای اولیه رو initialize کنه. برای این کار معمولا از u-boot استفاده می‌کنیم چون تعداد خیلی زیادی از SDRAMها و فلش‌ها و اکثر کنترلرهای سخت‌افزاری سطح‌پایین برای ذخیره‌سازی رو توش داره و شما خیلی راحت می‌تونید با یه کانفیگ کوچیک بوت‌لودر بسازید. مگر این که از باس‌های نامتعارف و Programmable IO استفاده کرده باشید (که معمولاً طراح سخت‌افزار این کار رو می‌کنه). اکثر سخت‌افزارها یک کانفیگ مشخص برای u-boot دارن و یا حتا نسخهٔ مناسب u-boot ارائه دادن. این نسخه‌ها بعد از مدتی در صورت صلاح‌دید نویسنده‌های u-boot وارد پروژهٔ اصلی میشن. در حال حاضر اکثر بوردهای پرکاربرد توسط u-boot پشتیبانی میشن. مثل کابی، رزبری، مری، هامینگ، اودرویدها و خیلی بوردهای دیگه.

خوب فکر می‌کنید دوست‌های چینی‌مون در مورد بوت‌لودر چه کاری انجام دادن؟ یه کانفیگ ساده برای u-boot یا یه رپوزیتوری مجزا؟ جواب اینه که هیچکدوم. این احمق‌ها صلاح ندیدن از روش استاندارد بوت که همه ازش استفاده می‌کنند استفاده کنن. نشستن خودشون یک بوت‌لودر مزخرف به اسم Superboot نوشتن و سورسش رو هم منتشر نکردن. این بوت‌لودر به‌قدری افتضاحه که شما باهاش هیچ‌کاری نمی‌تونید انجام بدید به جز این:

  • نصب سیستم‌عامل با لی‌آوت حافظهٔ تحمیل شده توسط سازنده‌های بورد
  • بالا آوردن سیستم‌عامل

خوب این لی‌آوت چیه؟ میشه گفت یه‌جور پارتیشن‌بندی برای MTD یا همون حافظهٔ فلش که قراره سیستم‌عامل بره روش بشینه. این پارتیشن‌بندی کاملاً ثابت و غیرقابل تغییر درنظر گرفته شده. یعنی من نمی‌تونم تصمیم بگیرم که کرنل رو کجا بریزم و یا این که اصلاً چند تا پارتیشن داشته باشم و هرکدوم چه اندازه‌ای داشته باشه و چه نودی روش سوار (مانت) بشه. خوب خوشحالیم آره؟ بهرحال از اینم گذشتیم… تصمیم گرفتم با همون بوت‌لودر مزخرف جلو برم. اول باید رایتش می‌کردم روی کارت اس‌دی. اما کجای کارت اس‌دی؟ اولش؟ آخرش؟ با ۵۱۲ بایت آفست؟ هر چیزی رو امتحان کردم و نشد. حضرات خودشون یه برنامهٔ فکستنی ویندوزی نوشتن که فقط با استفاده از اون میشه بوت‌لودر رو روی کارت ریخت. احمق‌ها! مجبور شدم برم روی ویندوز و بوت‌لودر و ایمیج سیستم‌عامل رو رایت کنم.

اولین تلاش برای بوت

خوب بعد از کلی فحش دادن اولین تلاش برای بوت با شکست مواجه شد. طبیعی هم هست البته. خیلی کم پیش میاد کرنلی بار اول کار کنه. لاگ سریال رو نگاه کردم که ببینم کدوم ماژول نتونسته بالا بیاد یا کجا پنیک داده یا چی. ولی چیزی که دیدم خیلی عجیب بود:

1
2
3
Freeing init memory: 1408K
/init: line 103: can't open /r/dev/console: no such file
Kernel panic - not syncing: Attempted to kill init!

وات د فاک؟ ‪/r/dev/console‬ دیگه چه کوفتیه؟ اصلا تا حالا همچین چیزی به گوش کسی نخورده بود. یه سرچ ساده نشون میده که این مشکل فقط مختص این بورد هست. کاشف به عمل آمد که این احمق‌ها کرنل‌شون بالا نمی‌اومده (ظاهراً به دلیل نبود initramfs یا اشکالی مشابه) بعد عوض این که بشینن مثل آدم یه دونه cpio تنظیم کنن که با کرنل کامپایل بشه و در نهایت بچسبه به کرنل، برداشتن یه cpio درست کردن باینری‌شو گذاشتن. و داخل این cpio هم یه اسکریپت احمقانه هست که معلوم نیست چه قلطی می‌کنه:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel

#
# Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
#
trap ":" INT QUIT TSTP
/bin/hostname FriendlyARM
/bin/mount -n -t proc proc /proc

cmdline=`cat /proc/cmdline`

ROOT=none
ROOTFLAGS=
ROOTFSTYPE=
NFSROOT=
IP=
INIT=/sbin/init

for x in $cmdline ; do
  case $x in
  root=*)
      ROOT=${x#root=}
      ;;
  rootfstype=*)
      ROOTFSTYPE="-t ${x#rootfstype=}"
      ;;
  rootflags=*)
      ROOTFLAGS="-o ${x#rootflags=}"
      ;;
  init=*)
      INIT=${x#init=}
      ;;
  nfsroot=*)
      NFSROOT=${x#nfsroot=}
      ;;
  ip=*)
      IP=${x#ip=}
      ;;

  esac
done

if [ ! -z $NFSROOT ] ; then
  echo $NFSROOT | sed s/:/\ /g  > /dev/x ;  read sip dir < /dev/x
  echo $IP | sed s/:/\ /g > /dev/x;  read cip sip2 gip netmask hostname
device autoconf < /dev/x
  rm /dev/x
  echo $sip $dir $cip $sip2 $gip $netmask $hostname $device $autoconf
  mount -t nfs $NFSROOT /r -o nolock,proto=tcp
  #[ -e /r/dev/console ] || exec /bin/sh

elif [ ! -z $run_fs_image ] ; then
  ROOTFSTYPE="-t ext3"
  for i in 1 2 3 4 5 ; do
  /bin/mount -n -o sync -o noatime -o nodiratime -t vfat /dev/mmcblk0p1
/sdcard && break
 echo Waiting for SD Card...
  sleep 1
  done
  /sbin/losetup /dev/loop0 /sdcard/$run_fs_image
  /bin/mount $ROOTFSTYPE /dev/loop0 /r
  mount -o move /sdcard /r/sdcard
  #/sbin/losetup /dev/loop1 /r/sdcard/swap
  #/sbin/swapon /dev/loop1

elif [ x${ROOT:0:13} = "x/dev/mmcblk0p" ] ; then
  for i in 1 2 3 4 5 ; do
  /bin/mount -n $ROOTFLAGS $ROOTFSTYPE $ROOT /r && break
 echo Waiting for SD Card...
  sleep 1
  done
else
  /bin/mount -n $ROOTFLAGS $ROOTFSTYPE $ROOT /r
fi

ONE_WIRE_PROC=/proc/driver/one-wire-info
ETC_BASE=/r/etc
[ -d /r/system/etc ] && ETC_BASE=/r/system/etc
[ -e $ETC_BASE/ts.detected ] && . $ETC_BASE/ts.detected
[ -z $CHECK_1WIRE ] && CHECK_1WIRE=Y
if [ $CHECK_1WIRE = "Y" -a -e $ONE_WIRE_PROC ] ; then
  if read lcd_type fw_ver tail < $ONE_WIRE_PROC ; then
      if [ x$lcd_type = "x0" -a x$fw_ver = "x0" ] ; then
          TS_DEV=/dev/touchscreen
      else
          TS_DEV=/dev/touchscreen-1wire
          echo "1Wire touchscreen OK"
      fi
      if [ -e $ETC_BASE/friendlyarm-ts-input.conf ]; then
          sed "s:^\(TSLIB_TSDEVICE=\).*:\1$TS_DEV:g"
$ETC_BASE/friendlyarm-ts-input.conf > $ETC_BASE/ts-autodetect.conf
          mv $ETC_BASE/ts-autodetect.conf
$ETC_BASE/friendlyarm-ts-input.conf -f
          echo "CHECK_1WIRE=N" > $ETC_BASE/ts.detected
          sync
      fi
  fi
fi

[ -e /r/etc/friendlyarm-ts-input.conf ] && . /r/etc/friendlyarm-ts-input.conf
[ -e /r/system/etc/friendlyarm-ts-input.conf ] && .
/r/system/etc/friendlyarm-ts-input.conf
export TSLIB_TSDEVICE

#exec /bin/sh

umount /proc
exec switch_root /r $INIT </r/dev/console >/r/dev/console 2>&1

آخرین خط این اسکریپت یه کار احمقانه انجام داده. با یه سرچ ساده می‌تونیم ببینیم که اصلا عبارت /r/dev ما رو مستقیم می‌بره به فروم‌های فرندلی‌آرم و هیچ نتیجهٔ دیگه‌ای از جستجو عایدمون نمی‌شه. پس این شاهکار مختص برادرای چینی‌مون در فرندلی‌آرم می‌باشد.

نتیجه‌گیری

من در آخر نتونستم کرنلی که چینی‌ها ساخته بودن رو با زیرساخت بیلدروت کامپایل کنم و تصمیم گرفتم خودم برای این بورد کرنل بسازم و کانفیگ کنم. این کار نباید خیلی سخت باشه چون با یه سری diffگیری از سورس کرنل فرندلی آرم و سورس اصلی کرنل (نسخهٔ ۳٫۰٫۸) به این نتیجه رسیدم که تنهاقسمت‌هایی که اضافه شدن ماژول‌هایی مربوط به کنترل LEDهای روی بورد و هشت تا دکمهٔ کنترلی هستند. خیر سرشون اصلا نخواستیم این‌ها رو. یک سری تغییراتی هم توی فلگ‌های ماژول FAT انجام دادن که حقیقتش نفهمیدم چرا.

در مورد بوت‌لودر اما به‌طور کلی نمیشه از بوت‌لودرهایی که این احمق‌ها فراهم کردن (اون هم به شکل باینری) برای یه سیستم‌عامل اختصاصی استفاده کرد. همچنین هیچ بوت‌لودر استانداردی برای این بورد کانفیگ نشده. تصمیم گرفتم U-Boot رو برای این بورد پورت کنم. هنوز خیلی از کاراش مونده که انجام بشه و واقعا کار سخت و طاقت‌فرسایی هست. از کد نوشتن اسمبلی ARM تا کنترل باس و دیوایس‌های روی بورد :(

نتیجهٔ آخر این که هرگز از یه تولیدکنندهٔ چینی بورد نخرید. اگر طراحی‌ش مال جای دیگه باشه شاید بشه مشکلات کیفیت و پایداری پایین‌ش رو تحمل کرد. اما اگه چینی‌ها طراحی‌ش کردن و نرم‌افزارهاش رو ارائه دادن سمتش نرید.

دیدگاه‌ها