Linux串口驱动阅读笔记

sccv串口驱动学习–8250.c

本文主要记录学习CGEL3(基于Linux version 2.6.21.7) 中关于8250串口驱动代码的心得。代码位于Z:\CGEL3\archto\mips\sccv\drivers\serial
该驱动的代码采用最传统的方式来实现对串口的支持,在最新的CGEL6内核中,已经采用设备树的方法实现,将设备和驱动分离。

为了阅读的方便,打乱了常规驱动写法的顺序。按照先后顺序对代码进行了排序。可能错误的地方会非常多,且看且完善吧。如果产生误导,请见谅并请指正。谢谢。

module_init(serial8250_init);

static int __init serial8250_init(void)
{
	int ret;
	#ifndef CONFIG_SERIAL_POLL_API
	int i;

	if (nr_uarts > UART_NR)
		nr_uarts = UART_NR;
	#endif


if(isIpmux2())
{
printk("8250: module not loaded for IPMUX2\n");
return -1;
}

	printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
		"%d ports, IRQ sharing %sabled\n", nr_uarts,
		share_irqs ? "en" : "dis");

	/* GPIO Direction, GPIO1:input */
		*(int *) 0xBF002884 &= 0xFFFFFFFD;
	
	/* GPIO Interrupt Trigger Mode, GPIO1: triggered by level */   
*(int *) 0xBF002898 |= 0x2;    //这几个地址不明白,哪里来的,功能是设置GPIO的中断触发为水平触发(有数据即触发)?

  /* GPIO Interrupt Level, triggered by active high */    //GPIO中断级,高电平触发。
*(int *) 0xBF00289C |= 0x2;   

#ifndef CONFIG_SERIAL_POLL_API
	for (i = 0; i < NR_IRQS; i++)
		spin_lock_init(&irq_lists[i].lock);
#else
	serial8250_polled_init();
#endif

	ret = uart_register_driver(&serial8250_reg);  
//注册进入内核,serial8250_reg是一个uart_driver类型的结构体,初始化uart_driver中的
子结构体tty_driver的信息,如驱动名称、主次设备号、模块号及其对应的console等等。另外重要
的是初始化了tty_driver中的open、close等回调函数(tty_set_operations(normal, &uart_ops)),将这些函数初始化了static const struct tty_operations uart_ops中的函数。

	if (ret)
		goto out;

	serial8250_isa_devs = platform_device_alloc("serial8250",
						PLAT8250_DEV_LEGACY);//创建一个平台设备对象。设备的名字是serial8250。该名字是串口设备和串口驱动匹配的判断条件。
	if (!serial8250_isa_devs) {
		ret = -ENOMEM;
		goto unreg_uart_drv;
	}

	ret = platform_device_add(serial8250_isa_devs);//注册平台设备,最后对加入到虚拟的平台总线platform_bus_type中。
	if (ret)
		goto put_dev;

	serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);//下面详细分析下。

	ret = platform_driver_register(&serial8250_isa_driver);//注册该串口设备对应的驱动程序。
	if (ret == 0)
		goto out;

	platform_device_del(serial8250_isa_devs);
 put_dev:
	platform_device_put(serial8250_isa_devs);
 unreg_uart_drv:
	uart_unregister_driver(&serial8250_reg);
 out:
	return ret;
}
module_exit(serial8250_exit);

初始化的代码主要如上面,除了几个固定的流程之外,有两个函数serial8250_register_ports和platform_driver_register比较复杂,单独分析下。
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

static struct uart_driver serial8250_reg = {
.owner			= THIS_MODULE,
.driver_name		= "serial", //驱动名称 cat proc/tty/driver/serial查看。
.dev_name		= "ttyS",     //设备名称--cat proc/devices可以查看
.major			= TTY_MAJOR,  //主设备号
.minor			= 64,        //次设备号
.nr			= UART_NR,     //支持最大的串口个数
.cons			= SERIAL8250_CONSOLE,
};


static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
	int i;

	serial8250_isa_init_ports();

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.dev = dev;
		
		if(i == 2)
			EXTERN_PORT_FLAG = 3;
		
		uart_add_one_port(drv, &up->port); //将驱动static struct uart_driver serial8250_reg 和具体的物理端口联系在一起。
	}
}



static void __init serial8250_isa_init_ports(void)
{
	struct uart_8250_port *up;
	static int first = 1;
	int i;

	if (!first)
		return;
	first = 0;
	//nr_uarts:static unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; 
	该宏在sccv中等于3,详见CGEL3/boards/9806h/SCCV/kernel.config

	//for循环,主要是初始化了静态结构体数组serial8250_ports,该结构体类型为
	uart_8250_port,有几个串口就有几个这样的结构体,该结构体包含了串口的物理
	信息,波特率、中断号等等,另外结构体中元素uart_ops结构体赋值为了
	serial8250_pops,这个serial8250_pops对应的是串口芯片的驱动。

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.line = i;
		spin_lock_init(&up->port.lock);

		init_timer(&up->timer);
		up->timer.function = serial8250_timeout;

		/*
		 * ALPHA_KLUDGE_MCR needs to be killed.
		 */
		up->mcr_mask = ~ALPHA_KLUDGE_MCR;
		up->mcr_force = ALPHA_KLUDGE_MCR;

		up->port.ops = &serial8250_pops; // serial8250_pops定义了许多的回调函数,这些函数实现了对串口的硬件IO端口的读写、寄存器的配置等等。
	}


	//第二个for循环,和上面一个类似,同样是对uart_8250_port 类型的结构体serial8250_ports进行了填充,
	填充的内容主要来自于old_serial_port结构体,通过查看几个serial.h,发现来自于./linux/include/asm-mips/serial.h中。
	关于这个结构体的具体信息见下文。而该for循环的主要作用就是用old_serial_port结构体中前3个元素对serial8250_ports结构体进行初始化。
	结合serial8250_pops中多个回调函数的代码发现,这几个回调函数会使用serial8250_ports中的硬件信息。

	for (i = 0, up = serial8250_ports;
	     i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
	     i++, up++) {
		up->port.iobase   = old_serial_port[i].port;
#ifdef CONFIG_MIPS_MALTA
		up->port.irq      = (old_serial_port[i].irq);
#else
		up->port.irq      = irq_canonicalize(old_serial_port[i].irq);
#endif
		up->port.uartclk  = old_serial_port[i].baud_base * 16;
		
		if(bspGetSccbType() && i==2)/*SCCBK的扩展串口波特率为16384000*/
	  {
	  		up->port.uartclk  = (16384000 / 16) * 16;
	  }
		
		up->port.flags    = old_serial_port[i].flags;
		up->port.hub6     = old_serial_port[i].hub6;
		up->port.membase  = old_serial_port[i].iomem_base;
		up->port.iotype   = old_serial_port[i].io_type;
		up->port.regshift = old_serial_port[i].iomem_reg_shift;
		if (share_irqs)
			up->port.flags |= UPF_SHARE_IRQ;
	}
}

//old_serial_port,定义了一些硬件信息,如波特率、中断、iomem_base、io_type等信息。
static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};

#define SERIAL_PORT_DFNS				\
	DDB5477_SERIAL_PORT_DEFNS			\
	EV64120_SERIAL_PORT_DEFNS			\
	IP32_SERIAL_PORT_DEFNS                          \
	JAZZ_SERIAL_PORT_DEFNS				\
	STD_SERIAL_PORT_DEFNS				\
	MOMENCO_OCELOT_G_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_C_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_SERIAL_PORT_DEFNS		\
	MOMENCO_OCELOT_3_SERIAL_PORT_DEFNS		\
	BCM947XX_SERIAL_PORT_DEFNS			\
	BCM56218_SERIAL_PORT_DEFNS

#endif




static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
	int i;

	serial8250_isa_init_ports();

	for (i = 0; i < nr_uarts; i++) {
		struct uart_8250_port *up = &serial8250_ports[i];

		up->port.dev = dev;
		
		if(i == 2)
			EXTERN_PORT_FLAG = 3;
		
		uart_add_one_port(drv, &up->port);  //该函数主要就是这一步,将三个物理的串口 uart_port和驱动serial8250_reg联系在一起。
	}
}

注册9250对应的驱动

以上分析的,基本上都是总线驱动模型中的设备信息,下面看下驱动信息。回到初始化函数serial8250_init,有如下的一行代码:

ret = platform_driver_register(&serial8250_isa_driver);

该行代码主要是注册8250对应的驱动程序。
platform_driver_register是虚拟平台总线的统一注册函数,而参数如下。该参数主要定义了probe函数以及.driver。.driver中的成员name是平台总线匹配驱动和设备的条件之一。

static struct platform_driver serial8250_isa_driver = {
	.probe		= serial8250_probe,
	.remove		= __devexit_p(serial8250_remove),
	.suspend	= serial8250_suspend,
	.resume		= serial8250_resume,
	.driver		= {
		.name	= "serial8250",
		.owner	= THIS_MODULE,
	},
};

当虚拟平台总线发现平台设备和平台驱动匹配时,那么serial8250_probe函数将会被调用,该函数主要做了什么呢?下面一起来看看。
static int __devinit serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev->dev.platform_data; //从平台设备中读取出硬件信息
struct uart_port port;
int ret, i;

	memset(&port, 0, sizeof(struct uart_port));

	for (i = 0; p && p->flags != 0; p++, i++) {
		port.iobase	= p->iobase;
		port.membase	= p->membase;            //将平台设备的各个信息读取出来,存放到结构体uart_port中。
		port.irq	= p->irq;
		port.uartclk	= p->uartclk;
		port.regshift	= p->regshift;
		port.iotype	= p->iotype;
		port.flags	= p->flags;
		port.mapbase	= p->mapbase;
		port.hub6	= p->hub6;
		port.dev	= &dev->dev;
		if (share_irqs)
			port.flags |= UPF_SHARE_IRQ;
		ret = serial8250_register_port(&port);	      //注册串口,比较重要,下面详细分析
		if (ret < 0) {
			dev_err(&dev->dev, "unable to register port at index %d "
				"(IO%lx MEM%lx IRQ%d): %d\n", i,
				p->iobase, p->mapbase, p->irq, ret);
		}
	}
	return 0;
}

serial8250_register_port:注册一个串口设备

int serial8250_register_port(struct uart_port *port)
{
	struct uart_8250_port *uart;
	int ret = -ENOSPC;

	if (port->uartclk == 0)
		return -EINVAL;

	mutex_lock(&serial_mutex);

	uart = serial8250_find_match_or_unused(port);   //查看串口信息是否存在。
	if (uart) {
		uart_remove_one_port(&serial8250_reg, &uart->port);  //如果存在,则清除,后面重新添加

		uart->port.iobase   = port->iobase;
		uart->port.membase  = port->membase;
		uart->port.irq      = port->irq;
		uart->port.uartclk  = port->uartclk;
		uart->port.fifosize = port->fifosize;
		uart->port.regshift = port->regshift;
		uart->port.iotype   = port->iotype;
		uart->port.flags    = port->flags | UPF_BOOT_AUTOCONF;
		uart->port.mapbase  = port->mapbase;
		if (port->dev)
			uart->port.dev = port->dev;

		ret = uart_add_one_port(&serial8250_reg, &uart->port); //这个子函数应该是比较重要的,细看下。
		if (ret == 0)
			ret = uart->port.line;
	}
	mutex_unlock(&serial_mutex);

	return ret;
}


int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
	struct uart_state *state;
	int ret = 0;

	BUG_ON(in_interrupt());

	if (port->line >= drv->nr)
		return -EINVAL;

	state = drv->state + port->line;

	mutex_lock(&port_mutex);
	mutex_lock(&state->mutex);
	if (state->port) {
		ret = -EINVAL;
		goto out;
	}

	state->port = port;
tty_fops
	port->cons = drv->cons;
#ifndef CONFIG_SERIAL_POLL_API
	 port->info = state->info;
#else
	/*
	 * Don't assign info if it has already been assigned.
	 *
	 * FIXME - is this assignment really necessary?  port->info
	 * doesn't seem to be used until after an open, and that
	 * assigns the value.
	 */
	if (!port->info)
		port->info = state->info;
#endif

	/*
	 * If this port is a console, then the spinlock is already
	 * initialised.
	 */
	if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
		spin_lock_init(&port->lock);
		lockdep_set_class(&port->lock, &port_lock_key);
	}

	uart_configure_port(drv, state, port);
	/*配置端口,会调用8250对应的配置函数 serial8250_config_port、设置modem控制serial8250_set_mctrl等等*/

	/*
	 * Register the port whether it's detected or not.  This allows
	 * setserial to be used to alter this ports parameters.
	 */
	tty_register_device(drv->tty_driver, port->line, port->dev);

	/*
	 * If this driver supports console, and it hasn't been
	 * successfully registered yet, try to re-register it.
	 * It may be that the port was not available.
	 */
	if (port->type != PORT_UNKNOWN &&
	    port->cons && !(port->cons->flags & CON_ENABLED))
		register_console(port->cons);

	/*
	 * Ensure UPF_DEAD is not set.
	 */
	port->flags &= ~UPF_DEAD;

#if defined(CONFIG_KGDB_8250)
	/* Add any 8250-like ports we find later. */
	if (port->type <= PORT_MAX_8250)
		kgdb8250_add_port(port->line, port);
#endif

 out:
	mutex_unlock(&state->mutex);
	mutex_unlock(&port_mutex);

	return ret;
}

##串口驱动关键的结构体解读

struct uart_driver
 {

    struct module  *owner; //拥有该uart_driver的模块,一般为THIS_MODULE 
    constchar *driver_name; // 串口驱动名,串口设备文件名以驱动名为基础 
    constchar *dev_name; // 串口设备名 ,cat /proc/devices查看
    int major; //主设备号 
    int minor; //次设备号 
    int nr; // 该uart_driver支持的串口个数(最大) 
    struct console  *cons;// 其对应的console.若该uart_driver支持serial console,否则为NULL 
    .............................
    struct uart_state  *state;
    struct tty_driver  *tty_driver;   //uart_driver封装了tty_driver,使底层uart驱动不用关心ttr_driver。

};

uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。有几个串口就需要几个这样的结构体,在本例子中,有3个串口,对应3个这样的结构体。

struct uart_port
{
	spinlock_t		lock;			/* port lock */
	unsigned int		iobase;			/* in/out[bwl] 端口基地址*/
	unsigned char __iomem	*membase;		/* read/write[bwl] IO内存基地址(经过ioremap后的虚拟地址)*/
	unsigned int		irq;			/* irq number 中断号*/
	unsigned int		uartclk;		/* base uart clock 串口时钟 */
	unsigned int		fifosize;		/* tx fifo size  串口FIFO缓冲大小*/
	unsigned char		x_char;			/* xon/xoff char */
	unsigned char		regshift;		/* reg offset shift 寄存器移位,外设访问方式分为8 16 32 位(分别对应regshift的值为0 1 2 ),因此需要移位,如果不懂,可以韦东山的嵌入式开发手册。*/
	unsigned char		iotype;			/* io access style :io访问方式*/
	unsigned char		unused1;

#define UPIO_PORT		(0)
#define UPIO_HUB6		(1)
#define UPIO_MEM		(2)
#define UPIO_MEM32		(3)
#define UPIO_AU			(4)			/* Au1x00 type IO */
#define UPIO_TSI		(5)			/* Tsi108/109 type IO */
#define UPIO_DWAPB		(6)			/* DesignWare APB UART */
#define UPIO_RM9000		(7)			/* RM9000 type IO */

	unsigned int		read_status_mask;	/* driver specific */
	unsigned int		ignore_status_mask;	/* driver specific */
	struct uart_info	*info;			/* pointer to parent info */
	struct uart_icount	icount;			/* statistics *//* 计数器 uart_icount为串口信息计数器,包含了发送字符计数、接收字符计数等。在串口的发送中断处理函数和接收中断处理函数中,我们需要管理这些计数。*/ 

	struct console		*cons;			/* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
	unsigned long		sysrq;			/* sysrq timeout */
#endif

	upf_t			flags;

/*
 * The port lock protects UPF_INUSE_DIRECT and UPF_INUSE_NORMAL.  One
 * of those two bits must be set before any other bits can be changed
 * in port->flags.
 */
#define UPF_FOURPORT		((__force upf_t) (1 << 1))
#define UPF_SAK			((__force upf_t) (1 << 2))
#define UPF_SPD_MASK		((__force upf_t) (0x1030))
#define UPF_SPD_HI		((__force upf_t) (0x0010))
#define UPF_SPD_VHI		((__force upf_t) (0x0020))
#define UPF_SPD_CUST		((__force upf_t) (0x0030))
#define UPF_SPD_SHI		((__force upf_t) (0x1000))
#define UPF_SPD_WARP		((__force upf_t) (0x1010))
#define UPF_SKIP_TEST		((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ		((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD		((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY		((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART		((__force upf_t) (1 << 14))
#define UPF_MAGIC_MULTIPLIER	((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW		((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ		((__force upf_t) (1 << 24))
#ifdef CONFIG_SERIAL_POLL_API
#define UPF_INUSE_NORMAL	((__force uif_t) (1 << 27))
#endif
#define UPF_BOOT_AUTOCONF	((__force upf_t) (1 << 28))
#ifdef CONFIG_SERIAL_POLL_API
#define UPF_INUSE_DIRECT	((__force uif_t) (1 << 29))
#endif
#define UPF_DEAD		((__force upf_t) (1 << 30))
#define UPF_IOREMAP		((__force upf_t) (1 << 31))

#define UPF_CHANGE_MASK		((__force upf_t) (0x17fff))
#define UPF_USR_MASK		((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

	unsigned int		mctrl;			/* current modem ctrl settings */
	unsigned int		timeout;		/* character-based timeout */
	unsigned int		type;			/* port type 端口类型 */
	const struct uart_ops	*ops;       /*串口端口回调函数集合,比较重要,后面单独看下*/
	unsigned int		custom_divisor;
	unsigned int		line;			/* port index */
	unsigned long		mapbase;		/* for ioremap IO内存物理基地址,用于ioremap*/
	struct device		*dev;			/* parent device 指向父设备 */
	unsigned char		hub6;			/* this should be in the 8250 driver */
	unsigned char		unused[3];
	#ifdef CONFIG_ARCH_M823XX
	void			*private_data;		/* generic platform data pointer 一般指向platform数据指针 */
	#endif
}

uart_ops这个结构体是一个回调函数集,这里面的函数和串口硬件进行交互,为上层应用提供读写接口。可以理解为裸机程序中的配置、读写串口控制器的各个寄存器。内核对这些方法的格式进行了统一封装,一般该结构体的函数指针芯片厂商会写好。

struct uart_ops {
	unsigned int	(*tx_empty)(struct uart_port *); //判断串口的tx Fifo缓存是否为空
	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);
	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 *);
	void		(*break_ctl)(struct uart_port *, int ctl);
	int		(*startup)(struct uart_port *);  //启动串口,用户态打开串口设备时,该函数会被调用。
	void		(*shutdown)(struct uart_port *); //关闭串口、用户态关闭串口设备时,函数被调用。
	void		(*set_termios)(struct uart_port *, struct ktermios *new,
				       struct ktermios *old);//设备串口参数
	void		(*pm)(struct uart_port *, unsigned int state,
			      unsigned int oldstate);  //电源管理
	int		(*set_wake)(struct uart_port *, unsigned int state);


#ifdef CONFIG_SERIAL_POLL_API
	/*
	 * Note: Poll routines must be called with the port lock held and
	 * interrupts off.
	 */

	/*
	 * The startup and shutdown routines must be called before
	 * poll is used and after done calling poll.  You cannot allow
	 * the driver code to be run by interrupts (or anything else)
	 * between this.  A state value is returned by the startup
	 * routine in pstate, you must pass that to the shutdown
	 * routine.
	 */
	int		(*poll_startup)(struct uart_port *,
					unsigned long *pstate);
	void		(*poll_shutdown)(struct uart_port *,
					 unsigned long pstate);

	/*
	 * Check the serial chip for I/O.  Flags is used to specify
	 * what to check, see UART_POLL_FLAGS_xxx above.
	 */
	void		(*poll)(struct uart_port *, unsigned int flags);

	/*
	 * Is the port in flow-control (CTS is not asserted).  It is
	 * optional an may be NULL and is only called if UPF_CONS_FLOW
	 * is set in port->flags.
	 */
	int             (*in_flow_control)(struct uart_port *);

	/*
	 * Return reasonable settings for the port; it is primarily
	 * there so firmware can pass console settings for the
	 * console.
	 */
	int		(*port_defaults)(struct uart_port *,
					 int *baud, int *parity, int *bits,
					 int *flow);
#endif	/* CONFIG_SERIAL_POLL_API */

	/*
	 * 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 *);          //请求端口
	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_SERIAL_POLL
	void	(*poll_put_char)(struct uart_port *, unsigned char);
	int		(*poll_get_char)(struct uart_port *);
#endif
}

###用户态对tty设备进行读写的分层次函数处理

对于普通的字符设备驱动,我们用户态open、read、write时会直接调用到驱动对应的open、read、write函数。
如韦东山的传统的字符设备驱动写法,用户态open时,直接调用了led_open.
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};

但是对于串口比较特别,由于终端种类较多,如键盘、显示器、网络实现的伪终端等,他们的驱动程序不仅仅是简单的初始化硬件、收发数据。在基本的硬件操作基础上,增加了许多的软件功能,是一个多层次的驱动程序。
一般都将串口驱动分层处理,详细可查阅网上资料或者韦东山的书籍也有简单的介绍。

那么,结合代码,简要的总结下打开一个tty设备时的处理步骤。

用户态open、read,收到走查如下的函数操作集。该函数集注册的地方是在 tty_register_driver 函数中调用了tty_cdev_add函数(driver->cdevs[index]->ops = &tty_fops;)这个回调函数集合是统一的,对于所有的tty设备,都是可以用的。

// linux/drivers/char/tty_io.c
static const struct file_operations tty_fops = {
	.llseek		= no_llseek,
	.read		= tty_read,
	.write		= tty_write,
	.poll		= tty_poll,
	.ioctl		= tty_ioctl,
	.open		= tty_open,
	.release	= tty_release,
	.fasync		= tty_fasync,
};

以上述回退函数集合中的tty_read为例,tty_ldisc_ref_wait(tty)找到该tty设备对应的线路规程操作函数集,并调用对应的read函数。

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
		loff_t *ppos)
{
	int i;
	struct inode *inode = file_inode(file);
	struct tty_struct *tty = file_tty(file);
	struct tty_ldisc *ld;

	if (tty_paranoia_check(tty, inode, "tty_read"))
		return -EIO;
	if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
		return -EIO;

	/* We want to wait for the line discipline to sort out in this
	   situation */
	ld = tty_ldisc_ref_wait(tty);
	if (ld->ops->read)
		i = ld->ops->read(tty, file, buf, count);
	else
		i = -EIO;
	tty_ldisc_deref(ld);

	if (i > 0)
		tty_update_time(&inode->i_atime);

	return i;
}

// linux/drivers/serial/serial_core.c --串口核心层。这一部分其实无需关心,uart_register_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,
	.stop		= uart_stop,
	.start		= uart_start,
	.hangup		= uart_hangup,
	.break_ctl	= uart_break_ctl,
	.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
	.read_proc	= uart_read_proc,
#endif
	.tiocmget	= uart_tiocmget,
	.tiocmset	= uart_tiocmset,
#ifdef CONFIG_SERIAL_POLL
	.poll_init	= uart_poll_init,
	.poll_get_char	= uart_poll_get_char,
	.poll_put_char	= uart_poll_put_char,
#endif
};

那么线路规程的操作函数集来自于哪里呢? ./drivers/tty/n_tty.c。以下的这个线路规程函数集注册的地方在。

struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic           = TTY_LDISC_MAGIC,
.name            = "n_tty",
.open            = n_tty_open,
.close           = n_tty_close,
.flush_buffer    = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read            = n_tty_read,
.write           = n_tty_write,
.ioctl           = n_tty_ioctl,
.set_termios     = n_tty_set_termios,
.poll            = n_tty_poll,
.receive_buf     = n_tty_receive_buf,
.write_wakeup    = n_tty_write_wakeup,
.fasync		 = n_tty_fasync,
.receive_buf2	 = n_tty_receive_buf2,
};


void tty_ldisc_begin(void)
{
	/* Setup the default TTY line discipline. */
	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}

再往下走,走到了如下的函数。该函数是串口驱动,由我们移植,一般来讲,这部分芯片厂商会写好的,稍作修改即可。

// archto/mips/sccv/drivers/serial/8250.c
static struct uart_ops serial8250_pops = {
.tx_empty	= serial8250_tx_empty,
.set_mctrl	= serial8250_set_mctrl,
.get_mctrl	= serial8250_get_mctrl,
.stop_tx	= serial8250_stop_tx,
.start_tx	= serial8250_start_tx,
.stop_rx	= serial8250_stop_rx,
.enable_ms	= serial8250_enable_ms,
.break_ctl	= serial8250_break_ctl,
.startup	= serial8250_startup,
.shutdown	= serial8250_shutdown,
.set_termios	= serial8250_set_termios,
.pm		= serial8250_pm,
#ifdef CONFIG_SERIAL_POLL_API
.poll		= serial8250_poll,
.poll_startup	= serial8250_poll_startup,
.poll_shutdown	= serial8250_poll_shutdown,
.in_flow_control= serial8250_in_flow_control,
#endif
.type		= serial8250_type,
.release_port	= serial8250_release_port,
.request_port	= serial8250_request_port,
.config_port	= serial8250_config_port,
.verify_port	= serial8250_verify_port,
#ifdef CONFIG_SERIAL_POLL
.poll_get_char = serial8250_get_poll_char,
.poll_put_char = serial8250_put_poll_char,
#endif
};

###对上面的内容依然一知半解的,但是看得好烦啊,云里来雾里去的。所以看下CGEL6内核的设备树方式实现串口驱动。

查看设备树文件知道串口的硬件信息如下:

	uart0: serial@01703000 {
		/* device_type = "serial";*/
		compatible = "zte,pl011-uart0";    //该字符串用于和驱动匹配
		reg = <0x0 0x01703000  0x0 0x1000>;
		interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART0_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};

	uart1: serial@01704000 {
		compatible = "zte,pl011-uart1";
		reg = <0x0 0x01704000  0x0 0x1000>;
		interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART1_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};
	uart2: serial@01705000 {
		compatible = "zte,pl011-uart2";
		reg = <0x0 0x01705000  0x0 0x1000>;
		interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART2_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};
	uart3: serial@01706000 {
		compatible = "zte,pl011-uart3";
		reg = <0x0 0x01706000  0x0 0x1000>;
		interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART3_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};

通过搜索字段zte,pl011-uart0找到代码中对应的驱动位于文件:linux/drivers/tty/serial/amba-pl011.c。不管三七二一了,先看看这个文件吧。

static struct uart_driver amba_reg = {
	.owner			= THIS_MODULE,
	.driver_name		= "ttyAMA",
	.dev_name		= "ttyAMA",
	.major			= SERIAL_AMBA_MAJOR,
	.minor			= SERIAL_AMBA_MINOR,
	.nr			= UART_NR,     //14,支持14个串口,该参数会用于分配tty_driver
	.cons			= AMBA_CONSOLE,
};


arch_initcall(pl011_init);  //注意这里不一样,一般的module_init的,至于为什么不一样呢?
arch_initcall定义的函数指针会比module_init的先初始化。如果进一步了解可以阅读文档:

https://www.cnblogs.com/chaozhu/p/6410271.html

static int __init pl011_init(void)
{
	int ret = -1;
	printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");

	ret = uart_register_driver(&amba_reg);
	if (ret < 0) {
		printk("Failed to register AMBA-PL011 driver\n");
		return ret;
	}

	//if (platform_driver_register(&arm_sbsa_uart_platform_driver))
	//	pr_warn("could not register SBSA UART platform driver\n");
	ret = platform_driver_register(&zx_uart_driver);
	if (ret)
		pr_warn("could not register zx UART platform driver\n");
	
	//return amba_driver_register(&pl011_driver);
	return ret;
}

从以上的初始化代码可以看得出来,该驱动的代码量要远比8250的简单不少。但是关键性的步骤还是相同的。下面分析下上面的初始化函数。

uart_register_driver(&amba_reg);

该函数是一个通用的函数,上面已经简单的分析过,回过头来再看一遍,发现这个函数主要是分配了tty_driver,并将该驱动与tty_operations uart_ops绑定。

之后另一个比较重要的函数就是

static const struct of_device_id zx_uart_dt_ids[] = {
	{ .compatible = "zte,pl011-uart0", },
	{ .compatible = "zte,pl011-uart1", },
	{ .compatible = "zte,pl011-uart2", },
	{ .compatible = "zte,pl011-uart3", },
	{ .compatible = "zte,pl011-uart4", },
	{ .compatible = "zte,pl011-uart5", },
	{ .compatible = "zte,pl011-uart6", },
	{ .compatible = "zte,pl011-uart7", },
	{ .compatible = "zte,pl011-uart", },
	{ /* sentinel */ }

static struct platform_driver zx_uart_driver = {
.driver = {
	.name	= "zx-uart",
	.owner	= THIS_MODULE,
	.pm	= &pl011_dev_pm_ops,
	.of_match_table = zx_uart_dt_ids,
},
.probe		= zx_uart_probe,
.remove		= zx_uart_remove,
};

platform_driver_register(&zx_uart_driver);  

该函数其实就是想平台驱动注册了一个驱动,当zx_uart_driver.driver->of_match_table->compatible(表zx_uart_dt_ids)与设备树中compatible匹配时,那么平台驱动 zx_uart_driver中的probe函数zx_uart_probe会被调用。

接下来,那就简单的看看zx_uart_probe干了什么吧。

static int zx_uart_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct uart_amba_port *uap;
	struct vendor_data *vendor = &vendor_zte;
	struct resource *res;
	void __iomem *base;
	int i, ret;

	/*申请内存,当设备或者驱动不使用了,内存会自动释放,无须手动free。*/
	uap = devm_kzalloc(&pdev->dev, sizeof(struct uart_amba_port),
			GFP_KERNEL);
	if (uap == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	i = of_alias_get_id(np, "serial");
	/*解析设备树,提取出alalis节点*/
	


	if (i < 0) {
		dev_err(&pdev->dev, "failed to get alias id: %d\n", i);
		ret = i;
		goto out;
	}

	/*从设备树中解析出IO资源,即是节点中的reg属性。一般是芯片控制器控制、数据寄存器的物理地址。
	uart0: serial@01703000 {
		/* device_type = "serial";*/
		compatible = "zte,pl011-uart0";
		reg = <0x0 0x01703000  0x0 0x1000>;           //对应于platform_get_resource的返回值res。
		interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&lsp2crpm ZXIC_UART0_WCLK>;
		clock-names = "uartclk";
		status = "disabled";
	};*/

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);//物理地址映射进虚拟地址空间。
	if (!base) {
		ret = -ENOMEM;
		goto out;
	}

#if 1
	uap->clk = devm_clk_get(&pdev->dev, NULL);

	printk(KERN_ALERT"zx_uart_probe serial id %d uap->clk is %p base is %p\n",i,uap->clk,base);
	
	if (IS_ERR(uap->clk)) {
		ret = PTR_ERR(uap->clk);
		goto out;
	}
#endif
	//uap->clk = NULL;  // add by whl 2016.11.29

	uap->vendor = vendor;
	uap->reg_lut = vendor->reg_lut;
	uap->fr_busy = vendor->fr_busy;
	uap->fr_dsr = vendor->fr_dsr;
	uap->fr_cts = vendor->fr_cts;
	uap->fr_ri = vendor->fr_ri;
	uap->lcrh_rx = vendor->lcrh_rx;
	uap->lcrh_tx = vendor->lcrh_tx;
	uap->old_cr = 0;
	uap->fifosize = 16;
	uap->port.dev = &pdev->dev;
	uap->port.mapbase = res->start;
	uap->port.membase = base;
	uap->port.iotype = UPIO_MEM;
	uap->port.irq = platform_get_irq(pdev, 0);
	uap->port.fifosize = uap->fifosize;
	uap->port.ops = &amba_pl011_pops;    //注意这一行比较重要,制定了该串口驱动对应的操作硬件寄存器函数。
	uap->port.flags = UPF_BOOT_AUTOCONF;
	uap->port.line = i;
	//INIT_DELAYED_WORK(&uap->tx_softirq_work, pl011_tx_softirq);

	/* Ensure interrupts from this UART are masked and cleared */
	pl011_writew(uap, 0, REG_IMSC);    //向中断控制寄存器写入0
	pl011_writew(uap, 0xffff, REG_ICR); //中断清除寄存器各位置1
	
	//pl011_writew(uap, 0x70, REG_LCRH);  //Word Length 8bit and Enable FIFOs
	
	amba_ports[i] = uap;

	platform_set_drvdata(pdev, uap);
	//printk(KERN_ALERT"zx_uart_probe serial id %d amba_reg-state is %p \n",i,amba_reg.state);

#ifdef CONFIG_EARLY_PRINTK
	disable_early_printk();
#endif
	ret = uart_add_one_port(&amba_reg, &uap->port);  //驱动和端口绑定。
	if (ret) {
		amba_ports[i] = NULL;
		pl011_dma_remove(uap);
	}
out:
	return ret;
}

该串口驱动对应的处理函数。

static struct uart_ops amba_pl011_pops = {
.tx_empty	= pl011_tx_empty,
.set_mctrl	= pl011_set_mctrl,
.get_mctrl	= pl011_get_mctrl,
.stop_tx	= pl011_stop_tx,
.start_tx	= pl011_start_tx,
.stop_rx	= pl011_stop_rx,
.enable_ms	= pl011_enable_ms,
.break_ctl	= pl011_break_ctl,
.startup	= pl011_startup,
.shutdown	= pl011_shutdown,
.flush_buffer	= pl011_dma_flush_buffer,
.set_termios	= pl011_set_termios,
.type		= pl011_type,
.release_port	= pl011_release_port,
.request_port	= pl011_request_port,
.config_port	= pl011_config_port,
.verify_port	= pl011_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_init     = pl011_hwinit,
.poll_get_char = pl011_get_poll_char,
.poll_put_char = pl011_put_poll_char,
#endi

上面回调函数中的 pl011_start_tx负责向串口发送数据的。

static void pl011_start_tx(struct uart_port *port)
{
	struct uart_amba_port *uap =
	    container_of(port, struct uart_amba_port, port);

	if (!pl011_dma_tx_start(uap))
		pl011_start_tx_pio(uap);
}

static void pl011_start_tx_pio(struct uart_amba_port *uap)
{
	uap->im |= UART011_TXIM;
	//writew(uap->im, uap->port.membase + UART011_IMSC);
	pl011_writew(uap, uap->im, REG_IMSC);
	pl011_tx_chars(uap, false);//发送字符集
}

static void pl011_tx_chars(struct uart_amba_port *uap, bool from_irq)

{
struct circ_buf *xmit = &uap->port.state->xmit;
int count = uap->fifosize >> 1;

if (uap->port.x_char) {
	if (!pl011_tx_char(uap, uap->port.x_char, from_irq))
		return;
	uap->port.x_char = 0;
	--count;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&uap->port)) {
	pl011_stop_tx(&uap->port);
	return;
}

/* If we are using DMA mode, try to send some characters. */
if (pl011_dma_tx_irq(uap))
	return;

do {
	if (likely(from_irq) && count-- == 0)
		break;

	if (!pl011_tx_char(uap, xmit->buf[xmit->tail], from_irq)) //循环调用该函数,每次想数据寄存器中写入一个字符。
		break;

	xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
} while (!uart_circ_empty(xmit));

if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
	uart_write_wakeup(&uap->port);

if (uart_circ_empty(xmit))
	pl011_stop_tx(&uap->port);

}

至于,如何从串口中读取数据,可以看下代码:
pl011_startup
	pl011_allocate_irq
		request_irq(uap->port.irq, pl011_int, 0, "uart-pl011", uap)
		pl011_int  --这个函数在收到中断后,会被调用的。应该是处理从串口读取数据。

一个非常修改的框架图:

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值