(1)并口移植
S3C2440共有3个并口,在SMDK2440平台上并口0和并口1都作为普通并口使用,并口2工作在红外收发模式。TQ2440开发板将它们都作为普通并口,目前所须要的只有并口0,作为控制终端linux vi 命令,所以此处不作更改。
在文件linux/arch/arm/plat-s3c24xx/devs.c中定义了三个并口的硬件资源。
staticstructresources3c2410_uart0_resource[]={
………………………………
};
staticstructresources3c2410_uart1_resource[]={
………………………………
};
staticstructresources3c2410_uart2_resource[]={
………………………………
};
在文件linux/arch/arm/plat-samsung/dev-uart.c中定义了每位并口对应的平台设备。
staticstructplatform_devices3c24xx_uart_device0={
.id=0,
};
staticstructplatform_devices3c24xx_uart_device1={
.id=1,
};
staticstructplatform_devices3c24xx_uart_device2={
.id=2,
};
在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有并口一些寄存器的初始化配置。
staticstructs3c2410_uartcfgsmdk2440_uartcfgs[]__initdata={
[0]={
…………………………
},
[1]={
…………………………
},
/*IRport*/
[2]={
…………………………
};
在文件linux/arch/arm/mach-s3c2440/mach-smdk2440.c上将调用函数
s3c24xx_init_uarts()最终将里面的硬件资源,初始化配置,平台设备整合到一起。
在文件linux/arch/arm/plat-s3c/init.c中有
staticint__inits3c_arch_init(void)
………………………………
ret=platform_add_devices(s3c24xx_uart_devs,nr_uarts);
returnret;
这个函数将并口所对应的平台设备添加到了内核。
(2)并口设备驱动原理探讨
我觉得任何设备在linux中的实现就“两条线”。一是设备模型的构建,二是读写数据流。并口驱动也是这样。
并口设备模型构建:
并口设备驱动的核心结构体在文件linux/drivers/serial/samsuing.c中如下
staticstructuart_drivers3c24xx_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,
};
并口驱动的注册
staticint__inits3c24xx_serial_modinit(void)
………………………………
ret=uart_register_driver(&s3c24xx_uart_drv);
………………………………
并口驱动似乎是一个典型的tty驱动
intuart_register_driver(structuart_driver*drv)
………………………………
//每一个端口对应一个state
drv->state=kzalloc(sizeof(structuart_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中
inttty_register_driver(structtty_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中实现
staticconststructfile_operationstty_fops={
.llseek=no_llseek,
.read=tty_read,
.write=tty_write,
………………………………
.open=tty_open,
………………………………
};
到此并口的驱动作为tty_driver被注册到了内核。上面提及并口的每一个端口都是作为平台设备被添加到内核的。这么这种平台设备就对应着有它们的平台设备驱动。在文件linux/drivers/serial/s3c2440.c中有:
staticstructplatform_drivers3c2440_serial_driver={
.probe=s3c2440_serial_probe,
.remove=__devexit_p(s3c24xx_serial_remove),
.driver={
.name="s3c2440-uart",
.owner=THIS_MODULE,
},
};
当其驱动与设备匹配时都会调用他的侦测函数
staticints3c2440_serial_probe(structplatform_device*dev)
returns3c24xx_serial_probe(dev,&s3c2440_uart_inf);
每一个端口都有一个描述它的结构体s3c24xx_uart_port在文件linux/drivers/serial/samsuing.c
staticstructs3c24xx_uart_ports3c24xx_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()来完成的
ints3c24xx_serial_probe(structplatform_device*dev,
structs3c24xx_uart_info*info)
………………………………
//依据平台设备提供的硬件资源等信息初始化端口描述结构体中的一些数组
ret=s3c24xx_serial_init_port(ourport,info,dev);
//上面注册了并口驱动,这儿便要注册并口设备
uart_add_one_port(&s3c24xx_uart_drv,&ourport->port);
………………………………
intuart_add_one_port(structuart_driver*drv,structuart_port*uport)
………………………………
//上面说并口驱动是tty_driverlinux系统下载,这儿可以看见并口设备似乎是tty_dev
tty_dev=tty_register_device(drv->tty_driver,uport->line,uport->dev);
………………………………
并口数据流剖析:
在并口设备模型构建中提及了三个操作函数集,uart_ops,tty_fopslinux 串口 驱动,s3c24xx_serial_ops数据的流动便是这种操作函数间的调用,这种调用关系如下:
在对一个设备进行其他操作之前必须先打开它,linux/drivers/char/tty_io.c
staticconststructfile_operationstty_fops={
………………………………
.open=tty_open,
………………………………
};
staticinttty_open(structinode*inode,structfile*filp)
………………………………
dev_tdevice=inode->i_rdev;
………………………………
driver=get_tty_driver(device,&index);//按照端口设备号获取它的索顿号
………………………………
if(tty){
………………………………
}else
tty=tty_init_dev(driver,index,0);//创建一个tty_struct并初始化
………………………………
structtty_struct*tty_init_dev(structtty_driver*driver,intidx,intfirst_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);
voidinitialize_tty_struct(structtty_struct*tty,structtty_driver*driver,intidx)
………………………………
//获取线路规程操作函数集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就是structtty_operationsuart_ops
tty->index=idx;//idx就是该tty_struct对应端口的索顿号
tty_line_name(driver,idx,tty->name);
端口设备打开以后就可以进行读写操作了,这儿只讨论数据的读取,在文件linux/drivers/char/tty_io.c中,
staticconststructfile_operationstty_fops={
………………………………
.read=tty_read,
………………………………
};
staticssize_ttty_read(structfile*file,char__user*buf,size_tcount,
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中:
structtty_ldisc_opstty_ldisc_N_TTY={
………………………………
.open=n_tty_open,
………………………………
.read=n_tty_read,
………………………………
};
staticssize_tn_tty_read(structtty_struct*tty,structfile*file,
unsignedchar__user*buf,size_tnr)
………………………………
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中,并口数据接收中断处理函数实现如下:
这是并口最原始的数据流入的地方
staticirqreturn_ts3c24xx_serial_rx_chars(intirq,void*dev_id)
………………………………
while(max_count-->0){
………………………………
ch=rd_regb(port,S3C2410_URXH);//从数据接收缓存中读取一个数据
………………………………
flag=TTY_NORMAL;//普通数据,还可能是其他数据类型在此不做讨论
………………………………
/*
下边函数做的最主要工作是这样
structtty_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:
returnIRQ_HANDLED;
voidtty_flip_buffer_push(structtty_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。
staticvoidflush_to_ldisc(structwork_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中实现
structtty_ldisc_opstty_ldisc_N_TTY={
………………………………
.receive_buf=n_tty_receive_buf,
………………………………
};
staticvoidn_tty_receive_buf(structtty_struct*tty,constunsignedchar*cp,char*fp,intcount)
………………………………
//如今可以看见缓冲区tty->read_buf中数据的来历了。
if(tty->real_raw){
//假如设置了tty->real_raw将前面提到的些传入数据批量拷贝到tty->read_head中。
//对环型缓存区的数据拷贝须要进行两次,第一次拷贝从当前位置考到缓存的末尾linux 串口 驱动,假如还//有没考完的数据并且缓存区开始出处还有剩余空间,就把没考完的数据考到开始的剩余空
//间中。
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中。