且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

I.MX6_Linux_UART_device&driver_hacking

更新时间:2022-02-28 03:18:24

/******************************************************************************************
 *                        I.MX6_Linux_UART_device&driver_hacking
 * 声明:
 *    1. 目录脚本生成: 
 *        grep -v '^\s' I.MX6_UART_hacking.txt | grep '\.'
 *    2. 该文章是在vim中编辑,请尽量是用vim来阅读,这样就不会出现缩进不对齐的问题;
 *    3. 本人阅读源码使用了ctags,强烈建议您使用类似高效的工具进行内核源代码阅读;
 *    3. 文章中的(device)表示的是串口设备侧的代码跟踪部分;
 *    4. 文章中的(driver)表示的是串口驱动侧的代码跟踪部分;
 *    5. 内核中的有些宏写成了函数的形式,从使用者的角度来说,个人感觉称作宏也行,
 *        叫函数也行,不过本人强烈希望你理解宏与函数的区别;   :)
 *    6. device跟踪架构如下:
 *      .( arch/arm/mach-mx6/board-mx6q_sabresd.c )
 *      \ -- mx6_sabresd_board_init()
 *          |
 *          | -- static iomux_v3_cfg_t mx6dl_sabresd_pads[] 
 *          |   \ -- #define MX6DL_PAD_CSI0_DAT10__UART1_TXD
 *          |       \ -- #define IOMUX_PAD(_pad_ctrl_ofs, _mux_ctrl_ofs,...)
 *          |           \ -- typedef u64 iomux_v3_cfg_t
 *          |
 *          | -- mxc_iomux_v3_setup_multiple_pads()
 *          |   \ -- int mxc_iomux_v3_setup_pad(iomux_v3_cfg_t pad)
 *          |       \ -- static inline void __raw_writel(u32 b, volatile void __iomem *addr)
 *          |
 *          \ -- mx6q_sabresd_init_uart()
 *              \ -- #define imx6q_add_imx_uart(id, pdata)
 *                  | -- const struct imx_imx_uart_1irq_data imx6q_imx_uart_data[] __initconst
 *                  |   \ -- #define imx6q_imx_uart_data_entry(_id, _hwid)
 *                  |       \ -- #define imx_imx_uart_1irq_data_entry(soc, _id, _hwid, _size)
 *                  |           | -- #define SZ_4K      0x00001000
 *                  |           | -- #define MX6Q_UART3_BASE_ADDR  UART3_BASE_ADDR
 *                  |           \ -- #define MXC_INT_UART3_ANDED        60
 *                  |
 *                  \ -- struct platform_device *__init imx_add_imx_uart_1irq(...)
 *                      \ -- static inline struct platform_device *imx_add_platform_device(...)
 *                          \ -- struct platform_device *__init imx_add_platform_device_dmamask(...)
 *                   
 *    7. driver跟踪架构如下:
 *      .( drivers/tty/serial/imx.c )
 *      \ -- module_init(imx_serial_init);
 *          \ -- static int __init imx_serial_init(void)
 *              | -- int uart_register_driver(struct uart_driver *drv)
 *              | -- static struct uart_driver imx_reg
 *              |   | -- struct uart_driver
 *              |   \ -- #define DEV_NAME   "ttymxc"
 *              |
 *              \ -- static struct platform_driver serial_imx_driver
 *                  \ -- static int serial_imx_probe(struct platform_device *pdev)
 *                      \ -- int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 *                          \ -- struct device *tty_register_device(...)
 *                              \ -- static void tty_line_name(...)
 *
 *
 *                                  2015-4-12 周日 晴 深圳 南山 西丽平山村 曾剑锋
 *****************************************************************************************/

           \\\\\\\\\\\\\\\\\\\\\\\\\\\--*目录*--/////////////////////////
           |  一. 需求说明:                                             |
           |  二. 参考文章:                                             |
           |  三. (device)从板级文件开始跟踪代码:                       |
           |  四. (device)跟踪mx6dl_sabresd_pads参数:                   |
           |  五. (device)跟踪mxc_iomux_v3_setup_multiple_pads()函数:   |
           |  六. (device)跟踪mx6q_sabresd_init_uart()函数:             |
           |  七. (device)跟踪imx6q_imx_uart_data参数:                  |
           |  八. (device)跟踪imx_add_imx_uart_1irq()函数:              |
           |  九. (driver)串口驱动跟踪:                                 |
           |  十. (driver)跟踪imx_reg参数:                              |
           |  十一. (driver)跟踪uart_register_driver()函数:             |
           |  十二. (driver)跟踪serial_imx_driver参数:                  |
           |  十三. Android下打开串口节点可能出现权限问题:              |
           \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////

一. 需求说明:
    1. 了解I.MX6 Linux内核是如何在板级文件中注册UART设备(device);
    2. 了解I.MX6 Linux内核是如何加载UART驱动(driver);
    3. 了解I.MX6 Linux内核串口设备节点为什么有ttymxc这个前缀;

二. 参考文章:
    1. MarS Board i.MX6 IOMUX解析:
        url: http://www.embest-tech.cn/community/thread-7822-1-1.html
    2. MarS Board-I.MX6 串口注册过程解析:
        url: http://www.embest-tech.cn/community/forum.php?mod=viewthread&tid=7825
    3. datasheet: IMX6DQRM_revC.pdf
    4. 内核源代码: myzr_android4_2_2_1_1_0.tar.bz2中的Linux内核

三. (device)从板级文件开始跟踪代码:
    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        /*
         * initialize __mach_desc_MX6Q_SABRESD data structure.
         */
        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
    
    2. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        /**
         * Board specific initialization.
         */
        static void __init mx6_sabresd_board_init(void)
        {
            ......
            /**
             * 1. 这部分有3个方向需要跟踪:
             *     1. mx6dl_sabresd_pads : 我们需要知道它为什么存在
             *     2. mxc_iomux_v3_setup_multiple_pads() : 同上
             *     3. mx6q_sabresd_init_uart() : 同上       
             * 2. 综上所述,我们下面分别对以上3种情况进行分析.
             */
            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参数
                mxc_iomux_v3_setup_multiple_pads(mx6dl_sabresd_pads,
                    ARRAY_SIZE(mx6dl_sabresd_pads)); 
            }
            ......
            mx6q_sabresd_init_uart();  //跟踪uart初始化函数
            ......
        }
        ......

四. (device)跟踪mx6dl_sabresd_pads参数:
    1. cat arch/arm/mach-mx6/board-mx6dl_sabresd.h
        ......
        static iomux_v3_cfg_t mx6dl_sabresd_pads[] = {
            ......
            /* UART1 for debug */
            MX6DL_PAD_CSI0_DAT10__UART1_TXD, //跟踪这个宏
            MX6DL_PAD_CSI0_DAT11__UART1_RXD,

            /* UART2*/
            MX6DL_PAD_EIM_D26__UART2_TXD,
            MX6DL_PAD_EIM_D27__UART2_RXD,

            /* UART3 for gps */
            MX6DL_PAD_EIM_D24__UART3_TXD,
            MX6DL_PAD_EIM_D25__UART3_RXD,

            /* UART4*/
            MX6DL_PAD_KEY_COL0__UART4_TXD,
            MX6DL_PAD_KEY_ROW0__UART4_RXD,

            /* UART5*/
            MX6DL_PAD_KEY_COL1__UART5_TXD,
            MX6DL_PAD_KEY_ROW1__UART5_RXD,
            ......
        }
        ......

    2. cat arch/arm/plat-mxc/include/mach/iomux-mx6dl.h
        ......
        #define MX6DL_PAD_CSI0_DAT10__UART1_TXD                             \
                IOMUX_PAD(0x0360, 0x004C, 3, 0x0000, 0, MX6DL_UART_PAD_CTRL) 跟踪这个函数,或者说宏
        ......

    3. cat arch/arm/plat-mxc/include/mach/iomux-v3.h
        ......
        /**
         * 跟踪iomux_v3_cfg_t数据结构.
         * 这里相当于数据合成存放在一个64位的数据中,包含了应该有的所有的数据
         */
        #define IOMUX_PAD(_pad_ctrl_ofs, _mux_ctrl_ofs, _mux_mode, _sel_input_ofs, \
                _sel_input, _pad_ctrl)                    \
            (((iomux_v3_cfg_t)(_mux_ctrl_ofs) << MUX_CTRL_OFS_SHIFT) |    \
                ((iomux_v3_cfg_t)(_mux_mode) << MUX_MODE_SHIFT) |    \
                ((iomux_v3_cfg_t)(_pad_ctrl_ofs) << MUX_PAD_CTRL_OFS_SHIFT) | \
                ((iomux_v3_cfg_t)(_pad_ctrl) << MUX_PAD_CTRL_SHIFT) |    \
                ((iomux_v3_cfg_t)(_sel_input_ofs) << MUX_SEL_INPUT_OFS_SHIFT) | \
                ((iomux_v3_cfg_t)(_sel_input) << MUX_SEL_INPUT_SHIFT))
        ......

    4. cat arch/arm/plat-mxc/include/mach/iomux-v3.h
        ......
        typedef u64 iomux_v3_cfg_t;
        ......

五. (device)跟踪mxc_iomux_v3_setup_multiple_pads()函数:
    1. cat arch/arm/plat-mxc/iomux-v3.c
        ......
        int mxc_iomux_v3_setup_multiple_pads(iomux_v3_cfg_t *pad_list, unsigned count)
        {
            iomux_v3_cfg_t *p = pad_list;
            int i;
            int ret;

            for (i = 0; i < count; i++) {
                ret = mxc_iomux_v3_setup_pad(*p); //跟踪该函数
                if (ret)
                    return ret;
                p++;
            }
            return 0;
        }
        ......

    2. cat arch/arm/plat-mxc/iomux-v3.c  
        ......
        /*
         * configures a single pad in the iomuxer
         */
        int mxc_iomux_v3_setup_pad(iomux_v3_cfg_t pad)
        {
            u32 mux_ctrl_ofs = (pad & MUX_CTRL_OFS_MASK) >> MUX_CTRL_OFS_SHIFT;
            u32 mux_mode = (pad & MUX_MODE_MASK) >> MUX_MODE_SHIFT;
            u32 sel_input_ofs = (pad & MUX_SEL_INPUT_OFS_MASK) >> MUX_SEL_INPUT_OFS_SHIFT;
            u32 sel_input = (pad & MUX_SEL_INPUT_MASK) >> MUX_SEL_INPUT_SHIFT;
            u32 pad_ctrl_ofs = (pad & MUX_PAD_CTRL_OFS_MASK) >> MUX_PAD_CTRL_OFS_SHIFT;
            u32 pad_ctrl = (pad & MUX_PAD_CTRL_MASK) >> MUX_PAD_CTRL_SHIFT;

            if (mux_ctrl_ofs)
                __raw_writel(mux_mode, base + mux_ctrl_ofs);  //跟踪这个函数

            if (sel_input_ofs)
                __raw_writel(sel_input, base + sel_input_ofs);

            if (!(pad_ctrl & NO_PAD_CTRL) && pad_ctrl_ofs)
                __raw_writel(pad_ctrl, base + pad_ctrl_ofs);

            return 0;
        }
        ......

    3. cat include/asm-generic/io.h  (找了个好理解的例子 :) )
        ......
        #ifndef __raw_writel
        static inline void __raw_writel(u32 b, volatile void __iomem *addr)
        {
            *(volatile u32 __force *) addr = b;
        }
        #endif
        ......

六. (device)跟踪mx6q_sabresd_init_uart()函数:
    1. cat arch/arm/mach-mx6/board-mx6q_sabresd.c
        ......
        static inline void mx6q_sabresd_init_uart(void)
        {
            imx6q_add_imx_uart(0, NULL); //跟踪imx6q_add_imx_uart宏,你也可以说是函数
            imx6q_add_imx_uart(1, NULL);
            imx6q_add_imx_uart(2, NULL);
            imx6q_add_imx_uart(3, NULL);
            imx6q_add_imx_uart(4, NULL);
        }
        ......
    
    2. cat arch/arm/mach-mx6/devices-imx6q.h
        ......
        extern const struct imx_imx_uart_1irq_data imx6q_imx_uart_data[] __initconst;
        /**
         * 1. 这里需要分别跟踪两个方向:
         *     1. imx6q_imx_uart_data : 我们需要知道它为什么存在
         *     2. imx_add_imx_uart_1irq() : 同上
         * 2. 综上所述,下面分别对跟踪上面的两种情况.
         */
        #define imx6q_add_imx_uart(id, pdata)    \
            imx_add_imx_uart_1irq(&imx6q_imx_uart_data[id], pdata)
        ......

七. (device)跟踪imx6q_imx_uart_data参数:
    1. cat arch/arm/plat-mxc/devices/platform-imx-uart.c
        ......
        #ifdef CONFIG_SOC_IMX6Q
        const struct imx_imx_uart_1irq_data imx6q_imx_uart_data[] __initconst = {
        /** 
         * 1. 分别跟踪以下内容:
         *     1. imx_imx_uart_1irq_data_entry : 跟踪这个宏,也可以说是函数
         *     2. SZ_4K : 跟踪这个宏
         * 2. 综上所述,下面分别对跟踪上面的两种情况,但由于内容比较简单,
         *     所以就不单独分出一小节来跟踪代码.   :)
         */
        #define imx6q_imx_uart_data_entry(_id, _hwid)                \
            imx_imx_uart_1irq_data_entry(MX6Q, _id, _hwid, SZ_4K) 
            imx6q_imx_uart_data_entry(0, 1),  //跟踪这个宏,也可以说是函数
            imx6q_imx_uart_data_entry(1, 2),
            imx6q_imx_uart_data_entry(2, 3),
            imx6q_imx_uart_data_entry(3, 4),
            imx6q_imx_uart_data_entry(4, 5),
        };
        #endif /* ifdef CONFIG_SOC_IMX6Q */
        ......

    2. cat arch/arm/plat-mxc/devices/platform-imx-uart.c
        ......
        /**
         * 1. 如果传入的参数如下:
         *      1. soc : MX6Q
         *      2. _id : 2
         *      3. _hwid : 3
         *      4. _size : SZ_4k
         * 2. 那么:
         *      1. .iobase = soc ## _UART ## _hwid ## _BASE_ADDR 合成结果:
         *          .iobase = MX6Q_UART3_BASE_ADDR = 0x21EC000
         *      2. .irq = soc ## _INT_UART ## _hwid 合成结果:
         *          .rq = MX6Q_INT_UART3
         *
         * 3. 如下是IMX6DQRM_revC.pdf第219页UART3的基地址:
         *   ------------------------------------------------------------
         *   | Start Address | End Address | Region | Allocation | Size |
         *   +---------------+-------------+--------+------------+------+
         *   |   021E_C000   |  021E_FFFF  | AIPS-2 |   UART3    | 16KB |
         *   ------------------------------------------------------------
         * 4. 如3表格中所示,SZ_4k和表中Size(16KB)不一致,目前不知道是为何.
         * 5. 有可能你不知道怎么去找到宏MX6Q_UART3_BASE_ADDR和MX6Q_INT_UART3,
         *      你可以在内核源码根目录下执行shell命令知道宏在哪个文件中:
         *      grep MX6Q_UART3_BASE_ADDR * -R
         */
        #define imx_imx_uart_1irq_data_entry(soc, _id, _hwid, _size) \
            [_id] = {                            \
                .id = _id,                        \
                .iobase = soc ## _UART ## _hwid ## _BASE_ADDR, \  //跟踪合成的宏,可以得到基地址
                .iosize = _size,                    \
                .irq = soc ## _INT_UART ## _hwid,    \    //跟踪合成的宏,可以得到irq
            }
        ......
    
    3. cat include/asm-generic/sizes.h
        ......
        #define SZ_4K      0x00001000
        ......
    
    4. cat arch/arm/plat-mxc/include/mach/mx6.h
        ......
        /**
         * 1. 如下主要是为了获取MX6Q芯片UART3的基地址制,推算过程如下:
         *     MX6Q_UART3_BASE_ADDR = UART3_BASE_ADDR
         *     MX6Q_UART3_BASE_ADDR = (AIPS2_OFF_BASE_ADDR + 0x6C000)
         *     MX6Q_UART3_BASE_ADDR = ((ATZ2_BASE_ADDR + 0x80000) + 0x6C000)
         *     MX6Q_UART3_BASE_ADDR = ((AIPS2_ARB_BASE_ADDR + 0x80000) + 0x6C000)
         *     MX6Q_UART3_BASE_ADDR = ((0x02100000 + 0x80000) + 0x6C000)
         *     MX6Q_UART3_BASE_ADDR = ((0x02100000 + 0x80000) + 0x6C000)
         *     MX6Q_UART3_BASE_ADDR = 0x21EC000
         * 2. 如1中演算所得,符合IMX6DQRM_revC.pdf第219页UART3的基地址:
         */
        #define MX6Q_UART3_BASE_ADDR        UART3_BASE_ADDR
        ......
        #define UART3_BASE_ADDR             (AIPS2_OFF_BASE_ADDR + 0x6C000)
        ......
        /* ATZ#2- Off Platform */
        #define AIPS2_OFF_BASE_ADDR         (ATZ2_BASE_ADDR + 0x80000)
        ......
        #define ATZ2_BASE_ADDR              AIPS2_ARB_BASE_ADDR
        ......
        #define AIPS2_ARB_BASE_ADDR         0x02100000
        ......
    
    5. cat arch/arm/plat-mxc/include/mach/mx6.h
        ......
        #define MX6Q_INT_UART3            MXC_INT_UART3_ANDED
        ......
        /*
         * 如下是IMX6DQRM_revC.pdf第226页UART3的中断号:
         *     -------------------------------------------------------------
         *     | RQ | Interrupt | Interrupt Description                    |
         *     |    |  Source   |                                          |
         *     +----+-----------+------------------------------------------+
         *     | 60 |   UART3   | Logical OR of UART3 interrupt requests.  |
         *     -------------------------------------------------------------
         */
        #define MXC_INT_UART3_ANDED        60
        ......
    
八. (device)跟踪imx_add_imx_uart_1irq()函数:
    1. cat arch/arm/plat-mxc/devices/platform-imx-uart.c    
        ......
        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 - 1,
                    .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));  //跟踪这个函数
        }
        ......

    2. cat arch/arm/plat-mxc/include/mach/devices-common.h
        ......
        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, 0); //跟踪这个函数
        }
        ......

    3. cat arch/arm/plat-mxc/devices.c
        ......
        struct platform_device *__init imx_add_platform_device_dmamask(
                const char *name, int id,
                const struct resource *res, unsigned int num_resources,
                const void *data, size_t size_data, u64 dmamask)
        {
            int ret = -ENOMEM;
            struct platform_device *pdev;
        
            //分配一个平台设备,名字叫做"imx-uart"
            pdev = platform_device_alloc(name, id);
            if (!pdev)
                goto err;
        
            //传入的参数是0,不用关心
            if (dmamask) {
                /*
                 * This memory isn't freed when the device is put,
                 * I don't have a nice idea for that though.  Conceptually
                 * dma_mask in struct device should not be a pointer.
                 * See http://thread.gmane.org/gmane.linux.kernel.pci/9081
                 */
                pdev->dev.dma_mask =
                    kmalloc(sizeof(*pdev->dev.dma_mask), GFP_KERNEL);
                if (!pdev->dev.dma_mask)
                    /* ret is still -ENOMEM; */
                    goto err;
        
                *pdev->dev.dma_mask = dmamask;
                pdev->dev.coherent_dma_mask = dmamask;
            }
            //往平台设备中添加平台设备资源,有利于设备驱动获取平台设备数据
            if (res) {
                ret = platform_device_add_resources(pdev, res, num_resources);
                if (ret)
                    goto err;
            }
        
            //传入的参数是NULL,不用关心
            if (data) {
                ret = platform_device_add_data(pdev, data, size_data);
                if (ret)
                    goto err;
            }
        
            /** 
             * 其实到这里可以结束了,后面是平台设备总线的实现方式,这里就不做跟踪了
             * 有兴趣的,可以自己去跟踪  :)
             */
            ret = platform_device_add(pdev);  
            if (ret) {
        err:
                if (dmamask)
                    kfree(pdev->dev.dma_mask);
                platform_device_put(pdev);
                return ERR_PTR(ret);
            }
        
            return pdev;
        }
        ......

九. (driver)串口驱动跟踪:
    1. cat drivers/tty/serial/imx.c
        ......
        module_init(imx_serial_init); //跟踪初始化函数
        module_exit(imx_serial_exit);
        
        MODULE_AUTHOR("Sascha Hauer");
        MODULE_DESCRIPTION("IMX generic serial port driver");
        MODULE_LICENSE("GPL");
        MODULE_ALIAS("platform:imx-uart");
        ......

    2. cat drivers/tty/serial/imx.c
        ......
        static int __init imx_serial_init(void)
        {
            int ret;

            printk(KERN_INFO "Serial: IMX driver\n");

            /**
             * 接下来需要跟踪以下三个方向:
             *     1. imx_reg : 我们需要知道它为什么存在
             *     2. serial_imx_driver: 理由同上
             *     3. uart_register_driver(): 理由同上
             */
            ret = uart_register_driver(&imx_reg);  //跟踪这个函数,参数,参数类型
            if (ret)
                return ret;

            ret = platform_driver_register(&serial_imx_driver); //跟踪serial_imx_driver参数
            if (ret != 0)
                uart_unregister_driver(&imx_reg);

            return 0;
        }
        ......

十. (driver)跟踪imx_reg参数:
    1. cat drivers/tty/serial/imx.c
        ......
        static struct uart_driver imx_reg = {           //跟踪结构体
                .owner          = THIS_MODULE,
                .driver_name    = DRIVER_NAME,
                /**
                 * 如果你想知道设备节点为什么有ttymxc前缀,那么你需要注意这个地方,
                 * 将来创建设备节点的时候,会用到这个名字来连接字符串,这个字符串
                 * 的值: ttymxc
                 */
                .dev_name       = DEV_NAME,            //跟踪DEV_NAME
                .major          = SERIAL_IMX_MAJOR,
                .minor          = MINOR_START,
                .nr             = ARRAY_SIZE(imx_ports),
                .cons           = IMX_CONSOLE,
            };
        ......

    2. cat include/linux/serial_core.h
        ......
        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;
        };
        ......

    3. cat drivers/tty/serial/imx.c
        ......
        #define DEV_NAME        "ttymxc"
        ......

十一. (driver)跟踪uart_register_driver()函数:
    cat drivers/tty/serial/imx.c
        /**
         * 串口驱动仅跟踪到这个函数,目前不做进行更进一步的跟踪
         */
        ......
        int uart_register_driver(struct uart_driver *drv)
        {
            ......
            /**
             * 非常关键的一行代码,后面serial_imx_probe中要生成的设备
             * 节点,需要用到normal->name,因为这里面报存了drv->dev_name,
             * 也就是: ttymxc
             */
            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;

            //初始波特率是9600,8,n,1
            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);
            ......
        }

十二. (driver)跟踪serial_imx_driver参数:
    1. cat drivers/tty/serial/imx.c
        ......
        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,
            },
        };
        ......

    2. cat drivers/tty/serial/imx.c
        ......
        static int serial_imx_probe(struct platform_device *pdev)
        {
            ......
            //获取前面的设备(device)资源数据
            res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
            if (!res) {
                ret = -ENODEV;
                goto free;
            }
            ......
            sport->port.line = pdev->id;    //后面要用到这个数字,和串口前缀合成节点名字
            ......
            imx_ports[pdev->id] = sport;  
            ......
            /**
             * 注意这里传入的参数imx_reg,里面有设备节点的字符串前缀
             */
            ret = uart_add_one_port(&imx_reg, &sport->port); //跟踪这个函数
            ......
        }
        ......

    3. cat drivers/tty/serial/serial_core.c
        ......
        int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
        {
            ......
            /*
             * 感觉这里很有用,以后可能会用到
             * 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.
             */
            /**
             * 1. 初始化函数imx_serial_init(void)已经通过uart_register_driver()初始化好了drv->tty_driver,
             *     其中drv->tty_driver->name的值: ttymxc
             * 2. uport->line = pdev->id, 用于和串口前缀合成串口节点名;
             */
            tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev); //跟踪这个函数
            ......
        }
        ......

    4. cat drivers/tty/tty_io.c
        ......
        struct device *tty_register_device(struct tty_driver *driver, unsigned index,
                           struct device *device)
        {
            ......
            /**
             * 查看前面的int uart_register_driver(struct uart_driver *drv)函数中的设置可知:
             * driver->type  = TTY_DRIVER_TYPE_SERIAL;
             */
            if (driver->type == TTY_DRIVER_TYPE_PTY)
                pty_line_name(driver, index, name);
            else
                tty_line_name(driver, index, name); 跟踪这个函数

            return device_create(tty_class, device, dev, NULL, name);   //到头了
        }
        ......

    5. cat drivers/tty/tty_io.c
        ......
        static void tty_line_name(struct tty_driver *driver, int index, char *p)
        {
            /**
             * 到了目的地了,也就是我想知道的设备节点的名字  :)
             */
            sprintf(p, "%s%d", driver->name, index + driver->name_base);
        }
        ......

十三. Android下打开串口节点可能出现权限问题:
    1. 可能会收到: you do not hava read/write permission to the serial port;
    2. 也可能会收到: 打开串口失败,没有串口读/写权限;
    3. 这里目前提供的解决方法是:
        1. 在串口shell中输入: chmod 777 <你的设备节点>
            例如: chmod 777 /dev/ttymxc2
        2. 上面只是针对本次有效,重启板子都不行,如果想要一直有效,就在init.rc文件
            里添加设备节点的权限.