轉:https://www.oipapio.com/cn/article-7191558
只分析串口驅動,和console相關的部分暫時省去。
內核串口部分需要用到tty部分,tty包含了tty內核和tty線路規程。這些是在串口代碼之前初始化。
1.tty_ldisc線路規程的初始化
只要是函數
driver/tty/tty_io.c
console_init()
- void __init console_init(void)
- {
- initcall_t *call;
- /* Setup the default TTY line discipline. */
- tty_ldisc_begin();
- /*
- * set up the console device so that later boot sequences can
- * inform about problems etc..
- */
- call = __con_initcall_start;
- while (call < __con_initcall_end) {
- (*call)();
- call++;
- }
- }
此處和tty相關的就是函數tty_ldisc_begin(),內核通過此函數來初始化tty線路規程的相關操作。
- void tty_ldisc_begin(void)
- {
- /* Setup the default TTY line discipline. */
- (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
- }
tty_ldisc_begin函數原來是對tty_register_ldisc的封裝,從名字就可以看出來這個是對tty線路規程的初始化操作(內核線路規程的簡稱是ldisc)
此處先看函數調用的兩個參數,其中第一個參數N_TTY是一個宏,第二個參數是內核tty線路規程的操作方法集
- #define N_TTY 0
- #define N_SLIP 1
- #define N_MOUSE 2
- struct tty_ldisc_ops tty_ldisc_N_TTY = {
- .magic = TTY_LDISC_MAGIC,
- .name = "n_tty",
- .open = n_tty_open,
- .close = n_tty_close,
- .flush_buffer = n_tty_flush_buffer,
- .chars_in_buffer = n_tty_chars_in_buffer,
- .read = n_tty_read,
- .write = n_tty_write,
- .ioctl = n_tty_ioctl,
- .set_termios = n_tty_set_termios,
- .poll = n_tty_poll,
- .receive_buf = n_tty_receive_buf,
- .write_wakeup = n_tty_write_wakeup
- };
然后具體的tty_register_ldisc函數源碼如下:
- int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
- {
- unsigned long flags;
- int ret = 0;
- if (disc < N_TTY || disc >= NR_LDISCS)
- return -EINVAL;
- spin_lock_irqsave(&tty_ldisc_lock, flags);
- tty_ldiscs[disc] = new_ldisc;
- new_ldisc->num = disc;
- new_ldisc->refcount = 0;
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- return ret;
- }
可以發現tty線路規程的操作很好理解。內核定義一個tty_ldiscs數組,然后根據數組下標來存放對應的線路規程的操作集,而這里的數組下標表示的就是具體的協議,在頭文件中已經通過宏定義好了。例如N_TTY 0。
所以可以發現
ldisc[0] 存放的是N_TTY對應的線路規程操作集
ldisc[1]存放的是N_SLIP對應的線路規程操作集
ldisc[2]存放的就是N_MOUSE對應的線路規程操作集
依次類推。
此處就是ldisc[N_TTY] = tty_ldisc_N_TTY。
然后函數退出,最后返回到console_init()函數。
假如串口作為console,則console_init()會通過剩余的代碼調用s3c_serial_console_init()執行console相關的初始化了。
- while (call < __con_initcall_end) {
- (*call)();
- call++;
- }
由於本文只分析不做console的串口驅動,所以這部分代碼和串口無關。console_init()函數和串口相關的操作就是
之前調用的tty_ldisc_begin函數了。這個tty_ldisc初始化到這一步就暫時放在一邊,待到后面通過open函數中再來調用
此處已注冊的的tty_disc線路規程
2.tty_driver初始化 uart_driver中uart_state的初始化
2.1tty_driver相關部分的初始化
初始化線路規程后,系統開始初始化tty_driver。
而tty_driver需要初始化很多字段例如
tty_driver.owner
tty_driver.driver_name
tty_driver.name
tty_driver.major
tty_driver.minor_start
tty_driver.type
這些字段根據不同的tty_driver需要初始化成不同的參數。
根據LDD3的解釋,內核的tty_driver有三種:控制台、串口、pty。這三種對應着三種不同的tty_driver,而這三種tty_driver對應的上述字段是不同的。
那么這些參數來源哪里呢?來源於程序員的定義,編譯之初確定好這些參數后,內核最后會將其賦值給tty_driver。
那這些參數定義在哪里呢?定義在uart_driver中,這個就是驅動需要修改的一個數據結構。
這就相當於在tty_driver外面就加了一層uart_driver。而uart_driver中的部分字段和tty_driver中的部分字段數值是一樣的。
此處的uart_driver定義如下:
- static struct uart_driver s3c24xx_uart_drv = {
- .owner = THIS_MODULE,
- .dev_name = "s3c2410_serial",
- .nr = CONFIG_SERIAL_SAMSUNG_UARTS,
- .cons = S3C24XX_SERIAL_CONSOLE,
- .driver_name = S3C24XX_SERIAL_NAME,
- .major = S3C24XX_SERIAL_MAJOR,
- .minor = S3C24XX_SERIAL_MINOR,
- };
內核通過
driver/serial/samsung.c文件中的
s3c24xx_modinit()函數來進行tty_driver的初始化,參數就是韋uart_driver結構的s3c24xx_uart_drv。
- static int __init s3c24xx_serial_modinit(void)
- {
- int ret;
- ret = uart_register_driver(&s3c24xx_uart_drv);
- if (ret < 0) {
- printk(KERN_ERR "failed to register UART driver\n");
- return -1;
- }
- return 0;
- }
uart_register_driver函數如下:
- int uart_register_driver(struct uart_driver *drv)
- {
- struct tty_driver *normal;
- int i, retval;
- BUG_ON(drv->state);
- /*
- * Maybe we should be using a slab cache for this, especially if
- * we have a large number of ports to handle.
- */
- drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
- if (!drv->state)
- goto out;
- normal = alloc_tty_driver(drv->nr);
- if (!normal)
- goto out_kfree;
- drv->tty_driver = normal;
- normal->owner = drv->owner;
- normal->driver_name = drv->driver_name;
- normal->name = drv->dev_name;
- normal->major = drv->major;
- normal->minor_start = drv->minor;
- normal->type = TTY_DRIVER_TYPE_SERIAL;
- normal->subtype = SERIAL_TYPE_NORMAL;
- normal->init_termios = tty_std_termios;
- normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
- normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
- normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
- normal->driver_state = drv;
- <span style="color: rgb(255, 0, 0);">tty_set_operations(normal, &uart_ops);</span>
- /*
- * Initialise the UART state(s).
- */
- for (i = 0; i < drv->nr; i++) {
- struct uart_state *state = drv->state + i;
- struct tty_port *port = &state->port;
- tty_port_init(port);
- port->ops = &uart_port_ops;
- port->close_delay = 500; /* .5 seconds */
- port->closing_wait = 30000; /* 30 seconds */
- tasklet_init(&state->tlet, uart_tasklet_action,
- (unsigned long)state);
- }
- <span style="color: rgb(255, 0, 0);">retval = tty_register_driver(normal);</span>
- if (retval >= 0)
- return retval;
- put_tty_driver(normal);
- out_kfree:
- kfree(drv->state);
- out:
- return -ENOMEM;
- }
這個函數就是用來初始化tty_drive的,並且可以看見tty_driver中的字段初始化的數值就是函數傳遞進來的uart_driver中的對應字段。
此外此函數還需要注意的地方都已經標紅。
其中,通過 tty_set_operations(normal, &uart_ops);
指定了tty_driver的操作集tty_operation為uart_ops,假如是其他的tty_driver相信這個ops也是需要修改的。
uart_ops定義如下
- static const struct tty_operations uart_ops = {
- .open = uart_open,
- .close = uart_close,
- .write = uart_write,
- .put_char = uart_put_char,
- .flush_chars = uart_flush_chars,
- .write_room = uart_write_room,
- .chars_in_buffer= uart_chars_in_buffer,
- .flush_buffer = uart_flush_buffer,
- .ioctl = uart_ioctl,
- .throttle = uart_throttle,
- .unthrottle = uart_unthrottle,
- .send_xchar = uart_send_xchar,
- .set_termios = uart_set_termios,
- .set_ldisc = uart_set_ldisc,
- .stop = uart_stop,
- .start = uart_start,
- .hangup = uart_hangup,
- .break_ctl = uart_break_ctl,
- .wait_until_sent= uart_wait_until_sent,
- #ifdef CONFIG_PROC_FS
- .proc_fops = &uart_proc_fops,
- #endif
- .tiocmget = uart_tiocmget,
- .tiocmset = uart_tiocmset,
- .get_icount = uart_get_icount,
- #ifdef CONFIG_CONSOLE_POLL
- .poll_init = uart_poll_init,
- .poll_get_char = uart_poll_get_char,
- .poll_put_char = uart_poll_put_char,
- #endif
- };
然后函數通過
- retval = tty_register_driver(normal);
實現tty_driver的"注冊"。具體的注冊函數如下:
- int tty_register_driver(struct tty_driver *driver)
- {
- int error;
- int i;
- dev_t dev;
- void **p = NULL;
- struct device *d;
- if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
- p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL); //注意這一句,為tty_driver分配了一個tty_struct *指針數組,並且使用的kzalloc方式也就是說。初始化的時候該數組內容是零!這點在tty_open判斷的時候會用到!
- if (!p)
- return -ENOMEM;
- }
- if (!driver->major) {
- error = alloc_chrdev_region(&dev, driver->minor_start,
- driver->num, driver->name);
- if (!error) {
- driver->major = MAJOR(dev);
- driver->minor_start = MINOR(dev);
- }
- } else {
- dev = MKDEV(driver->major, driver->minor_start);
- error = register_chrdev_region(dev, driver->num, driver->name);
- }
- if (error < 0) {
- kfree(p);
- return error;
- }
- if (p) {
- driver->ttys = (struct tty_struct **)p;
- driver->termios = (struct ktermios **)(p + driver->num);
- } else {
- driver->ttys = NULL;
- driver->termios = NULL;
- }
- <span style="color: rgb(255, 0, 0);">cdev_init(&driver->cdev, &tty_fops);</span>
- driver->cdev.owner = driver->owner;
- error = cdev_add(&driver->cdev, dev, driver->num);
- if (error) {
- unregister_chrdev_region(dev, driver->num);
- driver->ttys = NULL;
- driver->termios = NULL;
- kfree(p);
- return error;
- }
- mutex_lock(&tty_mutex);
- <span style="color: rgb(255, 0, 0);">list_add(&driver->tty_drivers, &tty_drivers);</span>
- mutex_unlock(&tty_mutex);
- if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
- for (i = 0; i < driver->num; i++) {
- d = tty_register_device(driver, i, NULL);
- if (IS_ERR(d)) {
- error = PTR_ERR(d);
- goto err;
- }
- }
- }
- proc_tty_register_driver(driver);
- driver->flags |= TTY_DRIVER_INSTALLED;
- return 0;
- err:
- for (i--; i >= 0; i--)
- tty_unregister_device(driver, i);
- mutex_lock(&tty_mutex);
- list_del(&driver->tty_drivers);
- mutex_unlock(&tty_mutex);
- unregister_chrdev_region(dev, driver->num);
- driver->ttys = NULL;
- driver->termios = NULL;
- kfree(p);
- return error;
- }
該“注冊”主要包含了如下工作:
1.根據主設備號、次設備號,注冊一個字符驅動,其操作集為tty_fops
- static const struct file_operations tty_fops = {
- .llseek = no_llseek,
- .read = tty_read,
- .write = tty_write,
- .poll = tty_poll,
- .unlocked_ioctl = tty_ioctl,
- .compat_ioctl = tty_compat_ioctl,
- .open = tty_open,
- .release = tty_release,
- .fasync = tty_fasync,
- };
並在/dev文件夾下生成對應的設備文件節點。
2.將tty_driver添加到tty_driver鏈表tty_drivers中,內核通過維護tty_drivers鏈表來維護tty_driver。
前面提到了內核的tty_driver主要包含三大類console、串口、pty。
完成之后tty_driver初始化也結束,然后進去s3c24xx的uart相關的初始化。
2.uart_driver的部分初始化
此函數關於uart_driver部分的初始化主要是初始化了uart_driver中的uart_state這個成員。
uart_state也是一個結構體,其中包含uart_pot、tty_port兩個成員。而系統中對具體硬件的操作就是通過uart_port.ops
來操作的,這個uart_port需要重點留意下,但是在此函數中就只初始化了tty_port這個成員,uart_port的初始化
在后面的platform_driver.probe函數中初始化,這也正好說明了uart_port是和具體硬件精密相關的。
3.uart_port初始化
uart_port的初始化是通過platform_driver中的probe函數來完成的。如何調用到probe函數的就不說了。
probe函數如下
- static int s3c2440_serial_probe(struct platform_device *dev)
- {
- dbg("s3c2440_serial_probe: dev=%p\n", dev);
- return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
- }
其第二個參數s3c2440_uart_inf如下
- static struct s3c24xx_uart_info s3c2440_uart_inf = {
- .name = "Samsung S3C2440 UART",
- .type = PORT_S3C2440,
- .fifosize = 64,
- .rx_fifomask = S3C2440_UFSTAT_RXMASK,
- .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT,
- .rx_fifofull = S3C2440_UFSTAT_RXFULL,
- .tx_fifofull = S3C2440_UFSTAT_TXFULL,
- .tx_fifomask = S3C2440_UFSTAT_TXMASK,
- .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT,
- .get_clksrc = s3c2440_serial_getsource,
- .set_clksrc = s3c2440_serial_setsource,
- .reset_port = s3c2440_serial_resetport,
- };
先簡單介紹下uart_port結構的初始化。uart_port和系統中具體的硬件對應,每個uart都有自己對應的uart_port。而這個結構體的初始化由點類似於
前面說的tty_driver,並不是直接初始化tty_driver的,而是在tty_driver外面包了一層uart_driver。此處也是的在uart_port外面包了一層struct s3c24xx_uart_port 。
這樣s3c24xx_uart_port除了包含uart_port這個重要結構外還可以存放s3c24xx系列串口的特有信息。
uart_port的初始化信息主要來源於struct s3c24xx_uart_info,如上所示。這些數值會在probe函數中賦值給uart_port用於初始化。
初始化uart_port之后就是調用函數將這個初始化好的uart_port和前面的uart_driver關聯上。因為uart_driver中即有tty相關的tty_driver又有好友uart_port信息的uart_state
,這樣tty和uart就有可能關聯上了。下面從probe函數開始分析具體代碼。
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);
- <span style="color: rgb(255, 0, 0);">ourport = &s3c24xx_serial_ports[probe_index];</span>
- probe_index++;
- dbg("%s: initialising port %p...\n", __func__, ourport);
- <span style="color: rgb(255, 0, 0);">ret = s3c24xx_serial_init_port(ourport, info, dev);</span>
- if (ret < 0)
- goto probe_err;
- dbg("%s: adding port\n", __func__);
- <span style="color: rgb(255, 0, 0);">uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);</span>
- 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);
- if (ret < 0)
- dev_err(&dev->dev, "failed to add cpufreq notifier\n");
- return 0;
- probe_err:
- return ret;
- }
函數總重要的語句全部標紅了。先分析第一句
- <span style="color: rgb(255, 0, 0);">ourport = &s3c24xx_serial_ports[probe_index];</span>
這個主要是從串口數組s3c24xx_serial_ports中選定初始化的串口。這里需要注意的uart_port中的ops,標紅的那句。這里是具體操作s3c24xx串口的操作函數集。
后面具體的write read等函數最終調用的是這里操作集,都是最底層的硬件操作。
- 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,
- <span style="color: rgb(255, 0, 0);">.ops = &s3c24xx_serial_ops,</span>
- .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,
- .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,
- .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,
- .flags = UPF_BOOT_AUTOCONF,
- .line = 3,
- }
- }
- #endif
- };
確定將要初始化的uart后,通過函數
- ret = s3c24xx_serial_init_port(ourport, info, dev);
進行初始化了。根據前面介紹的,他的初始化的信息主要來源於那個s3c2440_uart_inf 結構體中的信息。
uart_port函數具體的初始化就不展開分析了,里面都是些賦值的操作。
接着分析uart_port和uart_driver關聯的操作。這一步是通過函數
- uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
實現的。可以發現這個函數的第一個參數uart_driver是s3c24xx_uart_drv,和前面tty_driver初始化中提到的通過uart_register_driver注冊的uart_driver是一樣的!
這樣uart_driver中的uart_state也就初始化好了,其中uart_state包含了uart_port的信息。這個uart_port是和具體的串口硬件相關的。
總結
到這一步所有的tty和uart初始化的部分算是完成了。但是目前還不能進行read、write操作。
因為還有一個重要的函數tty_open沒有分析,作為tty設備的uart,這個tty_open函數也是非常重要的,下篇分析。
初始化的工作主要是
1.初始化uart_driver結構體,包括初始化uart_driver結構體中的tty_driver uart_state。
2.uart_state部分的初始化主要是初始化其中的uart_port,這部分的初始化在probe函數總完成
修改驅動需要設計的數據結構
1.uart_driver
uart_driver中的數據用於初始化tty_driver
2.s3c24xx_uart_info
用於初始化uart_port
3.s3c24xx_uart_port或者說uart_port。
uart_port的初始化,在s3c24xx的uart驅動中uart_port是內嵌在s3c24xx_uart_port中的
4.uart_ops
最底層的硬件操作