Sunday, May 01, 2011

How to port Cypress Barcode reader configuration from 2.6.38 to 2.6.32

一開始拿到barcode reader插入Andes的.32 kernel。UART console吐出這樣的錯誤訊息:


~ # usb 3-1: new low speed USB device using ohci_hcd and address 2
usb 3-1: configuration #1 chosen from 1 choice
generic-usb: probe of 0003:04B4:BCA1.0001 failed with error -22



接著在drivers/base/dd.c的really_probe()發現這段code:
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;

if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}


再試試看在really_probe()的一開頭加上dump_stack();得到:
[] driver_probe_device+0x42/0x158
[] __device_attach+0x22/0x2c
[] bus_for_each_drv+0x4c/0x7c
[] device_attach+0x52/0x6c
[] bus_probe_device+0x24/0x48
[] device_add+0x29a/0x3d0
[] hid_add_device+0x15a/0x18c
[] usbhid_probe+0x28a/0x2e0
[] usb_probe_interface+0x104/0x148
[] driver_probe_device+0xc8/0x158
[] __device_attach+0x22/0x2c
[] bus_for_each_drv+0x4c/0x7c
[] device_attach+0x52/0x6c
[] bus_probe_device+0x24/0x48
[] device_add+0x29a/0x3d0
[] usb_set_configuration+0x514/0x584
[] generic_probe+0x66/0xac
[] usb_probe_device+0x26/0x30
[] driver_probe_device+0xc8/0x158
[] __device_attach+0x22/0x2c
[] bus_for_each_drv+0x4c/0x7c
[] device_attach+0x52/0x6c
[] bus_probe_device+0x24/0x48
[] device_add+0x29a/0x3d0
[] usb_new_device+0x50/0xa4
[] hub_thread+0xc42/0x1174
[] kthread+0x74/0x88
[] do_exit+0x0/0x604


可以判斷出來這就是hid的driver找不到的問題。接著我把bca1,也就是barcode device ID拿來grep。
果然在"hid/hid-ids.h:#define USB_DEVICE_ID_CYPRESS_BARCODE_3 0xbca1"找到define。接著在把USB_DEVICE_ID_CYPRESS_BARCODE_3拿來grep。找到
hid/hid-core.c: { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
hid/hid-cypress.c: { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3),


再注意把menuconfig裡面的Device Drivers --->[*] HID Devices --->Special HID drivers ---><*> Cypress打開。


還要注意一下git diff v2.6.32 v2.6.38 drivers/hid/Kconfig
git diff v2.6.32 v2.6.38 drivers/hid/Makefile
git diff v2.6.32 v2.6.38 drivers/hid/hid-cypress.c
看起來cypress都沒什大的變動,所以可以不用修改直接上沒問題。
重新開機後load進新的kernel就可以抓到了。


input: Guest Barcode Reader as /devices/pci0000:00/0000:00:08.1/usb3/3-1/3-1:1.0/input/input0
cypress 0003:04B4:BCA1.0001: input: USB HID v1.00 Keyboard [Guest Barcode Reader] on usb-0000:00:08.1-1/input0

Tuesday, January 25, 2011

USB OHCI Introduction(part 2:ED structure)

不管是periodic list or non-periodic list,ED和TD的連接方式大概如下圖所示:

大家都知道USB基本的結構把每個device assign一個address,

並且每個device會有一些endpoints,透過address & endpoint number(ED裡面有address和endpoint欄位),

HC可以區分要把TD送到哪個device,對特定的endpoint做資料傳輸的動作。

不同的ED就代表不同的devices上的endpoint,對每個特定endpoint做動作就是TD。

舉個例子來說Control transferr基本上傳輸到address 0的endpoint 0,

所以address 0 & endpoint 0就佔有一個ED。一個Control transfer後面

接了三個transaction(setup, data, transaction)所以,會有三個TD接在ED後面。

接下來看ED的structure:值得注意的是寫code的時候ed structu要記得16 bytes aligned。


每個field的詳細內容:


spec 4.2.3 Endpoint Descriptor Description裡幾個keypoints:

1. 檢查Halted bit & sKip bit是否有on,若無繼續判斷ED裡是否有可以run的TD為比較HeadP == TailP。

如果不等於,則代表有TD可以被執行。

因此,需要一個dummy TD當空的,作為比較用。

2. 當HC想要修改ED裡面的欄位,如HeadP,則可以設sKip bit。

若是使用把ED整個移除掉,這樣要disable list(在HcControl register裡PeriodicListEnable, IsochronousEnable, ControlListEnable, BulkListEnable看是要砍哪個transfer的ED),整個cost太大。

HCD不應該直接動ED的HeadP/toggleCarry/Halted field,除非Halted & sKip bit有on。

這個可以衍生為只要修改ED or TD有兩種方式。第一:disable ED。第二:set sKip bit。

3. 執行完TD,有錯則設置Halted bit,並且把TD移到Done Queue。

直到HCD處理完錯誤狀況,HC才會繼續處理這個ED,不然HC看到Halted & sKip就直接跳過。


Thursday, January 20, 2011

USB OHCI Introduction(part 1)

開門見山直接進入主題:在一開機hardware reset後,device driver都還沒有介入之前,HC是在USBReset的state。OHCI spec 6.2.2提到,這個時候HC會拉reset signal通知整個bus,同時HcFmNumber這個register還不會增加count(HcFmNumber用來計算總共經過了幾個time frame)。HCD把HcControl裡的HCFS bit改成10b: UsbOPERATIONAL,從此以後進入UsbOPERATIONAL state。(其他state暫時不要管,主要focus在瞭解運作的原理)

以下這張圖個人認為可以當作OHCI controller如何運作的一個代表。
每個time frame在full/low speed device來說佔有1ms,也就是在下面兩個FRAME Boundary之間間隔。在進入operational state的下一個frame boundary開始會送出SOF packet。為什麼HC會知道要送出SOF是根據HcFmRemaining這個register從0變成HcFmInterval的時候。HcFmRemaining主要的公用是用來倒數計時,初始值是11999=0x2EDF是從HcFmInterval來。full speed是12MHz,所以外部的clock每秒供給12,000,000,1ms就有12,000次。連零都算則HcFmInterval就定為11999。(這邊會有疑慮,外部的clock如果不準,這時候counter算出來就會有問題,實際上的時間就會比較短或長,因此spec上面有說這個值driver可以自己設。而且在periodic list裡,每個interrupt entry list的transaction時間長度是用usb 2.0 spec 5.11.3上面提供的公式算出來的,所以如果frame counter的時間少於實際的時間,傳輸起來會產生schedule overrun,transaction會做不完)

在SOF Token傳完以後(不知道多久會傳完)首先開始Non-periodic lists傳輸Non-periodic包含了control & bulk transfer。在OHCI裡把control transfer看得比bulk transfer還重要,所以在HcControl register有CBSR(control bulk service ratio)這兩個bits可以設定做一個bulk transaction可以做幾個control transaction。把control list的transaction結束以後會檢查有沒有滿足比率,如果是四比一,會做四個control list的transaction,再做一個bulk的transaction。等到做到爽就會開始periodic list的處理。何謂做到爽?OHCI定義了一個HcPeriodicStart這個register用來作為何時開始開始periodic list(interrupt & isochronous)的傳輸,HC在每次frame的結束會把HcFmRemaining拿來和HcFmRemaining做比較。如果相等periodic list傳輸開始,non-periodic結束。在periodic list裡面Interrupt list的處理,是比isochronous list還先的,之後講periodic list transfer你可以看到periodic list interrupt table那個樹狀結構,isochronous ED總是放在整個list的最後面,interrupt ED擺在前面。等到periodic list做完,才會回到Non-periodic lists,回來以後Control:Bulk繼續按照比率執行。直到Frame結束他們的比例會繼續維持著到下一個frame。
在Frame Boundary做了四件事情,有前後順序:
1. HcFrameRemaining register的值從0變成HcFrameInterval所存的值。
2. HcFrameNumber加一
3. 把HcFrameNumber的值copy到HccaFrameNumber
4. 如果StartOfFrame的bit在interrupt enable register有設定則發出StartOfFrame interrupt。

一開始的SOF token:就像下面的USB Bus State會K...J...K...J...這樣一直不停的變換下去。

Wednesday, October 27, 2010

Frequency Scaling

因為工作比較慢,所以馬上做完又換下一個,到這間公司後,好一陣子沒有寫網誌了,
最近完成了frequency scheduling的driver,先寫個重點紀錄一下。
接著又要再搞通Uboot的USB。

全文開始:
在linux底下首先打開make menuconfig-> Power Management support --->
[*] CPU Frequency scaling --->
Default CPUFreq governor (userspace)
< > 'performance' governor
< > 'powersave' governor
-*- 'userspace' governor for userspace frequency scaling
< > 'ondemand' cpufreq policy governor
< > 'conservative' cpufreq governor

接著echo你想要的頻率到/sys底下的file node。
ex:"echo 500000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed"

CPU就會到你所設的500MHz底下run。而500MHz會依據你code裡面做的趨近方式選擇最適合的frequency。
(這邊的code要參考cpufreq_frequency_table_target,在drivers/cpufreq/freq_table.c)

Code放在arch/nds32/platforms/ag102/freq-scaling.c
當我們下echo 500000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
最後會call到ag102_set_target這隻function。
static struct cpufreq_driver ag102_cpufreq_driver = {

.verify = ag102_verify_policy,
.target = ag102_set_target,
.init = ag102_cpufreq_init,
.get = ag102_cpufreq_get,
.name = "AG102",
};


基本上來說這function會再去call cpufreq_frequency_table_target()以找到最後要調的頻率。
frequency scaling的基本運作原理為,程式裡面建個表這個表包涵了所有的可調頻率,他門是以基頻為單位(ex:33MHz)。調了scaling的raio,就如同33xn最後再除頻33n/d。

這其中比較有趣的是 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);和cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);他們是

分別寫在ag102_speedstep(idx)的前後用來通知相關人員頻率改變前後所有做的事情。
之後有發生一個bug,原因是沒有在frequency改變之後,修正相關timer interrupt的frequency。(timer interrupt的設法:假設cpu的主頻被設為500-->hclk=125-->apb=62.5,假設1/1000中斷一次,則要告訴timer interrupt controller 62.5MHz/1000次clock cycles就要發中斷。如果cpu clock被調為250,則timer interrupt controller則31.25MHz/1000就要中斷一次)
這個bug導致bogoMIPS測不準,bogoMIPS測量方式是在兩個timer tick中間測量所run的code需要多少個clock cycles。如果沒有重新設定timer,則導致所run的loop數,測出來會一樣。(原本的一秒可以run的clock cycles數,頻率調高後。因為timer interrupt controller沒有重設,所以導致一秒等比率變短,但頻率變快,雖然時間變短,還是可以run的和低頻率時的clock cycles一樣多)

有趣的還有下面這段bogoMIPS測量的source code:

#define LPS_PREC 8
void calibration()
{
unsigned long ticks, loopbit, lpj;
int lps_precision = LPS_PREC; //這個8代表到時後會除八次

lpj = (1<<12); //shift 12=4096x2instructions=8192,而__delay裡的addi & bgtz這兩個instruction平均是一點多個clock cycle所以再乘以一點多差不多接近一萬,這邊就用8192所以有個誤差值。現在的HZ=100,上面的8192是百分之一秒所run的instruction數量乘100就是0.8192Million instructions。

printk(KERN_INFO "Calibrating delay loop... ");
while ((lpj <<= 1) != 0) {
/* wait for "start of" clock tick */
ticks = jiffies; //不能一開始就計時,要等到下一個tick一開始才會準
while (ticks == jiffies) //直接計timer可能已經run到一半
/* nothing */;
/* Go .. */
ticks = jiffies;
__delay(lpj);
ticks = jiffies - ticks;
if (ticks)
break;
}

/*
* Do a binary approximation to get lpj set to
* equal one clock (up to lps_precision bits)
*/
lpj >>= 1; //最後出來的值是爆表的,所以要往回shfit一位,但是這樣又太小
loopbit = lpj;
//因此要加自己除以二,如果還是爆掉,再除以二再加,這樣做八次,想辦法填到最滿
while (lps_precision-- && (loopbit >>= 1)) {
lpj |= loopbit;
ticks = jiffies;
while (ticks == jiffies) //和上面一樣要等到下個jiffies開始再算
/* nothing */;
ticks = jiffies;
__delay(lpj);
if (jiffies != ticks) /* longer than 1 tick */
lpj &= ~loopbit;
}

printk(KERN_CONT "%lu.%02lu BogoMIPS modified(lpj=%lu)\n",
lpj/(500000/HZ), //lpj*2*HZ/10^6 想當於是後六位數都不看只看前面
(lpj/(5000/HZ)) % 100, lpj); //lpj*2*HZ/10^4 %100相當於是後四位數都不看只看前面幾位,又因為mod 100的關係,所以只看四位數前面的兩位數。
}


上面lpj/(500000/HZ)=(lpj*2*HZ)/10^6解釋:lpj是loop次數,這個loop裡面run了兩個instruction,測試的時間為1/HZ,所以乘HZ。在現代的cpu通常一個instruction粗糙點可以看作是一個clock cycle,所以直接除1MHz就是多少MHz。

__udelay長這樣,裡面只有兩行指令,不外乎減一和判斷有沒有大於零,如果小於就結束。

static inline void __delay(unsigned long loops)
{
__asm__ __volatile__ (
"1:\n"
"\taddi\t%0, %0, -1\n"
"\tbgtz\t%0, 1b\n"
: "=r" (loops) : "0" (loops));
}


順便一提__udelay(),其實ucdelay的loop=lpj*Hz*2*usecs/10^6。

乘一個0x8000000000000000ULL是放大精準度+0x80000000ULL是無條件進位。

500000/HZ相當於Hz*2/10^6。所以下面function第一行是把Hz*2*usecs/10^6先做掉。
lpj放到inline assembly裡去乘。

估計會這樣做的原因是防止truncation的問題,如果直接做lpj*Hz*2*usecs再除10^6怕會truncate掉很多。
但應該也有別的作法。


static inline void __udelay(unsigned long usecs, unsigned long lpj)
{
usecs *= (unsigned long) (((0x8000000000000000ULL / (500000 / HZ)) +
0x80000000ULL) >> 32);

__asm__ __volatile__ (
"mult64\t$d0, %1, %2\n"
"\tmfusr\t%0, $d0.hi\n"
: "=r" (usecs) : "0" (usecs), "r" (lpj)
: "$d0.hi", "$d0.lo");
__delay(usecs);
}



Friday, January 29, 2010

switch和if else的差別

下午無聊忽然想到這個問題,就寫個sample反組譯一下:
gcc -S practice.c


#include <stdio.h>

int main()
{
int a = 1;

switch(a) {
case 1:
printf("case 1\n");
break;
case 2:
printf("case 2\n");
break;
case 3:
printf("case 3\n");
};

if (a == 1) {
printf("case 1\n");
} else if (a == 2) {
printf("case 2\n");
} else if (a == 3) {
printf("case 3\n");
}
return 0;
}

組合語言的程式如下:

.file "practice.c"
.section .rodata
.LC0:
.string "case 1"
.LC1:
.string "case 2"
.LC2:
.string "case 3"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $1, 28(%esp)
movl 28(%esp), %eax
cmpl $2, %eax
je .L4
cmpl $3, %eax
je .L5
cmpl $1, %eax
jne .L2
.L3:
movl $.LC0, (%esp)
call puts
jmp .L2
.L4:
movl $.LC1, (%esp)
call puts
jmp .L2
.L5:
movl $.LC2, (%esp)
call puts
.L2:
cmpl $1, 28(%esp)
jne .L6
movl $.LC0, (%esp)
call puts
jmp .L7
.L6:
cmpl $2, 28(%esp)
jne .L8
movl $.LC1, (%esp)
call puts
jmp .L7
.L8:
cmpl $3, 28(%esp)
jne .L7
movl $.LC2, (%esp)
call puts
.L7:
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"
.section .note.GNU-stack,"",@progbits

你可以看出,switch case是在等於判斷條件的時候才會jmp,而if else是每次不等於判斷敘述就jmp。所以在使用上如果是case較於分散的例子,應該要用switch這種方式,若是有一種條件是主要的發生狀況(>50%),則要用if else,且把他放在第一判斷條件處(因為放在第一個位置可以減少一個jmp,若是放在switch則每次至少需要一次的jmp)。其他的話用哪個都沒差,主要使用上不管是switch or if else,都一定要按照發生的比率從前面排下來,才能減少判斷上的時間浪費。

Thursday, January 07, 2010

Spin lock & work queue 整理

SPIN LOCK
essential linux device driver的第二章A peek inside the kernel裡頭的Concurreny in the kernel有提到使用spin lock的四種case。寫得非常好。可以參考看看。

我的理解是這樣的,spin lock存在的目的是為了避免context switch的發生,什麼時候要避免使用context switch呢?就是處理共用變數的時間非常短。這時就不需要把process抓去睡覺,取而代之的是使用spin lock等待鎖的釋放,只要spin lock等待的時間少於兩次context switch(swap process out & swap process in)的時間。這樣就划的來了。而在單cpu上面只要你佔用了processor此時你要了一個lock,剛好的這個lock已經被其他的context(process or interrupt)所佔據。無論如何,當下你一定要進行context switch才能把執行權限放給其他的context把事情做完,並且把鎖釋放,這樣你才有機會獲得這把鎖。因此才有了以下不同的spin lock在單cpu裡面實做的變形,而不是真的在那邊spin。(在單處理器裡,spin lock的實做就是disable interrupt & disable preemption這樣就可以避免使用semaphore導致context switch)

下面把這三個function的source code攤出來看。
spin_lock() spin_lock_irq() spin_lock_irqsave()

在我的2.6.29的kernel上面spin lock的實做在uni-processor的source code裡頭是長這樣(include/linux/spinlock_api_up.h):

#ifndef __LINUX_SPINLOCK_API_UP_H
#define __LINUX_SPINLOCK_API_UP_H

#ifndef __LINUX_SPINLOCK_H
# error "please don't include this file directly"
#endif

/*
* include/linux/spinlock_api_up.h
*
* spinlock API implementation on UP-nondebug (inlined implementation)
*
* portions Copyright 2005, Red Hat, Inc., Ingo Molnar
* Released under the General Public License (GPL).
*/

#define in_lock_functions(ADDR) 0

#define assert_spin_locked(lock) do { (void)(lock); } while (0)

/*
* In the UP-nondebug case there's no real locking going on, so the
* only thing we have to do is to keep the preempt counts and irq
* flags straight, to suppress compiler warnings of unused lock
* variables, and to add the proper checker annotations:
*/
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

#define __LOCK_BH(lock) \
do { local_bh_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQ(lock) \
do { local_irq_disable(); __LOCK(lock); } while (0)

#define __LOCK_IRQSAVE(lock, flags) \
do { local_irq_save(flags); __LOCK(lock); } while (0)

#define __UNLOCK(lock) \
do { preempt_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_BH(lock) \
do { preempt_enable_no_resched(); local_bh_enable(); __release(lock); (void)(lock); } while (0)

#define __UNLOCK_IRQ(lock) \
do { local_irq_enable(); __UNLOCK(lock); } while (0)

#define __UNLOCK_IRQRESTORE(lock, flags) \
do { local_irq_restore(flags); __UNLOCK(lock); } while (0)

#define _spin_lock(lock) __LOCK(lock)
#define _spin_lock_nested(lock, subclass) __LOCK(lock)
#define _read_lock(lock) __LOCK(lock)
#define _write_lock(lock) __LOCK(lock)
#define _spin_lock_bh(lock) __LOCK_BH(lock)
#define _read_lock_bh(lock) __LOCK_BH(lock)
#define _write_lock_bh(lock) __LOCK_BH(lock)
#define _spin_lock_irq(lock) __LOCK_IRQ(lock)
#define _read_lock_irq(lock) __LOCK_IRQ(lock)
#define _write_lock_irq(lock) __LOCK_IRQ(lock)
#define _spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _spin_trylock(lock) ({ __LOCK(lock); 1; })
#define _read_trylock(lock) ({ __LOCK(lock); 1; })
#define _write_trylock(lock) ({ __LOCK(lock); 1; })
#define _spin_trylock_bh(lock) ({ __LOCK_BH(lock); 1; })
#define _spin_unlock(lock) __UNLOCK(lock)
#define _read_unlock(lock) __UNLOCK(lock)
#define _write_unlock(lock) __UNLOCK(lock)
#define _spin_unlock_bh(lock) __UNLOCK_BH(lock)
#define _write_unlock_bh(lock) __UNLOCK_BH(lock)
#define _read_unlock_bh(lock) __UNLOCK_BH(lock)
#define _spin_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _read_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _write_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _spin_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)
#define _read_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)
#define _write_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)

#endif /* __LINUX_SPINLOCK_API_UP_H */

其中

#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)

所以在uni-processor裡頭spin_lock的實做根本就只有把preempt關掉。再看看preempt_disable的實做在/include/linux/preempt.h

#ifdef CONFIG_PREEMPT
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#else
#define preempt_disable() do { } while (0)

#define inc_preempt_count() add_preempt_count(1)

# define add_preempt_count(val) do { preempt_count() += (val); } while (0)

#define preempt_count() (current_thread_info()->preempt_count)

上面都在同一個檔案裡,這邊只擷取用的到的部份。
所以preempt_disable如果在CONFIG_PREEMPT的環境就是把preempt_count加一而已。
如果是在non-preempt就根本什麼都沒做。所以如果你知道只有在process context會使用到spin_lock就可以使用最傳統的。這時儘管是preemptible kernel如果有timer interrupt進來。time slice時間已經用完了,由於切成non_preemptible的緣故,所以也不會被換出去。

接下來再看看其他幾個重要的spin_lock變形:
spin_lock_irq

#define _spin_lock_irq(lock) __LOCK_IRQ(lock)
#define __LOCK_IRQ(lock) \
do { local_irq_disable(); __LOCK(lock); } while (0)

include/linux/irqflags:

#define local_irq_disable() \
do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)

arch/x86/include/asm/irqflags:

static inline void raw_local_irq_disable(void)
{
native_irq_disable();
}
static inline void native_irq_disable(void)
{
asm volatile("cli": : :"memory");
}

所以在x86裡面spin_lock_irq追到最後就是把cli關掉然後在disable preemption而已。這個時候比較吶悶的是為何中斷已經關掉還要disable preemption,有可能在沒有interrupt進來的情快下被搶佔嗎?沒有timer interrupt還有可能被搶佔?後來想一想,有一個可能是這樣,儘管關掉了外部中斷,內部有可能發生像page fault的這種trap,假設現在page fault的情況是read only導致的,這時系統會copy-on-write製造新的page給這個process使用,這時若發生了記憶體空間不足的問題。就會被抓去睡覺,如此就導致了搶佔的發生。上面是個人的理解,有錯請指教。

spin_lock_irqsave

#define _spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define __LOCK_IRQSAVE(lock, flags) \
do { local_irq_save(flags); __LOCK(lock); } while (0)

include/linux/irqflags.h

#define local_irq_save(flags) \
do { \
typecheck(unsigned long, flags); \
raw_local_irq_save(flags); \
trace_hardirqs_off(); \
} while (0)

arch/x86/include/asm/irqflags.h

#define raw_local_irq_save(flags) \
do { (flags) = __raw_local_irq_save(); } while (0)

static inline unsigned long __raw_local_irq_save(void)
{
unsigned long flags = __raw_local_save_flags();

raw_local_irq_disable();

return flags;
}
static inline unsigned long __raw_local_save_flags(void)
{
return native_save_fl();
}
static inline unsigned long native_save_fl(void)
{
unsigned long flags;

asm volatile("# __raw_save_flags\n\t"
"pushf ; pop %0"
: "=g" (flags)
: /* no input */
: "memory");

return flags;
}

spin_lock_irqsave和spin_lock_irq的差別:多了把flag存起來的行為。
一般來講只要有interrupt和process context共享spin lock以及process & process context間共享spin lock這兩者同時發生且preemptible的情形,都會spin_lock_irqsave。若是只有process&interrupt context共享lock或是像上例但是non-preemptible kernel,則只需要local_irq_save。

至於spin_lock_bh則是disable softirq,詳細的使用情形則沒有研究。

WORK QUEUE
work queue是一種延遲作業的機制。一般來說預設是使用內建的worker thread。(event0/1/2/3)
使用方法如下:

1. 如果你認為使用預設的worker thread無法處理龐大工作量。此時可以create一個專有的worker thread。
Create_workqueue(const char *name):為每個processer都create一個worker thread。
Create_singlethread_workque(const char *name):只為當下的cpu create一個worker thread。
記得這邊的name就是thread的name,往後可以在程式執行起來以後由ps aux看到。

2. 之後宣告可以有兩種
compile time:DECLARE_WORK(name, void (*function)(void*), void *data)
run time:
INIT_WORK(struct work_struct *work, void (*function)(void*), void *data)
PREPARE_WORK(struct work_struct *work, void (*function)(void*), void *data)
PREPARE_WORK書上(linux device driver 3rd)說沒有初始化work_struct。

3. 之後就是把work排進worker thread開始執行
有兩種,第一種是排進預設的event worker thread:
schedule_work(), schedule_delayed_work()
第二種是排進你自己create的worker thread:
queue_work(struct workqueue_struct *queue, struct work_struct *work)
queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay)
第一個參數workqueue_struct就是你自己create的worker thread。

4. 若你想要快點把worker thread裡頭你自己的work執行,可以使用
flush_workqueue(struct workqueue_struct *queue)
若是想直接cancel你所排定的工作cancel_delayed_work(struct work_struct *work)
還是想要直接刪除你的kernel thread:destroy_workqueue(struct workqueue_struct *queue)

Thursday, November 05, 2009

Prilepin's Table

Prilepin's Table是蘇聯運動科學家Alexander Sergeyevitch Prilepin所發明的。

他掌握了1975~1985年的蘇聯舉重年代,據說死於1985,卒於任內。

主要用來增加力量和速度的用途。

假設我的硬舉1RM = 250kg如果我這一次的訓練量是打算在90%以上,則225kg以上的重量每組

最多做兩下,全部加起來不超過七下。總range在4-10下之間。過少則訓練不足,過多訓練過度。

要記住的是如果用負荷較低的重量訓練,每組都要用盡全力,盡量增加槓子的速度。才有訓練到CNS。

表格長這樣

Percent..............Reps per Set.......Optimal.......Total Range

70- ......................3-6.... ..................24 ...............18-30
70-80 ..................3-6 ......................18 ...............12-24
80-89 ..................2-4 ......................15 ...............10-20
90+ ....................1-2 ........................7 ................4-10

下面A.S. Prilepin’s Scientific – Practical Contribution to the Intensification of the Modern Training of Weightlifters這篇文章講到了Prilepin's Table的總結:

1) a significantly higher intensity of loading in which the most effective lifts are with near limit weights.(就是做幾乎極限的重量)

2) the application of the optimum number of sets and repetitions with a fundamental weight within a workout, varied in individual exercises, workouts, and periods of training.


參考文章:
Prilepin's Table(Iron Addict Forum)

A.S. Prilepin’s Scientific – Practical Contribution to the Intensification of the Modern Training of Weightlifters

USING PRILEPHIN'S TABLE