linux设备驱动之串口移植

ME3760_v2驱动移植方式,中兴4G网卡移植 USB串口驱动添加 配置内核将USB driver for GSM and CDMA modems选上 Cd kernel Make menuconfig Devicedrivers->usb surpport->usb serial converter support 选中如下组件 USB driver forGSM and CDMA modems 选中保存后即可编译内核 增加4 阅读详情

对于串口驱动的移植准备自己分析一下源代码的,但是发现自己好多地方都只知道一些皮毛,不明白其中的道理,所以我上网搜的时候发现有好多人写了很多很好的文章了,下面我转载的这篇就非常不错,一个困恼我好久的问题是驱动代码中只是注册了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学习的步骤 1、Linux 基础 安装Linux操作系统 Linux文件系统 Linux常用命令 Linux启动过程详解 熟悉Linux服务能够独立安装Linux操作系统 能够熟练使用Linux系统的基本命令 认识Linux系统的常用服务安装Linux操作系统 Linux基本命令实践 设置Linux环境变量 定制Linux的服务 Shell 编程基础使用vi编辑文件 使用Emacs编辑文件 使用其他编辑器 2、Shell 编程基础 Shell简介 认识后台程序Bash编程熟悉Linux系统下的编辑环境 熟悉Linux 阅读详情

相关推荐

基于S3C2440的嵌入式Linux内核移植及字符设备驱动开发.pdf

基于S3C2440的嵌入式Linux内核移植及字符设备驱动开发.pdf

整理网络_树莓派的串口配置

(注:文章中的内容来自网络,自行测试整理出来的,当前我的树莓派为:Raspberry Pi 3 Model B) 一、解除串口占用 使用putty的时候神奇的发现树莓派竟然是支持serial进行操作。试了试,和在ssh下使用没有多少区别,速度也很快。 树莓派的串口主要也就是被这个占用了,配置释放之后才能使用它来进行串口通信。 在命令行里输入如下指令: sudo raspi-config ...

百里丰子 3010

树莓派安装CH340驱动(USB转串口

为何有此需求 原本树莓派3B+带有两个串口,一个硬件串口,一个mini串口。硬件串口默认给了蓝牙使用,mini串口使用的是CPU的时钟(CPU频率变化不稳定)。因此,蓝牙与硬件串口不可兼得。 我的3B+用一块3.5寸屏幕插在排针上面,导致排针要另外接线非常麻烦。因此,考虑从USB接口再接一个串口使用。 驱动下载官方网址(linux版本) http://www.wch.cn/download/CH...

lwpo2008的博客 7439

树莓派 Linux 下的串口通讯 (C语言)

运行环境:Ubuntu14.04、树莓派3B+ 功能:实现串口数据的收发 main.c文件 #include "com.h" #define BUFFER_SIZE 30 /*最大缓存区*/ char *pstr[]={"NO1\n","NO2\n","NO3\n","NO4\n"}; int main(int argc, char *argv[]) { int fd;...

激萌の小宅的博客 5604

Linux系统下使用串口连接树莓派

1、安装 ckermitsudo apt-get install ckermit2、编辑配置文件 vim ~/.mykermrc,写入以下内容:set line /dev/ttyUSB0set speed 115200set carrier-watch offset handshake noneset flow-control nonerobustset file type binset file...

zhrq95的博客 1752

WK2204 - spi转uart调试记录

WK2204-spi转uart调试记录硬件芯片简介电路设计驱动添加设备树添加驱动调试查看启动加载检查串口通信数据乱码或丢失RS485只能收不能发系统中断响应异常思考 硬件 芯片简介   WK2204(DataSheet)是spi扩展uart的芯片,实现spi扩展4路uart,该芯片扩展的uart通道的具备以下功能特点: 每个通道的波特率、字长、校验格式可以独立设置,最高可以提供2Mbps 的通信速率 每个通道具有收/发独立的256 级FIFO,FIFO 中断可按用户需求进行编程触发方式且具备超时中断功能

u014414101的博客 9757

linux设备驱动串口移植,linux设备驱动之UART移植

对于串口驱动的移植准备自己分析一下源代码的,但是发现自己好多地方都只知道一些皮毛,不明白其中的道理,所以我上网搜的时候发现有好多人写了很多很好的文章了,下面我转载的这篇就非常不错,一个困恼我好久的问题是驱动代码中只是注册了platform驱动,而platform设备注册在哪里?这个问题困恼我好久,源代码中一直没找到,下面文章就解决了这个问题。当然文章中详细了讲述了很多细节的知识。(1)串口移植S3...

weixin_32589453的博客 469

linux设备驱动串口移植,Linux设备驱动之UART驱动结构

一、对于串口驱动Linux系统中UART驱动属于终端设备驱动,应该说是实现串口驱动和终端驱动来实现串口终端设备的驱动。要了解串口终端的驱动在Linux系统的结构就先要了解终端设备驱动Linux系统中的结构体系,一方面自己了解的不够,另一发面关于终端设备的体系结构网上有很多很好很详细的资料参考,这里我主要是鉴于终端设备体系的复杂性,我大概总结一下自己了解到的一些方面和一个整体的框架。二、终端设备的...

weixin_28864249的博客 223

linux3.18内核移植到GT2440成功---完善串口

linux3.18内核移植到GT2440成功---完善串口linux3.1.8内核里面只支持 2 个串口,也就是芯片的 UART0 和 UART1,而 UART2 的驱动是针对红外接口的,而不是串口驱动,这里将其修改为串口驱动。 一.修改 1.修改内核源码“arch/arm/mach-s3c2440/mach-smdk2440.c”文件的smdk2440_uartcfgs结构体数组,

sdwzhwen2的专栏 1215

基于arm11处理器S3C6410内核的linux串口通信实现

基于arm的linux串口通信与独立按键的实验 最近正好在学习基于ARM的linux串口通信,现将学习经验与大家共享。以本人刚刚做的例程来一步步讲解。 /*例程目的:通过向内核加载独立按键模块,串口通信模块,编写应用程序,使得当按键按下时,arm可以通过串口对外发送数据。已达到arm与其他设备间的串口通信的目的*/ 一、基础知识 1.串口基本知识:串口:计算机上一种非常通用设备通

方丈的寺院 3778

linux学习指南

1、Linux 基础 安装Linux操作系统 Linux文件系统 Linux常用命令 Linux启动过程详解 熟悉Linux服务能够独立安装Linux操作系统 能够熟练使用Linux系统的基本命令 认识Linux系统的常用服务安装Linux操作系统 Linux基本命令实践 设置Linux环境变量 定制Linux的服务 Shell 编程基础使用vi编辑文件 使用Emacs编辑文件 使用其他编辑器

cqboy1991的专栏 770

嵌入式 知识积累(三) 之 基本技能

嵌入式设计应具备的基本技能 1、Linux 基础 安装Linux操作系统 Linux文件系统 Linux常用命令 Linux启动过程详解 熟悉Linux服务能够独立安装Linux操作系统,能够熟练使用Linux系统的基本命令 认识Linux系统的常用服务安装Linux操作系统 Linux基本命令实践 设置Linux环境变量 定制Linux的服务Shell 编程基础使用vi编辑...

alianada的博客 330

在s3c2440上linux镜像在nand flash中吗,Blob在S3C2440A上的移植

1引言本文引用地址:http://www.eepw.com.cn/article/90712.htmBootloader是在操作系统运行之前执行的一段程序。通过这段小程序,初始化硬件设备,建立内存空间的映射表,设置适当的系统软硬件环境,为最终调用操作系统内核做准备。目前流行的可以引导加载Linux的Bootloader包括:支持x86体系结构的LILO,GRUB,ROLO,Loadlin,Ethe...

weixin_36421674的博客 222

树莓派LINUX内核移植

参考博文:https://editor.csdn.net/md?not_checkout=1&articleId=109006969 树莓派linux内核下载地址:https://github.com/raspberryPi/ 需要下载内核的版本可以在树莓派用uname -r查看 然后去下载对应的内核版本。 1.在linux中创个文件夹放下载好的内核版本,并解压。 2.cd进去内核文件夹。 3.配置config 第一种方式: cp 厂家.config .config 第二种方式: make m

weixin_44372427的博客 2115

Mini2440 Linux2.6.32.2移植_03

1.1 修改 Linux Logo 1.1.1 使用命令行工具修改 Linux LOGO 在上一小节中我们看到 Linux 系统启动时会出现一个小企鹅图像: 大部分Linux系统会有这个开机图片,它对应的文件其实就是 linux-2.6.32.2/drivers/video/logo/linux_logo_clut224.ppm 该文件是一个特殊格式的图像文件,有很多

mcvsyy521的专栏 691
上一篇: linux设备驱动之UART驱动结构
下一篇: 2.6.32关于bus_id的问题
yimu13
博客等级 码龄17年 153粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值