本文主要记录:
1、uart设备注册
2、uart驱动注册
3、上层应用调用
有些地方理解的还不是很透彻,希望指正。
1、uart设备注册过程
MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.boot_params = MX6_PHYS_OFFSET + 0x100,
.fixup = fixup_mxc_board,
.map_io = mx6_map_io,
.init_irq = mx6_init_irq,
.init_machine = mx6_sabresd_board_init, ------------------------+
.timer = &mx6_sabresd_timer, |
.reserve = mx6q_sabresd_reserve, |
MACHINE_END |
|
static void __init mx6_sabresd_board_init(void) <-----------------------+
{
int i;
int ret;
struct clk *clko, *clko2;
struct clk *new_parent;
int rate;
if (cpu_is_mx6q())
mxc_iomux_v3_setup_multiple_pads(mx6q_sabresd_pads, ----+
ARRAY_SIZE(mx6q_sabresd_pads)); |
else if (cpu_is_mx6dl()) { |
mxc_iomux_v3_setup_multiple_pads(mx6dl_sabresd_pads, |
ARRAY_SIZE(mx6dl_sabresd_pads)); |
} |
... ... |
mx6q_sabresd_init_uart(); ------------------------|----+
... ... | |
} | |
//设置IOMUX寄存器,将有功能复用的引脚设置为UART功能 | |
static iomux_v3_cfg_t mx6q_sabresd_pads[] = { <---------------+ |
... ... |
/* UART1 for debug */ |
MX6Q_PAD_CSI0_DAT10__UART1_TXD, |
MX6Q_PAD_CSI0_DAT11__UART1_RXD, |
|
/* UART2*/ |
MX6Q_PAD_EIM_D26__UART2_TXD, |
MX6Q_PAD_EIM_D27__UART2_RXD, |
|
/* UART3 for gps */ |
MX6Q_PAD_EIM_D24__UART3_TXD, |
MX6Q_PAD_EIM_D25__UART3_RXD, |
|
/* UART4*/ |
MX6Q_PAD_KEY_COL0__UART4_TXD, |
MX6Q_PAD_KEY_ROW0__UART4_RXD, |
|
/* UART5*/ |
MX6Q_PAD_KEY_COL1__UART5_TXD, |
MX6Q_PAD_KEY_ROW1__UART5_RXD, |
... ... |
}; |
|
//uart初始化 |
static inline void mx6q_sabresd_init_uart(void) <----------------+
{
imx6q_add_imx_uart(, NULL);
imx6q_add_imx_uart(, NULL);
imx6q_add_imx_uart(, NULL);
imx6q_add_imx_uart(, NULL);
imx6q_add_imx_uart(, NULL);
}
#define imx6q_add_imx_uart(id, pdata) \
imx_add_imx_uart_1irq(&imx6q_imx_uart_data[id], pdata)
// 注册为平台设备
struct platform_device *__init imx_add_imx_uart_1irq(
const struct imx_imx_uart_1irq_data *data,
const struct imxuart_platform_data *pdata)
{
struct resource res[] = {
{
.start = data->iobase,
.end = data->iobase + data->iosize - ,
.flags = IORESOURCE_MEM,
}, {
.start = data->irq,
.end = data->irq,
.flags = IORESOURCE_IRQ,
},
};
return imx_add_platform_device("imx-uart", data->id, res, ARRAY_SIZE(res),
pdata, sizeof(*pdata)); |
} |
|
V
static inline struct platform_device *imx_add_platform_device(
const char *name, int id,
const struct resource *res, unsigned int num_resources,
const void *data, size_t size_data)
{
return imx_add_platform_device_dmamask(
name, id, res, num_resources, data, size_data, );
}
2、驱动注册
kernel/drivers/tty/serial/imx.c
static int __init imx_serial_init(void)
{
int ret;
printk(KERN_INFO "Serial: IMX driver\n");
ret = uart_register_driver(&imx_reg); ----------------------------+
if (ret) | |
return ret; +----------------------------+ |
//注册uart驱动 | |
ret = platform_driver_register(&serial_imx_driver); | |
if (ret != ) | | |
uart_unregister_driver(&imx_reg); | | |
+--------------|-------|----+
return ; | | |
} | | |
| | |
static struct uart_driver imx_reg = { <----------------------+ | |
.owner = THIS_MODULE, | |
.driver_name = DRIVER_NAME, // IMX-uart | |
.dev_name = DEV_NAME, // ttymxc | |
.major = SERIAL_IMX_MAJOR, // 207 | |
.minor = MINOR_START, // 16 | |
.nr = ARRAY_SIZE(imx_ports), | |
.cons = IMX_CONSOLE, | |
}; | |
| |
// kernel/drivers/tty/serial/serial_core.c | |
int uart_register_driver(struct uart_driver *drv) <----------+ |
{ |
struct tty_driver *normal; |
int i, retval; |
|
BUG_ON(drv->state); |
//分配uart_state空间,一个串口对应一个 |
/* |
* Maybe we should be using a slab cache for this, especially if |
* we have a large number of ports to handle. |
*/ |
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); |
if (!drv->state) |
goto out; |
//所有的串口只有一个tty_driver |
normal = alloc_tty_driver(drv->nr); |
if (!normal) |
goto out_kfree; |
|
drv->tty_driver = normal; |
|
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 = ; |
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; |
/* tty_driver和uart_driver结构体关联, |
* 当驱动和设备匹配之后,调用probe函数, |
* |
*/ |
normal->driver_state = drv; |
tty_set_operations(normal, &uart_ops); |
|
/* |
* Initialise the UART state(s). |
*/ |
for (i = ; i < drv->nr; i++) { |
struct uart_state *state = drv->state + i; |
struct tty_port *port = &state->port; |
|
tty_port_init(port); |
port->ops = &uart_port_ops; |
port->close_delay = ; /* .5 seconds */ |
port->closing_wait = ; /* 30 seconds */ |
tasklet_init(&state->tlet, uart_tasklet_action, |
(unsigned long)state); |
} |
|
retval = tty_register_driver(normal); ------------------------+ |
if (retval >= ) | |
return retval; | |
| |
put_tty_driver(normal); | |
out_kfree: | |
kfree(drv->state); | |
out: | |
return -ENOMEM; | |
} | |
// kernel/drivers/tty/tty_io.c | |
int tty_register_driver(struct tty_driver *driver) <-------------------+ |
{ |
int error; |
int i; |
dev_t dev; |
void **p = NULL; |
struct device *d; |
|
if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { |
p = kzalloc(driver->num * * sizeof(void *), GFP_KERNEL); |
if (!p) |
return -ENOMEM; |
} |
|
if (!driver->major) { |
error = alloc_chrdev_region(&dev, driver->minor_start, |
driver->num, driver->name); |
if (!error) { |
driver->major = MAJOR(dev); |
driver->minor_start = MINOR(dev); |
} |
} else { |
dev = MKDEV(driver->major, driver->minor_start); |
/* 为设备分配主设备号和次设备号 |
* 主设备号207,次设备号从16开始 |
* 所以注册的串口设备次设备号是16,17,18,19... |
*/ |
error = register_chrdev_region(dev, driver->num, driver->name); |
} |
if (error < ) { |
kfree(p); |
return error; |
} |
|
if (p) { |
driver->ttys = (struct tty_struct **)p; |
driver->termios = (struct ktermios **)(p + driver->num); |
} else { |
driver->ttys = NULL; |
driver->termios = NULL; |
} |
|
cdev_init(&driver->cdev, &tty_fops); |
driver->cdev.owner = driver->owner; |
error = cdev_add(&driver->cdev, dev, driver->num); |
if (error) { |
unregister_chrdev_region(dev, driver->num); |
driver->ttys = NULL; |
driver->termios = NULL; |
kfree(p); |
return error; |
} |
|
mutex_lock(&tty_mutex); |
list_add(&driver->tty_drivers, &tty_drivers); |
mutex_unlock(&tty_mutex); |
|
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { |
for (i = ; i < driver->num; i++) { |
d = tty_register_device(driver, i, NULL); |
if (IS_ERR(d)) { |
error = PTR_ERR(d); |
goto err; |
} |
} |
} |
proc_tty_register_driver(driver); |
driver->flags |= TTY_DRIVER_INSTALLED; |
return ; |
|
err: |
for (i--; i >= ; i--) |
tty_unregister_device(driver, i); |
|
mutex_lock(&tty_mutex); |
list_del(&driver->tty_drivers); |
mutex_unlock(&tty_mutex); |
|
unregister_chrdev_region(dev, driver->num); |
driver->ttys = NULL; |
driver->termios = NULL; |
kfree(p); |
return error; |
} |
|
static struct platform_driver serial_imx_driver = { <----------------+
.probe = serial_imx_probe,
.remove = serial_imx_remove,
.suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.driver = {
.name = "imx-uart",
.owner = THIS_MODULE,
},
};
//当设备和驱动匹配之后,会调用probe函数,其中就指定了读写函数
static int serial_imx_probe(struct platform_device *pdev)
{
... ...
sport->port.ops = &imx_pops; -------+
... ... |
//将初始化用到的uart_driver和imx_port关联起来 |
//最终就将操作imx_pops结构体定义的函数与上层应用关联起来 |
//初始化的时候,就已经将uart_driver与tty_driver关联 |
ret = uart_add_one_port(&imx_reg, &sport->port); -------+ |
} | |
| |
static struct uart_ops imx_pops = { <-----|----+
.tx_empty = imx_tx_empty, |
.set_mctrl = imx_set_mctrl, |
.get_mctrl = imx_get_mctrl, |
.stop_tx = imx_stop_tx, |
.start_tx = imx_start_tx, |
.stop_rx = imx_stop_rx, |
.enable_ms = imx_enable_ms, |
.break_ctl = imx_break_ctl, |
.startup = imx_startup, |
.shutdown = imx_shutdown, |
.set_termios = imx_set_termios, |
.type = imx_type, |
.release_port = imx_release_port, |
.request_port = imx_request_port, |
.config_port = imx_config_port, |
.verify_port = imx_verify_port, |
#if defined(CONFIG_CONSOLE_POLL) |
.poll_get_char = imx_poll_get_char, |
.poll_put_char = imx_poll_put_char, |
#endif |
}; |
V
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = ;
struct device *tty_dev;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
state->uart_port = uport;
state->pm_state = -;
uport->cons = drv->cons;
uport->state = state;
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
uart_configure_port(drv, state, uport);
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this ports parameters.
*/
tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
if (likely(!IS_ERR(tty_dev))) {
device_set_wakeup_capable(tty_dev, );
} else {
printk(KERN_ERR "Cannot register tty device on line %d\n",
uport->line);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
3、 上层调用
kernel/drivers/tty/tty_io.c
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
// kernel/drivers/tty/tty_io.c
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->ops->write ||
(test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
tty->driver->name);
ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write)
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld); |
return ret; |
} |
V
static inline ssize_t do_tty_write(
ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
struct tty_struct *tty,
struct file *file,
const char __user *buf,
size_t count)
{
ssize_t ret, written = ;
unsigned int chunk;
ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
if (ret < )
return ret;
chunk = ;
if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
chunk = ;
if (count < chunk)
chunk = count;
/* write_buf/write_cnt is protected by the atomic_write_lock mutex */
if (tty->write_cnt < chunk) {
unsigned char *buf_chunk;
if (chunk < )
chunk = ;
buf_chunk = kmalloc(chunk, GFP_KERNEL);
if (!buf_chunk) {
ret = -ENOMEM;
goto out;
}
kfree(tty->write_buf);
tty->write_cnt = chunk;
tty->write_buf = buf_chunk;
}
/* Do the write .. */
for (;;) {
size_t size = count;
if (size > chunk)
size = chunk;
ret = -EFAULT;
if (copy_from_user(tty->write_buf, buf, size))
break;
ret = write(tty, file, tty->write_buf, size); -----------+
if (ret <= ) |
break; |
written += ret; |
buf += ret; |
count -= ret; |
if (!count) |
break; |
ret = -ERESTARTSYS; |
if (signal_pending(current)) |
break; |
cond_resched(); |
} |
if (written) { |
struct inode *inode = file->f_path.dentry->d_inode; |
inode->i_mtime = current_fs_time(inode->i_sb); |
ret = written; |
} |
out: |
tty_write_unlock(tty); |
return ret; |
} |
V
//kernel/drivers/tty/n_tty.c
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = ;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
/* Write out any echoed characters that are still pending */
process_echoes(tty);
add_wait_queue(&tty->write_wait, &wait);
while () {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > ) {
ssize_t num = process_output_block(tty, b, nr);
if (num < ) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == )
break;
c = *b;
if (process_output(c, tty) < )
break;
b++; nr--;
}
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
while (nr > ) {
c = tty->ops->write(tty, b, nr); -------------------+
if (c < ) { |
retval = c; |
goto break_out; |
} |
if (!c) |
break; |
b += c; |
nr -= c; |
} |
} |
if (!nr) |
break; |
if (file->f_flags & O_NONBLOCK) { |
retval = -EAGAIN; |
break; |
} |
schedule(); |
} |
break_out: |
__set_current_state(TASK_RUNNING); |
remove_wait_queue(&tty->write_wait, &wait); |
if (b - buf != nr && tty->fasync) |
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); |
return (b - buf) ? b - buf : retval; |
} |
// kernel/drivers/tty/serial/imx.c |
static void |
imx_console_write(struct console *co, const char *s, unsigned int count) <-+
{
struct imx_port *sport = imx_ports[co->index];
struct imx_port_ucrs old_ucr;
unsigned int ucr1;
unsigned long flags;
int locked = ;
local_irq_save(flags);
if (sport->port.sysrq)
locked = ;
else
spin_lock(&sport->port.lock);
/*
* First, save UCR1/2/3 and then disable interrupts
*/
imx_port_ucrs_save(&sport->port, &old_ucr);
ucr1 = old_ucr.ucr1;
if (cpu_is_mx1())
ucr1 |= MX1_UCR1_UARTCLKEN;
ucr1 |= UCR1_UARTEN;
ucr1 &= ~(UCR1_TXMPTYEN | UCR1_RRDYEN | UCR1_RTSDEN);
writel(ucr1, sport->port.membase + UCR1);
writel(old_ucr.ucr2 | UCR2_TXEN, sport->port.membase + UCR2);
uart_console_write(&sport->port, s, count, imx_console_putchar);
/*
* Finally, wait for transmitter to become empty
* and restore UCR1/2/3
*/
while (!(readl(sport->port.membase + USR2) & USR2_TXDC));
imx_port_ucrs_restore(&sport->port, &old_ucr);
if (locked)
spin_unlock(&sport->port.lock);
local_irq_restore(flags);
}