對于串口驅動的移植準備自己分析一下源代碼的,但是發(fā)現(xiàn)自己好多地方都只知道一
些皮毛,不明白其中的道理,所以我上網(wǎng)搜的時候發(fā)現(xiàn)有好多人寫了很多很好的文章了,下面我轉載的這篇就非常不錯,一個困惱我好久的問題是驅動代碼中只是注
冊了platform驅動,而platform設備注冊在哪里?這個問題困惱我好久,源代碼中一直沒找到,下面文章就解決了這個問題。當然文章中詳細了講
述了很多細節(jié)的知識。
原文地址 http://chxxxyg.blog.163.com/blog/static/150281193201082044140894/
(1)串口移植
S3C2440共有3個串口,在SMDK2440平臺上串口0和串口1都作為普通串口使用,串口2工作在紅外收發(fā)模式。TQ2440開發(fā)板將它們都作為普通串口,目前所需要的只有串口0,作為控制終端,所以此處不作修改。
在文件 linux/arch/arm/plat-s3c24xx/devs.c中定義了三個串口的硬件資源。
static struct resource s3c2410_uart0_resource[] = {
………………………………
};
static struct resource s3c2410_uart1_resource[] = {
………………………………
};
static struct resource s3c2410_uart2_resource[] = {
………………………………
};
在文件linux/arch/arm/plat-samsung/dev-uart.c中定義了每個串口對應的平臺設備。
static struct platform_device s3c24xx_uart_device0 = {
.id = 0,
};
static struct platform_device s3c24xx_uart_device1 = {
.id = 1,
};
static struct platform_device s3c24xx_uart_device2 = {
.id = 2,
};
在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有串口一些寄存器的初始化配置。
static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {
[0] = {
…………………………
},
[1] = {
…………………………
},
/* IR port */
[2] = {
…………………………
}
};
在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中將調用函數(shù)
s3c24xx_init_uarts()最終將上面的硬件資源,初始化配置,平臺設備整合到一起。
在文件 linux/arch/arm/plat-s3c/init.c中有
static int __init s3c_arch_init(void)
{
………………………………
ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
return ret;
}
這個函數(shù)將串口所對應的平臺設備添加到了內核。
(2)串口設備驅動原理淺析
我認為任何設備在linux中的實現(xiàn)就“兩條線”。一是設備模型的建立,二是讀寫數(shù)據(jù)流。串口驅動也是這樣。
串口設備模型建立:
串口設備驅動的核心結構體在文件linux/drivers/serial/samsuing.c中如下
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,
};
串口驅動的注冊
static int __init s3c24xx_serial_modinit(void)
{
………………………………
ret = uart_register_driver(&s3c24xx_uart_drv);
………………………………
}
串口驅動其實是一個典型的tty驅動
int uart_register_driver(struct uart_driver *drv)
{
………………………………
//每一個端口對應一個state
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
………………………………
normal = alloc_tty_driver(drv->nr); //分配該串口驅動對應的tty_driver
………………………………
drv->tty_driver = normal; //讓drv->tty_driver字段指向這個tty_driver
………………………………
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
………………………………
//設置該tty驅動對應的操作函數(shù)集tty_operations (linux/drivers/char/core.c)
tty_set_operations(normal, &uart_ops);
………………………………
retval = tty_register_driver(normal); //將tty驅動注冊到內核
………………………………
}
其實tty驅動的本質是一個字符設備,在文件 linux/drivers/char/tty_io.c中
int tty_register_driver(struct tty_driver *driver)
{
………………………………
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
………………………………
}
它所關聯(lián)的操作函數(shù)集tty_fops在文件linux/drivers/char/tty_io.c中實現(xiàn)
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
………………………………
.open = tty_open,
………………………………
};
到此串口的驅動作為tty_driver被注冊到了內核。前面提到串口的每一個端口都是作為平臺設備被添加到內核的。那么這些平臺設備就對應著有它們的平臺設備驅動。在文件linux/drivers/serial/s3c2440.c中有:
static struct platform_driver s3c2440_serial_driver = {
.probe = s3c2440_serial_probe,
.remove = __devexit_p(s3c24xx_serial_remove),
.driver = {
.name = "s3c2440-uart",
.owner = THIS_MODULE,
},
};
當其驅動與設備匹配時就會調用他的探測函數(shù)
static int s3c2440_serial_probe(struct platform_device *dev)
{
return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
}
每一個端口都有一個描述它的結構體s3c24xx_uart_port 在 文件linux/drivers/serial/samsuing.c
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, //該端口的操作函數(shù)集
.flags = UPF_BOOT_AUTOCONF,
.line = 0, //端口編號
}
},
………………………………
}
上面探測函數(shù)的具體工作是函數(shù)s3c24xx_serial_probe()來完成的
int s3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
{
………………………………
//根據(jù)平臺設備提供的硬件資源等信息初始化端口描述結構體中的一些字段
ret = s3c24xx_serial_init_port(ourport, info, dev);
//前面注冊了串口驅動,這里便要注冊串口設備
uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
………………………………
}
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
………………………………
//前面說串口驅動是tty_driver,這里可以看到串口設備其實是tty_dev
tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
………………………………
}
串口數(shù)據(jù)流分析:
在串口設備模型建立中提到了三個操作函數(shù)集,uart_ops ,tty_fops,s3c24xx_serial_ops數(shù)據(jù)的流動便是這些操作函數(shù)間的調用,這些調用關系如下:
在對一個設備進行其他操作之前必須先打開它,linux/drivers/char/tty_io.c
static const struct file_operations tty_fops = {
………………………………
.open = tty_open,
………………………………
};
static int tty_open(struct inode *inode, struct file *filp)
{
………………………………
dev_t device = inode->i_rdev;
………………………………
driver = get_tty_driver(device, &index); //根據(jù)端口設備號獲取它的索引號
………………………………
if (tty) {
………………………………
} else
tty = tty_init_dev(driver, index, 0); //創(chuàng)建一個tty_struct 并初始化
………………………………
}
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)
{
………………………………
tty = alloc_tty_struct(); //分配一個tty_struct結構
//一些字段的初始化,
initialize_tty_struct(tty, driver, idx);
//完成的主要工作是driver->ttys[idx] = tty;
retval = tty_driver_install_tty(driver, tty);
………………………………
/*
下面函數(shù)主要做的就是調用線路規(guī)程的打開函數(shù)ld->ops->open(tty)。
在這個打開函數(shù)中分配了一個重要的數(shù)據(jù)緩存
tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
*/
retval = tty_ldisc_setup(tty, tty->link);
}
void initialize_tty_struct(struct tty_struct *tty,struct tty_driver *driver, int idx)
{
………………………………
//獲取線路規(guī)程操作函數(shù)集tty_ldisc_N_TTY,并做這樣的工作tty->ldisc = ld;
tty_ldisc_init(tty);
………………………………
/*
下面函數(shù)的主要工作是INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);
初始化一個延時tty->buf.work 并關聯(lián)一個處理函數(shù)flush_to_ldisc(),這個函數(shù)將在
數(shù)據(jù)讀取的時候用到。
*/
tty_buffer_init(tty);
………………………………
tty->driver = driver;
tty->ops = driver->ops; //這里的ops就是struct tty_operations uart_ops
tty->index = idx; //idx就是該tty_struct對應端口的索引號
tty_line_name(driver, idx, tty->name);
}
端口設備打開之后就可以進行讀寫操作了,這里只討論數(shù)據(jù)的讀取,在文件 linux/drivers/char/tty_io.c中,
static const struct file_operations tty_fops = {
………………………………
.read = tty_read,
………………………………
};
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
………………………………
ld = tty_ldisc_ref_wait(tty); //獲取線路規(guī)程結構體
if (ld->ops->read) //調用線路規(guī)程操作函數(shù)集中的n_tty_read()函數(shù)
i = (ld->ops->read)(tty, file, buf, count);
else
………………………………
}
在linux/drivers/char/N_tty.c中:
struct tty_ldisc_ops tty_ldisc_N_TTY = {
………………………………
.open = n_tty_open,
………………………………
.read = n_tty_read,
………………………………
};
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
………………………………
while (nr) {
………………………………
if (tty->icanon && !L_EXTPROC(tty)) {
//如果設置了tty->icanon 就從緩存tty->read_buf[]中逐個數(shù)據(jù)讀取,并判斷讀出的每一個數(shù)//據(jù)的正確性或是其他數(shù)據(jù)類型等。
eol = test_and_clear_bit(tty->read_tail,tty->read_flags);
c = tty->read_buf[tty->read_tail];
………………………………
} else {
………………………………
//如果沒有設置tty->icanon就從緩存tty->read_buf[]中批量讀取數(shù)據(jù),之所以要進行兩次讀
//取是因為緩存tty->read_buf[]是個環(huán)形緩存
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
………………………………
}
}
………………………………
}
用戶空間是從緩存tty->read_buf[]中讀取數(shù)據(jù)讀的,那么緩存tty->read_buf[]中的數(shù)據(jù)有是從那里來的呢?分析如下:
回到文件 linux/drivers/serial/samsuing.c中,串口數(shù)據(jù)接收中斷處理函數(shù)實現(xiàn)如下:
這是串口最原始的數(shù)據(jù)流入的地方
static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
………………………………
while (max_count-- > 0) {
………………………………
ch = rd_regb(port, S3C2410_URXH); //從數(shù)據(jù)接收緩存中讀取一個數(shù)據(jù)
………………………………
flag = TTY_NORMAL; //普通數(shù)據(jù),還可能是其他數(shù)據(jù)類型在此不做討論
………………………………
/*
下面函數(shù)做的最主要工作是這樣
struct tty_buffer *tb = tty->buf.tail;
tb->flag_buf_ptr[tb->used] = flag;
tb->char_buf_ptr[tb->used++] = ch;
將讀取的數(shù)據(jù)和該數(shù)據(jù)對應標志插入 tty->buf。
*/
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
}
tty_flip_buffer_push(tty); //將讀取到的max_count個數(shù)據(jù)向上層傳遞。
out:
return IRQ_HANDLED;
}
void tty_flip_buffer_push(struct tty_struct *tty)
{
………………………………
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work.work);
else
schedule_delayed_work(&tty->buf.work, 1);
//這里這個延時work在上面串口設備打開中提到過,該work的處理函數(shù)也是flush_to_ldisc。
}
static void flush_to_ldisc(struct work_struct *work)
{
………………………………
while ((head = tty->buf.head) != NULL) {
………………………………
char_buf = head->char_buf_ptr + head->read;
flag_buf = head->flag_buf_ptr + head->read;
………………………………
//剛才在串口接收中斷處理函數(shù)中,將接收到的數(shù)據(jù)和數(shù)據(jù)標志存到tty->buf中,現(xiàn)在將
//這些數(shù)據(jù)和標志用char_buf 和flag_buf指向進一步向上傳遞。
disc->ops->receive_buf(tty, char_buf,flag_buf, count);
spin_lock_irqsave(&tty->buf.lock, flags);
}
}
上面調用的函數(shù)disc->ops->receive_buf在文件linux/drivers/char/N_tty.c中實現(xiàn)
struct tty_ldisc_ops tty_ldisc_N_TTY = {
………………………………
.receive_buf = n_tty_receive_buf,
………………………………
};
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
{
………………………………
//現(xiàn)在可以看到緩沖區(qū)tty->read_buf 中數(shù)據(jù)的由來了。
if (tty->real_raw) {
//如果設置了tty->real_raw將上面講到的些傳入數(shù)據(jù)批量拷貝到tty->read_head中。
//對環(huán)形緩存區(qū)的數(shù)據(jù)拷貝需要進行兩次,第一次拷貝從當前位置考到緩存的末尾,如果還//有沒考完的數(shù)據(jù)而且緩存區(qū)開始出處還有剩余空間,就把沒考完的數(shù)據(jù)考到開始的剩余空
//間中。
spin_lock_irqsave(&tty->read_lock, cpuflags);
i = min(N_TTY_BUF_SIZE - tty->read_cnt,N_TTY_BUF_SIZE - tty->read_head);
i = min(count, i);
memcpy(tty->read_buf + tty->read_head, cp, i);
tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
tty->read_cnt += i;
cp += i;
count -= i;
i = min(N_TTY_BUF_SIZE - tty->read_cnt,
N_TTY_BUF_SIZE - tty->read_head);
i = min(count, i);
memcpy(tty->read_buf + tty->read_head, cp, i);
tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
tty->read_cnt += i;
spin_unlock_irqrestore(&tty->read_lock, cpuflags);
} else {
for (i = count, p = cp, f = fp; i; i--, p++) {
//如果沒有設置tty->real_raw,就根據(jù)傳入數(shù)據(jù)標志分類獲取數(shù)據(jù)。
………………………………
}
………………………………
}
………………………………
}
到此,數(shù)據(jù)讀取的整個過程就結束了。可以看出數(shù)據(jù)讀取可以分為兩個階段,一個階段是上層函數(shù)從環(huán)形緩存區(qū)tty->read_buf 讀取數(shù)據(jù),第二階段是底層函數(shù)將接收的數(shù)據(jù)考到環(huán)形緩存區(qū)tty->read_buf 中。 |