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这些设备号,
这里注意:申请了四个设备号,意思是可以对应四个设备结点,并且这四个设备结点共用这个cdev,操作函数都是tty_fops。
*/ 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

shell@android:/ # 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;



    /* is the port configured? */

    // 这里的作用是:如果传入的console=ttySAC3,那么这里可以保证uart0~2在注册时因条件不满足返回,因为每个port在probe时才会给mapbase赋值。

    if (port->mapbase == 0x0) 

        return -ENODEV;



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

 

 

这样,字符就通过串口输出到终端了。注意这里只是说明了通过printk输出的字符是如何输出到终端的,是在内核空间。

 

下面分析函数tty_register_device的实现:

在执行tty_register_driver的时候申请了设备号(申请了四个),注册了相应的字符设备,但是还没有在/dev下生成相应的设备结点,这里就是来生成设备结点的。如对于我们的tiny4412就是

/dev/ttySAC0、/dev/ttySAC1、/dev/ttySAC2、/dev/ttySAC3

struct device *tty_register_device(struct tty_driver *driver, unsigned index, struct device *device) { char name[64]; dev_t dev = MKDEV(driver->major, driver->minor_start) + index; // 设备号,(204+64)+index

if (driver->type == TTY_DRIVER_TYPE_PTY) pty_line_name(driver, index, name); else tty_line_name(driver, index, name); // ttySACn



    return device_create(tty_class, device, dev, NULL, name); }

 

static void tty_line_name(struct tty_driver *driver, int index, char *p) { sprintf(p, "%s%d", driver->name, index + driver->name_base); }

至此,/dev/下就有相应的设备结点了。 

你可能感兴趣的:(print)