linux 串口驱动解析之2440
對于串口驅動的移植準備自己分析一下源代碼的,但是發現自己好多地方都只知道一些皮毛,不明白其中的道理,所以我上網搜的時候發現有好多人寫了很多很好的文章了,下面我轉載的這篇就非常不錯,一個困惱我好久的問題是驅動代碼中只是注冊了platform驅動,而platform設備注冊在哪里?這個問題困惱我好久,源代碼中一直沒找到,下面文章就解決了這個問題。當然文章中詳細了講述了很多細節的知識。
原文地址???http://chxxxyg.blog.163.com/blog/static/150281193201082044140894/
(1)串口移植
S3C2440共有3個串口,在SMDK2440平臺上串口0和串口1都作為普通串口使用,串口2工作在紅外收發模式。TQ2440開發板將它們都作為普通串口,目前所需要的只有串口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中將調用函數
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;
}
這個函數將串口所對應的平臺設備添加到了內核。
(2)串口設備驅動原理淺析
我認為任何設備在linux中的實現就“兩條線”。一是設備模型的建立,二是讀寫數據流。串口驅動也是這樣。
串口設備模型建立:
串口設備驅動的核心結構體在文件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驅動對應的操作函數集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);
………………………………
}
它所關聯的操作函數集tty_fops在文件linux/drivers/char/tty_io.c中實現
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,
},
};
當其驅動與設備匹配時就會調用他的探測函數
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,?//該端口的操作函數集
.flags =?UPF_BOOT_AUTOCONF,
.line =?0,?//端口編號
}
},
………………………………
}
上面探測函數的具體工作是函數s3c24xx_serial_probe()來完成的
int?s3c24xx_serial_probe(struct?platform_device?*dev,
?struct?s3c24xx_uart_info?*info)
{
………………………………
//根據平臺設備提供的硬件資源等信息初始化端口描述結構體中的一些字段
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);
………………………………
}
串口數據流分析:
????在串口設備模型建立中提到了三個操作函數集,uart_ops?,tty_fops,s3c24xx_serial_ops數據的流動便是這些操作函數間的調用,這些調用關系如下:
?
在對一個設備進行其他操作之前必須先打開它,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);?//根據端口設備號獲取它的索引號
………………………………
if?(tty)?{
………………………………
}?else
tty?=?tty_init_dev(driver,?index,?0);?//創建一個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);
………………………………
/*
下面函數主要做的就是調用線路規程的打開函數ld->ops->open(tty)。
在這個打開函數中分配了一個重要的數據緩存
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)
{
………………………………
//獲取線路規程操作函數集tty_ldisc_N_TTY,并做這樣的工作tty->ldisc?=?ld;
tty_ldisc_init(tty);
………………………………
/*
下面函數的主要工作是INIT_DELAYED_WORK(&tty->buf.work,?flush_to_ldisc);
初始化一個延時tty->buf.work?并關聯一個處理函數flush_to_ldisc(),這個函數將在
數據讀取的時候用到。
*/
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);
}
端口設備打開之后就可以進行讀寫操作了,這里只討論數據的讀取,在文件?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);?//獲取線路規程結構體
if?(ld->ops->read)?//調用線路規程操作函數集中的n_tty_read()函數
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[]中逐個數據讀取,并判斷讀出的每一個數//據的正確性或是其他數據類型等。
eol?=?test_and_clear_bit(tty->read_tail,tty->read_flags);
c?=?tty->read_buf[tty->read_tail];
………………………………
}?else?{
………………………………
//如果沒有設置tty->icanon就從緩存tty->read_buf[]中批量讀取數據,之所以要進行兩次讀
//取是因為緩存tty->read_buf[]是個環形緩存
uncopied?=?copy_from_read_buf(tty,?&b,?&nr);
uncopied?+=?copy_from_read_buf(tty,?&b,?&nr);
………………………………
}
}
………………………………
}
用戶空間是從緩存tty->read_buf[]中讀取數據讀的,那么緩存tty->read_buf[]中的數據有是從那里來的呢?分析如下:
回到文件?linux/drivers/serial/samsuing.c中,串口數據接收中斷處理函數實現如下:
這是串口最原始的數據流入的地方
static?irqreturn_t??s3c24xx_serial_rx_chars(int?irq,?void?*dev_id)
{
………………………………
while?(max_count--?>?0)?{
………………………………
ch?=?rd_regb(port,?S3C2410_URXH);?//從數據接收緩存中讀取一個數據
………………………………
flag?=?TTY_NORMAL;?//普通數據,還可能是其他數據類型在此不做討論
………………………………
/*
下面函數做的最主要工作是這樣
struct?tty_buffer?*tb?=?tty->buf.tail;
tb->flag_buf_ptr[tb->used]?=?flag;
tb->char_buf_ptr[tb->used++]?=?ch;
將讀取的數據和該數據對應標志插入?tty->buf。
*/
uart_insert_char(port,?uerstat,?S3C2410_UERSTAT_OVERRUN,?ch,?flag);
}
tty_flip_buffer_push(tty);?//將讀取到的max_count個數據向上層傳遞。
?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的處理函數也是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;
………………………………
//剛才在串口接收中斷處理函數中,將接收到的數據和數據標志存到tty->buf中,現在將
//這些數據和標志用char_buf?和flag_buf指向進一步向上傳遞。
disc->ops->receive_buf(tty,?char_buf,flag_buf,?count);
spin_lock_irqsave(&tty->buf.lock,?flags);
}
}
上面調用的函數disc->ops->receive_buf在文件linux/drivers/char/N_tty.c中實現
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)
{
………………………………
//現在可以看到緩沖區tty->read_buf?中數據的由來了。
if?(tty->real_raw)?{
//如果設置了tty->real_raw將上面講到的些傳入數據批量拷貝到tty->read_head中。
//對環形緩存區的數據拷貝需要進行兩次,第一次拷貝從當前位置考到緩存的末尾,如果還//有沒考完的數據而且緩存區開始出處還有剩余空間,就把沒考完的數據考到開始的剩余空
//間中。
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,就根據傳入數據標志分類獲取數據。
………………………………
}
………………………………
}
………………………………
}
到此,數據讀取的整個過程就結束了。可以看出數據讀取可以分為兩個階段,一個階段是上層函數從環形緩存區tty->read_buf?讀取數據,第二階段是底層函數將接收的數據考到環形緩存區tty->read_buf?中。
總結
以上是生活随笔為你收集整理的linux 串口驱动解析之2440的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java调用jrtplib,jrtpli
- 下一篇: c语言社交网络,图论在社交网络中的应用研