如何修改tiny4412 驱动串口驱动.让他支持485

sponsored links
tiny4412 串口驱动分析七
log打印的几个阶段之内核启动阶段(earlyprintk)
作者:彭东林
开发板: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,同时printk又分为两个阶段,从刚才的分析中知道,printk最终调用的是有register_console注册的console_drivers的write函数,在tiny4412平台上调用register_console的地方有两处,第一处是在arch/arm/kernel/early_printk.c中,另一处就是在串口驱动注册中,具体是在driver/tty/serial/samsung.c中,下面我们开始分析。
printascii的实现:
首先printascii需要配置内核才能使用:
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
Kernel low-level debugging functions
这样就可以使用printascii了:
在printk中会调用printascii,
#ifdef&&&&&&& CONFIG_DEBUG_LL
&&&&&&&& printascii(printk_buf);
print_asciii使用汇编实现的,在文件arch/arm/kernel/debug.S中:
addruart_current, rx, tmp1, tmp2
\tmp1, \tmp2
p15, 0, \rx, c1, c0
\rx, \tmp1
\rx, \tmp2
ENTRY(printascii)
addruart_current r3, r1, r2
waituart r2, r3
senduart r1, r3
busyuart r2, r3
r1, [r0], #1
mov pc, lr
ENDPROC(printascii)
其中 addruart 是在文件arch/arm/mach-exynos/include/mach/debug-macro.S中实现的,waituart、senduart以及busyuart是在arch/arm/plat-samsung/include/plat/debug-macro.S中实现的,大家可以参考这两个文件理解具体实现过程。
early_printk中调用register_console
tiny4412使用的内核默认是没有开启early_printk的,即log_buf中内容只有等driver/tty/serial下的串口驱动注册完成后才能输出到串口终端,在此之前调用printk的内容都缓存到log_buf中了,如果想提前使用的话,需要使能early_printk,下面说明一下如何使能early_printk。
make LOCALVERSION="" ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
Kernel hacking
&&&&&&& -- Kernel low-level debugging functions
&&&&&&& --Early printk
要使能early_printk首先必须使能Kernel low-level debugging functions,因为early_printk最终也是使用printascii实现的,这样arch/arm/kernel/early_printk.c就会参加编译
在early_printk.c中:
static int __init setup_early_printk(char *buf)
printk("%s enter\n", __func__);
register_console(&early_console);
early_param("earlyprintk", setup_early_printk);
虽然内核配置了,但是要让内核调用setup_early_printk还必须在u-boot给内核传参的时候给bootargs在加上一个参数&earlyprintk&,如下:
set bootargs &console=ttySAC0, androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70 earlyprintk&
那么内核是如何处理的呢?
在文件include/linux/init.h中:
#define __setup_param(str, unique_id, fn, early)
static const char __setup_str_##unique_id[] __initconst
__aligned(1) = \
static struct obs_kernel_param __setup_##unique_id
__used __section(.init.setup)
__attribute__((aligned((sizeof(long)))))
= { __setup_str_##unique_id, fn, early }
#define early_param(str, fn)
__setup_param(str, fn, fn, 1)
将early_param("earlyprintk", setup_early_printk);展开后:
static const char __setup_str_setup_early_printk[] __initconst __aligned(1) = "earlyprintk";
static struct obs_kernel_param __setup_setup_early_printk __used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) = \
{ __setup_str_setup_early_printk, setup_early_printk, 1 }
即上面的这个结构体被链接到了&.init.setup&段,在arch/arm/kernel/vmlinux.lds中:
. = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .;
将所有的.init.setup都链接到了__setup_start和__setup_end之间
在内核启动的时候:
start_kernel
setup_arch(&command_line);
//这个函数的目的是获得u-boot传给内核的参数(bootargs),并将参数存放在command_line中,然后解析command_line,并调用相关的函数处理 parse_early_param()
(arch/arm/kernel/setup.c)
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
parse_early_options用于解析tmp_cmdline
void __init parse_early_options(char *cmdline)
parse_args("early options", cmdline, NULL, 0, do_early_param);
在文件kernel/params.c中:
int parse_args(const char *name,
char *args,
const struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
char *param, *
/* Chew leading spaces */
args = skip_spaces(args); // 跳过args开头的空格
while (*args) {
int irq_was_
如:cmdline是&console=ttySAC0, androidboot.console=ttySAC0&
执行next_arg后:
params=&console&, val=& ttySAC0,& args=& androidboot.console=ttySAC0&
args = next_arg(args, &param, &val); // 获得下一个参数的位置,
irq_was_disabled = irqs_disabled();
parse_one所做的主要就是将params和val传递给unknown处理,这里unknown就是do_early_param,所以下面分析do_early_param
ret = parse_one(param, val, params, num, unknown);
/* All parsed OK. */
static int __init do_early_param(char *param, char *val)
const struct obs_kernel_param *p;
还记得early_param("earlyprintk", setup_early_printk)展开后的结果吗?
其中early为1,str是 &earlyprintk&,setup_func就是setup_early_printk
for (p = __setup_ p & __setup_ p++) {
if ((p-&early && strcmp(param, p-&str) == 0)
(strcmp(param, "console") == 0 &&
strcmp(p-&str, "earlycon") == 0)
if (p-&setup_func(val) != 0)
下面我们就分析setup_early_printk:
static int __init setup_early_printk(char *buf)
register_console(&early_console);
结构体early_console 的定义如下:
static struct console early_console = {
"earlycon",
early_console_write,
CON_PRINTBUFFER
看一下early_console_write干了什么:
static void early_console_write(struct console *con, const char *s, unsigned n)
early_write(s, n);
static void early_write(const char *s, unsigned n)
while (n-- & 0) {
if (*s == '\n')
printch('\r');
printch(*s);
可以看到,它调用的是printch,它在文件arch/arm/kernel/debug.S中实现:
ENTRY(printascii)
addruart_current r3, r1, r2
waituart r2, r3
senduart r1, r3
busyuart r2, r3
r1, [r0], #1
ENDPROC(printascii)
ENTRY(printch)
addruart_current r3, r1, r2
ENDPROC(printch)
这样当driver/tty/serial下的驱动尚未注册时,printk就已经可以使用了,它最终调用的是early_console_write输出到串口终端的
下面我们分析一个函数register_console
这段话最好看一下
* The console driver calls this routine during kernel initialization
* to register the console printing procedure with printk() and to
* print any messages that were printed by the kernel before the
* console driver was initialized.
* This can happen pretty early during the boot process (because of
* early_printk) - sometimes before setup_arch() completes - be careful
* of what kernel features are used - they may not be initialised yet.
* There are two types of consoles - bootconsoles (early_printk) and
* "real" consoles (everything which is not a bootconsole) which are
* handled differently.
- Any number of bootconsoles can be registered at any time.
- As soon as a "real" console is registered, all bootconsoles
will be unregistered automatically.
- Once a "real" console is registered, any attempt to register a
bootconsoles will be rejected
void register_console(struct console *newcon)
unsigned long
struct console *bcon = NULL;
* before we register a new CON_BOOT console, make sure we don't
* already have a valid console
这个判断的目的是:看当前系统是否有&real&类型的console注册,如果有的话,直接返回,即&real&类型和&bootconsoles&类型的console不能共存,
如果注册了&real&类型的console的话,则会对&bootconsoles&类型的console进行unregister
如果已经有&real&类型的console,则注册&bootconsoles&类型的console时会失败,&bootconsoles&类型的console的flags设置CON_BOOT位。
这里在内核刚启动,还没有任何console注册,console_drivers是NULL
if (console_drivers && newcon-&flags & CON_BOOT) {
/* find the last or real console */
for_each_console(bcon) {
if (!(bcon-&flags & CON_BOOT)) {
printk(KERN_INFO "Too late to register bootconsole %s%d\n",
newcon-&name, newcon-&index);
if (console_drivers && console_drivers-&flags & CON_BOOT)
bcon = console_
// perferred_console和selected_console的初始值都是-1,但是如果在bootargs中设置了类似&console=ttySAC0,&时,在解析console参数时会设置selected_console,这个一会分析
if (preferred_console & 0
!console_drivers)
preferred_console = selected_
if (newcon-&early_setup) // 如果有的话,则调用
newcon-&early_setup();
See if we want to use this console driver. If we
didn't select a console we take the first one
that registers here.
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;
See if this console matches one we selected on
the command line.
// console_cmdline会在解析bootargs的console参数时设置
for (i = 0; i & MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
if (strcmp(console_cmdline[i].name, newcon-&name) != 0)
if (newcon-&index &= 0 &&
newcon-&index != console_cmdline[i].index)
if (newcon-&index & 0)
newcon-&index = console_cmdline[i].
if (newcon-&setup &&
newcon-&setup(newcon, console_cmdline[i].options) != 0)
newcon-&flags = CON_ENABLED;
newcon-&index = console_cmdline[i].index;
if (i == selected_console) {
// selected_console是从bootargs中解析出来的
newcon-&flags = CON_CONSDEV;
preferred_console = selected_
if (!(newcon-&flags & CON_ENABLED))
* If we have a bootconsole, and are switching to a real console,
* don't print everything out again, since when the boot console, and
* the real console are the same physical device, it's annoying to
* see the beginning boot messages twice
if (bcon && ((newcon-&flags & (CON_CONSDEV
CON_BOOT)) == CON_CONSDEV))
newcon-&flags &= ~CON_PRINTBUFFER;
Put this console in the list - keep the
preferred driver at the head of the list.
console_lock();
// 这里会把跟selected_console一样的ttySAC尽量往前放
if ((newcon-&flags & CON_CONSDEV)
console_drivers == NULL) {
newcon-&next = console_drivers;
console_drivers =
if (newcon-&next)
newcon-&next-&flags &= ~CON_CONSDEV;
newcon-&next = console_drivers-&
console_drivers-&next =
if (newcon-&flags & CON_PRINTBUFFER) {
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_
spin_unlock_irqrestore(&logbuf_lock, flags);
exclusive_console =
console_unlock();
console_sysfs_notify();
* By unregistering the bootconsoles after we enable the real console
* we get the "console xxx enabled" message on all the consoles -
* boot consoles, real consoles, etc - this is to ensure that end
* users know there might be something in the kernel's log buffer that
* went to the bootconsole (that they do not see on the real console)
if (bcon &&
((newcon-&flags & (CON_CONSDEV
CON_BOOT)) == CON_CONSDEV) &&
!keep_bootcon) {
// 在命令行中可以设置参数,将keep_bootcon置1,就不会将bootconsole注销了
/* we need to iterate through twice, to make sure we print
* everything out, before we unregister the console(s)
如果使能了early_printk的话,下面的这条log会打印两次,因为一次是从real console,另一次是从boot consoles。原因是 当real已经注册时(上面更新了console_drivers),
但是此时boot consoles还尚未unregister。
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); // unregister boot consoles
printk(KERN_INFO "%sconsole [%s%d] enabled\n",
(newcon-&flags & CON_BOOT) ? "boot" : "" ,
newcon-&name, newcon-&index);
上面我们分析了early_printk,下面我们分析当内核启动之后的printk打印的实现。
当Linux内核启动之后,或者更确切的说是串口驱动注册后,使用的是real console,会把boot consoles都disable。
内核起来后,使用串口终端可以登录,在用户空间(对于android)是/system/bin/sh跟用户交互,它接收用户通过串口输入的命令,然后执行,它虽然在最底层都会操作串口硬件寄存器,但是跟内核的printk还不一样,前者走的是tty驱动架构,他们在底层是通过不同的机制来操作uart控制器的。下面一块分析。
在此之前,我们还要看一下u-boot给内核传参:
console=ttySAC0, androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=384m lcd=S70
由于tiny4412一共有4个串口,我们使用了com0和com3,在参数中的console参数console=ttySAC0,告诉Linux,将来sh要运行在ttySAC0上,它的波特率是115200,每帧8位。也就是我们要通过com0与Linux交互。
在此可以做一个实验,如果我们执行
&hello world&会通过串口ttySAC0打印到终端
什么反应也没有,因为我们接在com0上而不是com3上。后面我们会尝试修改默认的串口,从com0改成com3.
那么系统是如何解析console=ttySAC0,的呢?
在kernel/printk.c中:
__setup("console=", console_setup);
在include/linux/init.h中:
#define __setup(str, fn)
__setup_param(str, fn, fn, 0)
而__setup_param我们在上面已经分析了,最终__setup("console=", console_setup);
的展开结果是:
static const char __setup_str_console_setup[] __initconst
__aligned(1) = "console=";
static struct obs_kernel_param __setup_console_setup
__used __section(.init.setup)
__attribute__((aligned((sizeof(long)))))
= { __setup_str_console_setup, console_setup, 0 }
可以看到它也链接到了&.init.setup&段。在内核启动的时候:
asmlinkage void __init start_kernel(void)
char * command_
extern const struct kernel_param __start___param[], __stop___param[];
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
int parse_args(const char *name,
char *args,
const struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
char *param, *
DEBUGP("Parsing ARGS: %s\n", args);
/* Chew leading spaces */
args = skip_spaces(args);
while (*args) {
int irq_was_
args = next_arg(args, &param, &val);
ret = parse_one(param, val, params, num, unknown);
// 在parse_one中会调用unknown函数,并将param和val传给它
static int __init unknown_bootoption(char *param, char *val)
/* Change NUL term back to "=", to make "param" the whole string. */
if (val) {
/* param=val or param="val"? */
if (val == param+strlen(param)+1)
val[-1] = '=';
else if (val == param+strlen(param)+2) {
val[-2] = '=';
memmove(val-1, val, strlen(val)+1);
/* Handle obsolete-style parameters */
if (obsolete_checksetup(param))
/* Unused module parameter. */
if (strchr(param, '.') && (!val
strchr(param, '.') & val))
if (panic_later)
if (val) {
/* Environment option */
unsigned int
for (i = 0; envp_init[i]; i++) {
if (i == MAX_INIT_ENVS) {
panic_later = "Too many boot env vars at `%s'";
panic_param =
if (!strncmp(param, envp_init[i], val - param))
envp_init[i] =
/* Command line option */
unsigned int
for (i = 0; argv_init[i]; i++) {
if (i == MAX_INIT_ARGS) {
panic_later = "Too many boot init vars at `%s'";
panic_param =
argv_init[i] =
对于&console=ttySAC0,&符合这个条件
static int __init obsolete_checksetup(char *line)
const struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_
int n = strlen(p-&str);
if (!strncmp(line, p-&str, n)) {
if (p-&early) {
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0'
line[n] == '=')
had_early_param = 1;
} else if (!p-&setup_func) {
printk(KERN_WARNING "Parameter %s is obsolete,"
" ignored\n", p-&str);
} else if (p-&setup_func(line + n))
} while (p & __setup_end);
return had_early_
这样就会调用console_setup,并把参数:ttySAC0, 传给str变量
static int __init console_setup(char *str)
char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
char *s, *options, *brl_options = NULL;
* Decode str into name, index, options.
if (str[0] &= '0' && str[0] &= '9') {
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);
strncpy(buf, str, sizeof(buf) - 1);
//这里将ttySAC0,分成了两个字符串& ttySAC0&和&&
buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
// 此时options指向字符串&&
//这里会从ttySAC0中将0解析出来,这个循环执行完成后,*s就是&0&
for (s = *s; s++)
if ((*s &= '0' && *s &= '9')
*s == ',')
idx = simple_strtoul(s, NULL, 10);
// 将字符&0&转化成10进制类型的0,然后赋值给idx
// 将&0&所在的位置为0,那么就将&ttySAC0&变成了&ttySAC&
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
name=&ttySAC&, idx=0, options=&&, brl_options=NULL
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options)
struct console_cmdline *c;
// 此时console_cmdline还没有赋值,所以name[0]是空,循环不成立
// 如果在bootargs中传了两个console参数,那么在解析第二个console参数的时候name[0]就不是空的了
for (i = 0; i & MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
if (strcmp(console_cmdline[i].name, name) == 0 &&
console_cmdline[i].index == idx) {
if (!brl_options)
selected_console =
if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
// 从这里可以看出,如果bootargs中传递了两个console参数,如console=ttySAC0,console=ttySAC3,那么最终selected_console就是3
if (!brl_options)
selected_console =
c = &console_cmdline[i];
strlcpy(c-&name, name, sizeof(c-&name));
// &ttySAC&
c-&options =
c-&index =
这里可以想一想,如果在bootargs中传递了两个console,如&console=ttySAC0, console=ttySAC3,&,那么:
console_cmdline[0].name = &ttySAC&
console_cmdline[0].index = 0
console_cmdline[0].options = ""
console_cmdline[1].name = &ttySAC&
console_cmdline[1].index = 3
console_cmdline[1].options = ""
selected_console = 1
对于tiny4412,有四个串口,所以会调用四次register_console,但是只有一次能成功,那一次呢?如果bootargs中console参数是ttySAC0,那么只有名为ttySAC0的串口才能成功调用register_console。具体过程,我们下面分析。是在register_console中调用newcon-&setup时,因为port的mapbase没有设置而返回错误。因为这四个串口在注册时,会调用probe四次,probe的时候才会为这个port的mapbase赋值,然后才调用register_console的。
作者:彭东林 邮箱:
开发板: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/a ...
作者:彭东林 邮箱:
开发板: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
内核自解压时期 ...
作者:彭东林 邮箱:
开发板: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
tiny441 ...
作者:彭东林 邮箱:
开发板: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
我们以tiny ...
作者:彭东林 邮箱:
开发板: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
源码:kern ...他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)关于tty这部分请参考:
《Linux设备驱动开发详解 第二版》第14章 Linux终端设备驱动
《精通Linux设备驱动程序开发》第6章 串行设备驱动程序
《Linux设备驱动程序 第三版》第18章 TTY驱动程序
下面是一些串口相关的文档:
Makefile:
# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
DEBFLAGS = -O2
EXTRA_CFLAGS += $(DEBFLAGS)
EXTRA_CFLAGS += -I..
ifneq ($(KERNELRELEASE),)
# call from kernel build system
:= tiny_tty.o tiny_serial.o
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
KERNELDIR ?= /root/Tiny4412_android_4_1_2/linux-3.0.31
:= $(shell pwd)
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
depend .depend dep:
$(CC) $(EXTRA_CFLAGS) -M *.c & .depend
ifeq (.depend,$(wildcard .depend))
include .depend
tiny_tty.c
* Tiny TTY driver
* Copyright (C)
Greg Kroah-Hartman ()
This prog you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 2 of the License.
* This driver shows how to create a minimal tty driver.
It does not rely on
* any backing hardware, but creates a timer that emulates data being received
* from some kind of hardware.
#include &linux/kernel.h&
#include &linux/errno.h&
#include &linux/init.h&
#include &linux/module.h&
#include &linux/slab.h&
#include &linux/wait.h&
#include &linux/tty.h&
#include &linux/tty_driver.h&
#include &linux/tty_flip.h&
#include &linux/serial.h&
#include &linux/sched.h&
#include &linux/seq_file.h&
#include &asm/uaccess.h&
#define DRIVER_VERSION "v2.0"
#define DRIVER_AUTHOR "Greg Kroah-Hartman &&"
#define DRIVER_DESC "Tiny TTY driver"
/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");
#define DELAY_TIME
/* 2 seconds per character */
#define TINY_DATA_CHARACTER
#define TINY_TTY_MAJOR
/* experimental range */
#define TINY_TTY_MINORS
/* only have 4 devices */
struct tiny_serial {
struct tty_struct
/* pointer to the tty for this device */
/* number of times this port has been opened */
/* locks this structure */
struct timer_list
/* for tiocmget and tiocmset functions */
/* MSR shadow */
/* MCR shadow */
/* for ioctl fun */
struct serial_
wait_queue_head_
struct async_
static struct tiny_serial *tiny_table[TINY_TTY_MINORS];
/* initially all NULL */
static void tiny_timer(unsigned long timer_data)
struct tiny_serial *tiny = (struct tiny_serial *)timer_
struct tty_struct *
char data[1] = {TINY_DATA_CHARACTER};
int data_size = 1;
if (!tiny)
tty = tiny-&
/* send the data to the tty layer for users to read.
This doesn't
* actually push the data through unless tty-&low_latency is set */
for (i = 0; i & data_ ++i) {
if (!tty_buffer_request_room(tty, 1))
tty_flip_buffer_push(tty);
tty_insert_flip_char(tty, data[i], TTY_NORMAL);
tty_flip_buffer_push(tty);
/* resubmit the timer again */
tiny-&timer-&expires = jiffies + DELAY_TIME;
add_timer(tiny-&timer);
static int tiny_open(struct tty_struct *tty, struct file *file)
struct tiny_serial *
struct timer_list *
/* initialize the pointer in case something fails */
tty-&driver_data = NULL;
/* get the serial object associated with this tty pointer */
index = tty-&
tiny = tiny_table[index];
if (tiny == NULL) {
/* first time accessing this device, let's create it */
tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);
if (!tiny)
return -ENOMEM;
sema_init(&tiny-&sem, 1);
tiny-&open_count = 0;
tiny-&timer = NULL;
tiny_table[index] =
down(&tiny-&sem);
/* save our structure within the tty structure */
tty-&driver_data =
tiny-&tty =
++tiny-&open_
if (tiny-&open_count == 1) {
/* this is the first time this port is opened */
/* do any hardware initialization needed here */
/* create our timer and submit it */
if (!tiny-&timer) {
timer = kmalloc(sizeof(*timer), GFP_KERNEL);
if (!timer) {
up(&tiny-&sem);
return -ENOMEM;
tiny-&timer =
init_timer(tiny-&timer);
tiny-&timer-&data = (unsigned long )
tiny-&timer-&expires = jiffies + DELAY_TIME;
tiny-&timer-&function = tiny_
add_timer(tiny-&timer);
up(&tiny-&sem);
static void do_close(struct tiny_serial *tiny)
down(&tiny-&sem);
if (!tiny-&open_count) {
/* port was never opened */
--tiny-&open_
if (tiny-&open_count &= 0) {
/* The port is being closed by the last user. */
/* Do any hardware specific stuff here */
/* shut down our timer */
del_timer(tiny-&timer);
up(&tiny-&sem);
static void tiny_close(struct tty_struct *tty, struct file *file)
struct tiny_serial *tiny = tty-&driver_
do_close(tiny);
static int tiny_write(struct tty_struct *tty,
const unsigned char *buffer, int count)
struct tiny_serial *tiny = tty-&driver_
int retval = -EINVAL;
if (!tiny)
return -ENODEV;
down(&tiny-&sem);
if (!tiny-&open_count)
/* port was not opened */
/* fake sending the data out a hardware port by
* writing it to the kernel debug log.
printk(KERN_DEBUG "%s - ", __FUNCTION__);
for (i = 0; i & ++i)
printk("%02x ", buffer[i]);
printk("\n");
up(&tiny-&sem);
static int tiny_write_room(struct tty_struct *tty)
struct tiny_serial *tiny = tty-&driver_
int room = -EINVAL;
if (!tiny)
return -ENODEV;
down(&tiny-&sem);
if (!tiny-&open_count) {
/* port was not opened */
/* calculate how much room is left in the device */
room = 255;
up(&tiny-&sem);
#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
static void tiny_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
unsigned int
cflag = tty-&termios-&c_
/* check that they really want us to change something */
if (old_termios) {
if ((cflag == old_termios-&c_cflag) &&
(RELEVANT_IFLAG(tty-&termios-&c_iflag) ==
RELEVANT_IFLAG(old_termios-&c_iflag))) {
printk(KERN_DEBUG " - nothing to change...\n");
/* get the byte size */
switch (cflag & CSIZE) {
printk(KERN_DEBUG " - data bits = 5\n");
printk(KERN_DEBUG " - data bits = 6\n");
printk(KERN_DEBUG " - data bits = 7\n");
printk(KERN_DEBUG " - data bits = 8\n");
/* determine the parity */
if (cflag & PARENB)
if (cflag & PARODD)
printk(KERN_DEBUG " - parity = odd\n");
printk(KERN_DEBUG " - parity = even\n");
printk(KERN_DEBUG " - parity = none\n");
/* figure out the stop bits requested */
if (cflag & CSTOPB)
printk(KERN_DEBUG " - stop bits = 2\n");
printk(KERN_DEBUG " - stop bits = 1\n");
/* figure out the hardware flow control settings */
if (cflag & CRTSCTS)
printk(KERN_DEBUG " - RTS/CTS is enabled\n");
printk(KERN_DEBUG " - RTS/CTS is disabled\n");
/* determine software flow control */
/* if we are implementing XON/XOFF, set the start and
* stop character in the device */
if (I_IXOFF(tty) || I_IXON(tty)) {
unsigned char stop_char
= STOP_CHAR(tty);
unsigned char start_char = START_CHAR(tty);
/* if we are implementing INBOUND XON/XOFF */
if (I_IXOFF(tty))
printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, "
"XON = %2x, XOFF = %2x", start_char, stop_char);
printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");
/* if we are implementing OUTBOUND XON/XOFF */
if (I_IXON(tty))
printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, "
"XON = %2x, XOFF = %2x", start_char, stop_char);
printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");
/* get the baud rate wanted */
printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));
/* Our fake UART values */
#define MCR_DTR
#define MCR_RTS
#define MCR_LOOP
#define MSR_CTS
#define MSR_CD
#define MSR_RI
#define MSR_DSR
static int tiny_tiocmget(struct tty_struct *tty)
struct tiny_serial *tiny = tty-&driver_
unsigned int result = 0;
unsigned int msr = tiny-&
unsigned int mcr = tiny-&
result = ((mcr & MCR_DTR)
? TIOCM_DTR
/* DTR is set */
((mcr & MCR_RTS)
? TIOCM_RTS
/* RTS is set */
((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) |
/* LOOP is set */
((msr & MSR_CTS)
? TIOCM_CTS
/* CTS is set */
((msr & MSR_CD)
? TIOCM_CAR
/* Carrier detect is set*/
((msr & MSR_RI)
? TIOCM_RI
/* Ring Indicator is set */
((msr & MSR_DSR)
? TIOCM_DSR
/* DSR is set */
static int tiny_tiocmset(struct tty_struct *tty, unsigned int set,
unsigned int clear)
struct tiny_serial *tiny = tty-&driver_
unsigned int mcr = tiny-&
if (set & TIOCM_RTS)
mcr |= MCR_RTS;
if (set & TIOCM_DTR)
mcr |= MCR_RTS;
if (clear & TIOCM_RTS)
mcr &= ~MCR_RTS;
if (clear & TIOCM_DTR)
mcr &= ~MCR_RTS;
/* set the new MCR value in the device */
tiny-&mcr =
static int tiny_proc_show(struct seq_file *m, void *v)
struct tiny_serial *
seq_printf(m, "tinyserinfo:1.0 driver:%s\n", DRIVER_VERSION);
for (i = 0; i & TINY_TTY_MINORS; ++i) {
tiny = tiny_table[i];
if (tiny == NULL)
seq_printf(m, "%d\n", i);
#define tiny_ioctl tiny_ioctl_tiocgserial
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
struct tiny_serial *tiny = tty-&driver_
if (cmd == TIOCGSERIAL) {
struct serial_
return -EFAULT;
memset(&tmp, 0, sizeof(tmp));
= tiny-&serial.
= tiny-&serial.
= tiny-&serial.
= tiny-&serial.
= ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
tmp.xmit_fifo_size
= tiny-&serial.xmit_fifo_
tmp.baud_base
= tiny-&serial.baud_
tmp.close_delay
tmp.closing_wait
tmp.custom_divisor
= tiny-&serial.custom_
= tiny-&serial.hub6;
tmp.io_type
= tiny-&serial.io_
if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct)))
return -EFAULT;
return -ENOIOCTLCMD;
#undef tiny_ioctl
#define tiny_ioctl tiny_ioctl_tiocmiwait
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
struct tiny_serial *tiny = tty-&driver_
if (cmd == TIOCMIWAIT) {
DECLARE_WAITQUEUE(wait, current);
struct async_
struct async_
cprev = tiny-&
while (1) {
add_wait_queue(&tiny-&wait, &wait);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
remove_wait_queue(&tiny-&wait, &wait);
/* see if a signal woke us up */
if (signal_pending(current))
return -ERESTARTSYS;
cnow = tiny-&
if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
return -EIO; /* no change =& error */
if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD)
&& (cnow.dcd != cprev.dcd)) ||
((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
return -ENOIOCTLCMD;
#undef tiny_ioctl
#define tiny_ioctl tiny_ioctl_tiocgicount
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
struct tiny_serial *tiny = tty-&driver_
if (cmd == TIOCGICOUNT) {
struct async_icount cnow = tiny-&
struct serial_icounter_
icount.cts
icount.dsr
icount.rng
icount.dcd
icount.frame
icount.overrun
icount.parity
icount.brk
icount.buf_overrun = cnow.buf_
if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))
return -EFAULT;
return -ENOIOCTLCMD;
#undef tiny_ioctl
/* the real tiny_ioctl function.
The above is done to get the small functions in the book */
static int tiny_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg)
switch (cmd) {
case TIOCGSERIAL:
return tiny_ioctl_tiocgserial(tty, cmd, arg);
case TIOCMIWAIT:
return tiny_ioctl_tiocmiwait(tty, cmd, arg);
case TIOCGICOUNT:
return tiny_ioctl_tiocgicount(tty, cmd, arg);
return -ENOIOCTLCMD;
static int tiny_proc_open(struct inode *inode, struct file *file)
return single_open(file, tiny_proc_show, NULL);
static const struct file_operations serial_proc_fops = {
= THIS_MODULE,
= tiny_proc_open,
= seq_read,
= seq_lseek,
= single_release,
static struct tty_operations serial_ops = {
.open = tiny_open,
.close = tiny_close,
.write = tiny_write,
.write_room = tiny_write_room,
.set_termios = tiny_set_termios,
.proc_fops
= &serial_proc_fops,
.tiocmget = tiny_tiocmget,
.tiocmset = tiny_tiocmset,
.ioctl = tiny_ioctl,
static struct tty_driver *tiny_tty_
static int __init tiny_init(void)
/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
if (!tiny_tty_driver)
return -ENOMEM;
/* initialize the tty driver */
tiny_tty_driver-&owner = THIS_MODULE;
tiny_tty_driver-&driver_name = "tiny_tty";
tiny_tty_driver-&name = "ttty";
tiny_tty_driver-&major = TINY_TTY_MAJOR,
tiny_tty_driver-&type = TTY_DRIVER_TYPE_SERIAL,
tiny_tty_driver-&subtype = SERIAL_TYPE_NORMAL,
tiny_tty_driver-&flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV,
tiny_tty_driver-&init_termios = tty_std_
tiny_tty_driver-&init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
tty_set_operations(tiny_tty_driver, &serial_ops);
/* register the tty driver */
retval = tty_register_driver(tiny_tty_driver);
if (retval) {
printk(KERN_ERR "failed to register tiny tty driver");
put_tty_driver(tiny_tty_driver);
for (i = 0; i & TINY_TTY_MINORS; ++i)
tty_register_device(tiny_tty_driver, i, NULL);
printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION);
static void __exit tiny_exit(void)
struct tiny_serial *
for (i = 0; i & TINY_TTY_MINORS; ++i)
tty_unregister_device(tiny_tty_driver, i);
tty_unregister_driver(tiny_tty_driver);
/* shut down all of the timers and free the memory */
for (i = 0; i & TINY_TTY_MINORS; ++i) {
tiny = tiny_table[i];
if (tiny) {
/* close the port */
while (tiny-&open_count)
do_close(tiny);
/* shut down our timer and free the memory */
del_timer(tiny-&timer);
kfree(tiny-&timer);
kfree(tiny);
tiny_table[i] = NULL;
module_init(tiny_init);
module_exit(tiny_exit);
tiny_serial.c
* Tiny Serial driver
* Copyright (C)
Greg Kroah-Hartman ()
This prog you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 2 of the License.
* This driver shows how to create a minimal serial driver.
It does not rely on
* any backing hardware, but creates a timer that emulates data being received
* from some kind of hardware.
#include &linux/kernel.h&
#include &linux/errno.h&
#include &linux/init.h&
#include &linux/slab.h&
#include &linux/tty.h&
#include &linux/tty_flip.h&
#include &linux/serial.h&
#include &linux/serial_core.h&
#include &linux/module.h&
#define DRIVER_AUTHOR "Greg Kroah-Hartman &&"
#define DRIVER_DESC "Tiny serial driver"
/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");
#define DELAY_TIME
/* 2 seconds per character */
#define TINY_DATA_CHARACTER
#define TINY_SERIAL_MAJOR
/* experimental range */
#define TINY_SERIAL_MINORS
/* only have one minor */
#define UART_NR
/* only use one port */
#define TINY_SERIAL_NAME
#define MY_NAME
TINY_SERIAL_NAME
static struct timer_list *
static void tiny_stop_tx(struct uart_port *port)
static void tiny_stop_rx(struct uart_port *port)
static void tiny_enable_ms(struct uart_port *port)
static void tiny_tx_chars(struct uart_port *port)
struct circ_buf *xmit = &port-&state-&
if (port-&x_char) {
pr_debug("wrote %2x", port-&x_char);
port-&icount.tx++;
port-&x_char = 0;
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
tiny_stop_tx(port);
count = port-&fifosize && 1;
pr_debug("wrote %2x", xmit-&buf[xmit-&tail]);
xmit-&tail = (xmit-&tail + 1) & (UART_XMIT_SIZE - 1);
port-&icount.tx++;
if (uart_circ_empty(xmit))
} while (--count & 0);
if (uart_circ_chars_pending(xmit) & WAKEUP_CHARS)
uart_write_wakeup(port);
if (uart_circ_empty(xmit))
tiny_stop_tx(port);
static void tiny_start_tx(struct uart_port *port)
static void tiny_timer(unsigned long data)
struct uart_port *
struct tty_struct *
port = (struct uart_port *)
if (!port)
if (!port-&state)
tty = port-&state-&port.
/* add one character to the tty port */
/* this doesn't actually push the data through unless tty-&low_latency is set */
tty_insert_flip_char(tty, TINY_DATA_CHARACTER, 0);
tty_flip_buffer_push(tty);
/* resubmit the timer again */
timer-&expires = jiffies + DELAY_TIME;
add_timer(timer);
/* see if we have any data to transmit */
tiny_tx_chars(port);
static unsigned int tiny_tx_empty(struct uart_port *port)
static unsigned int tiny_get_mctrl(struct uart_port *port)
static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl)
static void tiny_break_ctl(struct uart_port *port, int break_state)
static void tiny_set_termios(struct uart_port *port,
struct ktermios *new, struct ktermios *old)
int baud, quot, cflag = new-&c_
/* get the byte size */
switch (cflag & CSIZE) {
printk(KERN_DEBUG " - data bits = 5\n");
printk(KERN_DEBUG " - data bits = 6\n");
printk(KERN_DEBUG " - data bits = 7\n");
default: // CS8
printk(KERN_DEBUG " - data bits = 8\n");
/* determine the parity */
if (cflag & PARENB)
if (cflag & PARODD)
pr_debug(" - parity = odd\n");
pr_debug(" - parity = even\n");
pr_debug(" - parity = none\n");
/* figure out the stop bits requested */
if (cflag & CSTOPB)
pr_debug(" - stop bits = 2\n");
pr_debug(" - stop bits = 1\n");
/* figure out the flow control settings */
if (cflag & CRTSCTS)
pr_debug(" - RTS/CTS is enabled\n");
pr_debug(" - RTS/CTS is disabled\n");
/* Set baud rate */
baud = uart_get_baud_rate(port, new, old, 0, port-&uartclk/16);
quot = uart_get_divisor(port, baud);
//UART_PUT_DIV_LO(port, (quot & 0xff));
//UART_PUT_DIV_HI(port, ((quot & 0xf00) && 8));
static int tiny_startup(struct uart_port *port)
/* this is the first time this port is opened */
/* do any hardware initialization needed here */
/* create our timer and submit it */
if (!timer) {
timer = kmalloc(sizeof(*timer), GFP_KERNEL);
if (!timer)
return -ENOMEM;
init_timer(timer);
timer-&data = (unsigned long)
timer-&expires = jiffies + DELAY_TIME;
timer-&function = tiny_
add_timer(timer);
static void tiny_shutdown(struct uart_port *port)
/* The port is being closed by the last user. */
/* Do any hardware specific stuff here */
/* shut down our timer */
del_timer(timer);
static const char *tiny_type(struct uart_port *port)
return "tinytty";
static void tiny_release_port(struct uart_port *port)
static int tiny_request_port(struct uart_port *port)
static void tiny_config_port(struct uart_port *port, int flags)
static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser)
static struct uart_ops tiny_ops = {
= tiny_tx_empty,
.set_mctrl
= tiny_set_mctrl,
.get_mctrl
= tiny_get_mctrl,
= tiny_stop_tx,
= tiny_start_tx,
= tiny_stop_rx,
.enable_ms
= tiny_enable_ms,
.break_ctl
= tiny_break_ctl,
= tiny_startup,
= tiny_shutdown,
.set_termios
= tiny_set_termios,
= tiny_type,
.release_port
= tiny_release_port,
.request_port
= tiny_request_port,
.config_port
= tiny_config_port,
.verify_port
= tiny_verify_port,
static struct uart_port tiny_port = {
= &tiny_ops,
static struct uart_driver tiny_reg = {
= THIS_MODULE,
.driver_name
= TINY_SERIAL_NAME,
= TINY_SERIAL_NAME,
= TINY_SERIAL_MAJOR,
= TINY_SERIAL_MINORS,
= UART_NR,
static int __init tiny_init(void)
printk(KERN_INFO "Tiny serial driver loaded\n");
result = uart_register_driver(&tiny_reg);
if (result)
result = uart_add_one_port(&tiny_reg, &tiny_port);
if (result)
uart_unregister_driver(&tiny_reg);
module_init(tiny_init);
阅读(...) 评论()

我要回帖

更多关于 tiny4412驱动开发 的文章

 

随机推荐