Linux kernel 之 uart 驱动解析

  • uart 是一种非常之常见的总线,比如DEBUG信息输出,小数据量数据传输,485,以及蓝牙的控制,GPS,很多都是通过uart 进行数据传输并进行控制。

  • 在Linux kernel 内部,uart 通常是作为 一个 tty 设备对其进行控制,也是就是一个字符设备文件,可对其进行读写操作。

  • kernel version 4.4.12

  • 首先先看一下基本的 结构体 和 API 操作。

    // include/linux/serial_core.h
    
    // uart 驱动结构体
    struct uart_driver {
        struct module       *owner;
        const char      *driver_name; // 驱动名字
        const char      *dev_name;    // 设备名字
        int          major;          //  主设备号
        int          minor;          //  次设备号
        int          nr;
        struct console      *cons;    // 看似控制台结构体                                              
    
        /*
         * these are private; the low level driver should not
         * touch these; they should be initialised to NULL
         */
        struct uart_state   *state;    // 状态结构体
        struct tty_driver   *tty_driver; // tty 驱动结构体
    };  
    
    struct uart_port {
        spinlock_t      lock;           /* port lock */
        unsigned long       iobase;         /* in/out[bwl] */
        unsigned char __iomem   *membase;       /* read/write[bwl] */
        unsigned int        (*serial_in)(struct uart_port *, int);
        void            (*serial_out)(struct uart_port *, int, int);
        void            (*set_termios)(struct uart_port *,
                               struct ktermios *new,
                               struct ktermios *old);
        void            (*set_mctrl)(struct uart_port *, unsigned int);
        int         (*startup)(struct uart_port *port);
        void            (*shutdown)(struct uart_port *port);
        void            (*throttle)(struct uart_port *port);
        void            (*unthrottle)(struct uart_port *port);
        int         (*handle_irq)(struct uart_port *);
        void            (*pm)(struct uart_port *, unsigned int state,
                      unsigned int old);
        void            (*handle_break)(struct uart_port *);
        int         (*rs485_config)(struct uart_port *,
                        struct serial_rs485 *rs485);
        unsigned int        irq;            /* irq number */
        unsigned long       irqflags;       /* irq flags  */
        unsigned int        uartclk;        /* base uart clock */
        unsigned int        fifosize;       /* tx fifo size */
        unsigned char       x_char;         /* xon/xoff char */
        unsigned char       regshift;       /* reg offset shift */
        unsigned char       iotype;         /* io access style */
        unsigned char       unused1;                                                
    
    #define UPIO_PORT       (SERIAL_IO_PORT)    /* 8b I/O port access */
    #define UPIO_HUB6       (SERIAL_IO_HUB6)    /* Hub6 ISA card */
    #define UPIO_MEM        (SERIAL_IO_MEM)     /* 8b MMIO access */
    #define UPIO_MEM32      (SERIAL_IO_MEM32)   /* 32b little endian */
    #define UPIO_AU         (SERIAL_IO_AU)      /* Au1x00 and RT288x type IO */
    #define UPIO_TSI        (SERIAL_IO_TSI)     /* Tsi108/109 type IO */
    #define UPIO_MEM32BE        (SERIAL_IO_MEM32BE) /* 32b big endian */            
    
        unsigned int        read_status_mask;   /* driver specific */
        unsigned int        ignore_status_mask; /* driver specific */
        struct uart_state   *state;         /* pointer to parent state */
        struct uart_icount  icount;         /* statistics */                        
    
        struct console      *cons;          /* struct console, if any */
    #if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
        unsigned long       sysrq;          /* sysrq timeout */
    #endif                                                                          
    
        /* flags must be updated while holding port mutex */
        upf_t           flags;                                                      
    
        /*
         * These flags must be equivalent to the flags defined in
         * include/uapi/linux/tty_flags.h which are the userspace definitions
        * assigned from the serial_struct flags in uart_set_info()
         * [for bit definitions in the UPF_CHANGE_MASK]
         *
         * Bits [0..UPF_LAST_USER] are userspace defined/visible/changeable
         * except bit 15 (UPF_NO_TXEN_TEST) which is masked off.
         * The remaining bits are serial-core specific and not modifiable by
         * userspace.
         */
    #define UPF_FOURPORT        ((__force upf_t) ASYNC_FOURPORT       /* 1  */ )
    #define UPF_SAK         ((__force upf_t) ASYNC_SAK            /* 2  */ )
    #define UPF_SPD_HI      ((__force upf_t) ASYNC_SPD_HI         /* 4  */ )
    #define UPF_SPD_VHI     ((__force upf_t) ASYNC_SPD_VHI        /* 5  */ )
    #define UPF_SPD_CUST        ((__force upf_t) ASYNC_SPD_CUST   /* 0x0030 */ )
    #define UPF_SPD_WARP        ((__force upf_t) ASYNC_SPD_WARP   /* 0x1010 */ )
    #define UPF_SPD_MASK        ((__force upf_t) ASYNC_SPD_MASK   /* 0x1030 */ )
    #define UPF_SKIP_TEST       ((__force upf_t) ASYNC_SKIP_TEST      /* 6  */ )
    #define UPF_AUTO_IRQ        ((__force upf_t) ASYNC_AUTO_IRQ       /* 7  */ )
    #define UPF_HARDPPS_CD      ((__force upf_t) ASYNC_HARDPPS_CD     /* 11 */ )
    #define UPF_SPD_SHI     ((__force upf_t) ASYNC_SPD_SHI        /* 12 */ )
    #define UPF_LOW_LATENCY     ((__force upf_t) ASYNC_LOW_LATENCY    /* 13 */ )
    #define UPF_BUGGY_UART      ((__force upf_t) ASYNC_BUGGY_UART     /* 14 */ )
    #define UPF_NO_TXEN_TEST    ((__force upf_t) (1 << 15))
    #define UPF_MAGIC_MULTIPLIER    ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )
    
    /* Port has hardware-assisted h/w flow control */
    #define UPF_AUTO_CTS        ((__force upf_t) (1 << 20))
    #define UPF_AUTO_RTS        ((__force upf_t) (1 << 21))
    #define UPF_HARD_FLOW       ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
    /* Port has hardware-assisted s/w flow control */
    #define UPF_SOFT_FLOW       ((__force upf_t) (1 << 22))
    #define UPF_CONS_FLOW       ((__force upf_t) (1 << 23))
    #define UPF_SHARE_IRQ       ((__force upf_t) (1 << 24))
    #define UPF_EXAR_EFR        ((__force upf_t) (1 << 25))
    #define UPF_BUG_THRE        ((__force upf_t) (1 << 26))
    /* The exact UART type is known and should not be probed.  */
    #define UPF_FIXED_TYPE      ((__force upf_t) (1 << 27))
    #define UPF_BOOT_AUTOCONF   ((__force upf_t) (1 << 28))
    #define UPF_FIXED_PORT      ((__force upf_t) (1 << 29))
    #define UPF_DEAD        ((__force upf_t) (1 << 30))
    #define UPF_IOREMAP     ((__force upf_t) (1 << 31))                             
    
    #define __UPF_CHANGE_MASK   0x17fff
    #define UPF_CHANGE_MASK     ((__force upf_t) __UPF_CHANGE_MASK)
    #define UPF_USR_MASK        ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))    
    
    #if __UPF_CHANGE_MASK > ASYNC_FLAGS
    #error Change mask not equivalent to userspace-visible bit defines
    #endif                                                                          
    
        /*                                                                               * Must hold termios_rwsem, port mutex and port lock to change;                  * can hold any one lock to read.                                                */
        upstat_t        status;                                                     
    
    #define UPSTAT_CTS_ENABLE   ((__force upstat_t) (1 << 0))
    #define UPSTAT_DCD_ENABLE   ((__force upstat_t) (1 << 1))
    #define UPSTAT_AUTORTS      ((__force upstat_t) (1 << 2))
    #define UPSTAT_AUTOCTS      ((__force upstat_t) (1 << 3))
    #define UPSTAT_AUTOXOFF     ((__force upstat_t) (1 << 4))                       
    
        int         hw_stopped;     /* sw-assisted CTS flow state */
        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 int        minor;
        resource_size_t     mapbase;        /* for ioremap */
        resource_size_t     mapsize;
        struct device       *dev;           /* parent device */
        unsigned char       hub6;           /* this should be in the 8250 driver */
        unsigned char       suspended;
        unsigned char       irq_wake;
        unsigned char       unused[2];
        struct attribute_group  *attr_group;        /* port specific attributes */
        const struct attribute_group **tty_groups;  /* all attributes (serial core use only) */
        struct serial_rs485     rs485;
        void            *private_data;      /* generic platform data pointer */
    };                                                                              
    
    // uart 驱动注册
    intuart_register_driver(struct uart_driver *uart);
    // uart 驱动注销
    voiduart_unregister_driver(struct uart_driver *uart); 
    
    intuart_add_one_port(struct uart_driver *reg, struct uart_port *port);
    intuart_remove_one_port(struct uart_driver *reg, struct uart_port *port);
    intuart_match_port(struct uart_port *port1, struct uart_port *port2);          
  • 通过实例看一下具体是怎么实现一个完整的 uart 驱动的

    // drivers/tty/serial/omap-serial.c
    // 在文件最后,还是老套路
    module_init(serial_omap_init);
    module_exit(serial_omap_exit);                                                  
    
    MODULE_DESCRIPTION("OMAP High Speed UART driver");
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Texas Instruments Inc");  
    
    // 让我们跟到 serial_omap_init
    static int __init serial_omap_init(void)
    {
        int ret;
        //  uart 驱动注册
        ret = uart_register_driver(&serial_omap_reg);
        if (ret != 0)
            return ret;
        // uart 平台驱动注册
        ret = platform_driver_register(&serial_omap_driver);
        if (ret != 0)
            uart_unregister_driver(&serial_omap_reg);
        return ret;
    } 
    
    // serial_omap_reg
    static struct uart_driver serial_omap_reg = {
        .owner      = THIS_MODULE,
        .driver_name    = "OMAP-SERIAL",
        .dev_name   = OMAP_SERIAL_NAME,
        .nr     = OMAP_MAX_HSUART_PORTS,
        .cons       = OMAP_CONSOLE,
    };   
    
    // serial_omap_driver
    static struct platform_driver serial_omap_driver = {
        .probe          = serial_omap_probe,   // probe 开始函数
        .remove         = serial_omap_remove,
        .driver     = {
            .name   = DRIVER_NAME,
            .pm = &serial_omap_dev_pm_ops,
            .of_match_table = of_match_ptr(omap_serial_of_match), // of_match_table 匹配函数
        },
    }; 
    
    // probe 函数
    static int serial_omap_probe(struct platform_device *pdev)
    {
        struct omap_uart_port_info *omap_up_info = dev_get_platdata(&pdev->dev);
        struct uart_omap_port *up;
        struct resource *mem;
        void __iomem *base;
        int uartirq = 0;
        int wakeirq = 0;
        int ret;                                                                    
    
        //  获取相关信息
        /* The optional wakeirq may be specified in the board dts file */
        if (pdev->dev.of_node) {
            uartirq = irq_of_parse_and_map(pdev->dev.of_node, 0);
            if (!uartirq)
                return -EPROBE_DEFER;
            wakeirq = irq_of_parse_and_map(pdev->dev.of_node, 1);
            omap_up_info = of_get_uart_port_info(&pdev->dev);
            pdev->dev.platform_data = omap_up_info;
        } else {
            uartirq = platform_get_irq(pdev, 0);
            if (uartirq < 0)
                return -EPROBE_DEFER;
        }                                                                           
    
        up = devm_kzalloc(&pdev->dev, sizeof(*up), GFP_KERNEL);
        if (!up)
            return -ENOMEM;                                                         
    
        // 内存,地址
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        base = devm_ioremap_resource(&pdev->dev, mem);
        if (IS_ERR(base))
            return PTR_ERR(base);                                                   
    
        up->dev = &pdev->dev;
        up->port.dev = &pdev->dev;
        up->port.type = PORT_OMAP;
        up->port.iotype = UPIO_MEM;
        up->port.irq = uartirq;
        up->port.regshift = 2;
        up->port.fifosize = 64;
        up->port.ops = &serial_omap_pops;                                           
    
        if (pdev->dev.of_node)  // 获取id
            ret = of_alias_get_id(pdev->dev.of_node, "serial");
        else
            ret = pdev->id;                                                         
    
        if (ret < 0) {
            dev_err(&pdev->dev, "failed to get alias/pdev id, errno %d\n",
                ret);
            goto err_port_line;
        }
            up->port.line = ret;                                                        
    
        if (up->port.line >= OMAP_MAX_HSUART_PORTS) {
            dev_err(&pdev->dev, "uart ID %d >  MAX %d.\n", up->port.line,
                OMAP_MAX_HSUART_PORTS);
            ret = -ENXIO;
            goto err_port_line;
        }                                                                           
    
        up->wakeirq = wakeirq;
        if (!up->wakeirq)
            dev_info(up->port.dev, "no wakeirq for uart%d\n",
                 up->port.line);                                                    
    
        ret = serial_omap_probe_rs485(up, pdev->dev.of_node);
        if (ret < 0)
            goto err_rs485;
        // 设置相关信息
        sprintf(up->name, "OMAP UART%d", up->port.line);
        up->port.mapbase = mem->start;
        up->port.membase = base;
        up->port.flags = omap_up_info->flags;
        up->port.uartclk = omap_up_info->uartclk;
        up->port.rs485_config = serial_omap_config_rs485;
        if (!up->port.uartclk) {
            up->port.uartclk = DEFAULT_CLK_SPEED;
            dev_warn(&pdev->dev,
                 "No clock speed specified: using default: %d\n",
                 DEFAULT_CLK_SPEED);
        }                                                                           
    
        up->latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
        up->calc_latency = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE;
        pm_qos_add_request(&up->pm_qos_request,
            PM_QOS_CPU_DMA_LATENCY, up->latency);
        INIT_WORK(&up->qos_work, serial_omap_uart_qos_work);                        
    
        platform_set_drvdata(pdev, up);
        if (omap_up_info->autosuspend_timeout == 0)
            omap_up_info->autosuspend_timeout = -1;                                 
    
        device_init_wakeup(up->dev, true);
        pm_runtime_use_autosuspend(&pdev->dev);
        pm_runtime_set_autosuspend_delay(&pdev->dev,
            omap_up_info->autosuspend_timeout);                                 
    
        pm_runtime_irq_safe(&pdev->dev);
        pm_runtime_enable(&pdev->dev);                                              
    
        pm_runtime_get_sync(&pdev->dev);                                            
    
        omap_serial_fill_features_erratas(up);                                      
    
        ui[up->port.line] = up;
        serial_omap_add_console_port(up);                                           
    
        ret = uart_add_one_port(&serial_omap_reg, &up->port);
        if (ret != 0)
            goto err_add_port;                                                      
    
        pm_runtime_mark_last_busy(up->dev);
        pm_runtime_put_autosuspend(up->dev);
        // John add.    这个是另外的,不属于原生驱动
        #define GPIO_TO_PIN(bank, gpio)     (32 * (bank) + (gpio))
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,22), "omap-serial");
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,23), "omap-serial");
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,19), "omap-serial");
        devm_gpio_request(up->dev, GPIO_TO_PIN(0,12), "omap-serial");
        gpio_direction_output(GPIO_TO_PIN(0,22),1); //COM0_MODE_0=1
        gpio_direction_output(GPIO_TO_PIN(0,23),0); //COM0_MODE_1=0
        gpio_direction_output(GPIO_TO_PIN(0,19),0); //COM0_TERM=0
        gpio_direction_output(GPIO_TO_PIN(0,12),1); //LVDS_BLKT_ON=1
        return 0;                                                                   
    
    err_add_port:
        pm_runtime_put(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
        pm_qos_remove_request(&up->pm_qos_request);
        device_init_wakeup(up->dev, false);
    err_rs485:
    err_port_line:
        return ret;
    }                                                                                                                                                                                                                                                                                                                                                            
  • 看一下 uart_register_driver 内部是怎么实现的

    int uart_register_driver(struct uart_driver *drv)
    {
        struct tty_driver *normal;   // 主要是对这个 tty 驱动结构体进行了初始化。
        int i, retval;                                                              
    
        BUG_ON(drv->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 驱动结构体
        normal = alloc_tty_driver(drv->nr);
        if (!normal)
            goto out_kfree;                                                         
    
        drv->tty_driver = normal;                                                   
    
        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;                                   
    
            tty_port_init(port);
            port->ops = &uart_port_ops;
        }
        // tty  驱动的注册
        retval = tty_register_driver(normal);
        if (retval >= 0)
            return retval;                                                          
    
        for (i = 0; i < drv->nr; i++)
            tty_port_destroy(&drv->state[i].port);
        put_tty_driver(normal);
    out_kfree:
        kfree(drv->state);
    out:
        return -ENOMEM;
    }                                                                               
时间: 2024-11-01 21:21:49

Linux kernel 之 uart 驱动解析的相关文章

linux kernel的cmdline参数解析原理分析

利用工作之便,今天研究了kernel下cmdline参数解析过程,记录在此,与大家共享,转载请注明出处,谢谢. Kernel 版本号:3.4.55 Kernel启动时会解析cmdline,然后根据这些参数如console root来进行配置运行. Cmdline是由bootloader传给kernel,如uboot,将需要传给kernel的参数做成一个tags链表放在ram中,将首地址传给kernel,kernel解析tags来获取cmdline等信息. Uboot传参给kernel以及kern

linux kernel的cmdline參数解析原理分析

利用工作之便,今天研究了kernel下cmdline參数解析过程.记录在此.与大家共享.转载请注明出处.谢谢. Kernel 版本:3.4.55 Kernel启动时会解析cmdline,然后依据这些參数如console root来进行配置执行. Cmdline是由bootloader传给kernel.如uboot.将须要传给kernel的參数做成一个tags链表放在ram中,将首地址传给kernel,kernel解析tags来获取cmdline等信息. Uboot传參给kernel以及kerne

Linux kernel中断子系统之(五):驱动申请中断API

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin

移植MT7620A+MT7610E驱动到Openwrt trunk(Linux Kernel 3.14.18)(续:MT7620A)

按照上一篇的内容修改文件重新编译后不会报错,但是烧到flash里后运行的时候有问题,如下: [ 16.840000] mt7620: module license 'unspecified' taints kernel. [ 16.840000] Disabling lock debugging due to kernel taint [ 16.870000] mt7620: Unknown symbol ra_mtd_write_nm (err 0) [ 16.880000] mt7620:

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环

[总结]给pcDuino v2编译Linux kernel

1.版本问题 推荐选择pcdunio提供的官方的kernel. 当然可以选用www.github.com/linux-sunxi 中的kernel,不过有很多驱动都用不了包括arduino. 我尝试了以下的几个版本,3.29,3.79,3.90,下面是我这些时间的总结,会慢慢更新. 2.具体问题 (1)gen_initramfs_list.sh的问题 Cannot open '../../linux-sunxi/rootfs/rootfs.cpio.xz' make[1]: *** [usr/i

基于tiny4412的Linux内核移植 -- MMA7660驱动移植(九)

作者信息 作者: 彭东林 邮箱:[email protected] QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本:Linux-4.4.0 (支持device tree) u-boot版本:友善之臂自带的 U-Boot 2010.12 (为支持uImage启动,做了少许改动) busybox版本:busybox 1.25 交叉编译工具链: arm-none-linux-gnueabi-gcc (gcc version 4

linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理

UART驱动分析

在linux用户层上要操作底层串口需要对/dev/ttySxxx操作,这里的ttySx指实际的终端串口. 以下以全志A64为实例,分析UART驱动以及浅谈TTY架构. linux-3.10/drivers/tty/serial/sunxi-uart.c: 1 static const struct of_device_id sunxi_uart_match[] = { 2 { .compatible = "allwinner,sun8i-uart", }, 3 { .compatibl