您的位置 首页 FPGA

Linux下的串口总线驱动(三)

五.线路规程内核代码底层的物理驱动程序和tty驱动程序负责从硬件上收发数据,而线路规程则负责处理这些数据,并在用户空间和内核空间知觉

五.线路规程内核代码

底层的物理驱动程序和tty驱动程序担任从硬件上收发数据,而线路规程则担任处理这些数据,并在用户空间和内核空间感觉传递数据。翻开串行端口时体系默许的线路规程是N_TTY,它完成终端I/O处理。线路规程也完成经过串行传输协议完成的网络接口,PPP(N_PPP),SLIP(串行线路网际协议)(N_SLIP),红外数据(N_IRDA),蓝牙主机操控接口(N_HCI)。

咱们在TTY层uart_register_driver函数里初始化termios的时分用到tty_std_termios,这个是线路的原始设置,详细界说如下

struct ktermios tty_std_termios = {

.c_iflag = ICRNL | IXON, //输入标志

.c_oflag = OPOST | ONLCR, //输出标志

.c_cflag = B38400 | CS8 | CREAD | HUPCL, //操控标志

.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |

ECHOCTL | ECHOKE | IEXTEN, //本地标志

.c_cc = INIT_C_CC, //字符操控

.c_ispeed = 38400, //输入速率

.c_ospeed = 38400 //输出速率

};

假设需求对线路原始设置的部分加以修正,则能够增加其他操作。首要分为内核空间修正线路规程和用户空间修正线路规程两个途径。内核空间修正线路规程很简单,只需求对需求修正项进行从头赋值就行了,关于用户空间修正线路规程咱们来讲解下。

假设用户空间程序翻开和接触操控器相连的串行端口时,N_TCH将被绑定到底层的串行驱动程序,但假设你想编写程序清空接触操控器接纳的一切原始数据而不处理它,那你就需求修正线路规程为N_TTY并清空一切接纳的数据的程序。用户空间修正线程代码如下

fd=open(“/dev/ttys0”,O_RDONLY|O_NOCTTY);

ldisc=N_TTY;

ioctl(fd,TIOCSETD,&ldisc);

好了,前面咱们从运用视点分析了线路规程的设置,现在咱们从理论视点,深度分析下线路规程是怎样完成的吧。

在TTY层咱们讲过TTY层的uart_register_driver和uart_register_port终究调用线路规程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的完成在线路规程中tty_io.c中完成的,咱们能够翻开tty_io.c这个文件。

首要咱们看tty_init函数,在tty_init函数中履行了cdev_init(&tty_cdev, &tty_fops)一行代码,阐明向内核中增加了一个cdev设备,咱们盯梢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,

};

这个结构体咱们很熟悉,在字符设备中,咱们便是运用的这个结构体吧。那阐明咱们用户进行open,read,write,ioctl等对串口操作时,第一步调用便是这儿的open,read,write,ioctl。那么咱们就看看怎样由这儿的open,read,write,ioctl跟TTY层,UART层的open,read,write,ioctl相联系的。

咱们就来看看这个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; //获取方针设备的设备号

unsigned saved_flags = filp->f_flags;

nonseekable_open(inode, filp);

retry_open:

noctty = filp->f_flags & O_NOCTTY;

index = -1;

retval = 0;

mutex_lock(&tty_mutex);

if (device == MKDEV(TTYAUX_MAJOR, 0)) { //当时进程的操控终端,/dev/tty

tty = get_current_tty();

if (!tty) { //该进程还没有操控终端

mutex_unlock(&tty_mutex);

return -ENXIO;

}

driver = tty_driver_kref_get(tty->driver); //假设翻开确实实是操控终端的处理

index = tty->index;

filp->f_flags |= O_NONBLOCK;

tty_kref_put(tty);

goto got_driver;

}

#ifdef CONFIG_VT

if (device == MKDEV(TTY_MAJOR, 0)) { //当时虚拟操控台,/dev/tty0

extern struct tty_driver *console_driver;

driver = tty_driver_kref_get(console_driver);

index = fg_console; // fg_console表明当时的前台操控台

noctty = 1; //由于虚拟操控台本来就翻开,故置位

goto got_driver;

}

#endif

if (device == MKDEV(TTYAUX_MAJOR, 1)) { //用于外接的操控台,/dev/console

struct tty_driver *console_driver = console_device(&index);

if (console_driver) {

driver = tty_driver_kref_get(console_driver);

if (driver) {

filp->f_flags |= O_NONBLOCK;

noctty = 1;

goto got_driver;

}

}

mutex_unlock(&tty_mutex);

return -ENODEV;

}

driver = get_tty_driver(device, &index);

if (!driver) {

mutex_unlock(&tty_mutex);

return -ENODEV;

}

got_driver:

if (!tty) {

//查看咱们是否重复翻开一个现已存在的tty

tty = tty_driver_lookup_tty(driver, inode, index);

if (IS_ERR(tty)) {

mutex_unlock(&tty_mutex);

return PTR_ERR(tty);

}

}

if (tty) {

retval = tty_reopen(tty); //从头翻开

if (retval)

tty = ERR_PTR(retval);

} else

tty = tty_init_dev(driver, index, 0); //初始化,为需求翻开的终端树立tty_struct结构体

mutex_unlock(&tty_mutex);

tty_driver_kref_put(driver);

if (IS_ERR(tty))

return PTR_ERR(tty);

filp->private_data = tty; //设置私有数据

file_move(filp, &tty->tty_files);

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)

retval = tty->ops->open(tty, filp); //调用tty_operations下的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_release_dev(filp);

if (retval != -ERESTARTSYS)

return retval;

if (signal_pending(current))

return retval;

schedule();

//需求复位f_op,以防挂起

if (filp->f_op == &hung_up_tty_fops)

filp->f_op = &tty_fops;

goto retry_open;

}

mutex_lock(&tty_mutex);

spin_lock_irq(&current->sighand->siglock);

if (!noctty &&

current->signal->leader &&

!current->signal->tty &&

tty->session == NULL)

__proc_set_tty(current, tty);

spin_unlock_irq(&current->sighand->siglock);

mutex_unlock(&tty_mutex);

return 0;

}

在上面这个open函数中,咱们首要触及为需求翻开的终端树立tty_struct结构体而履行的一条代码tty_init_dev(driver, index, 0),一起看到了怎样调用tty_operations下的open函数。在此咱们好好看看tty_init_dev(driver, index, 0)的内情吧。

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,

int first_ok)

{

struct tty_struct *tty;

int retval;

//查看是否pty被屡次翻开

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

if (retval < 0) {

free_tty_struct(tty);

module_put(driver->owner);

return ERR_PTR(retval);

}

retval = tty_ldisc_setup(tty, tty->link); //调用ldisc下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(tty, driver, idx)函数完成吧

void initialize_tty_struct(struct tty_struct *tty,

struct tty_driver *driver, int idx)

{

memset(tty, 0, sizeof(struct tty_struct));

kref_init(&tty->kref);

tty->magic = TTY_MAGIC;

tty_ldisc_init(tty); // tty_ldisc的初始化,

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->ops = driver->ops;

tty->index = idx;

tty_line_name(driver, idx, tty->name);

}

咱们持续盯梢initialize_tty_struct函数中的tty_ldisc_init(tty)函数

void tty_ldisc_init(struct tty_struct *tty)

{

struct tty_ldisc *ld = tty_ldisc_get(N_TTY); //设置线路规程N_TTY

if (IS_ERR(ld))

panic(“n_tty: init_tty”);

tty_ldisc_assign(tty, ld);

}

在tty_ldisc_init里,咱们总算找到了N_TTY,这是默许的线路规程。

持续看tty_init_dev,咱们发现retval = tty_ldisc_setup(tty, tty->link);持续盯梢

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

if (retval)

return retval;

if (o_tty) {

retval = tty_ldisc_open(o_tty, o_tty->ldisc);

if (retval) {

tty_ldisc_close(tty, ld);

return retval;

}

tty_ldisc_enable(o_tty);

}

tty_ldisc_enable(tty);

return 0;

}

然后咱们盯梢tty_ldisc_setup函数中的tty_ldisc_open函数

static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)

{

WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));

if (ld->ops->open)

return ld->ops->open(tty); //翻开ldisc下的open,进行链路的初始化

return 0;

}

在tty_ldisc_open这儿现现已过相应tty_ldisc结构所供给的函数指针调用了与链路规则有关的open操作。

前面tty_open函数中也有个open调用,这是为什么呢?由于详细的终端类型也可能有需求在翻开文件时加以调用的函数。关于用作操控台的虚拟终端,其tty_driver数据结构为console_driver,其open函数则为con_open()。

综上,咱们能够把tty_io.c看作是tty中心,然后tty中心里调用ldisc中的open,ldisc里的open调用tty层的open,tty层的open调用uart层的open,终究完成翻开操作。

最终再次总结如下几点:

其一,内核中有一个链表tty_drivers,体系在初始化时,或许装置某种终端设备的驱动模块时,经过函数tty_register_driver()将各种终端设备的tty_driver结构挂号到这个链表中。每逢新翻开一个终端设备时,就要依据其设备号经过函数get_tty_driver()在这个链表中找到的tty_driver结构,并把它复制到详细的tty_struct结构体中。

其二,当新创建一个tty_struct结构时,就把相应的tty_ldisc结构复制到tty_struct结构体中的这个成员中。

其三,别的内核中的一个重要指针termios,这个数据结构在某种程度上能够看作是对tty_ldisc结构的弥补,它规则了对接口上输入和输出的每个字符所作的处理以及传输的速度,即波特率。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/fangan/fpga/262305.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部