一 uboot传递参数'console=ttyXXX'的作用
linux启动时uboot传递进console=ttyS0,115200n8的参数
内核中用__setup()宏声明参数处理的方法
关于__setup宏参考 early_param和__setup宏
__setup("console=", console_setup);
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
static int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; //分配驱动名+index的缓冲区
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[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL) //获取options
*(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 == ',')
break;
idx = simple_strtoul(s, NULL, 10); //获取次设备号
*s = 0;
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return 1;
}
__add_preferred_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的驱动名
c->options = options; //填充配置选项115200n8
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
c->brl_options = brl_options;
#endif
c->index = idx; //填充索引号0
return 0;
}
整体的作用是根据uboot传递的参数设置全局console_cmdline数组
该数组及全局selected_console,在register_console中会使用到
二 console 设备驱动
二 console 设备驱动
一.结构体
1.console
struct console {
char name[16]; //console名
void (*write)(struct console *, const char *, unsigned); //写方法
int (*read)(struct console *, char *, unsigned); //读方法
struct tty_driver *(*device)(struct console *, int *); //tty驱动
void (*unblank)(void);
int (*setup)(struct console *, char *); //setup方法
int (*early_setup)(void); //early_setup方法
short flags;
short index;
int cflag;
void *data;
struct console *next;
};
2.标志
#define CON_PRINTBUFFER (1)
#define CON_CONSDEV (2) /* Last on the command line */
#define CON_ENABLED (4) //使能标志位
#define CON_BOOT (8) //内核引导使用的console
#define CON_ANYTIME (16) /* Safe to call when cpu is offline */
#define CON_BRL (32) /* Used for a braille device */
二.初始化
在Vmlinux.lds.h中定义
#define CON_INITCALL \
VMLINUX_SYMBOL(__con_initcall_start) = .; \
*(.con_initcall.init) \
VMLINUX_SYMBOL(__con_initcall_end) = .;
在init.h中定义
#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.con_initcall.init) = fn
在console_init初始化(start_kernel中调用了console_init函数)
void __init console_init(void)
{
initcall_t *call;
tty_ldisc_begin();
call = __con_initcall_start;
while (call < __con_initcall_end) { //遍历__con_initcall_end段的函数
(*call)(); //调用该函数
call++;
}
}
tty线路规程开始
void tty_ldisc_begin(void)
{
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); //N_TTY=0
}
tty注册默认的线路规程
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
unsigned long flags;
int ret = 0;
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;
spin_lock_irqsave(&tty_ldisc_lock, flags);
tty_ldiscs[disc] = new_ldisc; //设置全局tty_ldiscs[0]=tty_ldisc_N_TTY
new_ldisc->num = disc; //设置num域
new_ldisc->refcount = 0; //参考计数
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
return ret;
}
EXPORT_SYMBOL(tty_register_ldisc);
该全局数组在tty_ldisc_init函数中使用
三.console的注册和注销
1.注册console
void register_console(struct console *newcon)
{
int i;
unsigned long flags;
struct console *bcon = NULL;
if (console_drivers && newcon->flags & 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_setup函数
newcon->early_setup(); //则调用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_cmdline找到匹配的
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;
#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
if (newcon->setup &&newcon->setup(newcon, console_cmdline[i].options) != 0) //存在setup方法调用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; //设置preferred
}
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链表后面位置
}
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);
}
}
EXPORT_SYMBOL(register_console);
注册console主要是刷选preferred_console放置在全局console_drivers链表前面,剩下的console放置链表靠后的位置,并设置相应的flags,
console_drivers最终会在printk函数的层层调用中遍历到,并调用console的write方法将信息打印出来
2.注销console
int unregister_console(struct console *console)
{
struct console *a, *b;
int res = 1;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (console->flags & CON_BRL)
return braille_unregister_console(console);
#endif
acquire_console_sem();
if (console_drivers == console) {
console_drivers=console->next;
res = 0;
} else if (console_drivers) {
for (a=console_drivers->next, b=console_drivers ;
a; b=a, a=b->next) {
if (a == console) {
b->next = a->next;
res = 0;
break;
}
}
}
/*
* If this isn't the last console and it has CON_CONSDEV set, we
* need to set it on the next preferred console.
*/
if (console_drivers != NULL && console->flags & CON_CONSDEV)
console_drivers->flags |= CON_CONSDEV;
release_console_sem();
return res;
}
EXPORT_SYMBOL(unregister_console);
3./dev/console
在tty_init函数中
int __init tty_init(void)
{
cdev_init(&tty_cdev, &tty_fops);
if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
panic("Couldn't register /dev/tty driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL,"tty");
cdev_init(&console_cdev, &console_fops); //初始化字符设备并捆绑console_fops操作函数集合
if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) //注册字符设备
panic("Couldn't register /dev/console driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,"console"); //创建设备文件
#ifdef CONFIG_VT
vty_init(&console_fops);
#endif
return 0;
}
当操作/dev/console文件时会调用console_fops操作函数集的方法
static const struct file_operations console_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = redirected_tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
该函数集跟tty_fops基本一样不同的只是写方法
redirected_tty_write函数
ssize_t redirected_tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{
struct file *p = NULL;
spin_lock(&redirect_lock);
if (redirect) {
get_file(redirect);
p = redirect;
}
spin_unlock(&redirect_lock);
if (p) {
ssize_t res;
res = vfs_write(p, buf, count, &p->f_pos); //多了个写文件的方法
fput(p);
return res;
}
return tty_write(file, buf, count, ppos); //同样也会调用tty_write方法
}
在tty_open中有个分支专门处理/dev/console
if (device == MKDEV(TTYAUX_MAJOR, 1)) {
struct tty_driver *console_driver = console_device(&index); //获取console对应的tty-driver
if (console_driver) {
driver = tty_driver_kref_get(console_driver);
if (driver) {
filp->f_flags |= O_NONBLOCK;
noctty = 1;
goto got_driver;
}
}
tty_unlock();
mutex_unlock(&tty_mutex);
return -ENODEV;
}
console_device
struct tty_driver *console_device(int *index)
{
struct console *c;
struct tty_driver *driver = NULL;
acquire_console_sem();
for_each_console(c) {
if (!c->device) //console存在tty_driver 说明是/dev/console对应的console
continue;
driver = c->device(c, index);
if (driver)
break;
}
release_console_sem();
return driver;
}
三 printk如何打印信息
printk的定义
可变参数 参考va_list,va_start,va_arg,va_end可变参数
asmlinkage int printk(const char *fmt, ...)
{
va_list args; //可变参数链表
int r;
#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
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();
lockdep_on();
out_restore_irqs:
raw_local_irq_restore(flags);
preempt_enable();
return printed_len;
}
接着调用release_console_sem函数
void release_console_sem(void)
{
unsigned long flags;
unsigned _con_start, _log_end;
unsigned wake_klogd = 0;
if (console_suspended) {
up(&console_sem);
return;
}
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);
start_critical_timings();
local_irq_restore(flags);
}
console_locked = 0;
up(&console_sem);
spin_unlock_irqrestore(&logbuf_lock, flags);
if (wake_klogd)
wake_up_klogd();
}
EXPORT_SYMBOL(release_console_sem);
调用call_console_drivers函数
static void call_console_drivers(unsigned start, unsigned end)
{
unsigned cur_index, start_print;
static int msg_level = -1;
BUG_ON(((int)(start - end)) > 0);
cur_index = start;
start_print = start;
while (cur_index != end) {
if (msg_level < 0 && ((end - cur_index) > 2) &&LOG_BUF(cur_index + 0) == '<' &&LOG_BUF(cur_index + 1) >= '0' &&LOG_BUF(cur_index + 1) <= '7' &&LOG_BUF(cur_index + 2) == '>') {
msg_level = LOG_BUF(cur_index + 1) - '0';
cur_index += 3;
start_print = cur_index;
}
while (cur_index != end) {
char c = LOG_BUF(cur_index);
cur_index++;
if (c == '\n') {
if (msg_level < 0) {
msg_level = default_message_loglevel;
}
_call_console_drivers(start_print, cur_index, msg_level);
msg_level = -1;
start_print = cur_index;
break;
}
}
}
_call_console_drivers(start_print, end, msg_level);
}
_call_console_drivers函数
static void _call_console_drivers(unsigned start,unsigned end, int msg_log_level)
{
if ((msg_log_level < console_loglevel || ignore_loglevel) && console_drivers && start != end) {
if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
/* wrapped write */
__call_console_drivers(start & LOG_BUF_MASK,log_buf_len);
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}
__call_console_drivers函数
遍历console_drivers数组
#define for_each_console(con) \
for (con = console_drivers; con != NULL; con = con->next)
调用console的写方法
static void __call_console_drivers(unsigned start, unsigned end)
{
struct console *con;
for_each_console(con) {
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的写方法
}
}
console_drivers链表在register_console中会设置
二 console 设备驱动
四 linux tty驱动
分类: linux设备驱动 2013-01-29 23:24 313人阅读 评论(0)收藏 举报
一. tty结构体
1.tty_driver
struct tty_driver {
int magic;
struct kref kref; //参考计数
struct cdev cdev; //字符设备
struct module *owner; //模块所有者
const char *driver_name; //驱动名
const char *name; //设备名
int name_base;
int major; //主设备号
int minor_start; //起始次设备号
int minor_num; //设备个数
int num; //分配了的tty设备个数
short type; //tty设备的类型
short subtype; //tty设备子类型
struct ktermios init_termios; //初始化的ktermios
int flags; //tty驱动标志
struct proc_dir_entry *proc_entry; //procfs入口
struct tty_driver *other;
struct tty_struct **ttys;
struct ktermios **termios;
struct ktermios **termios_locked;
void *driver_state;
const struct tty_operations *ops; //操作函数集
struct list_head tty_drivers; //驱动链表
};
1.1 tty->flag
#define TTY_DRIVER_INSTALLED 0x0001
#define TTY_DRIVER_RESET_TERMIOS 0x0002
#define TTY_DRIVER_REAL_RAW 0x0004
#define TTY_DRIVER_DYNAMIC_DEV 0x0008
#define TTY_DRIVER_DEVPTS_MEM 0x0010
#define TTY_DRIVER_HARDWARE_BREAK 0x0020
1.2 tty->type tty设备类型
#define TTY_DRIVER_TYPE_SYSTEM 0x0001
#define TTY_DRIVER_TYPE_CONSOLE 0x0002
#define TTY_DRIVER_TYPE_SERIAL 0x0003
#define TTY_DRIVER_TYPE_PTY 0x0004
#define TTY_DRIVER_TYPE_SCC 0x0005 /* scc driver */
#define TTY_DRIVER_TYPE_SYSCONS 0x0006
2.ktermios结构体
struct ktermios {
tcflag_t c_iflag; /* input mode flags */ //输入模式标志
tcflag_t c_oflag; /* output mode flags */ //输出模式标志
tcflag_t c_cflag; /* control mode flags */ //控制模式标志
tcflag_t c_lflag; /* local mode flags */ //本地模式标志
cc_t c_line; /* line discipline */ //线路规程类型
cc_t c_cc[NCCS]; /* control characters */ //控制字符
speed_t c_ispeed; /* input speed */ //输入速度
speed_t c_ospeed; /* output speed */ //输出速度
};
3.tty_struct
struct tty_struct {
int magic; //魔数
struct kref kref; //参考计数
struct device *dev; //设备文件
struct tty_driver *driver; //tty驱动
const struct tty_operations *ops; //tty操作函数集
int index;
struct mutex ldisc_mutex;
struct tty_ldisc *ldisc; //线路规程
struct mutex termios_mutex;
spinlock_t ctrl_lock;
struct ktermios *termios, *termios_locked;
struct termiox *termiox;
char name[64]; //名字
struct pid *pgrp;
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize;
unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;
unsigned char low_latency:1, warned:1;
unsigned char ctrl_status;
unsigned int receive_room;
struct tty_struct *link;
struct fasync_struct *fasync;
struct tty_bufhead buf;
int alt_speed;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
unsigned int column;
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
unsigned char closing:1;
unsigned char echo_overrun:1;
unsigned short minimum_to_wake;
unsigned long overrun_time;
int num_overrun;
unsigned long process_char_map[256/(8*sizeof(unsigned long))];
char *read_buf;
int read_head;
int read_tail;
int read_cnt;
unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];
unsigned char *echo_buf;
unsigned int echo_pos;
unsigned int echo_cnt;
int canon_data;
unsigned long canon_head;
unsigned int canon_column;
struct mutex atomic_read_lock;
struct mutex atomic_write_lock;
struct mutex output_lock;
struct mutex echo_lock;
unsigned char *write_buf;
int write_cnt;
spinlock_t read_lock;
struct work_struct SAK_work;
struct tty_port *port;
};
4.tty_ldisc线路规程
struct tty_ldisc {
struct tty_ldisc_ops *ops;
atomic_t users;
};
4.1 线路规程操作函数集
struct tty_ldisc_ops {
int magic;
char *name;
int num;
int flags;
int (*open)(struct tty_struct *);
void (*close)(struct tty_struct *);
void (*flush_buffer)(struct tty_struct *tty);
ssize_t (*chars_in_buffer)(struct tty_struct *tty);
ssize_t (*read)(struct tty_struct * tty, struct file * file,unsigned char __user * buf, size_t nr);
ssize_t (*write)(struct tty_struct * tty, struct file * file,const unsigned char * buf, size_t nr);
int (*ioctl)(struct tty_struct * tty, struct file * file,unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct * tty, struct file * file,unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
unsigned int (*poll)(struct tty_struct *, struct file *,struct poll_table_struct *);
int (*hangup)(struct tty_struct *tty);
void (*receive_buf)(struct tty_struct *, const unsigned char *cp,char *fp, int count);
void (*write_wakeup)(struct tty_struct *);
void (*dcd_change)(struct tty_struct *, unsigned int,struct timespec *);
struct module *owner;
int refcount;
};
二.系统初始化
设备类初始化
static int __init tty_class_init(void)
{
tty_class = class_create(THIS_MODULE, "tty"); //创建tty类
if (IS_ERR(tty_class))
return PTR_ERR(tty_class);
tty_class->devnode = tty_devnode; //指定tty设备节点
return 0;
}
字符设备初始化
int __init tty_init(void)
{
cdev_init(&tty_cdev, &tty_fops); //初始化tty字符设备
if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || //添加tty字符设备
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) //注册/dev/tty字符设备
panic("Couldn't register /dev/tty driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL,"tty"); //注册tty设备
cdev_init(&console_cdev, &console_fops); //初始化console字符设备
if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || //添加console字符设备
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) //注册/dev/console设备
panic("Couldn't register /dev/console driver\n");
device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,"console"); //创建console设备
#ifdef CONFIG_VT
vty_init(&console_fops);
#endif
return 0;
}
虚拟终端/dev/tty0初始化vty_init
int __init vty_init(const struct file_operations *console_fops)
{
cdev_init(&vc0_cdev, console_fops);
if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
panic("Couldn't register /dev/tty0 driver\n");
device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), NULL, "tty0"); //创建/dev/tty0 (4,0)
vcs_init();
console_driver = alloc_tty_driver(MAX_NR_CONSOLES);
if (!console_driver)
panic("Couldn't allocate console driver\n");
console_driver->owner = THIS_MODULE;
console_driver->name = "tty";
console_driver->name_base = 1;
console_driver->major = TTY_MAJOR;
console_driver->minor_start = 1;
console_driver->type = TTY_DRIVER_TYPE_CONSOLE;
console_driver->init_termios = tty_std_termios;
if (default_utf8)
console_driver->init_termios.c_iflag |= IUTF8;
console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
tty_set_operations(console_driver, &con_ops);
if (tty_register_driver(console_driver))
panic("Couldn't register console driver\n");
kbd_init();
console_map_init();
#ifdef CONFIG_MDA_CONSOLE
mda_console_init();
#endif
return 0;
}
三.tty初始化步骤
1.分配tty结构体
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); //引用计数+1
driver->magic = TTY_DRIVER_MAGIC; //设置魔数0x5402
driver->num = lines; //设置tty line个数
}
return driver;
}
2.填充tty结构体成员
3.注册tty设备驱动
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 (!p)
return -ENOMEM;
}
if (!driver->major) { //指定了主设备号
error = alloc_chrdev_region(&dev, driver->minor_start,driver->num, driver->name); //分配多个设备号
if (!error) {
driver->major = MAJOR(dev); //主设备号
driver->minor_start = MINOR(dev); //次设备号
}
} else {
dev = MKDEV(driver->major, driver->minor_start); //获取第一个设备号
error = register_chrdev_region(dev, driver->num, driver->name); //分配多个设备号
}
if (error < 0) {
kfree(p);
return error;
}
if (p) {
driver->ttys = (struct tty_struct **)p; //tty_struct
driver->termios = (struct ktermios **)(p + driver->num); //ktermios
k } else {
driver->ttys = NULL;
driver->termios = NULL;
}
cdev_init(&driver->cdev, &tty_fops); //初始化字符设备
driver->cdev.owner = driver->owner; //设置模块所有者
error = cdev_add(&driver->cdev, dev, driver->num); //添加字符设备
if (error) {
unregister_chrdev_region(dev, driver->num);
driver->ttys = NULL;
driver->termios = NULL;
kfree(p);
return error;
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers); //添加到全局tty_drivers链表
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { //标志动态创建设备标志
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL); //添加tty设备文件/dev/ttyXX
if (IS_ERR(d)) {
error = PTR_ERR(d);
goto err;
}
}
}
proc_tty_register_driver(driver); //设置tty在/proc下的接口
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
err:
for (i--; i >= 0; i--)
tty_unregister_device(driver, i);
mutex_lock(&tty_mutex);
list_del(&driver->tty_drivers);
mutex_unlock(&tty_mutex);
unregister_chrdev_region(dev, driver->num);
driver->ttys = NULL;
driver->termios = NULL;
kfree(p);
return error;
}
4.创建tty设备文件/dev/ttyXXX
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; //获取设备号
if (index >= driver->num) {
printk(KERN_ERR "Attempt to register invalid tty line number (%d).\n", index);
return ERR_PTR(-EINVAL);
}
if (driver->type == TTY_DRIVER_TYPE_PTY) //伪终端
pty_line_name(driver, index, name);
else //"/dev/ttyXXX"
tty_line_name(driver, index, name);
return device_create(tty_class, device, dev, NULL, name); //创建设备文件
}
5.tty设备文件捆绑的操作函数集tty_fops
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
四. tty的操作
当打开/dev/ttyXX的时候会调用tty_open函数
tty_open操作
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev; //通过i节点获取设备号
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp); //设置打开关联文件的模式
retry_open:
noctty = filp->f_flags & O_NOCTTY; //设置O_NOCTTY标志
index = -1;
retval = 0;
mutex_lock(&tty_mutex);
tty_lock();
if (device == MKDEV(TTYAUX_MAJOR, 0)) { //打开的设备是/dev/tty (5,0)
tty = get_current_tty(); //取当前进程tty_struct
if (!tty) {
tty_unlock();
mutex_unlock(&tty_mutex);
return -ENXIO;
}
driver = tty_driver_kref_get(tty->driver); //获取tty_driver
index = tty->index;
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
tty_kref_put(tty); //引用计数
goto got_driver;
}
#ifdef CONFIG_VT //虚拟终端设备 /dev/tty0
if (device == MKDEV(TTY_MAJOR, 0)) { //(4,0)
extern struct tty_driver *console_driver;
driver = tty_driver_kref_get(console_driver); //获取tty_struct
index = fg_console;
noctty = 1;
goto got_driver;
}
#endif
if (device == MKDEV(TTYAUX_MAJOR, 1)) { //打开的是/dev/console (5,1)
struct tty_driver *console_driver = console_device(&index); //获取console对应的tty_driver
if (console_driver) {
driver = tty_driver_kref_get(console_driver); //获取tty_struct
if (driver) {
filp->f_flags |= O_NONBLOCK;
noctty = 1;
goto got_driver;
}
}
tty_unlock();
mutex_unlock(&tty_mutex);
return -ENODEV;
}
driver = get_tty_driver(device, &index); //获取tty_driver,设置次设备号(索引值) 非特殊的tty
if (!driver) {
tty_unlock();
mutex_unlock(&tty_mutex);
return -ENODEV;
}
got_driver:
if (!tty) {
tty = tty_driver_lookup_tty(driver, inode, index); //获取tty_drivver的tty_struct
if (IS_ERR(tty)) {
tty_unlock();
mutex_unlock(&tty_mutex);
return PTR_ERR(tty);
}
}
if (tty) { //若tty_struct不为空
retval = tty_reopen(tty);
if (retval)
tty = ERR_PTR(retval);
} else //若tty_struct为空
tty = tty_init_dev(driver, index, 0); //调用tty_init_dev函数(下面分析)
mutex_unlock(&tty_mutex);
tty_driver_kref_put(driver); //增加引用计数
if (IS_ERR(tty)) {
tty_unlock();
return PTR_ERR(tty);
}
retval = tty_add_file(tty, filp);
if (retval) {
tty_unlock();
return retval;
}
check_tty_count(tty, "tty_open");
if (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_MASTER)
noctty = 1;
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "opening %s...", tty->name);
#endif
if (!retval) {
if (tty->ops->open) //若tty_struct存在open方法
retval = tty->ops->open(tty, filp); //则调用其open方法
else
retval = -ENODEV;
}
filp->f_flags = saved_flags;
if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&!capable(CAP_SYS_ADMIN))
retval = -EBUSY;
if (retval) {
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "error %d in opening %s...", retval,tty->name);
#endif
tty_unlock(); /* need to call tty_release without BTM */
tty_release(inode, filp);
if (retval != -ERESTARTSYS)
return retval
if (signal_pending(current))
return retval;
schedule();
tty_lock();
if (filp->f_op == &hung_up_tty_fops)
filp->f_op = &tty_fops;
tty_unlock();
goto retry_open;
}
tty_unlock();
mutex_lock(&tty_mutex);
tty_lock();
spin_lock_irq(¤t->sighand->siglock);
if (!noctty && current->signal->leader &&!current->signal->tty && tty->session == NULL)
__proc_set_tty(current, tty);
spin_unlock_irq(¤t->sighand->siglock);
tty_unlock();
mutex_unlock(&tty_mutex);
return 0;
}
tty_init_dev函数
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)
{
struct tty_struct *tty;
int retval;
if (driver->subtype == PTY_TYPE_MASTER &&(driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok) {
return ERR_PTR(-EIO);
}
if (!try_module_get(driver->owner))
return ERR_PTR(-ENODEV);
tty = alloc_tty_struct(); //分配tty_struct
if (!tty)
goto fail_no_mem;
initialize_tty_struct(tty, driver, idx); //初始化tty_struct
retval = tty_driver_install_tty(driver, tty); //调用tty_driver的install方法,并初始化termios
if (retval < 0) {
free_tty_struct(tty);
module_put(driver->owner);
return ERR_PTR(retval);
}
retval = tty_ldisc_setup(tty, tty->link); //调用线路规程的open方法,及线路规程link的open方法
if (retval)
goto release_mem_out;
return tty;
fail_no_mem:
module_put(driver->owner);
return ERR_PTR(-ENOMEM);
release_mem_out:
if (printk_ratelimit())
printk(KERN_INFO "tty_init_dev: ldisc open failed,clearing slot %d\n", idx);
release_tty(tty, idx);
return ERR_PTR(retval);
}
tty_init_dev>>>initialize_tty_struct
void initialize_tty_struct(struct tty_struct *tty,struct tty_driver *driver, int idx)
{
memset(tty, 0, sizeof(struct tty_struct)); //初始化tty_struct
kref_init(&tty->kref);
tty->magic = TTY_MAGIC; //魔数
tty_ldisc_init(tty); //初始化线路规程
tty->session = NULL;
tty->pgrp = NULL;
tty->overrun_time = jiffies;
tty->buf.head = tty->buf.tail = NULL;
tty_buffer_init(tty);
mutex_init(&tty->termios_mutex);
mutex_init(&tty->ldisc_mutex);
init_waitqueue_head(&tty->write_wait); //初始化写等待队列头
init_waitqueue_head(&tty->read_wait); //初始化读等待队列头
INIT_WORK(&tty->hangup_work, do_tty_hangup);
mutex_init(&tty->atomic_read_lock);
mutex_init(&tty->atomic_write_lock);
mutex_init(&tty->output_lock);
mutex_init(&tty->echo_lock);
spin_lock_init(&tty->read_lock);
spin_lock_init(&tty->ctrl_lock);
INIT_LIST_HEAD(&tty->tty_files);
INIT_WORK(&tty->SAK_work, do_SAK_work);
tty->driver = driver; //tty_struct->driver=tty_driver 捆绑tty_struct和tty_driver
tty->ops = driver->ops; //tty_strcut的操作函数集=tty_driver的操作函数集
tty->index = idx; //设置索引值
tty_line_name(driver, idx, tty->name);
tty->dev = tty_get_device(tty); //设置tty_struct的设备文件
}
tty_init_dev>>>initialize_tty_struct>>>tty_ldisc_init
void tty_ldisc_init(struct tty_struct *tty)
{
struct tty_ldisc *ld = tty_ldisc_get(N_TTY); //获取线路规程数组tty_ldiscs[N_TTY]项
if (IS_ERR(ld))
panic("n_tty: init_tty");
tty_ldisc_assign(tty, ld); //设置线路规程
}
在start_kernel-->console_init-->tty_ldisc_begin-->tty_register_ldisc将tty_ldiscs[N_TTY]=tty_ldisc_N_TTY
tty_driver_install_tty
static int tty_driver_install_tty(struct tty_driver *driver,struct tty_struct *tty)
{
int idx = tty->index;
int ret;
if (driver->ops->install) { //tty驱动操作函数集存在install方法
ret = driver->ops->install(driver, tty); //则调用install方法
return ret;
}
if (tty_init_termios(tty) == 0) { //初始化termios
tty_driver_kref_get(driver);
tty->count++;
driver->ttys[idx] = tty;
return 0;
}
return -ENOMEM;
}
tty_driver_install_tty>>>tty_init_termios
int tty_init_termios(struct tty_struct *tty)
{
struct ktermios *tp;
int idx = tty->index;
tp = tty->driver->termios[idx]; //获取termios
if (tp == NULL) {
tp = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL);
if (tp == NULL)
return -ENOMEM;
memcpy(tp, &tty->driver->init_termios,sizeof(struct ktermios));
tty->driver->termios[idx] = tp;
}
tty->termios = tp;
tty->termios_locked = tp + 1;
/* Compatibility until drivers always set this */
tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios); //设置接收波特率
tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios); //设置波特率
return 0;
}
EXPORT_SYMBOL_GPL(tty_init_termios);
tty_ldisc_setup
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
{
struct tty_ldisc *ld = tty->ldisc;
int retval;
retval = tty_ldisc_open(tty, ld); //调用tty_struct的open方法
if (retval)
return retval;
if (o_tty) {
retval = tty_ldisc_open(o_tty, o_tty->ldisc); //调用tty_struct->link的open方法
if (retval) {
tty_ldisc_close(tty, ld);
return retval;
}
tty_ldisc_enable(o_tty); //使能tty_struct->kink
}
tty_ldisc_enable(tty); //使能tty_struct
return 0;
}
tty_read操作
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
int i;
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file); //获取tty_strcut
struct tty_ldisc *ld;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
ld = tty_ldisc_ref_wait(tty); //获取线路规程
if (ld->ops->read) //线路规程存在读方法
i = (ld->ops->read)(tty, file, buf, count); //调用其读方法
else
i = -EIO;
tty_ldisc_deref(ld); //引用计数
if (i > 0)
inode->i_atime = current_fs_time(inode->i_sb);
return i;
}
tty_write操作
static ssize_t tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file); //获取tty_struct
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->ops->write ||(test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
printk(KERN_ERR "tty driver %s lacks a write_room method.\n",tty->driver->name);
ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write) //线路规程存在写方法
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count); //调用线路规程写方法
tty_ldisc_deref(ld);
return ret;
}
do_tty_write函数
do_tty_write(ld->ops->write,
tty, file, buf, count);
这个函数第一个参数是调用线路规程的写方法,写方法函数的参数是后面四个参数
该写方法将数据写入tty_struct相关的file里
static inline ssize_t do_tty_write(ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
struct tty_struct *tty,struct file *file,const char __user *buf,size_t count)
{
ssize_t ret, written = 0;
unsigned int chunk;
ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
if (ret < 0)
return ret;
chunk = 2048;
if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
chunk = 65536;
if (count < chunk)
chunk = count;
/* write_buf/write_cnt is protected by the atomic_write_lock mutex */
if (tty->write_cnt < chunk) {
unsigned char *buf_chunk;
if (chunk < 1024)
chunk = 1024;
buf_chunk = kmalloc(chunk, GFP_KERNEL);
if (!buf_chunk) {
ret = -ENOMEM;
goto out;
}
kfree(tty->write_buf);
tty->write_cnt = chunk;
tty->write_buf = buf_chunk;
}
/* Do the write .. */
for (;;) {
size_t size = count;
if (size > chunk)
size = chunk;
ret = -EFAULT;
if (copy_from_user(tty->write_buf, buf, size))
break;
ret = write(tty, file, tty->write_buf, size);
if (ret <= 0)
break;
written += ret;
buf += ret;
count -= ret;
if (!count)
break;
ret = -ERESTARTSYS;
if (signal_pending(current))
break;
cond_resched();
}
if (written) {
struct inode *inode = file->f_path.dentry->d_inode;
inode->i_mtime = current_fs_time(inode->i_sb);
ret = written;
}
out:
tty_write_unlock(tty);
return ret;
}
五.线路规程操作
tty_driver的open,read,write最终都会调用线路规程的对应操作方法
线路规程默认函数集
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
};
1.open方法
static int n_tty_open(struct tty_struct *tty)
{
if (!tty)
return -EINVAL;
if (!tty->read_buf) {
tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
if (!tty->read_buf)
return -ENOMEM;
}
if (!tty->echo_buf) {
tty->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
if (!tty->echo_buf)
return -ENOMEM;
}
reset_buffer_flags(tty);
tty->column = 0;
n_tty_set_termios(tty, NULL);
tty->minimum_to_wake = 1;
tty->closing = 0;
return 0;
}
2.写方法
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
/* Write out any echoed characters that are still pending */
process_echoes(tty);
add_wait_queue(&tty->write_wait, &wait);
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > 0) {
ssize_t num = process_output_block(tty, b, nr);
if (num < 0) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == 0)
break;
c = *b;
if (process_output(c, tty) < 0)
break;
b++; nr--;
}
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty); //调用tty_struct操作函数集合的flush_chars方法
} else {
while (nr > 0) {
c = tty->ops->write(tty, b, nr); //调用tty_struct操作函数集合的write方法
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
schedule();
}
break_out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
if (b - buf != nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
return (b - buf) ? b - buf : retval;
}
3.读方法
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
int minimum, time;
ssize_t retval = 0;
ssize_t size;
long timeout;
unsigned long flags;
int packet;
do_it_again:
BUG_ON(!tty->read_buf);
c = job_control(tty, file);
if (c < 0)
return c;
minimum = time = 0;
timeout = MAX_SCHEDULE_TIMEOUT;
if (!tty->icanon) {
time = (HZ / 10) * TIME_CHAR(tty);
minimum = MIN_CHAR(tty);
if (minimum) {
if (time)
tty->minimum_to_wake = 1;
else if (!waitqueue_active(&tty->read_wait) ||
(tty->minimum_to_wake > minimum))
tty->minimum_to_wake = minimum;
} else {
timeout = 0;
if (time) {
timeout = time;
time = 0;
}
tty->minimum_to_wake = minimum = 1;
}
}
if (file->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&tty->atomic_read_lock))
return -EAGAIN;
} else {
if (mutex_lock_interruptible(&tty->atomic_read_lock))
return -ERESTARTSYS;
}
packet = tty->packet;
add_wait_queue(&tty->read_wait, &wait);
while (nr) {
/* First test for status change. */
if (packet && tty->link->ctrl_status) {
unsigned char cs;
if (b != buf)
break;
spin_lock_irqsave(&tty->link->ctrl_lock, flags);
cs = tty->link->ctrl_status;
tty->link->ctrl_status = 0;
spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
if (tty_put_user(tty, cs, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
break;
}
set_current_state(TASK_INTERRUPTIBLE);
if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf));
if (!input_available_p(tty, 0)) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
/* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
continue;
}
__set_current_state(TASK_RUNNING);
/* Deal with packet mode. */
if (packet && b == buf) {
if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (tty->icanon && !L_EXTPROC(tty)) {
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol;
eol = test_and_clear_bit(tty->read_tail,tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &(N_TTY_BUF_SIZE-1));
tty->read_cnt--;
if (eol) {
if (--tty->canon_data < 0)
tty->canon_data = 0;
}
spin_unlock_irqrestore(&tty->read_lock, flags);
if (!eol || (c != __DISABLED_CHAR)) {
if (tty_put_user(tty, c, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
int uncopied;
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
if (uncopied) {
retval = -EFAULT;
break;
}
}
if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
n_tty_set_room(tty);
check_unthrottle(tty);
}
if (b - buf >= minimum)
break;
if (time)
timeout = time;
}
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait);
if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum;
__set_current_state(TASK_RUNNING);
size = b - buf;
if (size) {
retval = size;
if (nr)
clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
goto do_it_again;
n_tty_set_room(tty);
return retval;
}
六.线路规程的操作函数集会调用对应的tty_struct的操作函数集
在initialize_tty_struct中tty->ops = driver->ops也就是说tty_struct的操作函数集就是tty_driver的操作函数集合
tty_driver的操作函数集设置在其register之前,例如串口的注册函数uart_register_driver中调用tty_set_operations(normal, &uart_ops)
将tty_driver->ops设置为uart_ops。
这样我们对/dev/ttyXXX的操作的调用顺序:字符设备的操作函数集tty_fops-->线路规程的操作函数集tty_ldisc_N_TTY-->串口核心或其他类型设备的操作函数集uart_ops
五 linux 串口驱动
分类: linux设备驱动 2013-01-29 23:25 312人阅读 评论(0)收藏 举报
一.串口结构体
1.串口驱动结构体
struct uart_driver {
struct module *owner; //模块所有者
const char *driver_name; //驱动名
const char *dev_name; //设备名
int major; //主设备号
int minor; //次设备号
int nr; //支持串口个数
struct console *cons; //控制台设备
struct uart_state *state; //串口状态
struct tty_driver *tty_driver; //tty设备
};
2.串口端口结构体
struct uart_port {
spinlock_t lock;
unsigned long iobase; //io端口基地址
unsigned char __iomem *membase; //内存端口基地址
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
void (*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old);
void (*pm)(struct uart_port *, unsigned int state,unsigned int old);
unsigned int irq; //中断号
unsigned long irqflags; //中断标志
unsigned int uartclk;
unsigned int fifosize; //fifo大小
unsigned char x_char;
unsigned char regshift; //寄存器偏移值
unsigned char iotype; //io访问类型
unsigned char unused1;
unsigned int read_status_mask;
unsigned int ignore_status_mask;
struct uart_state *state; //uart_state结构体
struct uart_icount icount; //串口使用计数
struct console *cons; //console控制台
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq;
#endif
upf_t flags;
unsigned int mctrl;
unsigned int timeout;
unsigned int type;
const struct uart_ops *ops; //串口操作函数集
unsigned int custom_divisor;
unsigned int line; //端口号
resource_size_t mapbase;
struct device *dev; //设备文件
unsigned char hub6;
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
void *private_data;
};
3.操作函数集
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *); //发送缓冲区为空
void (*set_mctrl)(struct uart_port *, unsigned int mctrl); //设置串口modem控制模式
unsigned int (*get_mctrl)(struct uart_port *); //获取串口modem控制模式
void (*stop_tx)(struct uart_port *); //停止发送
void (*start_tx)(struct uart_port *); //开始发送
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *); //停止接收
void (*enable_ms)(struct uart_port *); //使能modem状态信息
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *); //打开串口
void (*shutdown)(struct uart_port *); //关闭串口
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old); //设置串口参数
void (*set_ldisc)(struct uart_port *, int new);
void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *); //释放端口
int (*request_port)(struct uart_port *); //请求端口
void (*config_port)(struct uart_port *, int); //配置端口
int (*verify_port)(struct uart_port *, struct serial_struct *); //校验端口
int (*ioctl)(struct uart_port *, unsigned int, unsigned long); //控制
#ifdef CONFIG_CONSOLE_POLL
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
4.uart_state
struct uart_state {
struct tty_port port;
int pm_state;
struct circ_buf xmit;
struct tasklet_struct tlet;
struct uart_port *uart_port;
};
二.串口驱动的注册与注销
注册
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
BUG_ON(drv->state);
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); //分配uart_state内存
if (!drv->state)
goto out;
normal = alloc_tty_driver(drv->nr); //分配tty_driver
if (!normal)
goto out_kfree;
drv->tty_driver = normal; //tty_driver和uart_driver捆绑
normal->owner = drv->owner; //模块所有者
normal->driver_name = drv->driver_name; //驱动名
normal->name = drv->dev_name; //设备名
normal->major = drv->major; //主设备号
normal->minor_start = drv->minor; //次设备号
normal->type = TTY_DRIVER_TYPE_SERIAL; //tty类型
normal->subtype = SERIAL_TYPE_NORMAL; //tty子类型
normal->init_termios = tty_std_termios; //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); //设置tty操作函数集
for (i = 0; i < drv->nr; i++) { //初始化uart_state
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port); //初始化tty端口
port->ops = &uart_port_ops; //tty端口操作函数集
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); //注册tty驱动
if (retval >= 0)
return retval;
put_tty_driver(normal); //引用计数
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
注销
void uart_unregister_driver(struct uart_driver *drv)
{
struct tty_driver *p = drv->tty_driver;
tty_unregister_driver(p); //注销tty驱动
put_tty_driver(p); //减少引用计数
kfree(drv->state);
drv->tty_driver = NULL;
}
三.端口注册与注销
注册
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;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line; //获取uart_state
port = &state->port; //获取tty_port
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
state->uart_port = uport; //设置uart_state->uart_port
state->pm_state = -1;
uport->cons = drv->cons; //设置uart_port->cons控制台
uport->state = state; //设置uart_port->state
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); //生成tty设备文件/dev/ttyXXX
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;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
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;
if (!port->iobase && !port->mapbase && !port->membase)
return;
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); //调用uart_port的config_port方法
}
if (port->type != PORT_UNKNOWN) {
unsigned long flags;
uart_report_port(drv, port);
uart_change_pm(state, 0);
spin_lock_irqsave(&port->lock, flags);
port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR); //调用uart_port的set_mctrl方法
spin_unlock_irqrestore(&port->lock, flags);
if (port->cons && !(port->cons->flags & CON_ENABLED)) //若console存在且设置了CON_ENABLED标志
register_console(port->cons); //注册控制台设备
if (!uart_console(port))
uart_change_pm(state, 3);
}
}
注销
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
BUG_ON(in_interrupt());
if (state->uart_port != uport)
printk(KERN_ALERT "Removing wrong port: %p != %p\n",state->uart_port, uport);
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
uport->flags |= UPF_DEAD;
mutex_unlock(&port->mutex);
tty_unregister_device(drv->tty_driver, uport->line);
if (port->tty)
tty_vhangup(port->tty);
if (uport->type != PORT_UNKNOWN)
uport->ops->release_port(uport);
uport->type = PORT_UNKNOWN;
tasklet_kill(&state->tlet);
state->uart_port = NULL;
mutex_unlock(&port_mutex);
return 0;
}
四.串口对应的tty_driver的操作函数集
1.tty的操作函数集uart_ops
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
.get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
2.open方法
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
struct uart_state *state;
struct tty_port *port;
int retval, line = tty->index;
BUG_ON(!tty_locked());
pr_debug("uart_open(%d) called\n", line);
retval = -ENODEV;
if (line >= tty->driver->num)
goto fail;
state = uart_get(drv, line); //获取uart_state
if (IS_ERR(state)) {
retval = PTR_ERR(state);
goto fail;
}
port = &state->port; //获取tty_port
tty->driver_data = state;
state->uart_port->state = state;
tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
tty->alt_speed = 0;
tty_port_tty_set(port, tty);
if (tty_hung_up_p(filp)) {
retval = -EAGAIN;
port->count--;
mutex_unlock(&port->mutex);
goto fail;
}
if (port->count == 1)
uart_change_pm(state, 0);
retval = uart_startup(tty, state, 0); //启动串口
mutex_unlock(&port->mutex);
if (retval == 0)
retval = tty_port_block_til_ready(port, tty, filp);
fail:
return retval;
}
uart_startup
static int uart_startup(struct tty_struct *tty, struct uart_state *state, int init_hw)
{
struct uart_port *uport = state->uart_port; //获取uart_port
struct tty_port *port = &state->port; //获取tty_port
unsigned long page;
int retval = 0;
if (port->flags & ASYNC_INITIALIZED)
return 0;
set_bit(TTY_IO_ERROR, &tty->flags);
if (uport->type == PORT_UNKNOWN)
return 0;
if (!state->xmit.buf) {
/* This is protected by the per port mutex */
page = get_zeroed_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
state->xmit.buf = (unsigned char *) page;
uart_circ_clear(&state->xmit);
}
retval = uport->ops->startup(uport); //调用uart_port的startup方法
if (retval == 0) {
if (init_hw) {
uart_change_speed(tty, state, NULL);
if (tty->termios->c_cflag & CBAUD)
uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
}
if (port->flags & ASYNC_CTS_FLOW) {
spin_lock_irq(&uport->lock);
if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS))
tty->hw_stopped = 1;
spin_unlock_irq(&uport->lock);
}
set_bit(ASYNCB_INITIALIZED, &port->flags);
clear_bit(TTY_IO_ERROR, &tty->flags);
}
if (retval && capable(CAP_SYS_ADMIN))
retval = 0;
return retval;
}
3.写方法
static int uart_write(struct tty_struct *tty,const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data; //获取uart_state
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
if (!state) {
WARN_ON(1);
return -EL3HLT;
}
port = state->uart_port; //获取uart_port
circ = &state->xmit;
if (!circ->buf)
return 0;
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty); //调用uart_start方法
return ret;
}
uart_start
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data; //获取tty_state
struct uart_port *port = state->uart_port; //获取uart_port
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
__uart_start(tty); //调用__uart_start函数
spin_unlock_irqrestore(&port->lock, flags);
}
uart_start>>>__uart_start
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data; //获取uart_state
struct uart_port *port = state->uart_port; //获取uart_port
if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&!tty->stopped && !tty->hw_stopped)
port->ops->start_tx(port); //调用uart_port的start_tx方法
}
六 linux串口编程
一.结构体
1.termios
struct termios {
tcflag_t c_iflag; /* 输入模式标志 */
tcflag_t c_oflag; /* 输出模式标志 */
tcflag_t c_cflag; /* 控制模式标志 */
tcflag_t c_lflag; /* 本地模式标志 */
cc_t c_line; /* 线路规程 */
cc_t c_cc[NCCS]; /* 控制字符 */
speed_t c_ispeed; /* 输入速率 */
speed_t c_ospeed; /* 输出速率 */
};
二.定义
1.类型定义
typedef unsigned char cc_t;
typedef unsigned int speed_t;
typedef unsigned int tcflag_t;
2.c_cc
#define VINTR 0 //中断 Ctrl-C
#define VQUIT 1 //退出 Ctrl-Z
#define VERASE 2 //擦除 空格BS
#define VKILL 3 //删行 Ctrl-U
#define VEOF 4 //文件结尾 Ctrl-D
#define VTIME 5 //等待时间
#define VMIN 6 //读取的最小字符数
#define VSWTC 7 //
#define VSTART 8 //软件流开始
#define VSTOP 9 //软件流停止
#define VSUSP 10 //
#define VEOL 11 //行结尾 回车CR
#define VREPRINT 12 //
#define VDISCARD 13 //
#define VWERASE 14 //
#define VLNEXT 15 //
#define VEOL2 16 //第二行结尾 进行LF
3.c_iflag
#define IGNBRK 0000001 //忽略任何中断条件
#define BRKINT 0000002 //传送SIGINT,当检测到中断
#define IGNPAR 0000004 //忽略校验错误
#define PARMRK 0000010 //标记校验错误
#define INPCK 0000020 //校验有效
#define ISTRIP 0000040 //剥离校验位
#define INLCR 0000100 //映射NL到CR
#define IGNCR 0000200 //忽略CR
#define ICRNL 0000400 //映射CR到NL
#define IUCLC 0001000 //映射大写到小写
#define IXON 0002000 //软件流控制输出作用
#define IXANY 0004000 //允许任何字符再次启动流
#define IXOFF 0010000 //软件流控制输入作用
#define IMAXBEL 0020000 //在输入线上回显BEL过长
#define IUTF8 0040000
4.c_oflag
#define OPOST 0000001 //输出后处理(不设置=行式输出)
#define OLCUC 0000002 //映射小写到大写
#define ONLCR 0000004 //映射NL到CR-NL
#define OCRNL 0000010 //映射CR到NL
#define ONOCR 0000020 //
#define ONLRET 0000040 //NL实现CR功能
#define OFILL 0000100 //对于延时用填充字符
#define OFDEL 0000200 //填充字符DEL
#define NLDLY 0000400 //在行之间,需要掩蔽延时
#define NL0 0000000 //NL没有延时
#define NL1 0000400 //在换行后,新的输出前延时100ms
#define CRDLY 0003000 //回车到左边,需要掩蔽的延时
#define CR0 0000000 //CR没有延时
#define CR1 0001000 //CR后的延时,依赖于当前的列位置
#define CR2 0002000 //CR后延时100ms
#define CR3 0003000 //CR后延时150ms
#define TABDLY 0014000 //TAB后需要延时
#define TAB0 0000000 //TAB后没有延时
#define TAB1 0004000 //TAB后的延时根据当前的列位置
#define TAB2 0010000 //TAB后延时100ms
#define TAB3 0014000 //TAB扩展为空格
#define XTABS 0014000 //
#define BSDLY 0020000 //BS后需要延时掩蔽
#define BS0 0000000 //BS后无延时
#define BS1 0020000 //BS后延时50ms
#define VTDLY 0040000 //VT后需要延时掩蔽
#define VT0 0000000 //VT后无延时
#define VT1 0040000 //VT后延时2秒
#define FFDLY 0100000 //FF后需要延时掩蔽
#define FF0 0000000 //FF后无延时
#define FF1 0100000 //FF后延时2秒
5.c_cflag
#define CBAUD 0010017 //波特率掩码
#define B0 0000000 //挂起
#define B50 0000001 //50 b/s
#define B75 0000002 //75 b/s
#define B110 0000003 //110 b/s
#define B134 0000004 //134.5 b/s
#define B150 0000005 //150 b/s
#define B200 0000006 //200 b/s
#define B300 0000007 //300 b/s
#define B600 0000010 //600 b/s
#define B1200 0000011 //1200 b/s
#define B1800 0000012 //1800 b/s
#define B2400 0000013 //2400 b/s
#define B4800 0000014 //4800 b/s
#define B9600 0000015 //9600 b/s
#define B19200 0000016 //19200 b/s
#define B38400 0000017 //38400 b/s
#define EXTA B19200 //外部时钟A
#define EXTB B38400 //外部时钟B
#define CSIZE 0000060 //位数据位掩码
#define CS5 0000000 //5位数据位
#define CS6 0000020 //6位数据位
#define CS7 0000040 //7位数据位
#define CS8 0000060 //8位数据位
#define CSTOPB 0000100 //2位停止位
#define CREAD 0000200 //接收有效
#define PARENB 0000400 //校验位有效
#define PARODD 0001000 //奇偶校验
#define HUPCL 0002000 //最后关闭后,挂起
#define CLOCAL 0004000 //本地线-不 改变端口的"拥有者"
#define CBAUDEX 0010000 //
#define BOTHER 0010000 //
#define B57600 0010001 //57600 b/s
#define B115200 0010002 //115200 b/s
#define B230400 0010003 //230400 b/s
#define B460800 0010004 //460800 b/s
#define B500000 0010005 //500000 b/s
#define B576000 0010006 //576000 b/s
#define B921600 0010007 //921600 b/s
#define B1000000 0010010 //1000000 b/s
#define B1152000 0010011 //1152000 b/s
#define B1500000 0010012 //1500000 b/s
#define B2000000 0010013 //2000000 b/s
#define B2500000 0010014 //2500000 b/s
#define B3000000 0010015 //3000000 b/s
#define B3500000 0010016 //3500000 b/s
#define B4000000 0010017 //4000000 b/s
#define CIBAUD 002003600000 //输入波特率
#define CMSPAR 010000000000 /* mark or space (stick) parity */
#define CRTSCTS 020000000000 /* flow control */
#define IBSHIFT 16
6.c_lflag
#define ISIG 0000001 //使SIGINTR,SIGSUSP,SIGDSUSP,SIGQUIT等信号作用
#define ICANON 0000002 //设定规范canonical(或行式raw)
#define XCASE 0000004 //map uppercase\lowercase(废除)
#define ECHO 0000010 //回显输入字符
#define ECHOE 0000020 //回显擦除字符 BS-SP-BS
#define ECHOK 0000040 //在删除字符后,回显NL
#define ECHONL 0000100 //回显NL
#define NOFLSH 0000200 //不在中断后退出字符后刷新输入缓冲
#define TOSTOP 0000400 //传送SIGTTOU信号作为背景
#define ECHOCTL 0001000 //以^char方式回显控制字符和以~?方式显示删除字符
#define ECHOPRT 0002000 //回显提示有删除字符
#define ECHOKE 0004000 //BS-SP-BS整行,在有行删除时
#define FLUSHO 0010000 //刷新输出
#define PENDIN 0040000 //在下次读或输入时,将未决字符重打
#define IEXTEN 0100000
#define EXTPROC 0200000
7.其他
/* tcflow() and TCXONC use these */
#define TCOOFF 0
#define TCOON 1
#define TCIOFF 2
#define TCION 3
/* tcflush() and TCFLSH use these */
#define TCIFLUSH 0
#define TCOFLUSH 1
#define TCIOFLUSH 2
/* tcsetattr 使用到这些参数*/
#define TCSANOW 0 //立即改变,不等待数据结束
#define TCSADRAIN 1 //等待直到所有的都传完
#define TCSAFLUSH 2 //刷新输入输出缓冲,然后改变
三.API函数
1.基本操作
打开串口 O_NOCTTY表示不是系统终端,这样就不会受键盘Ctrl-C等操作影响
fd = open("/dev/ttyXXX", O_RDWR | O_NOCTTY | O_NDELAY);
写(返回写入字节数)
ssize_t write(int fildes, const void *buf, size_t nbyte);
读(返回读取字节数)
ssize_t read(int fildes, void *buf, size_t nbyte);
关闭串口
close(unsigned int fd)
控制
int fcntl(int fildes, int cmd, ...);
fcntl(fd,F_SETFD,FNDELAY); //非阻塞
fcntl(fd,F_SETFD,0); //阻塞
控制命令还有以下几个针对tty的(魔数为0x54xx或‘T’),具体使用参考tty_ioctl函数,
#define TCGETS 0x5401
#define TCSETS 0x5402
#define TCSETSW 0x5403
#define TCSETSF 0x5404
#define TCGETA 0x5405
#define TCSETA 0x5406
#define TCSETAW 0x5407
#define TCSETAF 0x5408
#define TCSBRK 0x5409
#define TCXONC 0x540A
#define TCFLSH 0x540B
#define TIOCEXCL 0x540C
#define TIOCNXCL 0x540D
#define TIOCSCTTY 0x540E
#define TIOCGPGRP 0x540F
#define TIOCSPGRP 0x5410
#define TIOCOUTQ 0x5411
#define TIOCSTI 0x5412
#define TIOCGWINSZ 0x5413
#define TIOCSWINSZ 0x5414
#define TIOCMGET 0x5415
#define TIOCMBIS 0x5416
#define TIOCMBIC 0x5417
#define TIOCMSET 0x5418
#define TIOCGSOFTCAR 0x5419
#define TIOCSSOFTCAR 0x541A
#define FIONREAD 0x541B
#define TIOCINQ FIONREAD
#define TIOCLINUX 0x541C
#define TIOCCONS 0x541D
#define TIOCGSERIAL 0x541E
#define TIOCSSERIAL 0x541F
#define TIOCPKT 0x5420
#define FIONBIO 0x5421
#define TIOCNOTTY 0x5422
#define TIOCSETD 0x5423
#define TIOCGETD 0x5424
#define TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */
#define TIOCSBRK 0x5427 /* BSD compatibility */
#define TIOCCBRK 0x5428 /* BSD compatibility */
#define TIOCGSID 0x5429 /* Return the session ID of FD */
#define TCGETS2 _IOR('T', 0x2A, struct termios2)
#define TCSETS2 _IOW('T', 0x2B, struct termios2)
#define TCSETSW2 _IOW('T', 0x2C, struct termios2)
#define TCSETSF2 _IOW('T', 0x2D, struct termios2)
#define TIOCGRS485 0x542E
#ifndef TIOCSRS485
#define TIOCSRS485 0x542F
#endif
#define TIOCGPTN _IOR('T', 0x30, unsigned int) /* Get Pty Number (of pty-mux device) */
#define TIOCSPTLCK _IOW('T', 0x31, int) /* Lock/unlock Pty */
#define TCGETX 0x5432 /* SYS5 TCGETX compatibility */
#define TCSETX 0x5433
#define TCSETXF 0x5434
#define TCSETXW 0x5435
#define TIOCSIG _IOW('T', 0x36, int) /* pty: generate signal */
#define FIONCLEX 0x5450
#define FIOCLEX 0x5451
#define FIOASYNC 0x5452
#define TIOCSERCONFIG 0x5453
#define TIOCSERGWILD 0x5454
#define TIOCSERSWILD 0x5455
#define TIOCGLCKTRMIOS 0x5456
#define TIOCSLCKTRMIOS 0x5457
#define TIOCSERGSTRUCT 0x5458 /* For debugging only */
#define TIOCSERGETLSR 0x5459 /* Get line status register */
#define TIOCSERGETMULTI 0x545A /* Get multiport config */
#define TIOCSERSETMULTI 0x545B /* Set multiport config */
#define TIOCMIWAIT 0x545C /* wait for a change on serial input line(s) */
#define TIOCGICOUNT 0x545D /* read serial port __inline__ interrupt counts */
2.串口设置
int tcgetattr(int fildes, struct termios *termios_p);//获取串口参数
int tcsetattr(int fildes, int optional_actions,const struct termios *termios_p);//设置串口参数
speed_t cfgetispeed(const struct termios *termios_p);//获取输入波特率
int cfsetispeed(struct termios *termios_p, speed_t speed);//设置输入波特率
speed_t cfgetospeed(const struct termios *termios_p);//获取输出波特率
int cfsetospeed(struct termios *termios_p, speed_tspeed);//设置输出波特率
大致这样使用
struct termios options;
tcgetattr(fd, &options); //--------获取属性
//--------设置波特率
if(cfgetispeed(&options)!=B115200) //获取输入波特率
cfsetispeed(&options, B19200); //设置输入波特率
cfsetospeed(&options, B19200); //设置输出波特率
if(cfgetospeed(&options)!=B19200) //获取输出波特率
perror("set baud rates fail!\n");
//--------本地模式|串行数据接收
options.c_cflag!=(CLOCAL|CREAD);
//--------设置数据位格式
ifdef UART_8N1
//8N1 - 无校验
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
#endif
ifdef UART_7E1
//7E1 - 偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
#endif
ifdef UART_7O1
//7O1 - 奇校验
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
#endif
ifdef UART_7S1
//7S1 - 空校验设置同无校验
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
#endif
//--------硬件流控制
#ifdef CTSRTS
options.c_cflag |= CNEW_RTSCTS;
#else
options.c_cflag &= ~CNEW_RTSCTS;
#endif
tcsetattr(fd, TCSANOW, &options); //--------设置属性
3.其他API函数
int tcflow(int fildes, int action);//软件流控制
int tcflush(int fildes, int queue_selector);//丢弃要写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据
int tcdrain(int fildes);//等待直到所有写入
int tcsendbreak(int fildes, int duration);//传送连续的0值比特流,持续一段时间
pid_t tcgetpgrp(int fildes); //获取组id
int tcsetpgrp(int fildes, pid_t pgid_id); //设置组id
pid_t tcgetsid(int fildes); //获取sid
int isatty(int fildes); //判断使用的是不是tty终端
char *ttyname(int fildes); //获取tty路径名
四.例子
网上找来个好的例子
/************************Copyright(c)*******************************
** 西安邮电学院
** graduate school
** XNMS项目组
** WebSite :blog.youkuaiyun.com/tigerjb
**------------------------------------------FileInfo-------------------------------------------------------
** File name: main.c
** Last modified Date: 2011-01-31
** Last Version: 1.0
** Descriptions:
**------------------------------------------------------------------------------------------------------
** Created by: 冀博
** Created date: 2011-01-31
** Version: 1.0
** Descriptions: The original version
**------------------------------------------------------------------------------------------------------
** Modified by:
** Modified date:
** Version:
** Descriptions:
*******************************************************************/
//串口相关的头文件
#include<stdio.h> /*标准输入输出定义*/
#include<stdlib.h> /*标准函数库定义*/
#include<unistd.h> /*Unix 标准函数定义*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h> /*文件控制定义*/
#include<termios.h> /*PPSIX 终端控制定义*/
#include<errno.h> /*错误号定义*/
#include<string.h>
//宏定义
#define FALSE -1
#define TRUE 0
/*******************************************************************
* 名称: UART0_Open
* 功能: 打开串口并返回串口设备文件描述
* 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART0_Open(int fd,char* port)
{
fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY);
if(FALSE == fd){
perror("Can't Open Serial Port");
return(FALSE);
}
//恢复串口为阻塞状态
if(fcntl(fd, F_SETFL, 0) < 0){
printf("fcntl failed!\n");
return(FALSE);
}
else{
printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
}
//测试是否为终端设备
if(0 == isatty(STDIN_FILENO)){
printf("standard input is not a terminal device\n");
return(FALSE);
}
else{
printf("isatty success!\n");
}
printf("fd->open=%d\n",fd);
return fd;
}
/*******************************************************************
* 名称: UART0_Close
* 功能: 关闭串口并返回串口设备文件描述
* 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
* 出口参数: void
*******************************************************************/
void UART0_Close(int fd)
{
close(fd);
}
/*******************************************************************
* 名称: UART0_Set
* 功能: 设置串口数据位,停止位和效验位
* 入口参数:fd 串口文件描述符
* speed 串口速度
* flow_ctrl 数据流控制 0不使用 1硬件流控制 2软件流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART0_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)
{
int i;
int status;
int speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300};
struct termios options;
/*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将其保存于options,该函数还可以测试配置是否正确,
该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1*/
if (tcgetattr( fd,&options)!= 0){
perror("SetupSerial 1");
return(FALSE);
}
//设置串口输入波特率和输出波特率
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++){
if(speed == name_arr[i]){
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
//修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;
//修改控制模式,使得能够从串口中读取输入数据
options.c_cflag |= CREAD;
//设置数据流控制
switch(flow_ctrl)
{
case 0://不使用流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1://使用硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2://使用软件流控制
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
//设置数据位
//屏蔽其他标志位
options.c_cflag &= ~CSIZE;
switch (databits)
{
case 5:
options.c_cflag |= CS5;
break;
case 6:
options.c_cflag |= CS6;
break;
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n");
return (FALSE);
}
//设置校验位
switch (parity)
{
case 'n':
case 'N': //无奇偶校验位。
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O'://设置为奇校验
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E'://设置为偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S': //设置为空格
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
// 设置停止位
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB; break;
case 2:
options.c_cflag |= CSTOPB; break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
//修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//我加的
//options.c_lflag &= ~(ISIG | ICANON);
//设置等待时间和最小接收字符
options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush(fd,TCIFLUSH);
//激活配置 (将修改后的termios数据设置到串口中)
if (tcsetattr(fd,TCSANOW,&options) != 0){
perror("com set error!\n");
return (FALSE);
}
return (TRUE);
}
/*******************************************************************
* 名称: UART0_Init()
* 功能: 串口初始化
* 入口参数:fd : 文件描述符
* speed : 串口速度
* flow_ctrl 数据流控制
* databits 数据位 取值为 7 或者8
* stopbits 停止位 取值为 1 或者2
* parity 效验类型 取值为N,E,O,,S
*
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART0_Init(int fd, int speed,int flow_ctrl,int databits,int stopbits,int parity)
{
int err;
//设置串口数据帧格式
if (UART0_Set(fd,19200,0,8,1,'N') == FALSE){
return FALSE;
}
else{
return TRUE;
}
}
/*******************************************************************
* 名称: UART0_Recv
* 功能: 接收串口数据
* 入口参数: fd :文件描述符
* rcv_buf :接收串口中数据存入rcv_buf缓冲区中
* data_len :一帧数据的长度
* 出口参数: 正确返回为1,错误返回为0
*******************************************************************/
int UART0_Recv(int fd, char *rcv_buf,int data_len)
{
int len,fs_sel;
fd_set fs_read;
struct timeval time;
FD_ZERO(&fs_read);
FD_SET(fd,&fs_read);
time.tv_sec = 10;
time.tv_usec = 0;
//使用select实现串口的多路通信
fs_sel = select(fd+1,&fs_read,NULL,NULL,&time);
if(fs_sel){
len = read(fd,rcv_buf,data_len);
printf("I am right!(version1.2) len = %d fs_sel = %d\n",len,fs_sel);
return len;
}
else{
printf("Sorry,I am wrong!");
return FALSE;
}
}
/********************************************************************
* 名称: UART0_Send
* 功能: 发送数据
* 入口参数:fd :文件描述符
* send_buf :存放串口发送数据
* data_len :一帧数据的个数
* 出口参数:正确返回为1,错误返回为0
*******************************************************************/
int UART0_Send(int fd, char *send_buf,int data_len)
{
int len = 0;
len = write(fd,send_buf,data_len);
if (len == data_len )
{
return len;
}
else
{
tcflush(fd,TCOFLUSH);
return FALSE;
}
}
int main(int argc, char **argv)
{
int fd; //文件描述符
int err; //返回调用函数的状态
int len;
int i;
char rcv_buf[100];
char send_buf[20]="tiger john";
if(argc != 3){
printf("Usage: %s /dev/ttySn 0(send data)/1 (receive data) \n",argv[0]);
return FALSE;
}
fd = UART0_Open(fd,argv[1]); //打开串口,返回文件描述符
do{
err = UART0_Init(fd,19200,0,8,1,'N');
printf("Set Port Exactly!\n");
}while(FALSE == err || FALSE == fd);
if(0 == strcmp(argv[2],"0")){
for(i = 0;i < 10;i++){
len = UART0_Send(fd,send_buf,10);
if(len > 0)
printf(" %d send data successful\n",i);
else
printf("send data failed!\n");
sleep(2);
}
UART0_Close(fd);
}
else{
while (1){ //循环读取数据
len = UART0_Recv(fd, rcv_buf,9);
if(len > 0){
rcv_buf[len] = '\0';
printf("receive data is %s\n",rcv_buf);
printf("len = %d\n",len);
}
else{
printf("cannot receive data\n");
}
sleep(2);
}
UART0_Close(fd);
}
}
/*********************************************************************
End Of File
*********************************************************************/
必须的表