為了讓Linux在一個全新的ARM SoC上運行,需要提供大量的底層支撐,如定時器節拍、中斷控制器、SMP啟動、CPU hotplug以及底層的GPIO、clock、pinctrl和DMA硬件的封裝等。定時器節拍、中斷控制器、SMP啟動和CPU hotplug這幾部分相對來說沒有像早期GPIO、clock、pinctrl和DMA的實現那麼雜亂,基本上有個固定的套路。定時器節拍為Linux基於時間片的調度機制以及內核和用戶空間的定時器提供支撐,中斷控制器的驅動則使得Linux內核的工程師可以直接調用local_irq_disable()、disable_irq()等通用的中斷API,而SMP啟動支持則用於讓SoC內部的多個CPU核都投入運行,CPU hotplug則運行運行時掛載或拔除CPU。這些工作,在Linux 3.7內核中,進行了良好的層次劃分和架構設計。
在GPIO、clock、pinctrl和DMA驅動方面,Linux 2.6時代,內核已或多或少有GPIO、clock等底層驅動的架構,但是核心層的代碼太薄弱,各SoC對這些基礎設施實現方面存在巨大差異,而且每個SoC仍然需要實現大量的代碼。pinctrl和DMA則最為混亂,幾乎各家公司都定義了自己的獨特的實現和API。
社區必須改變這種局面,於是內核社區在2011~2012年進行了如下工作,這些工作在目前的3.7內核中基本准備就緒:
§ TI的工程師Mike Turquette提供了common clk框架,讓具體SoC實現clk_ops成員函數並通過clk_register、clk_register_clkdev注冊時鐘源以及源與設備對應關系,具體的clock驅動都統一遷移到drivers/clk目錄;
§ 在GPIO方面,drivers/gpio下的gpiolib已能與新的pinctrl完美共存,實現引腳的GPIO和其他功能之間的復用,具體的SoC只需實現通用的gpio_chip結構體的成員函數。
經過以上工作,基本上就把芯片底層的基礎架構方面的驅動的架構統一了,實現方法也統一了。另外,目前GPIO、clock、pinmux等功能都能良好的進行Device Tree的映射處理,譬如我們可以方面的在.dts中定義一個設備要的時鐘、pinmux引腳以及GPIO。
除了上述基礎設施以外,在將Linux移植入新的SoC過程中,工程師常常強烈依賴於早期的printk功能,內核則提供了相關的DEBUG_LL和EARLY_PRINTK支持,只需要SoC提供商實現少量的callback或宏。
本文主要對上述各個組成部分進行架構上的剖析以及關鍵的實現部分的實例分析,以求完整歸納將Linux移植入新SoC的主要工作。本文基於3.7.4內核。
Linux 2.6的早期(2.6.21之前)基於tick設計,一般SoC公司在將Linux移植到自己的芯片上的時候,會從芯片內部找一個定時器,並將該定時器配置會HZ的頻率,在每個時鐘節拍到來時,調用ARM Linux內核核心層的timer_tick()函數,從而引發系統裡的一系列行為。如2.6.17中arch/arm/mach-s3c2410/time.c的做法是:
127/*
128 * IRQ handler for the timer
129 */
130static irqreturn_t
131s3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
132{
133 write_seqlock(&xtime_lock);
134 timer_tick(regs);
135 write_sequnlock(&xtime_lock);
136 return IRQ_HANDLED;
137}
138
139static struct irqaction s3c2410_timer_irq = {
140 .name = "S3C2410 Timer Tick",
141 .flags = SA_INTERRUPT | SA_TIMER,
142 .handler = s3c2410_timer_interrupt,
143};
252static void __init s3c2410_timer_init (void)
253{
254 s3c2410_timer_setup();
255 setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
256}
257
當前Linux多采用tickless方案,並支持高精度定時器,內核的配置一般會使能NO_HZ(即tickless,或者說動態tick)和HIGH_RES_TIMERS。要強調的是tickless並不是說系統中沒有時鐘節拍了,而是說這個節拍不再像以前那樣,周期性地產生。Tickless意味著,根據系統的運行情況,以事件驅動的方式動態決定下一個tick在何時發生。如果畫一個時間軸,周期節拍的系統tick中斷發生的時序看起來如下:
而NO_HZ的Linux看起來則是,2次定時器中斷發生的時間間隔可長可短:
在當前的Linux系統中,SoC底層的timer被實現為一個clock_event_device和clocksource形式的驅動。在clock_event_device結構體中,實現其set_mode()和set_next_event()成員函數;在clocksource結構體中,主要實現read()成員函數。而定時器中斷服務程序中,不再調用timer_tick(),而是調用clock_event_device的event_handler()成員函數。一個典型的SoC的底層tick定時器驅動形如:
61static irqreturn_t xxx_timer_interrupt(int irq, void *dev_id)
62{
63 struct clock_event_device *ce = dev_id;
65 …
70 ce->event_handler(ce);
71
72 return IRQ_HANDLED;
73}
74
75/* read 64-bit timer counter */
76static cycle_t xxx_timer_read(struct clocksource *cs)
77{
78 u64 cycles;
79
80 /* read the 64-bit timer counter */
81 cycles = readl_relaxed(xxx_timer_base + XXX_TIMER_LATCHED_HI);
83 cycles = (cycles << 32) | readl_relaxed(xxx_timer_base + XXX_TIMER_LATCHED_LO);
84
85 return cycles;
86}
87
88static int xxx_timer_set_next_event(unsigned long delta,
89 struct clock_event_device *ce)
90{
91 unsigned long now, next;
92
93 writel_relaxed(XXX_TIMER_LATCH_BIT, xxx_timer_base + XXX_TIMER_LATCH);
94 now = readl_relaxed(xxx_timer_base + XXX_TIMER_LATCHED_LO);
95 next = now + delta; 96 writel_relaxed(next, xxx_timer_base + SIRFSOC_TIMER_MATCH_0); 97 writel_relaxed(XXX_TIMER_LATCH_BIT, xxx_timer_base + XXX_TIMER_LATCH); 98 now = readl_relaxed(xxx_timer_base + XXX_TIMER_LATCHED_LO); 99 100 return next - now > delta ? -ETIME : 0; 101} 102 103static void xxx_timer_set_mode(enum clock_event_mode mode, 104 struct clock_event_device *ce) 105{ 107 switch (mode) { 108 case CLOCK_EVT_MODE_PERIODIC: 109 … 111 case CLOCK_EVT_MODE_ONESHOT: 112 … 114 case CLOCK_EVT_MODE_SHUTDOWN: 115 … 117 case CLOCK_EVT_MODE_UNUSED: 118 case CLOCK_EVT_MODE_RESUME: 119 break; 120 } 121} 144static struct clock_event_device xxx_clockevent = { 145 .name = "xxx_clockevent", 146 .rating = 200, 147 .features = CLOCK_EVT_FEAT_ONESHOT, 148 .set_mode = xxx_timer_set_mode, 149 .set_next_event = xxx_timer_set_next_event, 150}; 151 152static struct clocksource xxx_clocksource = { 153 .name = "xxx_clocksource", 154 .rating = 200, 155 .mask = CLOCKSOURCE_MASK(64), 156 .flags = CLOCK_SOURCE_IS_CONTINUOUS, 157 .read = xxx_timer_read, 158 .suspend = xxx_clocksource_suspend, 159 .resume = xxx_clocksource_resume, 160}; 161 162static struct irqaction xxx_timer_irq = { 163 .name = "xxx_tick", 164 .flags = IRQF_TIMER, 165 .irq = 0, 166 .handler = xxx_timer_interrupt, 167 .dev_id = &xxx_clockevent, 168}; 169 176static void __init xxx_clockevent_init(void) 177{ 178 clockevents_calc_mult_shift(&xxx_clockevent, CLOCK_TICK_RATE, 60); 179 180 xxx_clockevent.max_delta_ns = 181 clockevent_delta2ns(-2, &xxx_clockevent); 182 xxx_clockevent.min_delta_ns = 183 clockevent_delta2ns(2, &xxx_clockevent); 184 185 xxx_clockevent.cpumask = cpumask_of(0); 186 clockevents_register_device(&xxx_clockevent); 187} 188 189/* initialize the kernel jiffy timer source */ 190static void __init xxx_timer_init(void) 191{ 192 … 214 215 BUG_ON(clocksource_register_hz(&xxx_clocksource, CLOCK_TICK_RATE)); 218 219 BUG_ON(setup_irq(xxx_timer_irq.irq, &xxx_timer_irq)); 220 221 xxx_clockevent_init(); 222} 249struct sys_timer xxx_timer = { 250 .init = xxx_timer_init, 251};
上述代碼中,我們特別關注其中的如下函數: clock_event_device的set_next_event 成員函數xxx_timer_set_next_event() 該函數的delta參數是Linux內核傳遞給底層定時器的一個差值,它的含義是下一次tick中斷產生的硬件定時器中計數器counter的值相對於當前counter的差值。我們在該函數中將硬件定時器設置為在“當前counter計數值” + delta的時刻產生下一次tick中斷。xxx_clockevent_init()函數中設置了可接受的最小和最大delta值對應的納秒數,即xxx_clockevent.min_delta_ns和xxx_clockevent.max_delta_ns。 clocksource 的read成員函數xxx_timer_read() 該函數可讀取出從開機以來到當前時刻定時器計數器已經走過的值,無論有沒有設置計數器達到某值的時候產生中斷,硬件的計數總是在進行的。因此,該函數給Linux系統提供了一個底層的准確的參考時間。 定時器的中斷服務程序xxx_timer_interrupt() 在該中斷服務程序中,直接調用clock_event_device的event_handler()成員函數,event_handler()成員函數的具體工作也是Linux內核根據Linux內核配置和運行情況自行設置的。 clock_event_device的set_mode成員函數 xxx_timer_set_mode() 用於設置定時器的模式以及resume和shutdown等功能,目前一般采用ONESHOT模式,即一次一次產生中斷。當然新版的Linux也可以使用老的周期性模式,如果內核編譯的時候未選擇NO_HZ,該底層的timer驅動依然可以為內核的運行提供支持。
這些函數的結合,使得ARM Linux內核底層所需要的時鐘得以運行。下面舉一個典型的場景,假定定時器的晶振時鐘頻率為1MHz(即計數器每加1等於1us),應用程序透過nanosleep() API睡眠100us,內核會據此換算出下一次定時器中斷的delta值為100,並間接調用到xxx_timer_set_next_event()去設置硬件讓其在100us後產生中斷。100us後,中斷產生,xxx_timer_interrupt()被調用,event_handler()會間接喚醒睡眠的進程導致nanosleep()函數返回,從而用戶進程繼續。
這裡特別要強調的是,對於多核處理器來說,一般的做法是給每個核分配一個獨立的定時器,各個核根據自身的運行情況動態設置自己時鐘中斷發生的時刻。看看我們說運行的電腦的local timer中斷即知:
barry@barry-VirtualBox:~$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
…
20: 945 0 0 0 IO-APIC-fasteoi vboxguest
21: 4456 0 0 21592 IO-APIC-fasteoi ahci, Intel 82801AA-ICH
22: 26 0 0 0 IO-APIC-fasteoi ohci_hcd:usb2
NMI: 0 0 0 0 Non-maskable interrupts
LOC: 177279 177517 177146 177139 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 0 0 0 0 Performance monitoring
…
而比較低效率的方法則是只給CPU0提供定時器,由CPU0將定時器中斷透過IPI(Inter Processor Interrupt,處理器間中斷)廣播到其他核。對於ARM來講,1號IPIIPI_TIMER就是來負責這個廣播的,從arch/arm/kernel/smp.c可以看出:
62 enum ipi_msg_type {
63 IPI_WAKEUP,
64 IPI_TIMER,
65 IPI_RESCHEDULE,
66 IPI_CALL_FUNC,
67 IPI_CALL_FUNC_SINGLE,
68 IPI_CPU_STOP,
69 };
本文出自 “宋寶華的博客” 博客,請務必保留此出處http://21cnbao.blog.51cto.com/109393/1127016