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 --这个函数在收到中断后,会被调用的。应该是处理从串口读取数据。
一个非常修改的框架图: