tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)

作者:彭东林

邮箱:[email protected]

开发板:tiny4412ADK+S700 4GB Flash

主机:Wind7 64位

虚拟机:Vmware+Ubuntu12_04

u-boot:U-Boot 2010.12

Linux内核版本:linux-3.0.31

Android版本:android-4.1.2

在arch/arm/mach-exynos/mach-tiny4412.c中:

MACHINE_START(TINY4412, "TINY4412")
    .boot_params    = S5P_PA_SDRAM + 0x100,
    .init_irq    = exynos4_init_irq,
    .map_io        = smdk4x12_map_io,
    .init_machine    = smdk4x12_machine_init,
    .timer        = &exynos4_timer,
    .reserve    = &exynos4_reserve,
MACHINE_END

在文件arch/arm/kernel/setup.c中:

static int __init customize_machine(void)
{
    if (machine_desc->init_machine)
        machine_desc->init_machine();
    return 0;
}
arch_initcall(customize_machine);

在文件arch/arm/plat-samsung/init.c中:

static int __init s3c_arch_init(void)
{
    int ret;
…
    ret = (cpu->init)();
…
    ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
    return ret;
}

arch_initcall(s3c_arch_init);

这几个函数跟uart有关,我们先看一下内核启动的时候是如何调用这个函数:

start_kernel   (init/main.c)
   --- setup_arch  (arch/arm/kernel/setup.c)
        --- paging_init  (arch/arm/mm/mmu-cma.c)
             --- devicemaps_init (arch/arm/mm/mmu-cma.c)
                  --- mdesc->map_io() ----- smdk4x12_map_io
   --- rest_init
             ---kernel_init
                      --- do_pre_smp_initcalls (init/main.c)
                      --- do_basic_setup
                                  --- do_initcalls
static void __init do_pre_smp_initcalls(void)
{
    initcall_t *fn;

    for (fn = __initcall_start; fn < __early_initcall_end; fn++)
        do_one_initcall(*fn);
}
static void __init do_initcalls(void)
{
    initcall_t *fn;

    for (fn = __early_initcall_end; fn < __initcall_end; fn++)
        do_one_initcall(*fn);
// 在这里会调用customize_machine和s3c_arch_init
}

在文件include/linux/init.h中:

#define __define_initcall(level,fn,id)     static initcall_t __initcall_##fn##id __used     __attribute__((__section__(".initcall" level ".init"))) = fn

#define arch_initcall(fn)        __define_initcall("3",fn,3)

对于arch_initcall(customize_machine)展开后就是:

    static initcall_t __initcall_ customize_machine3 __used     __attribute__((__section__(".initcall3.init"))) = customize_machine

在arch/arm/kernel/vmlinux.lds中:

  __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcallbresume.init) *(.initcallresume.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;

通过上面的分析,大致知道这几个函数的调用过程了。

下面继续分析:

static void __init smdk4x12_map_io(void)
{
    clk_xusbxti.rate = 24000000;
// 宏S5P_VA_CHIPID的值是S3C_ADDR(0x02000000)
    s5p_init_io(NULL, 0, S5P_VA_CHIPID);  // 获取cpu id
    s3c24xx_init_clocks(24000000);       // 初始化时钟资源
    s3c24xx_init_uarts(smdk4x12_uartcfgs, ARRAY_SIZE(smdk4x12_uartcfgs));
/*
初始化uart,此时并没有进行注册,只是填充了一些结构体,提供给注册时用,数组smdk4x12_uartcfgs中是关于每一个uart控制器的寄存器默认参数,如:
static struct s3c2410_uartcfg smdk4x12_uartcfgs[] __initdata = {
    [0] = {
        .hwport        = 0,
        .flags        = 0,
        .ucon        = SMDK4X12_UCON_DEFAULT,
        .ulcon        = SMDK4X12_ULCON_DEFAULT,
        .ufcon        = SMDK4X12_UFCON_DEFAULT,
    },
...
}
*/
    exynos4_reserve_mem();  // 预留内存
}

下面我们一一分析上面的每个函数

  • s5p_init_io 函数
void __init s5p_init_io(struct map_desc *mach_desc,
            int size, void __iomem *cpuid_addr)
{
    /* initialize the io descriptors we need for initialization */
/*
 数组s5p_iodesc中记录了一些物理地址和虚拟地址的对应关系,如:
static struct map_desc s5p_iodesc[] __initdata = {
    {
        .virtual    = (unsigned long)S5P_VA_CHIPID,
        .pfn        = __phys_to_pfn(S5P_PA_CHIPID),
        .length        = SZ_4K,
        .type        = MT_DEVICE,
    },
……
}

*/
    iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc)); // 静态映射内存资源
    if (mach_desc)
        iotable_init(mach_desc, size);

    /* detect cpu id and rev.
这里cpuid_addr的值是S5P_VA_CHIPID,从上面的代码可以知道S5P_VA_CHIPID对应的物理地址是S5P_PA_CHIPID,即0x10000000,这个是exynos4412的PRO_ID寄存器,通过在u-boot读取到寄存器0x10000000的值,注意:由于在u-boot中开启了mmu,需要判断这个物理地址0x10000000对应的虚拟机地址是多少,还好,我们的u-boot中将物理地址0x0000_0000 -- 0x1FFF_FFFF 映射到了0x0000_0000 -- 0x1FFF_FFFF,所以我们直接使用命令 md 0x10000000 即可,我试了一下,结果如下:
TINY4412 # md 0x10000000 0x4
10000000: e4412011 08051008 00000009 00000010    . A............
*/
    s5p_init_cpu(cpuid_addr);  // 读取cpu_id
/*
下面是函数s5p_init_cpu的实现
void __init s5p_init_cpu(void __iomem *cpuid_addr)
{
    samsung_cpu_id = __raw_readl(cpuid_addr);
    samsung_cpu_rev = samsung_cpu_id & 0xFF;
}
所以samsung_cpu_id的值是 0xe4412011,samsung_cpu_id的值是0x11
*/

    s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));
/*
数组cpu_ids中列出了一些类samsung的soc芯片的信息,关于exynos4412的信息如下:
static struct cpu_table cpu_ids[] __initdata = {
    ...
    {
        .idcode        = EXYNOS4412_CPU_ID,  // 0xE4412200
        .idmask        = EXYNOS_CPU_MASK,   // 0xFFFE0000
        .map_io        = exynos4_map_io,
        .init_clocks    = exynos4_init_clocks,
        .init_uarts    = exynos4_init_uarts,
        .init        = exynos4_init,
        .name        = name_exynos4412,    // "EXYNOS4412"

    },
    ...
}
*/

}

下面的函数的目的是从cpu_ids中找到与samsung_cpu_id匹配的数组元素

void __init s3c_init_cpu(unsigned long idcode,
             struct cpu_table *cputab, unsigned int cputab_size)
{
    cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);
/* 下面是函数s3c_lookup_cpu的实现,目的就是从上面的cpu_ids中找到了跟刚才读到的cpu id相等的数组元素
static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode,
                        struct cpu_table *tab,
                        unsigned int count)
{
    for (; count != 0; count--, tab++) {
        if ((idcode & tab->idmask) == (tab->idcode & tab->idmask))
            return tab;
    }

    return NULL;
}
通过上面的循环就可以找到exynos4412对应的cpu_ids
*/
…
    cpu->map_io();  // 调用的是exynos4_map_io
}
  • s3c24xx_init_clocks 函数
void __init s3c24xx_init_clocks(int xtal)
{
…
        (cpu->init_clocks)(xtal); // 调用的是exynos4_init_clocks,初始化系统时钟资源
}
  • s3c24xx_init_uarts

cfg指向了一个数组,no是数组的元素个数,cfg数组中每一项对应了一个uart控制器的寄存器默认配置参数

void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
…
        (cpu->init_uarts)(cfg, no);  // 调用函数exynos4_init_uarts
}

在文件arch/arm/plat-s5p/include/plat/exynos4.h中:

#define exynos4_init_uarts exynos_common_init_uarts
void __init exynos_common_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
    struct s3c2410_uartcfg *tcfg = cfg;
    u32 ucnt;
// 下面这个循环的目的是为每一个uart指定之中源
    for (ucnt = 0; ucnt < no; ucnt++, tcfg++) {
        if (!tcfg->clocks) {
            tcfg->has_fracval = 1;
            tcfg->clocks = exynos_serial_clocks;
            tcfg->clocks_size = ARRAY_SIZE(exynos_serial_clocks);
        }
        tcfg->flags |= NO_NEED_CHECK_CLKSRC;
    }
// s5p_uart_resources数组中存放的是每一个uart控制器的使用的寄存器资源、中断资源
    s3c24xx_init_uartdevs("s5pv210-uart", s5p_uart_resources, cfg, no);
}

下面这个函数的目的是填充每一个uart控制器对应的platform_device

void __init s3c24xx_init_uartdevs(char *name,
                  struct s3c24xx_uart_resources *res,
                  struct s3c2410_uartcfg *cfg, int no)
{
    struct platform_device *platdev;
    struct s3c2410_uartcfg *cfgptr = uart_cfgs;
    struct s3c24xx_uart_resources *resp;
    int uart;

    memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);

    for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {
        platdev = s3c24xx_uart_src[cfgptr->hwport]; // 每一个uart对应一个platform_device

        resp = res + cfgptr->hwport;  // 找到编号为cfgptr->hwport的uart对应的res资源地址

        s3c24xx_uart_devs[uart] = platdev; // 将来会注册其中的每一个platform_device

        platdev->name = name;  // "s5pv210-uart",将来会执行同名的platform_driver的probe函数
        platdev->resource = resp->resources; // platform_device对应的资源
        platdev->num_resources = resp->nr_resources; // platform_device对应的资源格式

        platdev->dev.platform_data = cfgptr;  // 每个uart控制器的寄存器默认值
    }

    nr_uarts = no;  // uart个数
}

至此,注册uart设备所需要的条件都已准备好,其实就是填充每一个uart对应的platform_device结构体,下面开始注册:

static int __init s3c_arch_init(void)
{
    int ret;
…
    ret = (cpu->init)();  // 调用函数exynos4_init
    …
    ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
    return ret;
}

至此uart对应的platform_device已经注册完成了,下面开始分析uart对应的platform_driver,关于部分代码在drivers/tty/serial/s5pv210.c中:

static struct platform_driver s5p_serial_driver = {
    .probe        = s5p_serial_probe,
    .remove        = __devexit_p(s3c24xx_serial_remove),
    .driver        = {
        .name    = "s5pv210-uart",
        .owner    = THIS_MODULE,
    },
};
static int __init s5p_serial_init(void)
{
// s5p_uart_inf 数组中每个元素对应一个uart控制器的fifo配置
    return s3c24xx_serial_init(&s5p_serial_driver, *s5p_uart_inf);
}
static struct s3c24xx_uart_info *s5p_uart_inf[] = {
    [0] = &s5p_port_fifo256,
…
};
static struct s3c24xx_uart_info s5p_port_fifo256 = {
    S5PV210_UART_DEFAULT_INFO(256),
};
#define S5PV210_UART_DEFAULT_INFO(fifo_size)            \
        .name        = "Samsung S5PV210 UART0",            .type        = PORT_S3C6400,                    .fifosize    = fifo_size,                    .has_divslot    = 1,                        .rx_fifomask    = S5PV210_UFSTAT_RXMASK,            .rx_fifoshift    = S5PV210_UFSTAT_RXSHIFT,            .rx_fifofull    = S5PV210_UFSTAT_RXFULL,            .tx_fifofull    = S5PV210_UFSTAT_TXFULL,            .tx_fifomask    = S5PV210_UFSTAT_TXMASK,            .tx_fifoshift    = S5PV210_UFSTAT_TXSHIFT,            .get_clksrc    = s5pv210_serial_getsource,            .set_clksrc    = s5pv210_serial_setsource,            .reset_port    = s5pv210_serial_resetport

看来驱动的注册是在s3c24xx_serial_init中完成的:

int s3c24xx_serial_init(struct platform_driver *drv,
            struct s3c24xx_uart_info *info)
{
    drv->suspend = s3c24xx_serial_suspend;  // 休眠函数
    drv->resume = s3c24xx_serial_resume;    // 唤醒函数
    return platform_driver_register(drv);
}

然后s5p_serial_probe就会获得执行,可以知道,由于有4个uart,所以这个函数会执行四次。

static int s5p_serial_probe(struct platform_device *pdev)
{
    return s3c24xx_serial_probe(pdev, s5p_uart_inf[pdev->id]);
}

在文件drivers/tty/serial/samsung.c中:

static int probe_index;

int s3c24xx_serial_probe(struct platform_device *dev,
             struct s3c24xx_uart_info *info)
{
    struct s3c24xx_uart_port *ourport;
    int ret;

    ourport = &s3c24xx_serial_ports[probe_index];
    probe_index++;

    ret = s3c24xx_serial_init_port(ourport, info, dev);
…
    uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
    platform_set_drvdata(dev, &ourport->port);

    ret = device_create_file(&dev->dev, &dev_attr_clock_source);
…
    ret = s3c24xx_serial_cpufreq_register(ourport);
…
    return 0;
…
}

要继续分析这个函数,就需要先分析drivers/tty/serial/samsung.c,因为uart_add_one_port依赖uart_register_driver。

下面我们分析samsung.c:

static int __init s3c24xx_serial_modinit(void)
{
    int ret;
    ret = uart_register_driver(&s3c24xx_uart_drv);
…
    return 0;
}

结构体s3c24xx_uart_drv定义如下:

static struct uart_driver s3c24xx_uart_drv = {
    .owner        = THIS_MODULE,
    .driver_name    = "s3c2410_serial",
    .nr        = CONFIG_SERIAL_SAMSUNG_UARTS,  // 4
    .cons        = S3C24XX_SERIAL_CONSOLE,
    .dev_name    = S3C24XX_SERIAL_NAME,   // “ttySAC”
    .major        = S3C24XX_SERIAL_MAJOR,  // 204
    .minor        = S3C24XX_SERIAL_MINOR,  // 64
};

#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console

下面这个结构体会在register_console时调用,将来内核中的printk就会调用这个结构体中的write函数向串口终端中输出信息

static struct console s3c24xx_serial_console = {
    .name        = S3C24XX_SERIAL_NAME,  // “ttySAC”
    .device        = uart_console_device,
    .flags        = CON_PRINTBUFFER,
    .index        = -1,
    .write        = s3c24xx_serial_console_write,
    .setup        = s3c24xx_serial_console_setup,
    .data        = &s3c24xx_uart_drv,
};

下面分析uart_register_driver

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal;
    int i, retval;

…
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); // 4
…
    normal = alloc_tty_driver(drv->nr);  // normal->num = 4;
/* 函数alloc_tty_driver的定义如下:
struct tty_driver *alloc_tty_driver(int lines)
{
    struct tty_driver *driver;

    driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
    if (driver) {
        kref_init(&driver->kref);
        driver->magic = TTY_DRIVER_MAGIC;
        driver->num = lines;
        /* later we‘ll move allocation of tables here */
    }
    return driver;
}
*/
…
    drv->tty_driver = normal;

    normal->owner        = drv->owner;
    normal->driver_name    = drv->driver_name;  // "s3c2410_serial"
    normal->name        = drv->dev_name;    // “ttySAC”
    normal->major        = drv->major;        // 204
    normal->minor_start    = drv->minor;        // 64
    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);  // normal->ops = &uart_ops

    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;
        port->close_delay     = 500;    /* .5 seconds */
        port->closing_wait    = 30000;    /* 30 seconds */
        tasklet_init(&state->tlet, uart_tasklet_action,
                 (unsigned long)state);
    }

    retval = tty_register_driver(normal);
…
    put_tty_driver(normal);
out_kfree:
    kfree(drv->state);
out:
    return -ENOMEM;
}

下面分析函数tty_register_driver:

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 * 2 * sizeof(void *), GFP_KERNEL);
…
    }

    if (!driver->major) {
...
    } else {
/*
 这里会申请4个设备号,从204开始,被名为“ttySAC”的设备占有
 通过命令 cat /proc/devices 可以看到,但是此时还没有在/dev/下生成ttySAC0~4这些设备号
*/
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }
…

    if (p) {
        driver->ttys = (struct tty_struct **)p;
        driver->termios = (struct ktermios **)(p + driver->num);
    } else {
…
    }

    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
…
    list_add(&driver->tty_drivers, &tty_drivers);  // tty_drivers 是一个全局双向循环链表
    …
    proc_tty_register_driver(driver); // 在/proc下生成相关的的文件
    driver->flags |= TTY_DRIVER_INSTALLED;
    return 0;
…
}

下面分析proc_tty_register_driver

void proc_tty_register_driver(struct tty_driver *driver)
{
    struct proc_dir_entry *ent;

…
    // driver_name是“s3c2410_serial”
    ent = proc_create_data(driver->driver_name, 0, proc_tty_driver,
                   driver->ops->proc_fops, driver);
    driver->proc_entry = ent;
}

其中,proc_tty_driver是其父结点,通过看他的父结点的创建可以知道将来到/proc下的去找名为” s3c2410_serial”的结点

start_kernel

----  proc_root_init   (fs/proc/root.c)

---- proc_tty_init  (fs/proc/proc_tty.c)

void __init proc_tty_init(void)
{
    if (!proc_mkdir("tty", NULL))
        return;
    proc_tty_ldisc = proc_mkdir("tty/ldisc", NULL);
    proc_tty_driver = proc_mkdir_mode("tty/driver", S_IRUSR|S_IXUSR, NULL);
    proc_create("tty/ldiscs", 0, NULL, &tty_ldiscs_proc_fops);
    proc_create("tty/drivers", 0, NULL, &proc_tty_drivers_operations);
}

从这里可以知道,将来会在/proc/tty/driver下面生成结点s3c2410_serial

[email protected]:/ # ls /proc/tty/driver/ -l
-r--r--r-- root     root            0 2014-01-01 19:20 s3c2410_serial
-r--r--r-- root     root            0 2014-01-01 19:20 serial
-r--r--r-- root     root            0 2014-01-01 19:20 usbserial

下面我们还必须分析一下tty线路规程的注册,线路规程在tty驱动架构中的位置如下:

线路规划层的目的是:以协议转换的方式,格式化从一个用户或硬件收到的数据, 如PPP协议或蓝牙协议。

下面我们以我们用到的线路规划层的注册为例分析:

start_kernel

--- console_init    (drivers/tty/tty_io.c)

--- tty_ldisc_begin  (drivers/tty/tty_ldisc.c)

void tty_ldisc_begin(void)
{
// 在Linux中可以有多中线路规程,N_TTY只是其中一种,是默认的TTY线路规程
//  N_TTY是一个宏,为0
    (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}
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
};
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
    unsigned long flags;
    int ret = 0;
…
    tty_ldiscs[disc] = new_ldisc;
// tty_ldiscs是一个全局静态变量数组,记录了当前可以的线路规程
// 在tty_open的时候会设置相应的线路规程
    new_ldisc->num = disc;
    new_ldisc->refcount = 0;
…
    return ret;
}

好了,我们继续分析drivers/tty/serial/samsung.c

static int probe_index;

int s3c24xx_serial_probe(struct platform_device *dev,
             struct s3c24xx_uart_info *info)
{
    struct s3c24xx_uart_port *ourport;
    int ret;

    ourport = &s3c24xx_serial_ports[probe_index];
    probe_index++;
// info 数组中每个元素对应一个uart控制器的的配置方法
    ret = s3c24xx_serial_init_port(ourport, info, dev);
…
    uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
    platform_set_drvdata(dev, &ourport->port);

    ret = device_create_file(&dev->dev, &dev_attr_clock_source);
…
    ret = s3c24xx_serial_cpufreq_register(ourport);
…
    return 0;
…
}

下面是结构体s3c24xx_serial_ports 的定义:

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 int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
                    struct s3c24xx_uart_info *info,
                    struct platform_device *platdev)
{
    struct uart_port *port = &ourport->port;
    struct s3c2410_uartcfg *cfg;
    struct resource *res;
    int ret;

…
    cfg = s3c24xx_dev_to_cfg(&platdev->dev); // 存放的是每个uart控制器的寄存器默认配置
…
    /* setup info for port */
    port->dev    = &platdev->dev;
    ourport->info    = info;

    /* copy the info in from provided structure */
    ourport->port.fifosize = info->fifosize;

    port->uartclk = 1;

…

    res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
…
    port->mapbase = res->start;     // 获得uart控制寄存器的物理基地址
    port->membase = S3C_VA_UART + (res->start & 0xfffff);
// 计算虚拟地址,因为之前已经执行了静态映射,就不用ioremap了
    ret = platform_get_irq(platdev, 0);  // 获取中断资源
    if (ret < 0)
        port->irq = 0;
    else {
        port->irq = ret;
        ourport->rx_irq = ret;
        ourport->tx_irq = ret + 1;
    }

    ret = platform_get_irq(platdev, 1);
    if (ret > 0)
        ourport->tx_irq = ret;

    ourport->clk    = clk_get(&platdev->dev, "uart"); // 获取时钟资源
…
    /* reset the fifos (and setup the uart) */
//利用默认配置设置uart控制器,但是此时没有设置波特率和每帧的位数
    s3c24xx_serial_resetport(port, cfg);
    return 0;
}
static inline int s3c24xx_serial_resetport(struct uart_port *port,
                       struct s3c2410_uartcfg *cfg)
{
    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);

    return (info->reset_port)(port, cfg); // 调用函数s5pv210_serial_resetport
}

下面的函数的作用是利用默认参数配置uart控制器,这里从cfg的参数中没有看到设置波特率以及每帧的数据位,这个会在register_console中匹配成功时调用。

static int s5pv210_serial_resetport(struct uart_port *port,
                    struct s3c2410_uartcfg *cfg)
{
    unsigned long ucon = rd_regl(port, S3C2410_UCON);

    ucon &= S5PV210_UCON_CLKMASK;
    wr_regl(port, S3C2410_UCON,  ucon | cfg->ucon);
    wr_regl(port, S3C2410_ULCON, cfg->ulcon);

    /* reset both fifos */
    wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
    wr_regl(port, S3C2410_UFCON, cfg->ufcon);

    wr_regl(port, S3C64XX_UINTM, 0xf);
    wr_regl(port, S3C64XX_UINTP, 0xf);

    /* It is need to delay when reset FIFO register */
    udelay(1);

    return 0;
}

wr_regl是如何实现的呢?

#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg))

#define portaddr(port, reg) ((port)->membase + (reg))

下面分析uart_add_one_port

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
    struct uart_state *state;
    struct tty_port *port;
    int ret = 0;
    struct device *tty_dev;

…
    state = drv->state + uport->line;
// 每一个uart控制器在uart_driver中都有一个state与之对应
    port = &state->port;
…

    state->uart_port = uport;
    state->pm_state = -1;

    uport->cons = drv->cons;
    uport->state = state;

/*
 #define uart_console(port)    ((port)->cons && (port)->cons->index == (port)->line)
 这里的cons就是s3c24xx_serial_console,只有一个,它的index初始值是-1,在register_console中如果跟console_cmdline的参数匹配成功,cons的index会被设置为相应的编号,如果bootargs中console=ttySAC3,那么将来cons的index会被设置为3
*/
    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);

    tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
    if (likely(!IS_ERR(tty_dev))) {
        device_init_wakeup(tty_dev, 1);
        device_set_wakeup_enable(tty_dev, 0);
    } else
        printk(KERN_ERR "Cannot register tty device on line %d\n",
               uport->line);
        uport->flags &= ~UPF_DEAD;
…
    return ret;
}

下面分析uart_configure_port函数的实现

static void
uart_configure_port(struct uart_driver *drv, struct uart_state *state,
            struct uart_port *port)
{
    unsigned int flags;
…
    flags = 0;
    if (port->flags & UPF_AUTO_IRQ)
        flags |= UART_CONFIG_IRQ;
    if (port->flags & UPF_BOOT_AUTOCONF) {  // 这里成立
        if (!(port->flags & UPF_FIXED_TYPE)) {
            port->type = PORT_UNKNOWN;
            flags |= UART_CONFIG_TYPE;
        }
        port->ops->config_port(port, flags);  // 调用函数s3c24xx_serial_config_port
/*
static void s3c24xx_serial_config_port(struct uart_port *port, int flags)
{
    struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);

    if (flags & UART_CONFIG_TYPE &&
        s3c24xx_serial_request_port(port) == 0)  // 给port->mapbase赋值
        port->type = info->type;     // PORT_S3C6400
}
*/
    }

    if (port->type != PORT_UNKNOWN) {  // 条件成立
        unsigned long flags;

        uart_report_port(drv, port);
/*
这个函数只是打印的一些信息,没有做什么工作
static inline void
uart_report_port(struct uart_driver *drv, struct uart_port *port)
{
    char address[64];

    switch (port->iotype) {
…
    case UPIO_MEM:
…
        snprintf(address, sizeof(address),
             "MMIO 0x%llx", (unsigned long long)port->mapbase);
        break;
…
    }

    printk(KERN_INFO "%s%s%s%d at %s (irq = %d) is a %s\n",
           port->dev ? dev_name(port->dev) : "",
           port->dev ? ": " : "",
           drv->dev_name,
           drv->tty_driver->name_base + port->line,
           address, port->irq, uart_type(port));
}
*/

        /* Power up port for set_mctrl() */
        uart_change_pm(state, 0);
…
/*
在这里调用了register_console,对于tiny4412有四个串口,如果传入的console参数的是ttySAC0,由于是uart0先注册的,所以第一次就匹配成功了并且cons->flags会置位CON_ENABLED,后面在注册uart1~3的时候这里的条件不成立。因为这里的cons都执行了全局变量s3c24xx_serial_console。如果u-boot传入的console参数是ttySAC3的话,这里register_console就会执行四次,因为uart0~2都没有匹配成功。在上面的分析中有一个疑问,何时设置波特率以及位宽,其实就是在register_console中设置的,但是他只给与传入的console参数匹配的uart设置波特率和位宽,当然这里的波特率和位宽也是u-boot传给内核的。tiny4412的u-boot传给内核的参数是:
set bootargs ‘console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70‘
在上面分析内核解析console参数的时候,知道对于上面的参数console=ttySAC0,115200n8,会有一个结构体被赋值:
console_cmdline[0].name = “ttySAC”
console_cmdline[0].index = 0
console_cmdline[0].options = “115200n8”
*/
        if (port->cons && !(port->cons->flags & CON_ENABLED))
            register_console(port->cons);
        if (!uart_console(port))
            uart_change_pm(state, 3);
    }
}

下面看一下,register_console是如何设置波特率和位宽以及disable boot console的,关于这部分请参考前面分析register_console的代码,这里把关键部分列出来:

for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
            i++) {
        if (strcmp(console_cmdline[i].name, newcon->name) != 0)
            continue;
        if (newcon->index >= 0 &&
            newcon->index != console_cmdline[i].index)
            continue;
        if (newcon->index < 0)
            newcon->index = console_cmdline[i].index;
        if (newcon->setup &&
            newcon->setup(newcon, console_cmdline[i].options) != 0)
            break;
        newcon->flags |= CON_ENABLED;
        newcon->index = console_cmdline[i].index;
        if (i == selected_console) {  // selected_console是从bootargs中解析出来的
            newcon->flags |= CON_CONSDEV;
            preferred_console = selected_console;
        }
        break;
    }

可以看到,当匹配成功后,会调用函数setup,对于tiny4412就是:s3c24xx_serial_console_setup

static int __init
s3c24xx_serial_console_setup(struct console *co, char *options)
{
    struct uart_port *port;
    int baud = 9600; // 初始波特率
    int bits = 8;     // 初始帧宽
    int parity = ‘n‘;  // 无奇偶校验
    int flow = ‘n‘; // 无流控
…

    port = &s3c24xx_serial_ports[co->index].port;
…
    cons_uart = port;
…
// 对于tiny4412,这里解析出来的options是”115200n8”
// uart_parse_options会解析这个字符串,然后给相应的成员赋值,对于没有指定的参数,使用初始值
// 如果bootargs中没有指定波特率等参数,就会通过函数s3c24xx_serial_get_options动态计算出这些参数,这些参数就不可预知了,所以最好在bootargs中执行用哪个tty设备,同时指定波特率等参数,否则可能会乱码
    if (options)
           uart_parse_options(options, &baud, &parity, &bits, &flow);
    else
        s3c24xx_serial_get_options(port, &baud, &parity, &bits);
…
    return uart_set_options(port, co, baud, parity, bits, flow);
}
int
uart_set_options(struct uart_port *port, struct console *co,
         int baud, int parity, int bits, int flow)
{
    struct ktermios termios;
    static struct ktermios dummy;
    int i;

    /*
     * Ensure that the serial console lock is initialised
     * early.
     */
    spin_lock_init(&port->lock);
    lockdep_set_class(&port->lock, &port_lock_key);

    memset(&termios, 0, sizeof(struct ktermios));

    termios.c_cflag = CREAD | HUPCL | CLOCAL;

    /*
     * Construct a cflag setting.
     */
    for (i = 0; baud_rates[i].rate; i++)
        if (baud_rates[i].rate <= baud)
            break;

    termios.c_cflag |= baud_rates[i].cflag;

    if (bits == 7)
        termios.c_cflag |= CS7;
    else
        termios.c_cflag |= CS8;

    switch (parity) {
    case ‘o‘: case ‘O‘:
        termios.c_cflag |= PARODD;
        /*fall through*/
    case ‘e‘: case ‘E‘:
        termios.c_cflag |= PARENB;
        break;
    }

    if (flow == ‘r‘)
        termios.c_cflag |= CRTSCTS;

    port->mctrl |= TIOCM_DTR;

    port->ops->set_termios(port, &termios, &dummy);
 // 调用函数s3c24xx_serial_set_termios设置
    if (co)
        co->cflag = termios.c_cflag;

    return 0;
}

当register_console执行完成后,通过printk输出的信息就可以通过选定的串口输出了,调用的就是下面这个结构体中的write函数

static struct console s3c24xx_serial_console = {
    .name        = S3C24XX_SERIAL_NAME,
    .device        = uart_console_device,
    .flags        = CON_PRINTBUFFER,
    .index        = -1,
    .write        = s3c24xx_serial_console_write,
    .setup        = s3c24xx_serial_console_setup,
    .data        = &s3c24xx_uart_drv,
};
static void
s3c24xx_serial_console_write(struct console *co, const char *s,
                 unsigned int count)
{
    uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar);
}
void uart_console_write(struct uart_port *port, const char *s,
            unsigned int count,
            void (*putchar)(struct uart_port *, int))
{
    unsigned int i;

    for (i = 0; i < count; i++, s++) {
        if (*s == ‘\n‘)
            putchar(port, ‘\r‘);
        putchar(port, *s);
    }
}
static void

s3c24xx_serial_console_putchar(struct uart_port *port, int ch)

{

         unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON);

         while (!s3c24xx_serial_console_txrdy(port, ufcon))

                   barrier();

         wr_regb(cons_uart, S3C2410_UTXH, ch);

}

这样,字符就通过串口输出到终端了。

时间: 2024-10-10 04:26:23

tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)的相关文章

tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 下面要分析的是内核Log打印的几个阶段 自解压阶段 内核启动阶段 内核启动完全以后 shell终端下 在这个阶段内核log打印可以调用printk和printascii,同

tiny4412 串口驱动分析三 --- log打印的几个阶段之内核自解压

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 内核自解压时期的串口打印 在zImage格式的内核启动时会自解压内核,此时打印信息如下: Uncompressing Linux... 这句话是在arch/arm/boot

蓝牙驱动分析 linux

蓝牙驱动分析 这个驱动分析的是OK6410开发板自带的内核版本是linux3.0.1,所支持的wifi和蓝牙一体芯片是marvell的8688和8787.根据开发板的设计,芯片与主机之间是通过sdio协议接口通信的,所以驱动也是通过sdio的方式写的. 个人分析驱动的过程是从插入设备驱动的动作开始的. 首先每次插入设备和拔出设备驱动都会通过终端打印相应的信息,判断在sd卡槽中肯定是触发中断的,通过看硬件原理图和数据手册中的SDMMC控制器可知用于mmc的中断号分别为56和57,回到代码中.在内核

Tiny4412 Linux 内核启动流程

Linux内核的启动分为压缩内核和非压缩内核两种,这里我们以压缩内核为例.压缩内核运行时,将运行一段解压缩程序,得到真正的内核镜像,然后跳转到内核镜像运行.此时,Linux进入非压缩内核入口,在非压缩内核入口中,完成各种初始化操作后跳转到C语言入口处运行.主要流程如下所示. 1.解压缩内核镜像 解压缩程序通常在arch/arm/boot/compressed/目录中 ├── atags_to_fdt.c ├── big-endian.S ├── decompress.c ├── head.S ├

tiny4412 串口驱动分析 --- u-boot中的串口驱动

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 我们以tiny4412为例分析串口驱动,下面我们从u-boot开始分析,然后再分析到Linux. 串口初始化 关于这部分代码流程参考件:tiny4412 u-boot 启动

linux串口驱动分析

linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作.UART 使用系统时钟能够支持最高 115.2Kbps 的波特率.每一个 UART 通道对于接收器和发送器包含了 2 个 64 位的 FIFO. 寄存器 名称 地址 在linux中的描写叙述 (2410 和 2440 处理器对内存地址映射关系同样) UART 线性控制寄存器(ULCONn) ULC

RT-Thread下的串口驱动程序分析【转载】

编写本文稿的目的,在于通过分析stm32平台上的串口中断源码,学习 RTT中如何编写中断处理程序 如何编写RTT设备驱动接口代码 了解串行设备的常见处理机制 先以RTT官方源码中的STM32 BSP包来分析.rt-thread\bsp\stm32f10x 下,涉及的文件为: usart.c usart.h serail.c serail.h RTT的设备驱动程序概述 编写uart的驱动程序,首先需要了解RTT的设备框架,RTT的设备框架我们已经大致的介绍了一下,这里以usart的驱动来具体分析R

linux gsensor驱动分析【转】

本文转载自:http://blog.sina.com.cn/s/blog_89f592f501013sr2.html 本文以Bma250驱动为例子,详细介绍Gsensor设计的一个模板. gsensor驱动在系统中的层次如下图所示: 图中包含三个部分:hardware, driver, input: n         Hardware:其实我们可以认为Gsensor也是一个I2C设备.整个Gsensor芯片分为两部分,一个是sensor传感器,另一个是controller控制器,用于将sens

Linux SD/MMC/SDIO驱动分析

一.SD/MMC/SDIO概念区分 SD(SecureDigital)与 MMC(MultimediaCard) SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代.在维基百科上有相当详细的 SD/MMC 规格说明:[http://zh.wikipedia.org/wiki/Secure_Digital]. SDIO(SecureDigital I/O) SDIO 是目前我们比较关心的技术,