linux串口驱动分析——打开设备

  串口驱动是由tty_driver架构实现的。一个应用程序中的函数要操作硬件,首先会经过tty,级级调用之后才会到达驱动之中。本文先介绍应用程序中打开设备的open函数的整个历程。

  首先在串口初始化中会先注册一个串口驱动,函数原型为

  int uart_register_driver(struct uart_driver *drv)

  在这个函数中会调用注册tty驱动的函数

  int tty_register_driver(struct tty_driver *driver)
  {
    ...
    cdev_init(&driver->cdev, &tty_fops);
    ...
  }

从这一句代码可以看出串口实质上也是一个字符设备。用soucesight对参数tty_fops进行回溯,可以找出应用程序与tty架构的函数调用关系表file_operations

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,
};

可以看出应用程序中的open函数实际上是tty架构中的tty_open函数,查看该函数

static int tty_open(struct inode *inode, struct file *filp)
{
  struct tty_struct *tty = NULL;
    int noctty, retval;
    struct tty_driver *driver;
    int index;
    dev_t device = inode->i_rdev;
    unsigned saved_flags = filp->f_flags;
  ...
  if (tty->ops->open)
  ...
}

这里调用到了tty->ops中的open函数,是struct tty_operations类型的,实际上是uart_ops这一结构

static const struct tty_operations uart_ops = {
    .open        = uart_open,
    ...
};

可以看出这里又调用到了uart_open函数

static int uart_open(struct tty_struct *tty, struct file *filp)
{
  ...
  retval = uart_startup(tty, state, 0);
  ...
}
 
static int uart_startup(struct tty_struct *tty, struct uart_state *state, int init_hw)
{
  struct uart_port *uport = state->uart_port;
    struct tty_port *port = &state->port;
    unsigned long page;
    int retval = 0;
  ...
  retval = uport->ops->startup(uport);
  ...
}

层层调用之后到这里,调用到uport结构中的函数,uport为struct uart_port类型,每一个uart_port对应一个串口设备,也就是说这里已经调用到了底层驱动的startup函数。在串口初始化时用数组来初始化uart_port

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,
        }
    },
    ...
}

函数操作集

static struct uart_ops s3c24xx_serial_ops = {
    .pm        = s3c24xx_serial_pm,
    .tx_empty    = s3c24xx_serial_tx_empty,
    .get_mctrl    = s3c24xx_serial_get_mctrl,
    .set_mctrl    = s3c24xx_serial_set_mctrl,
    .stop_tx    = s3c24xx_serial_stop_tx,
    .start_tx    = s3c24xx_serial_start_tx,
    .stop_rx    = s3c24xx_serial_stop_rx,
    .enable_ms    = s3c24xx_serial_enable_ms,
    .break_ctl    = s3c24xx_serial_break_ctl,
    .startup    = s3c24xx_serial_startup,
    .shutdown    = s3c24xx_serial_shutdown,
    .set_termios    = s3c24xx_serial_set_termios,
    .type        = s3c24xx_serial_type,
    .release_port    = s3c24xx_serial_release_port,
    .request_port    = s3c24xx_serial_request_port,
    .config_port    = s3c24xx_serial_config_port,
    .verify_port    = s3c24xx_serial_verify_port,
};

所以,retval = uport->ops->startup(uport);这里最终调用了s3c24xx_serial_startup函数,真相基本上已经浮出水面。应用程序中的open函数通过tty架构,层层调用,最后调用到了samsung.c驱动文件中的s3c24xx_serial_startup函数。

static int s3c24xx_serial_startup(struct uart_port *port)
{
    struct s3c24xx_uart_port *ourport = to_ourport(port);
    int ret;

    dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)
",
        port->mapbase, port->membase);

    rx_enabled(port) = 1;

    ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
              s3c24xx_serial_portname(port), ourport);

    if (ret != 0) {
        printk(KERN_ERR "cannot get irq %d
", ourport->rx_irq);
        return ret;
    }

    ourport->rx_claimed = 1;

    dbg("requesting tx irq...
");

    tx_enabled(port) = 1;

    ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
              s3c24xx_serial_portname(port), ourport);

    if (ret) {
        printk(KERN_ERR "cannot get irq %d
", ourport->tx_irq);
        goto err;
    }

    ourport->tx_claimed = 1;

    dbg("s3c24xx_serial_startup ok
");

    /* the port reset code should have done the correct
     * register setup for the port controls */

    return ret;

 err:
    s3c24xx_serial_shutdown(port);
    return ret;

这个函数主要做了四件事情,代码已高亮标出:

  1、打开接收使能

  2、注册数据接收中断

  3、打开发送使能

  4、注册数据发送中断

至此,linux串口驱动程序打开设备的实现已分析完毕。如果有疑问或建议,欢迎指出。

原文地址:https://www.cnblogs.com/51qianrushi/p/4323656.html