如何写TTY设备驱动程序可以参考Linux Device Driver的第18章和例子程序tiny_tty.c,这里主要分析驱动程序调用的kernel API后面的细节.
驱动程序主要做3件事:
struct tty_driver *alloc_tty_driver(int lines)
int tty_register_driver(struct tty_driver *driver)
struct device *tty_register_device(struct tty_driver *driver, unsigned index,struct device *device)
其中最重要的就是tty_register_driver,它会注册一组函数指针:
struct tty_operations {
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
void (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
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);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
void (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*read_proc)(char *page, char **start, off_t off,
int count, int *eof, void *data);
int (*write_proc)(struct file *file, const char __user *buffer,
unsigned long count, void *data);
int (*tiocmget)(struct tty_struct *tty, struct file *file);
int (*tiocmset)(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear);
};
tty_register_driver分析
根据驱动程序里面提供的major和minor值,注册一个字符设备:
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);
}
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
该字符设备的struct file_operations结构是:
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
当我们打开串口设备的时候,tty_open会被调用:
1 init_dev(driver, index, &tty): 该函数初始化tty_struct结构,tty的line discpline被设置为N_TTY.(在打开TTY的时候: tty_open->init_dev->initialize_tty_struct: tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));)
2 tty->driver->open(tty, filp);
TTY设备的读写
tty_write:
获得line discipline,并调用do_tty_write, do_tty_write中会调用ld->write来进行实际的写操作.
ld = tty_ldisc_ref_wait(tty);
if (!ld->write)
ret = -EIO;
else
ret = do_tty_write(ld->write, tty, file, buf, count);
对于N_TTY,line discipline在系统启动中被定义为:(console_init->tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY))
struct tty_ldisc 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 = read_chan,
.write = write_chan,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = normal_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
因此写操作就是调用write_chan, 而它会调用驱动程序中注册的函数tty->driver->write(tty, b, nr).
对于读操作tty_read,类似于写操作,会调用到read_chan,而read_chan会等待驱动程序往上传来的数据.驱动程序会调用
tty_insert_flip_char, tty_insert_flip_string 和tty_flip_buffer_push等等往上传递数据,最终会调用到flush_to_ldisc, flush_to_ldisc会调用disc->receive_buf往line discipline传递数据,对于N_TTY,就是调用n_tty_receive_buf, n_tty_receive_buf在收到数据后,会唤醒read_chan, read_chan就可以读到数据了.
总之,数据的读写都是通过line discipline进行的。
改变line discipline
我们也可以通过IO control的方式改变TTY的line discipline:
tty_ioctl(TIOCSETD)->tiocsetd(tty, p)->tty_set_ldisc: 这个函数里面会调用old line discipline的close函数还有new line discipline的open函数.当然前提是新的line discipline是已经存在了的, line discipline是通过tty_register_ldisc注册到系统中的。