天天看點

Linux下的序列槽總線驅動(一)

    版權所有,轉載請說明轉自  http://my.csdn.net/weiqing1981127

一.系統理論

PC機南橋的LPC總線(Low Pin Count并行總線,代替以前的ISA總線)上挂接了一個超級I/O子產品,而UART是這個超級子產品晶片組的一部分,這個UART通過RS232線程轉換與串行端口相連。與RS232不同,RS485并不是标準的PC接口,但在嵌入式領域,會為了可靠通信而使用RS485,RS485使用差分信号,是以其傳輸距離可以達到數百米,而RS232傳輸距離僅數幾米,在處理器一端,RS485接口是半雙工的UART操作。

Linux包含如下幾種終端裝置:串行端口終端(/dev/ttySn)、僞終端(/dev/pty)、控制終端(/dev/tty)、控制台終端(/dev/ttyn,/dev/conslole)。串行端口終端使用的裝置名為/dev/ttyS0,/dev/ttyS1等,對應的裝置号為(4,0),(4,1)。通過檢視/proc/tty/drivers檔案可以知道什麼類型的tty裝置存在以及什麼驅動被加載到核心,這個檔案包括一個目前存在的不同tty驅動的清單,包括驅動名,預設的節點名,驅動的主編号,驅動的次編号範圍,以及tty驅動的類型。

I/O系統調用是從帶有線路規程的TTY I/O核心開始,然後通過TTY層,最後到達UART驅動層。主要涉及序列槽核心配置、UART層核心代碼、TTY層核心代碼、線路規程核心代碼、序列槽測試代碼五個部分。

二.序列槽核心配置

對于Mini2440序列槽驅動,我想從配置開始講起。在核心中Kconfig必須完成一層層調用,如果沒有在上一個Kconfig中調用該層Kconfig,那麼該層Kconfig中的内容不會在此出現。這種情況下,隻有當該層的Kconfig被其他層調用,該層Kconfig中的内容才會被顯示。是以我們找找drivers/serial/Kconfig在哪裡被調用的呢?

在/drivers/char/kconfig中可以看到一行代碼source "drivers/serial/Kconfig",那我們就到drivers/serial/Kconfig下看看

Samsung SoC serial support對應于samsung.o  serial_core.o

config SERIAL_SAMSUNG

       tristate "Samsung SoC serial support"

       depends on ARM && PLAT_S3C

       select SERIAL_CORE

Support for console on Samsung SoC serial port對應于控制台驅動

Samsung S3C2440/S3C2442 Serial port support對應于s3c2440.o

在/drivers/char/Makefile中可以看到

obj-y       += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.o tty_port.o

我們知道tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o已編入核心

自此,我們知道關于序列槽驅動,我們核心中被編譯了s3c2440.o   samsung.o  serial_core.o tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o

我們對此進行分類,屬于UART層的是s3c2440.o   samsung.o;屬于TTY層的是serial_core.o;屬于線路規程的是tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o 。

好了,對于序列槽的地圖我們已經分析好了,那我們就按照UART層,TTY層,線路規程一個個的逛逛吧。

三.UART層核心代碼

我們先看看samsung.o的init代碼吧,這裡面完成了uart_driver的注冊

static int __init s3c24xx_serial_modinit(void)

{

       int ret;

       ret = uart_register_driver(&s3c24xx_uart_drv);  //注冊uart_driver

       if (ret < 0) {

              printk(KERN_ERR "failed to register UART driver\n");

              return -1;

       }

       return 0;

}

static struct uart_driver s3c24xx_uart_drv = {

       .owner           = THIS_MODULE,                           

       .dev_name      = "s3c2410_serial",                            //裝置名

       .nr          = CONFIG_SERIAL_SAMSUNG_UARTS,  //UART端口個數

       .cons             = S3C24XX_SERIAL_CONSOLE,  //指向控制台結構

       .driver_name   = S3C24XX_SERIAL_NAME,    //驅動的名字

       .major            = S3C24XX_SERIAL_MAJOR,   //序列槽主裝置号

       .minor            = S3C24XX_SERIAL_MINOR,   //序列槽次裝置号

};

我們關注下上面這個結構體中一個成員S3C24XX_SERIAL_CONSOLE

#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console

static struct console s3c24xx_serial_console = {

       .name             = S3C24XX_SERIAL_NAME,

       .device           = uart_console_device,

       .flags             = CON_PRINTBUFFER,

       .index             = -1,

       .write             = s3c24xx_serial_console_write,

       .setup             = s3c24xx_serial_console_setup

};

上面是控制台的結構體成員。

對于UART驅動,我們除了需要注冊uart_driver外,還需要注冊端口,我們看看s3c2440.o。

這個檔案裡面注冊了一個平台裝置,其中平台裝置的探測函數最終調用了samsung.o中的s3c24xx_serial_probe函數。

int s3c24xx_serial_probe(struct platform_device *dev,

                      struct s3c24xx_uart_info *info)

{

       struct s3c24xx_uart_port *ourport;

       int ret;

       dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);

       ourport = &s3c24xx_serial_ports[probe_index]; //選擇s3c24xx_uart_port

       probe_index++; //索引号自增

       dbg("%s: initialising port %p...\n", __func__, ourport);

       ret = s3c24xx_serial_init_port(ourport, info, dev);  //初始化序列槽

       if (ret < 0)

              goto probe_err;

       dbg("%s: adding port\n", __func__);

       uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); //向核心注冊端口

       platform_set_drvdata(dev, &ourport->port); //設定私有資料

       ret = device_create_file(&dev->dev, &dev_attr_clock_source); //添加裝置屬性

       if (ret < 0)

              printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);

       ret = s3c24xx_serial_cpufreq_register(ourport);  //注冊CPU頻率

       if (ret < 0)

              dev_err(&dev->dev, "failed to add cpufreq notifier\n");

       return 0;

 probe_err:

       return ret;

}

通過上面的函數,我們發現在UART層,我們調用了uart_add_one_port函數完成端口的添加,我們來看看添加了什麼端口呢?

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {

       [0] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),

                     .iotype            = UPIO_MEM,    

                     .irq         = IRQ_S3CUART_RX0,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //對UART操作的函數

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 0,

              }

       },

       [1] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX1,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,   //對UART操作的函數

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 1,

              }

       },

#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

       [2] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX2,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //對UART操作的函數

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 2,

              }

       },

#endif

#if CONFIG_SERIAL_SAMSUNG_UARTS > 3

       [3] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX3,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //對UART操作的函數

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 3,

              }

       }

#endif

};

在端口的定義中,我們知道s3c24xx_uart_port中定義了一個uart_port結構體,繼續跟蹤對UART的操作函數

static struct uart_ops s3c24xx_serial_ops = {

       .pm         = s3c24xx_serial_pm,

       .tx_empty       = s3c24xx_serial_tx_empty,  //發送是否忙

       .get_mctrl       = s3c24xx_serial_get_mctrl,

       .set_mctrl       = s3c24xx_serial_set_mctrl,

       .stop_tx   = s3c24xx_serial_stop_tx,   

       .start_tx  = s3c24xx_serial_start_tx,   //類似于write

       .stop_rx  = s3c24xx_serial_stop_rx,  

       .enable_ms     = s3c24xx_serial_enable_ms,

       .break_ctl       = s3c24xx_serial_break_ctl,

       .startup   = s3c24xx_serial_startup,  //類似于open

       .shutdown      = s3c24xx_serial_shutdown,  //類似于close

       .set_termios    = s3c24xx_serial_set_termios,  //設定線路規程

       .type              = s3c24xx_serial_type,

       .release_port   = s3c24xx_serial_release_port,  //釋放端口資源

       .request_port  = s3c24xx_serial_request_port,  //申請端口資源

       .config_port    = s3c24xx_serial_config_port,  //配置端口

       .verify_port    = s3c24xx_serial_verify_port,

};

對于上述uart_ops函數,我們需要自己去實作uart層的具體操作。

我們在UART層主要涉及uart_driver,uart_port,uart_ops三個結構體,并調用tty層的uart_register_driver和uart_add_one_port完成驅動和端口的注冊,UART層具體操作函數需要使用者自己設計。

好了,總結下UART驅動層需要完成的任務:

其一,定義uart_driver、uart_ops、uart_port等結構體的執行個體并在适當的地方根據具體硬體和驅動的情況初始化它們,當然具體裝置XXX的驅動可以将這些結構套在新定義的XXX_uart_driver、XXX_uart_ops、XXX_uart_port之内。

其二,在子產品初始化時調用uart_register_driver()和uart_add_one_port()以注冊UART驅動并添加端口,在子產品解除安裝時調用uart_unregister_driver()和uart_remove_one_port()以登出UART驅動并移除端口。

其三,根據具體硬體的datasheet實作uart_ops中的成員函數,這些函數的實作成為UART驅動的主體工作。

繼續閱讀