#include <linux/init.h>
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/tty_ldisc.h>
static int my_ldisc_tty_open(struct tty_struct *tty)
{
printk("%s\n", __func__);
if (tty->ops->write == NULL)
return -EOPNOTSUPP;
tty->disc_data = NULL;
tty->receive_room = 65536;
tty_driver_flush_buffer(tty);
return 0;
}
static void my_ldisc_tty_close(struct tty_struct *tty)
{
printk("%s\n", __func__);
}
static ssize_t my_ldisc_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
printk("%s\n", __func__);
return 0;
}
static ssize_t my_ldisc_tty_write(struct tty_struct *tty, struct file *flie,
const unsigned char *buf, size_t nr)
{
printk("%s %s\n", __func__, buf);
int space = tty_write_room(tty);
if (space >= nr)
return tty->ops->write(tty, buf, nr);
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
return -ENOBUFS;
}
static void my_ldisc_tty_receive(struct tty_struct *tty, const u8 *data,
char *flags, int count)
{
int i;
printk("%s\n", __func__);
for (i = 0; i < count; i++)
printk("%02x ", *data++);
printk("\n");
}
static struct tty_ldisc_ops my_ldisc_ops = {
.magic = TTY_LDISC_MAGIC,
.owner = THIS_MODULE,
.name = "my_ldisc",
.open = my_ldisc_tty_open,
.close = my_ldisc_tty_close,
.read = my_ldisc_tty_read,
.write = my_ldisc_tty_write,
.receive_buf = my_ldisc_tty_receive,
};
static int __init my_ldisc_init(void)
{
printk("%s\n", __func__);
return tty_register_ldisc(N_MYLDISC, &my_ldisc_ops);
}
static void __exit my_ldisc_exit(void)
{
printk("%s\n", __func__);
tty_unregister_ldisc(N_MYLDISC);
}
module_init(my_ldisc_init);
module_exit(my_ldisc_exit);
MODULE_LICENSE("GPL");
注册一个ldisc驱动使用tty_register_ldisc()函数,注销使用tty_unregister_ldisc()函数,tty_register_ldisc()函数需要两个参数,第一个参数是一个数字编号,在tty.h中定义,如果你新增了一个ldisc驱动的话,需要定义一个数字编号。在串口应用用,如果你没有指定一个ldisc的话,默认使用的是N_TTY这个ldisc。另一个参数是操作接口,那么我们只需要实现这个接口中的一些函数就行了。常用的接口函数有,open、close、read、write、receive_buf等。
在串口应用中,调用write系统调用时将触发这里的write接口,在write接口函数中,调用tty驱动的write函数将数据发送的串口中,这样串口数据就通过tx线缆发送出去了。可以看出ldisc相当于一个中转站,起到一个承上启下的作用。
如果串口电路收到数据,将触发这里的receive_buf接口,这里呢并没有做任何处理,只是简单将收到的数据给打印出来了。
ldisc驱动用在什么地方呢?通常用在在驱动层需要操作串口的地方,例如蓝牙的hci_ldisc.c、GSM的n_gsm.c都是这一类应用。
还有一个问题,在驱动层我们实际上是不知道我们到底操作的那个串口,这需要在串口应用层指定。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <termios.h>
#define DEV_NAME "/dev/s3c2410_serial0"
void tty_config(int fd)
{
struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
options.c_iflag &= ~(IXON | IXOFF | IXANY);
tcsetattr(fd, TCSANOW, &options);
}
int main(void)
{
int i, fd;
char *tmp = "ldisc test\n";
fd = open(DEV_NAME, O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("open tty device error\n");
exit(EXIT_FAILURE);
}
tty_config(fd);
i = 20;
if (ioctl(fd, TIOCSETD, &i)) {
printf("set line discipline error\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < 10; i++)
write(fd, tmp, strlen(tmp));
while (1);
close(fd);
exit(EXIT_SUCCESS);
}
在串口应用中,打开一个tty设备之后,使用ioctl来指定到底用哪一个ldisc,注意编号需要和底层驱动相对应。