文章目录
背景
leadcore1861平台在到板后发现引出的串口控制台没有任何输出,询问厂家后了解到厂家代码指定串口4作为串口控制台,而我们使用了串口0,所以需要对uboot串口和linux串口控制台进行修改,指定到串口0输出。
uboot 串口驱动实现
相关文件
(1)common/serial.c
实现了U-Boot串口设备的通用接口。
(2)include/serial.h
定义了串口设备的数据结构及一些函数声明。
(3)board/开发板名字/serial.c、cpu/cpu名字/serial.c、drivers/serial/serial.c、drivers/serial/serial_xxx.c
各种开发板、控制器的串口操作的具体实现。
数据结构
在include/serial.h 中定义了表示串口设备的数据结构。
struct serial_device {
char name[NAMESIZE];
char ctlr[CTLRSIZE];
int (*init) (void);
void (*setbrg) (void);
int (*getc) (void);
int (*tstc) (void);
void (*putc) (const char c);
void (*puts) (const char *s);
struct serial_device *next;
};
name表示设备名称。
ctrl表示串口硬件的名称。
init表示串口初始化函数指针。
setbrg表示串口波特率设置函数指针。
getc表示从串口获取一个字符的函数指针。
tstc表示检查串口是否接收到数据的函数指针。
putc表示串口发送一个字符的函数指针。
puts表示串口发送字符串的函数指针。
next表示指向下一个串口设备的指针。各个串口设备通过next组成一个单向链表。
串口驱动的实现
common/serial.c实现了U-Boot串口设备的通用接口,外部调用这些接口函数来完成串口设备的操作。这些接口函数有:
(1)int serial_register (struct serial_device *dev)
注册串口设备。各个串口设备的操作在board/开发板名字/serial.c、cpu/cpu名字/serial.c、drivers/serial/serial.c、drivers/serial/serial_xxx.c文件里实现。
(2)int serial_assign (char *name)
分配串口设备。
(3)int serial_init (void)
串口初始化
(4)void serial_setbrg (void)
设置串口波特率
(5)int serial_getc (void)
从串口获取一个字符
(6)void serial_putc (const char c)
向串口发送一个字符。
(7)void serial_puts (const char *s)
向串口发送字符串。
(8)int serial_tstc (void)
检查串口是否接收到字符。
(9)void serial_initialize (void)
注册一些控制器、开发板的串口设备,并把控制台的默认串口设备分配为本文件操作的串口设备。
(10)void serial_reinit_all (void)
初始化所有串口。
uboot串口输出修改
leadcore1861使用了 serial_ns16550
__weak struct serial_device *default_serial_console(void)
{
#if CONFIG_CONS_INDEX == 1
return &eserial1_device;
#elif CONFIG_CONS_INDEX == 2
return &eserial2_device;
#elif CONFIG_CONS_INDEX == 3
return &eserial3_device;
#elif CONFIG_CONS_INDEX == 4
return &eserial4_device;
#elif CONFIG_CONS_INDEX == 5
return &eserial5_device;
#elif CONFIG_CONS_INDEX == 6
return &eserial6_device;
#else
#error "Bad CONFIG_CONS_INDEX."
#endif
}
可以看出其是通过CONFIG_CONS_INDEX 来控制默认串口控制台,全局搜索:CONFIG_CONS_INDEX
将include/configs/comip_lc1861evb.h
中的
#define CONFIG_CONS_INDEX 4
修改为
#define CONFIG_CONS_INDEX 1
Linux 串口驱动实现
Linux串口驱动框架
Linux系统提供了串口驱动框架,我们需要按照相应的串口框架编写驱动程序即可。串口驱动没有主机端和设备端之分,就只有一个串口驱动,我们真正要做的就是在设备数中添加所要使用的串口节点信息。当系统启动以后串口和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttySx文件。
struct uart_driver{
struct module *owner; //模块所属者
const char *drvier_name; //驱动名字
const char *dev_name; //设备名字
int major; //主设备号
int minor; //此设备号
int nr; //设备树
struct consle *cons; //控制台
struct uart_state *state;
struct tty_driver *tty_driver;
};
//串口驱动要定义uart_driver,向系统中注册这个uart_driver
int uart_register_driver(struct uart_driver *drv)
uart_ops的具体实现
uart_port中的ops成员变量很重要,因为ops包含了针对UART具体的驱动函数,Linux系统收发数据最终调用的都是ops中的函数。ops是uart_ops类型的结构体指针变量。
struct uart_ops{
unsigned int (*tx_empty)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
};
Linux console
uboot传参分析
linux启动时uboot传递进console=ttyS0,115200n8的参数
内核中用__setup()宏声明参数处理的方法:__setup("console=", console_setup);
1.console_cmdline结构体
struct console_cmdline
{
char name[8]; //驱动名
int index; //次设备号
char *options; //选项
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
char *brl_options;
#endif
};
2.内核调用console_setup()函数处理uboot传进的console参数
static int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; //分配驱动名+index的缓冲区,分配12个字节
char *s, *options, *brl_options = NULL;
int idx;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (!memcmp(str, "brl,", 4)) {
brl_options = "";
str += 4;
} else if (!memcmp(str, "brl=", 4)) {
brl_options = str + 4;
str = strchr(brl_options, ',');
if (!str) {
printk(KERN_ERR "need port name after brl=\n");
return 1;
}
*(str++) = 0;
}
#endif
if (str[0] >= '0' && str[0] <= '9') { //第一个参数属于[0,9]
strcpy(buf, "ttyS"); //则将其驱动名设为ttyS
strncpy(buf + 4, str, sizeof(buf) - 5);//将次设备号放其后面
} else {
strncpy(buf, str, sizeof(buf) - 1); //否则直接将驱动名+设备号拷贝到buf中
}
buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL) //获取options,即“115200n8”
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')//移动指针s到次设备号处
break;
idx = simple_strtoul(s, NULL, 10); //获取次设备号,字符串转换成unsigend long long型数据,s表示字符串的开始,NULL表示字符串的结束,10表示进制
//这里返回的是次设备号=0
*s = 0;
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return 1;
}
3.__add_preferred_console()函数
//整体的作用是根据uboot传递的参数设置全局console_cmdline数组
//该数组及全局selected_console,在register_console中会使用到
static int __add_preferred_console(char *name, int idx, char *options,char *brl_options)
{
struct console_cmdline *c;
int i;
for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)//可以最多8个console
if (strcmp(console_cmdline[i].name, name) == 0 && console_cmdline[i].index == idx) {
//比较已注册的console_cmdline数组中的项的名字及次设备号,若console_cmdline已经存在
if (!brl_options)
selected_console = i;//设置全局selected_console索引号
return 0;//则返回
}
if (i == MAX_CMDLINECONSOLES)//判断console_cmdline数组是否满了
return -E2BIG;
if (!brl_options)
selected_console = i; //设置全局selected_console索引号
c = &console_cmdline[i];//获取全局console_cmdline数组的第i项地址
strlcpy(c->name, name, sizeof(c->name)); //填充全局console_cmdline的驱动名“ttyS2”
c->options = options; //填充配置选项115200n8
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
c->brl_options = brl_options;
#endif
c->index = idx; //填充索引号2,即次设备号
return 0;
}
early_printk
//在调用console_init之前调用printk也能打印出信息,这是為什麼呢?在start_kernel函数中很早就调用了 parse_early_param函数,
//该函数会调用到链接脚本中.init.setup段的函数。其中就有 setup_early_serial8250_console函数。
//该函数通过 register_console(&early_serial8250_console);
//注册了一个比较简单的串口设备。可以用来打印内核启 动早期的信息。
//对于early printk的console注册往往通过内核的early_param完成。
early_param(“earlycon”,setup_early_serial8250_console);
//定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数
1.setup_early_serial8250_console()函数
//earlycon = uart8250-32bit,0xfa882000,115200n8
int __init setup_early_serial8250_console(char *cmdline)
{
char *options;
int err;
options = strstr(cmdline, "uart8250,");//找到“uart8250,”字符串,返回此字符串的起始位置
if (!options) {
options = strstr(cmdline, "uart,");
if (!options)
return 0;
}
options = strchr(cmdline, ',') + 1;//options指针指向第一个逗号后边的字符串地址
err = early_serial8250_setup(options);//进行配置
if (err < 0)
return err;
/*
static struct console early_serial8250_console __initdata = {
.name = "uart",
.write = early_serial8250_write,
.flags = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是
.index = -1,
};
*/
//注册一个早期的console,到真正的console_init时,此console会被注销,因为设置了CON_BOOT标志
register_console(&early_serial8250_console);
return 0;
}
static int __init early_serial8250_setup(char *options)
{
struct early_serial8250_device *device = &early_device;
int err;
if (device->port.membase || device->port.iobase)//early_device设备的端口地址若配置过则返回
return 0;
err = parse_options(device, options);//解析参数并配置early_device设备对应的uart_port结构
if (err < 0)
return err;
init_port(device);//early_device设备对应的初始化uart_port结构
return 0;
}
static int __init parse_options(struct early_serial8250_device *device,char *options)
{
struct uart_port *port = &device->port;//找到early_device设备对应的uart_port结构
int mmio, mmio32, length;
if (!options)
return -ENODEV;
port->uartclk = BASE_BAUD * 16;//串口时钟
mmio = !strncmp(options, "mmio,", 5);//查找"mmio,"字符串,找到mmio=1
mmio32 = !strncmp(options, "mmio32,", 7);//mmio32=0
if (mmio || mmio32) {
port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);//串口类型设为UPIO_MEM=2
port->mapbase = simple_strtoul(options + (mmio ? 5 : 7),&options, 0);//获得串口的配置寄存器基础地址(物理地址),这里是得到0xff5e0000
if (mmio32)
port->regshift = 2;
#ifdef CONFIG_FIX_EARLYCON_MEM
set_fixmap_nocache(FIX_EARLYCON_MEM_BASE,port->mapbase & PAGE_MASK);
port->membase =(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
port->membase += port->mapbase & ~PAGE_MASK;
#else
port->membase = ioremap_nocache(port->mapbase, 64);//映射到内存的配置寄存器基础地址
if (!port->membase) {
printk(KERN_ERR "%s: Couldn't ioremap 0x%llx\n", __func__,(unsigned long long) port->mapbase);
return -ENOMEM;
}
#endif
} else if (!strncmp(options, "io,", 3)) {
port->iotype = UPIO_PORT;
port->iobase = simple_strtoul(options + 3, &options, 0);
mmio = 0;
} else
return -EINVAL;
options = strchr(options, ',');//指针移到“115200n8”字符串处
if (options) {//存在
options++;
device->baud = simple_strtoul(options, NULL, 0);//取得波特率115200
length = min(strcspn(options, " "), sizeof(device->options));
strncpy(device->options, options, length);//将字符串115200n8拷贝到设备的device->options字段中
} else {
device->baud = probe_baud(port);
snprintf(device->options, sizeof(device->options), "%u",device->baud);
}
if (mmio || mmio32)
printk(KERN_INFO "Early serial console at MMIO%s 0x%llx (options '%s')\n",mmio32 ? "32" : "",(unsigned long long)port->mapbase,device->options);
else
printk(KERN_INFO
"Early serial console at I/O port 0x%lx (options '%s')\n",port->iobase,device->options);
return 0;
}
static void __init init_port(struct early_serial8250_device *device)
{
struct uart_port *port = &device->port;
unsigned int divisor;
unsigned char c;
serial_out(port, UART_LCR, 0x3); /* 8n1 */
serial_out(port, UART_IER, 0); /* no interrupt */
serial_out(port, UART_FCR, 0); /* no fifo */
serial_out(port, UART_MCR, 0x3); /* DTR + RTS */
divisor = port->uartclk / (16 * device->baud);//根据波特率设置分频
c = serial_in(port, UART_LCR);
serial_out(port, UART_LCR, c | UART_LCR_DLAB);
serial_out(port, UART_DLL, divisor & 0xff);
serial_out(port, UART_DLM, (divisor >> 8) & 0xff);
serial_out(port, UART_LCR, c & ~UART_LCR_DLAB);
}
void register_console(struct console *newcon)
{
int i;
unsigned long flags;
struct console *bcon = NULL;
/*
现在是注册一个early console,即
static struct console early_serial8250_console __initdata = {
.name = "uart",
.write = early_serial8250_write,
.flags = CON_PRINTBUFFER | CON_BOOT,//所用具有CON_BOOT属性的console都会在内核初始化到late initcall阶段被注销,相互消他们的函数是
.index = -1,
};
*/
if (console_drivers && newcon->flags & CON_BOOT) {//注册的是否是引导控制台。early console的CON_BOOT置位,表示只是一个引导控制台,以后会被注销
for_each_console(bcon) {遍历全局console_drivers数组
if (!(bcon->flags & CON_BOOT)) {//判断是否已经有引导控制台了,有了的话就直接退出
printk(KERN_INFO "Too late to register bootconsole %s%d\n",newcon->name, newcon->index);
return;
}
}
}
if (console_drivers && console_drivers->flags & CON_BOOT)//如果注册的是引导控制台
bcon = console_drivers;//让bcon指向全局console_drivers
if (preferred_console < 0 || bcon || !console_drivers)
preferred_console = selected_console;//设置preferred_console为uboot命令选择的selected_console(即索引)
if (newcon->early_setup)//early console没有初始化early_setup字段,以下这个函数不执行
newcon->early_setup();//调用serial8250_console_early_setup()
if (preferred_console < 0) {
if (newcon->index < 0)
newcon->index = 0;
if (newcon->setup == NULL ||newcon->setup(newcon, NULL) == 0) {
newcon->flags |= CON_ENABLED;
if (newcon->device) {
newcon->flags |= CON_CONSDEV;
preferred_console = 0;
}
}
}
//传给内核参数:
//所以这里将根据传参console=ttyGS0,115200来配置作为console的ttyGS0串口
for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];i++) {//遍历全局console_cmdline找到匹配的
if (strcmp(console_cmdline[i].name, newcon->name) != 0)//比较终端名称“ttyS”
continue;
if (newcon->index >= 0 &&newcon->index != console_cmdline[i].index)//console_cmdline[i].index=2。//比较次设备号
continue;
if (newcon->index < 0)
newcon->index = console_cmdline[i].index;//将终端号赋值给serial8250_console->index
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE//没有定义,下边不执行
if (console_cmdline[i].brl_options) {
newcon->flags |= CON_BRL;
braille_register_console(newcon,console_cmdline[i].index,console_cmdline[i].options,console_cmdline[i].brl_options);
return;
}
#endif
//console_cmdline[i].options = "115200n8",对于early console而言setup字段未被初始化,故下边的函数不执行
if (newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0)//调用serial8250_console_setup()对终端进行配置
break;
newcon->flags |= CON_ENABLED; //设置标志为CON_ENABLE(这个在printk调用中使用到)
newcon->index = console_cmdline[i].index;//设置索引号
if (i == selected_console) { //索引号和uboot指定的console的一样
newcon->flags |= CON_CONSDEV;//设置标志CON_CONSDEV(全局console_drivers链表中靠前)
preferred_console = selected_console;
}
break;
}//for循环作用大致是查看注册的console是否是uboot知道的引导console,是则设置相关标志和preferred_console
if (!(newcon->flags & CON_ENABLED))
return;
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))//防止重复打印
newcon->flags &= ~CON_PRINTBUFFER;
acquire_console_sem();
if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {//如果是preferred控制台
newcon->next = console_drivers;
console_drivers = newcon;//添加进全局console_drivers链表前面位置(printk中会遍历该表调用合适的console的write方法打印信息)
if (newcon->next)
newcon->next->flags &= ~CON_CONSDEV;
} else {//如果不是preferred控制台
newcon->next = console_drivers->next;
console_drivers->next = newcon; //添加进全局console_drivers链表后面位置
}
//主册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,
//console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来
if (newcon->flags & CON_PRINTBUFFER) {
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) {
printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled\n",newcon->name, newcon->index);
for_each_console(bcon)
if (bcon->flags & CON_BOOT)
unregister_console(bcon);
} else {//调用这里
printk(KERN_INFO "%sconsole [%s%d] enabled\n",(newcon->flags & CON_BOOT) ? "boot" : "" ,newcon->name, newcon->index);
}
}
printk流程
//内核的打印函数
asmlinkage int printk(const char *fmt, ...)
{
va_list args; //可变参数链表
int r;
va_start(args, fmt); //获取第一个可变参数
r = vprintk(fmt, args); //调用vprintk函数
va_end(args); //释放可变参数链表指针
return r;
}
//vprintk函数
asmlinkage int vprintk(const char *fmt, va_list args)
{
int printed_len = 0;
int current_log_level = default_message_loglevel;
unsigned long flags;
int this_cpu;
char *p;
boot_delay_msec();
printk_delay();
preempt_disable();
raw_local_irq_save(flags);
this_cpu = smp_processor_id();
if (unlikely(printk_cpu == this_cpu)) {
if (!oops_in_progress) {
recursion_bug = 1;
goto out_restore_irqs;
}
zap_locks();
}
lockdep_off();
spin_lock(&logbuf_lock);
printk_cpu = this_cpu;
if (recursion_bug) {
recursion_bug = 0;
strcpy(printk_buf, recursion_bug_msg);
printed_len = strlen(recursion_bug_msg);
}
printed_len += vscnprintf(printk_buf + printed_len,sizeof(printk_buf) - printed_len, fmt, args);
p = printk_buf;
if (p[0] == '<') {//处理打印级别字段
unsigned char c = p[1];
if (c && p[2] == '>') {
switch (c) {
case '0' ... '7': /* loglevel */
current_log_level = c - '0';
case 'd': /* KERN_DEFAULT */
if (!new_text_line) {
emit_log_char('\n');
new_text_line = 1;
}
case 'c': /* KERN_CONT */
p += 3;
break;
}
}
}
for ( ; *p; p++) {
if (new_text_line) {
/* Always output the token */
emit_log_char('<');
emit_log_char(current_log_level + '0');
emit_log_char('>');
printed_len += 3;
new_text_line = 0;
if (printk_time) { //打印时间信息
/* Follow the token with the time */
char tbuf[50], *tp;
unsigned tlen;
unsigned long long t;
unsigned long nanosec_rem;
t = cpu_clock(printk_cpu);
nanosec_rem = do_div(t, 1000000000);
tlen = sprintf(tbuf, "[%5lu.%06lu] ",(unsigned long) t,nanosec_rem / 1000);
for (tp = tbuf; tp < tbuf + tlen; tp++)
emit_log_char(*tp);
printed_len += tlen;
}
if (!*p)
break;
}
emit_log_char(*p);
if (*p == '\n')
new_text_line = 1;
}
if (acquire_console_semaphore_for_printk(this_cpu))
release_console_sem();
...
}
//接着调用release_console_sem函数
void release_console_sem(void)
{
...
console_may_schedule = 0;
for ( ; ; ) {
spin_lock_irqsave(&logbuf_lock, flags);
wake_klogd |= log_start - log_end;
if (con_start == log_end)
break; /* Nothing to print */
_con_start = con_start;
_log_end = log_end;
con_start = log_end; /* Flush */
spin_unlock(&logbuf_lock);
stop_critical_timings(); /* don't trace print latency */
call_console_drivers(_con_start, _log_end);
...
}
...
}
EXPORT_SYMBOL(release_console_sem);
//调用call_console_drivers函数
static void call_console_drivers(unsigned start, unsigned end)
{
...
_call_console_drivers(start_print, end, msg_level);
}_call_console_drivers函数
//调用console的写方法
static void __call_console_drivers(unsigned start, unsigned end)
{
struct console *con;
for_each_console(con) {//遍历console_drivers数组 #define for_each_console(con) for (con = console_drivers; con != NULL; con = con->next)
if ((con->flags & CON_ENABLED) && con->write &&(cpu_online(smp_processor_id()) ||(con->flags & CON_ANYTIME)))
con->write(con, &LOG_BUF(start), end - start); //调用console的写方法
}
}
//由于已经注册的终端是serial_comip_console,这个终端的写方法是调用serial_comip_console_write()函数--->serial_comip_console_write()--->serial_out()
//--->serial_out()最终打印在串口终端上
/*
static struct console serial_comip_console = {
.name = "ttyS",
.write = serial_comip_console_write,
.device = uart_console_device,
.setup = serial_comip_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &serial_comip_reg,
};
*/
console_drivers链表在register_console中会设置
Linux 修改串口控制台
修改earlyprintk:
修改 printk: