顯示具有 driver 標籤的文章。 顯示所有文章
顯示具有 driver 標籤的文章。 顯示所有文章

2012年9月2日 星期日

如何撰寫在Linux Kernel 2.6.x的GPIO驅動程式


前言
GPIO是目前SoC常常會使用到的一個元件,以往的GPIO使用上並不普遍,所以在Linux Kernel並未加以統一寫法,因此以往的寫法則各家自行撰寫,比較多的是架構在misc的驅動之下,但今日新的Linux Kernel則有給予新的統一做法,它讓kernel中有要用GPIO的其它驅動程式可以透過其定義的library API來使用,而GPIO本身則實現其實體層的控制就好,如此一來其它使用GPIO的驅動則在porting更容易了。

什麼是GPIO
對於GPIO我們要先有一些了解,GPIO – Generic Program Input Output,中文說法是可程式輸出輸入的控制訊號,意思是該點可以設定為input或者output訊號,而其輸出的訊號則是單純的數位訊號,不是high就是low,而輸入訊號的偵測也是一樣,而其電位則是TTL訊號,而這樣的東西,通常可以例如模擬I2C HOST,或者拿來讀取RTC的介面,又或接一個LED當做某些事件的指示使用。

Kernel中的GPIO Library
還没有寫驅動程式之前,我們先來了解這個library,它是最近的Linux kernel版本所定義的,是給其它需要用到GPIO的驅動程式所使用,意思就是在Kernel內部定義了義標準的GPIO Library API,如此一來對需要使用到GPIO的驅動程式便可以不用理會其底層的GPIO,也不會因平台不同而各家自行所定義的GPIO API而造成上層的驅動程式而跟著修改,我們可以用以下的軟體加構圖來了解其中的做法:












目前定義較常用的Kernel API有如下:
  • int gpio_direction_output(unsigned gpio, int value);
這是設定該GPIO為output,第一個參數為GPIO number,第二個參數則是設定其為輸出時的初始值。
  • int gpio_set_value(unsigned gpio, int value);
這是針對己經設為output的GPIO設定其值為high或者為low。
  • int gpio_direction_input(unsigned gpio);
這是設定該GPIO為input。
  • int gpio_get_value(unsigned gpio);
這是針對己設為input的GPIO讀回其現在的輸入值為何。
  • int gpio_request_array(struct gpio *array, size_t num);
這是若要使用任何GPIO時,必須先呼叫該API,這個動作有點類似open的動作,以免其它人會佔用到相同的GPIO pin,這個API可以讓你要求不連續任意個存在的GPIO,之後你就可以使用以上的所有API。
struct gpio {
        unsigned        gpio;
        unsigned long   flags;
        const char      *label;
};
flags可以設定的值如下:
#define GPIOF_DIR_OUT   (0 << 0)
#define GPIOF_DIR_IN    (1 << 0)

#define GPIOF_INIT_LOW  (0 << 1)
#define GPIOF_INIT_HIGH (1 << 1)

#define GPIOF_IN             (GPIOF_DIR_IN)
#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | GPIOF_INIT_LOW)
#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | GPIOF_INIT_HIGH)
  • int gpio_free_array(struct gpio *array, size_t num);
上一個若類似open的動作,這一個API就是close的動作。
以上這些kernel API都是給kernel內部其它driver使用的,使用它之前必需確定GPIO驅動已經被載入,否則將會無法使用,所以這時驅動載入的順序是非常重要的。另外由於有這一層的介面,使得其它會使用到GPIO的驅動程式,會統一的介面,不會因為底層的GPIO控制驅動程式不同因而有不同的介面,而產生需要修改程式,這對軟體的維護會是一大負擔。

實體層的GPIO chip controller的驅動程式
這是一個底層的驅動程式,直接控制GPIO的晶片,並接受上層的管理層的指令動作,並以此提供其它需要使用GPIO的驅動程式,可以有統一的介面。該驅動程式會用到一個資料結構,它是宣告在include/asm-generic/gpio.h,並且在kernel中必需將CONFIG_GENERIC_GPIO啓動,其宣告如下:
/**
 * struct gpio_chip - abstract a GPIO controller
 * @label: for diagnostics
 * @dev: optional device providing the GPIOs
 * @owner: helps prevent removal of modules exporting active GPIOs
 * @request: optional hook for chip-specific activation, such as
 *      enabling module power and clock; may sleep
 * @free: optional hook for chip-specific deactivation, such as
 *      disabling module power and clock; may sleep
 * @direction_input: configures signal "offset" as input, or returns error
 * @get: returns value for signal "offset"; for output signals this
 *      returns either the value actually sensed, or zero
 * @direction_output: configures signal "offset" as output, or returns error
 * @set: assigns output value for signal "offset"
 * @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
 *      implementation may not sleep
 * @dbg_show: optional routine to show contents in debugfs; default code
 *      will be used when this is omitted, but custom code can show extra
 *      state (such as pullup/pulldown configuration).
 * @base: identifies the first GPIO number handled by this chip; or, if
 *      negative during registration, requests dynamic ID allocation.
 * @ngpio: the number of GPIOs handled by this controller; the last GPIO
*      handled is (base + ngpio - 1).
 * @can_sleep: flag must be set iff get()/set() methods sleep, as they
 *      must while accessing GPIO expander chips over I2C or SPI
 * @names: if set, must be an array of strings to use as alternative
 *      names for the GPIOs in this chip. Any entry in the array
 *      may be NULL if there is no alias for the GPIO, however the
 *      array must be @ngpio entries long.  A name can include a single printk
 *      format specifier for an unsigned int.  It is substituted by the actual
 *      number of the gpio.
 *
 * A gpio_chip can help platforms abstract various sources of GPIOs so
 * they can all be accessed through a common programing interface.
 * Example sources would be SOC controllers, FPGAs, multifunction
 * chips, dedicated GPIO expanders, and so on.
 *
 * Each chip controls a number of signals, identified in method calls
 * by "offset" values in the range 0..(@ngpio - 1).  When those signals
 * are referenced through calls like gpio_get_value(gpio), the offset
 * is calculated by subtracting @base from the gpio number.
 */
struct gpio_chip {
        const char              *label;
        struct device           *dev;
        struct module           *owner;

        int                     (*request)(struct gpio_chip *chip,
                                                unsigned offset);
        void                    (*free)(struct gpio_chip *chip,
                                                unsigned offset);

        int                     (*direction_input)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*get)(struct gpio_chip *chip,
                                                unsigned offset);
        int                     (*direction_output)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        int                     (*set_debounce)(struct gpio_chip *chip,
                                                unsigned offset, unsigned debounce);

        void                    (*set)(struct gpio_chip *chip,
                                                unsigned offset, int value);
        int                     (*to_irq)(struct gpio_chip *chip,
                                                unsigned offset);

        void                    (*dbg_show)(struct seq_file *s,
                                                struct gpio_chip *chip);
        int                     base;
        u16                     ngpio;
        const char              *const *names;
        unsigned                can_sleep:1;
        unsigned                exported:1;

#if defined(CONFIG_OF_GPIO)
        /*
         * If CONFIG_OF is enabled, then all GPIO controllers described in the
         * device tree automatically may have an OF translation
         */
        struct device_node *of_node;
        int of_gpio_n_cells;
        int (*of_xlate)(struct gpio_chip *gc, struct device_node *np,
                        const void *gpio_spec, u32 *flags);
#endif
};
另外向上註冊及移除的API如下:
/* add/remove chips */
extern int gpiochip_add(struct gpio_chip *chip);
extern int __must_check gpiochip_remove(struct gpio_chip *chip);
extern struct gpio_chip *gpiochip_find(void *data, int (*match)(struct gpio_chip *chip, void *data));

程式範例
GPIO控制晶片驅動程式:
struct myart_gpio_module {
struct gpio_chip gpio;
……………………………….
};

int myart_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
struct myart_gpio_module *module;

module = container_of(chip, struct myart_gpio_module, gpio);
return module->intr_start + offset;

}

int myart_gpio_direction_input(struct gpio_chip *chip,unsigned offset)
{
myart_gpio_inout_pin(offset , MCPU_GPIO_INPUT);
return 0;
}

int myart_gpio_get_value(struct gpio_chip *chip, unsigned offset)
{
return myart_gpio_get_pin(offset);
}

int myart_gpio_direction_output(struct gpio_chip *chip,unsigned offset, int value)
{
myart_gpio_inout_pin(offset , MCPU_GPIO_OUTPUT);
return 0;
}

void myart_gpio_set_value(struct gpio_chip *chip, unsigned offset, int value)
{
myart_gpio_set_pin(offset , value);
}

static int __devinit myart_gpio_probe(struct platform_device *pdev)
{
……………………………….
/* Initialize the GPIO data structures */
module->gpio.dev = &pdev->dev;
module->gpio.label = pdev->name;
module->gpio.owner = THIS_MODULE;
module->gpio.direction_input = myart_gpio_direction_input;
module->gpio.get = myart_gpio_get_value;
module->gpio.direction_output = myart_gpio_direction_output;
module->gpio.set = myart_gpio_set_value;
module->gpio.to_irq = myart_gpio_to_irq;
module->gpio.can_sleep = 0;
module->gpio.base = 0;
module->gpio.ngpio = MCPU_GPIO_NUM;
ret = gpiochip_add(&(module->gpio));
……………………………………..
}

static int __devexit my_gpio_remove(struct platform_device *pdev)
{
……………………………..
ret = gpiochip_remove(&module->gpio);
if (ret) {
dev_err(dev, "unable to remove GPIO chip\n");
return ret;
}
………………………..
return 0;
}

static struct platform_driver my_gpio_driver = {
.driver = {
.name = "GPIO Driver",
.owner = THIS_MODULE,
},
.probe = my_gpio_probe,
.remove = __devexit_p(my_gpio_remove),
};

static int __init my_gpio_module_init(void)
{
return platform_driver_register(&my_gpio_driver);
}

static void __exit my_gpio_module_exit(void)
{
platform_driver_unregister(&my_gpio_driver);
}

module_init(my_gpio_module_init);
module_exit(my_gpio_module_exit);

上層GPIO應用驅動程式
上層的GPIO應用驅程式則是使用GPIO Kernel Library所提供的介面來撰寫驅動程式,如RTC, I2C等等都是常看到的使用GPIO來應用的驅動程式,它必須使用gpio_request_array()來向kernel取得所要用的GPIO,並確定它是没被其它驅動程式所使用,使用後並將其佔住不被其它驅動程式所佔用,在該應用驅動程式下載時需要呼叫gpio_free_array()將所佔用的GPIO free給其它的驅動程式使用,在整個驅動程式中則可以使用gpio_direction_input(), gpio_direction_output(), gpio_set_value(), gpio_get_value()來個別控制相對的GPIO為input or output,並且讀回或者設定相對GPIO為high or low。

程式範例
GPIO RTC驅動程式:
static void rtc_write(u8 cmd, u8 Data)
{
………………………
gpio_direction_output(GPIO_RTC_DATA, MCPU_GPIO_LOW);
    gpio_set_value(GPIO_RTC_RESET, MCPU_GPIO_HIGH);
………………………
}

static u8 rtc_read(u8 cmd)
{
…………………
gpio_direction_input(GPIO_RTC_DATA);
………………..
v = gpio_get_value(GPIO_RTC_DATA);
…………………….
}

static int myart_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
……………………..
v = rtc_read(RTC_MONTH_R);
………………………………
}

static int myart_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
……………………
rtc_write(RTC_YEAR_W, v);
……………………..
}

static const struct rtc_class_ops myart_rtc_ops = {
.read_time = myart_rtc_read_time,
.set_time = myart_rtc_set_time,
};

static struct gpio rtc_gpios[] = {
    { GPIO_RTC_RESET, GPIOF_DIR_OUT, "rtc_reset"}, 
    { GPIO_RTC_SCLK, GPIOF_DIR_OUT, "rtc_sclk"}, 
    { GPIO_RTC_DATA, GPIOF_DIR_OUT, "rtc_data"}, 
};

static int __init myart_rtc_probe(struct platform_device *pdev)
{
…………………………..
err = gpio_request_array(rtc_gpios, ARRAY_SIZE(rtc_gpios));
…………………………..
rtc = rtc_device_register("rtc-myart", &pdev->dev, &myart_rtc_ops,
  THIS_MODULE);
…………………………..
}

static int __exit myart_rtc_remove(struct platform_device *pdev)
{
…………………………..
rtc_device_unregister(rtc);
    gpio_free_array(rtc_gpios, ARRAY_SIZE(rtc_gpios)); 
return 0;
}

static struct platform_driver myart_rtc_driver = {
.driver = {
.name = "RTC", 
.owner = THIS_MODULE,
},
.remove = __exit_p(myart_rtc_remove),
};

static int __init myart_rtc_init(void)
{
return platform_driver_probe(&myart_rtc_driver, myart_rtc_probe);
}

static void __exit myart_rtc_exit(void)
{
    platform_driver_unregister(&myart_rtc_driver);
}

module_init(myart_rtc_init);
module_exit(myart_rtc_exit);

2012年3月19日 星期一

Linux Kernel New HAL Timer Device Driver


前言
由於現在的作業系統對於Real Time的需求越來越多,而對於Real Time的需求則是需要一個較精確的時間計時,以往的Linux在時間的設計上已經不符這個需求,所以在2.6.38之後的版本中已經針對它來修改,這份文件將會說明對於這個新架構的HAL Timer Device Driver的寫法為何。
以往的問題
  • 時間不夠精細,需要能夠取得nanosecond
  • 在計算時間時必需能夠考慮到timer counter overflow的問題
現在的做法
你可以註冊多個clocksource, 所謂的clocksource就是可以產生一個固定的clock, 並且可以讀回其計數值, 系統中將會比較各個clocksource的精確度, 以找出最佳的clocksource做為系統的clocksource, 而這個clocksource將會做為系統的計算時間用, 另外必須註冊一個做為clock event使用, 這就是取代以往的timer interrupt, 而clocksource和clockevent可以為同一個, 也可以為分別不同的timer, 若系統的timer夠多, 建議可以設定不同的timer
How to
  • 在arch/arm/mach-xxx的目錄中必須包以下的程式碼
    • 在MACHINE_START宣告中須有timer的資料結構指定
MACHINE_START(MOXAART, "Moxa ART CPU platform")
        .boot_params = 0x100,
        .fixup = moxaart_fixup,
        .map_io = moxaart_map_io, /* map physical to virtual address */
        .init_irq = irq_init_irq,
        .timer = &moxaart_timer,
        .init_machine = moxaart_init,  /* register  platform_device */
MACHINE_END
其中timer的資料結構如下
extern void     moxaart_timer_init(void);
static struct sys_timer moxaart_timer = {
        .init   = moxaart_timer_init,
};
其中init的成員為一個call back function,其寫法如下
void __init moxaart_timer_init(void)
{
        *timer_ctrl_reg = timer_ctrl_reg_value;
        setup_irq(IRQ_TIMER1, &moxaart_timer_irq);
        moxaart_clocksource_init();
        moxaart_clockevent_init();
}
請注意其中將會註冊一個clock source以及一個clock event。
  • 如何初始化以及設定一個clock source
    • 啓動該計時器,準備一個call back function讓系統可以隨時讀取讓計時器的值,最後呼叫一個kernel API clocksource_mmio_init()來設定一個clock source, 以下為範例程式
static cycle_t  moxaart_clocksource_read(struct clocksource *c)
{
        return ~moxaart_timer_get_counter(CLOCK_TIMER);
}
static void __init      moxaart_clocksource_init(void)
{
        moxaart_timer_set_reload(CLOCK_TIMER, MAX_COUNTER);
        moxaart_timer_set_counter(CLOCK_TIMER, MAX_COUNTER);
        moxaart_timer_enable(CLOCK_TIMER);
        clocksource_mmio_init(NULL, "timer2", APB_CLK, CLOCK_RATING, 32, moxaart_clocksource_read);
}
請注意在讀取計時器的值call back function中的return值,若讓計時器是遞減的值則必須反相加一個 ”~”, 若是遞加則不用。
  • 如何初始化以及設定一個clock event
    • 這個初始化較為繁複,首先你要準備一個資料結構struct clock_evelt_device, 以下為其範例
static struct clock_event_device        clockevent_moxaart = {
        .name           = "timer1",
        .features       = CLOCK_EVT_FEAT_PERIODIC|CLOCK_EVT_FEAT_ONESHOT,
        .irq            = IRQ_TIMER1,
        .rating         = CLOCK_RATING,
        .shift          = CLOCK_SHIFT,
        .set_mode       = moxaart_set_mode,
        .set_next_event = moxaart_set_next_event,
};
其中的features設定CLOCK_EVT_FEAT_PERIODIC, 是較為傳統的timer功能,就是會一直產生的固定時間,也就是以前所謂的tick功能,而CLOCK_EVT_FEAT_ONESHOT的功能較為新的設計,若要有high resolution (至nanosecond等級),則必須有這個功能,意思就是timer可以只產生一次到的interrupt,而不是前面所提的periodic一直產生的timer interrupt。另外對shift成員的設定為一個為2的幾次方值,如32則表示所有的timer counter值將會先乘於2的32次方。
    • 接下需要準備ISR, 其設定如下
static struct irqaction moxaart_timer_irq = {
        .name           = "timer1",
        .flags          = IRQF_DISABLED|IRQF_TIMER|IRQF_TRIGGER_FALLING|IRQF_IRQPOLL,
        .handler        = moxaart_timer_interrupt,
        .dev_id         = &clockevent_moxaart
};
請注意其成員dev_id為上面所宣告的clockevent_moxaart。
    • 在clock_event_device中的成員set_mode call back function則是在設定該timer是要使用何種方,以下為其寫法
static void     moxaart_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
{
        switch ( mode ) {
        case CLOCK_EVT_MODE_PERIODIC:
                moxaart_timer_set_reload(USED_TIMER, LATCH);
                moxaart_timer_set_counter(USED_TIMER, LATCH);
                moxaart_timer_enable(USED_TIMER);
                break;
        case CLOCK_EVT_MODE_ONESHOT:
                moxaart_timer_set_reload(USED_TIMER, 0);
                moxaart_timer_set_match1(USED_TIMER, 0);
                timer_ctrl_reg_value &= ~(TIMER1_INT_ENABLE);
                timer_ctrl_reg_value |= (TIMER1_ENABLE|TIMER1_USED_PCLK);
                *timer_ctrl_reg = timer_ctrl_reg_value;
                break;
        case CLOCK_EVT_MODE_RESUME:
                moxaart_timer_enable(USED_TIMER);
                break;
        case CLOCK_EVT_MODE_SHUTDOWN:
        case CLOCK_EVT_MODE_UNUSED:
        default:
                moxaart_timer_disable(USED_TIMER);
break;
        }
}
請注意其模式中的periodic和oneshot定義的不同,在oneshot的模式中會將reload的值設為零。
    • 而對於set_next_event的call back function,則是只是設定讓計時器的reload值
static int      moxaart_set_next_event(unsigned long evt, struct clock_event_device *unused)
{
        moxaart_timer_set_reload(USED_TIMER, evt);
        return 0;
}
    • 對於ISR的寫法如下
static irqreturn_t moxaart_timer_interrupt(int irq, void *dev_id)
{
        struct clock_event_device       *evt=dev_id;
        *(volatile unsigned int *)(MOXAART_TIMER_VA_BASE+TIMER_INTR_STATE) = 0;
        evt->event_handler(evt);
        return IRQ_HANDLED;
}
對於timer interrupt的寫法其實很制式,大家都一樣。
    • 最後則是clock event的初始化,其寫法如下
static void __init      moxaart_clockevent_init(void)
{
        clockevent_moxaart.mult = div_sc(APB_CLK, NSEC_PER_SEC, clockevent_moxaart.shift);
        clockevent_moxaart.max_delta_ns = clockevent_delta2ns(0xffffffff, &clockevent_moxaart);
        clockevent_moxaart.min_delta_ns = clockevent_delta2ns(0xf, &clockevent_moxaart);
        clockevent_moxaart.cpumask = cpumask_of(0);
        clockevents_register_device(&clockevent_moxaart);
}
  • 在arch/arm/Kconfig的有關自己CPU的選項中必須有以下的設定
select GENERIC_CLOCKEVENTS
select CLKSRC_MMIO
以下是一個範例:
config ARCH_MOXAART
        bool "MOXA ART CPU"
        select CPU_FA526
        select USB_ARCH_HAS_EHCI
        select MIGHT_HAVE_PCI
        select ARCH_REQUIRE_GPIOLIB
        select GENERIC_GPIO
        select GENERIC_CLOCKEVENTS
        select CLKSRC_MMIO
        help
          Support for MOXA ART CPU
附錄
Kernel APIs
/**
 * clocksource_mmio_init - Initialize a simple mmio based clocksource
 * @base:       Virtual address of the clock readout register
 * @name:       Name of the clocksource
 * @hz:         Frequency of the clocksource in Hz
 * @rating:     Rating of the clocksource
 * @bits:       Number of valid bits
 * @read:       One of clocksource_mmio_read*() above
 */
int __init clocksource_mmio_init(void __iomem *base, const char *name,
        unsigned long hz, int rating, unsigned bits,
        cycle_t (*read)(struct clocksource *))

2011年9月27日 星期二

Serial驅動程式中的Flow Control問題

什麼是Flow Control
Flow Control簡單講就是流量控制,其主要目的就是避免通訊的雙方資料被掉包,那為何資料會掉包呢?我們想像一個情境,通訊一定是至少兩人以上互相傳送資料,互相傳送資料雙方硬體不一定會一樣,所以在效能上也會不一樣,若當接收端來不及處理已接收到資料時,若沒有通知對方暫停傳送資料,因而造成對方持續送資料,這樣就會產一種現象,就是接收端會將收進來的資料丟棄,或者不接收對方送過來的資料,因而造成資料的留失,所以我們就使用流量控制的部分來確保資料不要留失。其實其中的做法也不是太難,就是利用一個機制來通知對方不要送資料就好了,而問題在於以前這種的控制都由軟體來做,但是最後還是控制使用硬體來通知對方,在控制硬體送出訊號給對方會需要一些時間處理,而這段時間長短是無法預測的,並且對方還是在繼續傳送資料,假如這段時間過長,就會造成和没有流量控制一樣的情形,也因此為了解決這個問題,現在就有人改用硬體來處理這個問題,就可以大大降低這個問題的產生,但是若要真正有效發揮硬體的功能是需要修改軟體的,所以以下我們會來說明如何修改軟體。
Serial Port的流量控制方式
RS232的流量控制方式有兩種,一種稱為軟體流量控制方式,另外一種就是所謂的硬體流量方式,什麼是軟體流量控制方式,其控制方式就是將控制訊息加到資料之中,用另外一個方式說明,就是當接收端無法收資料時就送一個字元Xoff (0x13)給對方,可以收的時候就送一個字元Xon (0x11)給對方,所以軟體控制的方式有幾種限制,首先是在傳送的資料不可以包含這兩個字完,否則將會嚴重破壞流量控制的正常運行,另外一個限制就這兩個字元必須是要即時被送出,若和要送給對方的資料一起排隊的話,有可能會延誤流量控制的時間點,因而造成流量控制的失敗。
另外一種的流量控制方式為硬體方式,其作法是必需另外再接兩條線,一條為RTS至對方的CTS,因為是雙向的所以另外一條就是我的CTS接至對方的RTS,另外的說法雙方都會增加兩隻腳為RTS及CTS,其做法很簡單就是當接收端資料快滿的時候,將RTS設為low,一直到可以接收資料時再將RTS拉為high,相對的傳送端會隨時檢查CTS,當它為low時就停止送資料,當它為high時就開始送資料。
RS232的上層控制驅動程式為tty_io,而這層則會負責流量控制,當需要做相對流量控制的行為時,它會呼叫你的RS232驅動程式作動作,所以只要做相對的動作即可,而這個動作則是前一篇文章-serial port device driver中所提的throttle & unthrottle這兩個call back function。
有什麼問題嗎?
若按照前面所提的,在寫串列式驅動程式的時候有什麼問題?看是沒有問題,但問題就在後面,因為現在的UART越做越好,所以就將流量控制的動作直接由IC本身實現,就不需要軟體來處理,IC實現有它的好處,因為它是由硬體來處理,所以反應的時間會非常快,能夠避免軟體處理時需要冗長的時間,可能會造成反應不及而讓流量控制無效。在找出問題之前我們先看以下的圖:

以上為驅動程式的架構圖,通常每一層驅程式都會有自已的buffer來存放收送的資料,而前面有說過在tty_io層會做流量控制,而它是如何判斷何時需要做流量控制呢?當然是依照它自已的buffer來做判斷,快滿的時候就叫對方不要送資料,buffer已被讀完則通知對方可以送資料,問題在於當上層的buffer滿並不代表低層IC上的buffer已滿,如此一來有一種情境將會發生,底層的IC buffer已滿,因為有支援流量控制,所以IC已經主動通知對方不要送資料,可是這時候因為上層的資料未滿,或者剛到低水位,所以通知低層送出通知對方可以送資料,如此一來將會弄亂底層的流量控制,簡單的說明就是上層的buffer水位和底層是不會同步的,用另外一種方式來看待這個問題,其實這麼多層的驅動程式,只要其中有一層做流量控制即可,這時候當然讓最低層的IC來處理即可,因為可以反應最快,並且因為由IC自行處理,所以可以減輕CPU的負擔,所以在有IC支援流量控制時RS232的驅動程式,應該如以下的寫法將會是比較好的:
static void uart_throttle(struct tty_struct *tty)
{
…………
disable_IC_receiving_and_receiving_interrupt();
………………..
}
static void uart_unthrottle(struct tty_struct *tty)
{
………………
enable_IC_receiving_and_receiving_interrupt();
…………………
}
没錯一個簡單的動作,就是在throttle的callback function中停止收資料就好了,因為如此一來將會造成IC buffer到達高水位而讓IC自動作成流量控制的行為即可,另外unthrottle的callback function中再一次啓動收資料即可,因為將IC中的buffer讀走而達低水位時,IC將會再次自動做出流量控制,如此一來上層可以不用知道低層IC的buffer使用情形,也不會弄亂IC的流量控制,這是在IC支援流量控制時所要做的修正。

2011年9月12日 星期一

驅動程式中的DMA問題

前言
什麼是DMA(directly memory access)? 簡單的講就直接記憶體存取,或許你還是無法明白,這樣說好了,在一個電腦系統中,通常都是由CPU (中央控制單元) 來對記憶體做存取的動作,也就是在各個電腦週邊元件中,若要對記憶體做存取動作,則必須經由 CPU 來做仲介的角色,而DMA則可以取代CPU這個工作,但僅至於資料而已無法對指令做這個動作,因為記憶體在系統中是唯一的,若要讓CPU及DMA可以很順暢的工作而不會互相干擾,則必需在軟體上做一些協調的動作,另外因為CPU有MMU及Cache的元件,也使得為了要讓DMA取得正確的資料,所以在寫這方面的驅動程式時需要特別注意,以下就來說明這方面的作法及問題。
硬體架構
圖一
問題點
從上圖中我們可以清楚地了解以下幾件事:
  • 當CPU要向DRAM或者是週邊元件存取時,所以放出來的位置將會經過cache及MMU的處理。
    • 在cache中的處理主要是提高對記憶體的存取速度。
    • 當作業啓動時所有程式均使用虛擬記憶體,所以一定會經過MMU的位址轉換,因為當實際要對週邊元件或者記憶體存取時一定會使用實體位址。
  • 當DMA要存取記憶體時並不會經過MMU模組的轉換,換句話說就是DMA會直接使用實體位址來存取記憶體。
    • 另外一個問題是DMA也沒有經過cache模組,所以存取的資直接從記憶體存取。
解決方案
由以上的問題描述,我們可以看一個很基本的問題,就是CPU和DMA之間對記憶體的存取可能會有不相符的現象產生。首先我們先來看週邊元件要送資料,也就是從CPU寫入資料至記憶體,然後DMA從記憶體取得資料給週邊元件,進而週邊元件送出資料,這裡問題在於CPU寫入資料時,可能不會一定寫入記憶體,有可能保留至cache中,若這時DMA來取資料將會是不對的資料,因此在DMA存取之前,一定要讓CPU的cache單元做一次sync的動作,將若有還有cache中的資料,真的寫入記憶體中,如此一來DMA將可以取得正確資料。換一個簡單的說法,就是週邊元件要送資料時必須對cache做sync的動作,確認最後的資料是有寫入記憶體。
另一個資料方向就是當週邊元件收到資料時,將會由DMA直接寫入記憶體,完作接收後,週邊元件會產生一個中斷通知CPU來讀取資料,這時的問題是在於存放收到資料,有可能是已被cache住,若CPU不做任何動作而直接來讀取資料,一樣會產生資料不正確的情形,這時在CPU讀取時必須對該區塊作一個cache invalid的動作,讓CPU在讀取該區塊資料,會實際至記憶體做讀取的動作,因此重新讓cache元件,針對該區域重新做cache的動作,如此一來就不會有資料不正確的情形產生。所以說週邊元件在收資料時,必需對cache做invalid的動作。
另外有一個簡單的作法,就是在準備這些記憶體時,就取得讓記憶體是不會做cache的動作,如此一來就不需要做資料同步動作。
Kernel API
我們從以上知道問題,也知道該如何解決,現在的問題是有多少的kernel API可供使用,以下我們就來看:
dma_addr_t dma_map_single(struct device *, void *, size_t, enum dma_data_direction);
void dma_unmap_single(struct device *, dma_addr_t, size_t, enum dma_data_direction);
dma_addr_t dma_map_page(struct device *, struct page *,unsigned long, size_t, enum dma_data_direction);
void dma_unmap_page(struct device *, dma_addr_t, size_t,enum dma_data_direction);
void dma_cache_maint(const void *kaddr, size_t size, int rw);
void dma_cache_maint_page(struct page *page, unsigned long offset,size_t size, int rw);
enum dma_data_direction {
        DMA_BIDIRECTIONAL = 0,
        DMA_TO_DEVICE = 1,
        DMA_FROM_DEVICE = 2,
        DMA_NONE = 3,
};
以上的kernel API主要是針對將一般取得的記憶體(例如由kmalloc所取得),是會被cache的記憶體,將cache中的資料和實體記憶體資料做同步的動作,其DMA_TO_DEVICE就是做invalidate的動作,而DMA_FROM_DEVICE就是synchronize的動作,這些API必需要在呼叫DMA之前完成。
void *dmam_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t gfp);
void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,dma_addr_t dma_handle); 
以上的kernel API是在要allocate記憶體時就取得不會被cache的記憶體,如此一來對這塊記憶體的存取時就不需要前面所提的動作 – invalidate or synchronize,另外也可取得該記憶體的實體記憶體,這個實體記憶體將會給DMA controller所使用。