Linux系统TTY串口驱动实例详解

目录

    • 一、简介
    • 二、源码详解
      • 2.1 uart_driver的注册:
      • 2.2 上层tty_core层
      • 2.3 注册platform_driver驱动
    • 三、其他相关链接
      • Linux下tty串口驱动数据的发送、接收过程源码实例详解

一、简介

在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。由于串口也是一种终端,因此这里引入终端这个概念 。
Linux tty子系统包含:tty核心,tty线路规程和tty驱动。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。它们的关系如下图:
Linux系统TTY串口驱动实例详解_第1张图片
简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。下面,就来分析分析它们的层次结构。

二、源码详解

从整个串口驱动注册程序来看,该程序只做了两件事:
1、注册uart_driver;
2、注册platform_driver,也就是uart_add_one_port();

具体请细看下面的源码分析:

2.1 uart_driver的注册:

在 s3c2440 平台,它是这样来注册串口驱动的,分配一个struct uart_driver 简单填充,并调用uart_register_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,
};
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_driver 中,我们只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的,那是怎么回事呢?来看一下完整的 uart_driver 结构或许就明白了。

struct uart_driver {
	struct module		*owner;	/* 拥有该uart_driver的模块,一般为THIS_MODULE */
	const char		*driver_name;	/* 串口驱动名,串口设备文件名以驱动名为基础 */
	const char		*dev_name;	/* 串口设备名 */
	int			 major;			/* 主设备号 */
	int			 minor;			/* 次设备号 */
	int			 nr;			/* 该uart_driver支持的串口个数(最大) */
	struct console		*cons;	/* 其对应的console.若该uart_driver支持serial console,否则为NULL */
 
	/* 下面这俩,初始化为NULL */
	struct uart_state	*state;	/* 下层,串口驱动层 */
	struct tty_driver	*tty_driver;	/* tty相关 */
};

在我们上边填充的结构体中,有两个成员未被赋值,对于tty_driver 代表的是上层,它会在 register_uart_driver 中的过程中赋值,而uart_state 则代表下层,uart_state 也会在register_uart_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。

下面先分析串口驱动层的uart_state:

struct uart_state {
	struct tty_port		port;
 
	int			pm_state;
	struct circ_buf		xmit;
	struct tasklet_struct	tlet;
	struct uart_port	*uart_port;	// 对应于一个串口设备
};

分配空间uart_driver->uart_state[nr]空间,即申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息;

struct uart_port {
	spinlock_t		lock;			/* port lock */
	unsigned long		iobase;			/* io端口基地址(物理) */
	unsigned char __iomem	*membase;		/* io内存基地址(虚拟) */
	unsigned int		(*serial_in)(struct uart_port *, int);
	void			(*serial_out)(struct uart_port *, int, int);
	unsigned int		irq;			/* 中断号 */
	unsigned long		irqflags;		/* 中断标志  */
	unsigned int		uartclk;		/* 串口时钟 */
	unsigned int		fifosize;		/* 串口缓冲区大小 */
	unsigned char		x_char;			/* xon/xoff char */
	unsigned char		regshift;		/* 寄存器位移 */
	unsigned char		iotype;			/* IO访问方式 */
	unsigned char		unused1;
 
	unsigned int		read_status_mask;	/* 关心 Rx error status */
	unsigned int		ignore_status_mask;	/* 忽略 Rx error status */
	struct uart_state	*state;			/* pointer to parent state */
	struct uart_icount	icount;			/* 串口信息计数器 */
 
	struct console		*cons;			/* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
	unsigned long		sysrq;			/* sysrq timeout */
#endif
 
	upf_t			flags;
 
	unsigned int		mctrl;			/* 当前的Moden 设置 */
	unsigned int		timeout;		/* character-based timeout */
	unsigned int		type;			/* 端口类型 */
	const struct uart_ops	*ops;		/* 串口端口操作函数 */
	unsigned int		custom_divisor;
	unsigned int		line;			/* 端口索引 */
	resource_size_t		mapbase;		/* io内存物理基地址 */
	struct device		*dev;			/* 父设备 */
	unsigned char		hub6;			/* this should be in the 8250 driver */
	unsigned char		suspended;
	unsigned char		unused[2];
	void			*private_data;		/* generic platform data pointer */
};

这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的,后面代码会详细分析从用户层到硬件层是如何一步步通过ops进行调用的。

struct uart_ops {
	unsigned int	(*tx_empty)(struct uart_port *);	 /* 串口的Tx FIFO缓存是否为空 */
	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);	/* 设置串口modem控制 */
	unsigned int	(*get_mctrl)(struct uart_port *);	/* 获取串口modem控制 */
	void		(*stop_tx)(struct uart_port *);		/* 禁止串口发送数据 */
	void		(*start_tx)(struct uart_port *);	/* 使能串口发送数据 */	
	void		(*send_xchar)(struct uart_port *, char ch);	/* 发送xChar */
	void		(*stop_rx)(struct uart_port *);		/* 禁止串口接收数据 */
	void		(*enable_ms)(struct uart_port *);	/* 使能modem的状态信号 */
	void		(*break_ctl)(struct uart_port *, int ctl);	/* 设置break信号 */
	int			(*startup)(struct uart_port *);		/* 启动串口,应用程序打开串口设备文件时,该函数会被调用 */
	void		(*shutdown)(struct uart_port *);/* 关闭串口,应用程序关闭串口设备文件时,该函数会被调用 */
	void		(*flush_buffer)(struct uart_port *);
	void		(*set_termios)(struct uart_port *, struct ktermios *new,
				       struct ktermios *old);	/* 设置串口参数 */
	void		(*set_ldisc)(struct uart_port *);/* 设置线路规程 */
	void		(*pm)(struct uart_port *, unsigned int state,
			      unsigned int oldstate);	/* 串口电源管理 */
	int		    (*set_wake)(struct uart_port *, unsigned int state);
 
	/*
	 * Return a string describing the type of the port
	 */
	const char *(*type)(struct uart_port *);
 
	/*
	 * Release IO and memory resources used by the port.
	 * This includes iounmap if necessary.
	 */
	void		(*release_port)(struct uart_port *);
 
	/*
	 * Request IO and memory resources used by the port.
	 * This includes iomapping the port if necessary.
	 */
	int		(*request_port)(struct uart_port *);	/* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */
	void		(*config_port)(struct uart_port *, int); /* 执行串口所需的自动配置 */
	int		(*verify_port)(struct uart_port *, struct serial_struct *); /* 核实新串口的信息 */
	int		(*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
	void	(*poll_put_char)(struct uart_port *, unsigned char);
	int		(*poll_get_char)(struct uart_port *);
#endif
};

2.2 上层tty_core层

tty 层要从 register_uart_driver 来看起了,因为 tty_driver 是在注册过程中构建的,我们也就顺便了解了注册过程。

int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal = NULL;
	int i, retval;
 
 
	/* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port */
	drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
	
	/* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
	normal  = alloc_tty_driver(drv->nr);
	drv->tty_driver = normal;
	
	/* 对 tty_driver 进行设置 */
	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;
		
	tty_set_operations(normal, &uart_ops);
 
	/*
	 * 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;	/* driver->state->tty_port */
 
		tty_port_init(port);
		port->close_delay     = 500;	/* .5 seconds */
		port->closing_wait    = 30000;	/* 30 seconds */
		/* 初始化 tasklet */
		tasklet_init(&state->tlet, uart_tasklet_action,
			     (unsigned long)state);
	}
	
	/* tty层:注册 driver->tty_driver */
	retval = tty_register_driver(normal);
 
}
 

uart_register_driver(&s3c24xx_uart_drv)注册驱动时会有下面几个操作:
1、分配空间uart_driver->uart_state[nr]空间,即申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息;
2、分配tty_driver,将dev_name/major/minor/nr赋值给tty_driver;
3、设置ty_core层的ops为strucy tty_operations uart_ops,设置flags为TTY_DRIVER_REAL_RAM|TTY_DRIVER_DYNAMIC_DEV;
4、初始化tty_port:uart_driver->uart_state[nr]->tty_port,如ttybuffer、flush_to_ldisc、tty_port->ops=&uart_port_ops(struct uart_port_ops uart_port_ops);
5、注册tty_driver,后面从源码中可以分析出uart驱动的注册,实际就是tty_driver的注册,都是将uart的参数传给tty_driver,然后注册字符设备、分配设备文件、将驱动写进总线管理的tty_driver链表中;

注:从代码可以看到uart_register_drive()函数中最终要调用tty_register_driver()函数来注册tty驱动,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可;

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,	// 当termios设置被改变时又tty核心调用
	.set_ldisc	= uart_set_ldisc,		// 设置线路规程函数
	.stop		= uart_stop,	
	.start		= uart_start,
	.hangup		= uart_hangup,		// 挂起函数,当驱动挂起tty设备时调用
	.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,	// 获得当前tty的线路规程的设置
	.tiocmset	= uart_tiocmset,	// 设置当前tty线路规程的设置
#ifdef CONFIG_CONSOLE_POLL
	.poll_init	= uart_poll_init,
	.poll_get_char	= uart_poll_get_char,
	.poll_put_char	= uart_poll_put_char,
#endif
};

这个是 tty 核心的 ops ,简单一看,后面分析调用关系时,我们在来看具体的里边的函数,下面来看 tty_driver 的注册。

int tty_register_driver(struct tty_driver *driver)
{
	int error;
	int i;
	dev_t dev;
	void **p = NULL;
 
	if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
		p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
	}
	
	/* 如果没有主设备号则申请 */
	if (!driver->major) {
		error = alloc_chrdev_region(&dev, driver->minor_start,
						driver->num, driver->name);
	} else {
		dev = MKDEV(driver->major, driver->minor_start);
		error = register_chrdev_region(dev, driver->num, driver->name);
	}
 
	if (p) { /* 为线路规程和termios分配空间 */
		driver->ttys = (struct tty_struct **)p;
		driver->termios = (struct ktermios **)(p + driver->num);
	} else {
		driver->ttys = NULL;
		driver->termios = NULL;
	}
 
	/* 创建字符设备,使用 tty_fops */
	cdev_init(&driver->cdev, &tty_fops);
	driver->cdev.owner = driver->owner;
	error = cdev_add(&driver->cdev, dev, driver->num);
 
	mutex_lock(&tty_mutex);
	
	/* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);
 
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
		for (i = 0; i < driver->num; i++)
		    tty_register_device(driver, i, NULL);
	}
	
	/* 向proc文件系统注册driver */
	proc_tty_register_driver(driver);
	driver->flags |= TTY_DRIVER_INSTALLED;
	return 0;
}

从上面源码可以分析到:
1、为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。
2、注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。
3、将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers 。
4、向 proc 文件系统添加 driver。

2.3 注册platform_driver驱动

该函数就是实现将驱动挂到platform总线上去,总线接管设备和驱动的管理工作。

int s3c24xx_serial_init(struct platform_driver *drv,
			struct s3c24xx_uart_info *info)
{
	dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
 
#ifdef CONFIG_PM
	drv->suspend = s3c24xx_serial_suspend;
	drv->resume = s3c24xx_serial_resume;
#endif
 
	return platform_driver_register(drv);
}
static void __exit s3c2440_serial_exit(void)
{
	platform_driver_unregister(&s3c2440_serial_driver);
}
 
module_init(s3c2440_serial_init);
module_exit(s3c2440_serial_exit);

然后我们分析一下platform_driver函数:

static struct platform_driver s3c2440_serial_driver = {
    .probe        = s3c2440_serial_probe,
    .remove        = __devexit_p(s3c24xx_serial_remove),
    .driver        = {
        .name    = "s3c2440-uart",
        .owner    = THIS_MODULE,
    },
};

在平台设备注册驱动的时候,总线会对驱动和设备匹配,如果匹配成功,将调用驱动的prob函数,我们具体追一下s3c2440_serial_driver 的prob函数:

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);
 
	ourport = &s3c24xx_serial_ports[probe_index];
	probe_index++;
 
	dbg("%s: initialising port %p...\n", __func__, ourport);
 
	ret = s3c24xx_serial_init_port(ourport, info, dev);
	if (ret < 0)
		goto probe_err;
 
	dbg("%s: adding port\n", __func__);
	uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
	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;
}

我们来分析上面这段代码:
这段代码首先从s3c24xx_serial_ports数组中寻找一个元素,这个数组里保存的是各个串口的信息。
假如说找到了串口0,拿到串口0后调用s3c24xx_serial_init_port完成串口的初始化,看看初始化函数:

/* s3c24xx_serial_init_port
 *
 * initialise a single serial port from the platform device given
 */
 
static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
				    struct s3c24xx_uart_info *info,
				    struct platform_device *platdev)
{
	struct uart_port *port = &ourport->port;
	struct s3c2410_uartcfg *cfg;
	struct resource *res;
	int ret;
 
	dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
 
	if (platdev == NULL)
		return -ENODEV;
 
	cfg = s3c24xx_dev_to_cfg(&platdev->dev);
 
	if (port->mapbase != 0)
		return 0;
 
	if (cfg->hwport > CONFIG_SERIAL_SAMSUNG_UARTS) {
		printk(KERN_ERR "%s: port %d bigger than %d\n", __func__,
		       cfg->hwport, CONFIG_SERIAL_SAMSUNG_UARTS);
		return -ERANGE;
	}
 
	/* setup info for port */
	port->dev	= &platdev->dev;
	ourport->info	= info;
 
	/* copy the info in from provided structure */
	ourport->port.fifosize = info->fifosize;
 
	dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);
 
	port->uartclk = 1;
 
	if (cfg->uart_flags & UPF_CONS_FLOW) {
		dbg("s3c24xx_serial_init_port: enabling flow control\n");
		port->flags |= UPF_CONS_FLOW;
	}
 
	/* sort our the physical and virtual addresses for each UART */
 
	res = platform_get_resource(platdev, IORESOURCE_MEM, 0);// 取得物理地址
	if (res == NULL) {
		printk(KERN_ERR "failed to find memory resource for uart\n");
		return -EINVAL;
	}
 
	dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
 
	port->mapbase = res->start;
	port->membase = S3C_VA_UART + res->start - (S3C_PA_UART & 0xfff00000); // 静态映射
	ret = platform_get_irq(platdev, 0);
	if (ret < 0)
		port->irq = 0;
	else {
		port->irq = ret;
		ourport->rx_irq = ret;
		ourport->tx_irq = ret + 1;
	}
	
	ret = platform_get_irq(platdev, 1);
	if (ret > 0)
		ourport->tx_irq = ret;
 
	ourport->clk	= clk_get(&platdev->dev, "uart");
 
	dbg("port: map=%08x, mem=%08x, irq=%d (%d,%d), clock=%ld\n",
	    port->mapbase, port->membase, port->irq,
	    ourport->rx_irq, ourport->tx_irq, port->uartclk);
 
	/* reset the fifos (and setup the uart) */
	s3c24xx_serial_resetport(port, cfg);
	return 0;
}

上面代码主要完成3项工作:
1、取串口的基地址
2、取串口的中断号
3、复位FIFO
在回到s3c24xx_serial_probe函数,在初始化串口后,接下来完成下面的操作:
1、添加端口uart_add_one_port
2、添加属性文件,这样在sys下面就可以看到串口的信息了
3、初始化动态频率调节s3c24xx_serial_cpufreq_register。

至此uart_driver和platform_driver都已注册完毕,后续章节会细讲TTY驱动数据的收发过程。

三、其他相关链接

Linux下tty串口驱动数据的发送、接收过程源码实例详解

你可能感兴趣的:(驱动,linux,驱动开发)