终端驱动学习笔记

 

一、基本概念

终端是一种字符型设备,通常使用tty简称各种类型的终端。linux的终端类型:

/dev/ttySn,串行口终端

/dev/pty,伪终端

/dev/tty,当前进程的控制终端,可以是介绍的其它任何一种终端

/dev/ttyn,tty1~tty6是虚拟终端,tty0当前虚拟终端的别名。

/dev/console,控制台终端(显示器)

二、设备驱动结构

tty驱动中,除了tty核心,tty驱动,还有一个tty线路规程。tty线路规程的主要工作,将数据格式转换成协议格式。

特定tty设备驱动主体工作是填充tty_driver结构体成员。

struct tty_driver {

       int    magic;            /* magic number for this structure */

       struct cdev cdev;

       struct module  *owner;

       const char      *driver_name;

       const char      *name;

       int    name_base;     /* offset of printed name */

       int    major;            /* major device number */

       int    minor_start;    /* start of minor device number */

       int    minor_num;    /* number of *possible* devices */

       int    num;              /* number of devices allocated */

       short       type;              /* type of tty driver */

       short       subtype;  /* subtype of tty driver */

       struct ktermios init_termios; /* Initial termios */

       int    flags;             /* tty driver flags */

       int    refcount; /* for loadable tty drivers */

       struct proc_dir_entry *proc_entry; /* /proc fs entry */

       struct tty_driver *other; /* only used for the PTY driver */

 

       /*

        * Pointer to the tty data structures

        */

       struct tty_struct **ttys;

       struct ktermios **termios;

       struct ktermios **termios_locked;

       void *driver_state;

 

       /*

        * Driver methods

        */

 

       const struct tty_operations *ops;

       struct list_head tty_drivers;

};

magic,设为TTY_DRIVER_MAGIC

name,设备节点名

driver_name,驱动名

type,subtype,描述tty驱动的类型和子类型。

type

subtype

 

TTY_DRIVER_TYPE_SYSTEM

SYSTEM_TYPE_TTY

SYSTEM_TYPE_CONSOLE

SYSTEM_TYPE_SYSCONS

SYSTEM_TYPE_SYSPTMX

TTY_DRIVER_TYPE_CONSOLE

 

TTY_DRIVER_TYPE_SERIAL

SERIAL_TYPE_NORMAL

TTY_DRIVER_TYPE_PTY

PTY_TYPE_MASTER/PTY_TYPE_SLAVE

TTY_DRIVER_TYPE_SCC

 

init_termios,为一个termios结构体,这个成员提供线路设置的集合。驱动会copy tty_std_termios变量来初始化这个结构成员。

ops,为一个tty_operations结构体,提供驱动的操作函数。

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

       int  (*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);

       int (*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 (*tiocmget)(struct tty_struct *tty, struct file *file);

       int (*tiocmset)(struct tty_struct *tty, struct file *file,

                     unsigned int set, unsigned int clear);

       int (*resize)(struct tty_struct *tty, struct tty_struct *real_tty,

                            struct winsize *ws);

#ifdef CONFIG_CONSOLE_POLL

       int (*poll_init)(struct tty_driver *driver, int line, char *options);

       int (*poll_get_char)(struct tty_driver *driver, int line);

       void (*poll_put_char)(struct tty_driver *driver, int line, char ch);

#endif

};

 

flush_chars()与wait_until_sent()函数都用于刷新数据到硬件

write_room()指示有多少缓冲区空闲

char_in_buffer()指示缓冲中包含的数据数

set_termios()函数用于改变termios设置

throttle(),unthrottle(),当tty核心的输入缓冲区满时,throttle()函数将被调用,tty驱动试图通知设备不应当发送字符给它。当输入缓冲区清空,unthrottle()调用。

stop(),start(),停止和恢复数据发送

hangup(),挂起tty设备

breakctl()有关于RS-232端口状态

flush_buffer()刷新缓冲区并丢弃任何剩下的数据。

set_ldisc()设置线路规程(通常不需要被实现)

send_xchar(),”X- ”类型字符发送函数,(XON或XOFF)要发送的字符在第2个参数中指定。

read_proc()和write_proc(),/proc接口函数

tiocmget(),tiocmset()获得和设置tty线路规程的设置

 

相关的操作函数

1)  分配tty驱动

struct tty_driver *alloc_tty_driver(int lines);

       2)注册tty驱动

              int tty_register_driver(struct tty_driver *driver);

       3)注销tty驱动

              int tty_unregister_driver(struct tty_driver *driver);

       4)注册tty设备

              void tty_register_device(struct tty_driver *driver,unsigned index,struct device *device);

       5)注销tty设备

              void tty_unregister_device(struct tty_driver *driver,unsigned index);

       6)设置tty驱动操作

              void tty_set_operations(struct tty_driver *driver,struct tty_operations *op);

三、驱动编写

终端设备驱动围绕tty_driver结构体展开,一般完成以下两个步骤

1.       模块加载和卸载函数,完成注册和注销tty_driver, 初始化和释放终端设备对应的tty_driver结构体成员及硬件资源。

2.       实现tty_operations成员函数。

 

第一步实现实例(关于硬件资源申请,有待以后研究)

int uart_register_driver(struct uart_driver *drv)

{

       struct tty_driver *normal = NULL;

       int i, retval;

 

       BUG_ON(drv->state);

 

       /*

        * Maybe we should be using a slab cache for this, especially if

        * we have a large number of ports to handle.

        */

       drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

       retval = -ENOMEM;

       if (!drv->state)

              goto out;

 

       normal  = alloc_tty_driver(drv->nr);

       if (!normal)

              goto out;

 

       drv->tty_driver = normal;

 

       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;

       normal->subtype           = SERIAL_TYPE_NORMAL;

       normal->init_termios      = tty_std_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);

 

       /*

        * Initialise the UART state(s).

        */

       for (i = 0; i < drv->nr; i++) {

              struct uart_state *state = drv->state + i;

 

              state->close_delay     = 500;     /* .5 seconds */

              state->closing_wait    = 30000;  /* 30 seconds */

 

              mutex_init(&state->mutex);

       }

 

       retval = tty_register_driver(normal);

 out:

       if (retval < 0) {

              put_tty_driver(normal);

              kfree(drv->state);

       }

       return retval;

}

 

关于tty_operations各成员函数,以后再做研究。tty_struct结构体被tty核心用来保存当前tty端口的状态。其中有一个driver_data的数据指针,可以用来指向设备的”私有”结构指针。

write实例(从buf指向的用户空间发送count个字节)

uart_write(struct tty_struct *tty, const unsigned char *buf, int count)

{

       struct uart_state *state = tty->driver_data;

       struct uart_port *port;

       struct circ_buf *circ;

       unsigned long flags;

       int c, ret = 0;

 

       /*

        * This means you called this function _after_ the port was

        * closed.  No cookie for you.

        */

       if (!state || !state->info) {

              WARN_ON(1);

              return -EL3HLT;

       }

 

       port = state->port;

       circ = &state->info->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);

       return ret;

}

 

tty_operations结构体中没有提供read()函数,tty核心在struct tty_filp_buffer结构中缓冲数据。调用tty_filp_buffer_push()可以将filp缓冲区刷新到用户。

模板实例

for(i=0;i<data_size;++i){

if (tty->flip.count>=TTY_FLIPBUF_SIZE)

tty_filp_buffer_push(tty);

tty_insert_flip_char(tty,data[i],TTY_NORMAL);

}

tty_flip_buffer_push(tty);

 

有关于TTY线路设置

用户空间有两种方式可以获得和设置当前线路设置:调用termios库函数和对tty设备节点进行ioctl调用。大部分termios用户空间函数被库函数转换为对驱动节点ioctl()调用。即用户空间的termios库函数首先转换成ioctl调用,然后再转换成对内核空间的iotctl调用。而tty ioctl中的大部分命令会被tty核心转换为set_termios()函数的调用。

void(*set_termios)(struct tty_stuct *tty,struct ktermios *old)

对于TIOCMGET,TIOCMSET,TIOCMBIC,TIOCMBIS则会转换为对tiocmget()和tiocmset()函数的调用。它们用于读取和设置Modem控制的设置。

还有TIOCSERGETLSR,TIOCGSERIAL,TIOCMIWAIT,TIOCGICOUNT这些命令则是直接调用驱动程序ioctl()函数调用。

四、UART设备驱动

由于linux已存在文件serial_core.c,它对tty设备中的UART设备驱动进行了一次封装(具体节省了多少力气,待议),编写tty驱动,就转化为:

1.       定义uart_driver,uart_ops,uart_port,初始化它们模块加载和卸载时调用uart_register_driver()和uart_add_one_port(),uart_unregister_driver()和uart_remove_one_port()

2.       实现uart_ops的成员函数

 

三个重要的结构体

1)  uart_driver,它封装了一个tty_driver。

2)  uart_port用于描述uart的物理端口信息

3)  uart_ops,它的实现相当于tty_ops。

相关操作函数

注册和注销驱动

int uart_register_driver(struct uart_driver *drv);

void uart_unregister_driver(struct uart_driver *drv);

添加/去除端口

int uart_add_one_port(struct uart_driver *drv,struct uart_port *port);

int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port);

上面两个函数包含了设备注册和注销函数

 

内核printk支持8个级别:KERN_EMERG,ERNEL_ALERT,KERN_CRIT,

KERN_ERR,KERN_WARNING,KERN_NOTICE,KERN_INFO,KERN_DEBUG

在linux中,用于printk输出的是内核console,专门用console结构体来描述。

struct console {

       char name[16];

       void (*write)(struct console *, const char *, unsigned);

       int    (*read)(struct console *, char *, unsigned);

       struct tty_driver *(*device)(struct console *, int *);

       void (*unblank)(void);

       int    (*setup)(struct console *, char *);

       int    (*early_setup)(void);

       short       flags;

       short       index;

       int    cflag;

       void *data;

       struct      console *next;

};

write()用于将打印信息写入console,setup()用于设置console的特性,如波特率,停止位等。

内核用以下函数来注册/注销console

void register_console(struct console *);

int unregister_console(struct console *);

驱动使用的printk()函数经过层层调用,最终使用console的write()将控制台的消息打印出去。内核初始化时会调用console_init()函数,该函数会调用放在”.con_initcall.init”节中的代码。放置的方法是在驱动中使用console_initcall(void *function)。

为了在console_init()函数被调用前使用printk(),可以使用内核的“early printk”,对于early printk的console注册往往通过内核的early_param完成。

early_param(“earlycon”,setup_early_serial8250_console)

定义一个earlycon的内核参数,内核解析这个参数时调用setup_early_serial8250_console()函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值