TTY设备驱动程序开发指南

1. 前言

本文介绍如何编写符合AliOS Things标准的teletype设备(一般用于实现UART或虚拟终端)驱动程序。

2. 头文件

在程序中使用本文提及的功能应包含头文件 aos/tty_core.h

3. 数据结构

AliOS Things提供TTY设备的抽象基础结构:

typedef struct aos_tty aos_tty_t;

采用结构体嵌套的方式从基础结构派生出具体的硬件结构。派生类型应包含具体硬件操作所需的各种变量。例如:

typedef struct {
aos_tty_t tty;
/* private data */
void *reg_base;
int irq_num;
} tty_abc_t;

使用宏aos_container_of实现从基础结构指针到派生结构指针的转换。例如:

aos_tty_t *tty_dev = foo();
tty_abc_t *tty_abc = aos_container_of(tty_dev, tty_abc_t, tty);

4. 注册及注销

4.1. 注册

AliOS Things提供如下函数用于注册TTY设备:

aos_status_t aos_tty_register(aos_tty_t *tty);
int32_t aos_status_t
Definition: kernel.h:185

调用注册函数之前,BSP开发者应自行分配一个aos_tty_t类型或其派生类型的变量并对包含的如下变量赋值:

  • dev.iduint32_t类型,表示该设备的ID。不同TTY设备不能拥有相同的ID。
  • opsconst aos_tty_ops_t *类型,指向一组面向硬件的回调函数:
    typedef struct {
    void (*unregister)(aos_tty_t *tty);
    aos_status_t (*startup)(aos_tty_t *tty);
    void (*shutdown)(aos_tty_t *tty);
    aos_status_t (*set_attr)(aos_tty_t *tty);
    void (*enable_rx)(aos_tty_t *tty);
    void (*disable_rx)(aos_tty_t *tty);
    void (*start_tx)(aos_tty_t *tty);
    void (*stop_tx)(aos_tty_t *tty);
    } aos_tty_ops_t;
  • flagsuint32_t类型,可包含如下字段,各字段使用按位或运算连接:
    • AOS_TTY_F_UNIQUE_REF:表示该设备只能同时被引用一次。

调用注册函数之前,BSP开发者应初始化派生类型中的私有变量,并执行具体硬件相关的注册时初始化工作(例如映射寄存器地址等)。

4.2. 注销

AliOS Things提供如下函数用于注销TTY设备:

aos_status_t aos_tty_unregister(uint32_t id);

调用此函数之后,BSP开发者可回收相关联的aos_tty_t类型或其派生类型的变量。

5. 回调函数

驱动程序应实现aos_tty_ops_t定义的一组面向硬件的回调函数。

5.1. unregister

void (*unregister)(aos_tty_t *tty);

unregister回调函数在设备注销时被调用,可在该函数中执行具体硬件相关的注销时反初始化工作(例如解除寄存器地址映射等)。

5.2. startup

aos_status_t (*startup)(aos_tty_t *tty);

startup回调函数在设备引用计数从0增加到1时被调用,可在该函数中执行具体硬件相关的运行时初始化工作。初始化成功时应返回0;失败时应返回errno(负值)。该函数不应使能硬件发送和接收功能。

startup回调函数可读取tty->termios.c_cflag变量获取设备初始属性并修改硬件状态;也可修改该变量使其反映硬件的实际状态。该变量是tcflag_t类型,可包含如下字段,各字段使用按位或运算连接:

  • 波特率,掩码为CBAUD,取值必须为以下当中的一个:
    • B50
    • B75
    • B110
    • B134
    • B150
    • B200
    • B300
    • B600
    • B1200
    • B1800
    • B2400
    • B4800
    • B9600
    • B19200
    • B38400
    • B57600
    • B115200
    • B230400
    • B460800
    • B500000
    • B576000
    • B921600
    • B1000000
    • B1152000
    • B1500000
    • B2000000
    • B2500000
    • B3000000
    • B3500000
    • B4000000
  • 字节长度,掩码为CSIZE,取值只能为以下当中的一个:
    • CS5
    • CS6
    • CS7
    • CS8
  • CSTOPB:该标志有效时表示停止位为2位,否则为1位。
  • PARENB:表示使能校验位。
  • PARODDPARENB有效时,若PARODD有效则校验方式为奇校验,否则为偶校验。

5.3. shutdown

void (*shutdown)(aos_tty_t *tty);

shutdown回调函数在设备引用计数从1减小到0时被调用,可在该函数中执行具体硬件相关的运行时反初始化工作。该函数被调用时,硬件发送和接收功能已被禁用。

5.4. set_attr

aos_status_t (*set_attr)(aos_tty_t *tty);

set_attr回调函数在修改设备属性时被调用,驱动程序应在该函数中根据新属性(存放在tty->termios.c_cflag变量中)修改硬件状态。修改成功后返回0;失败时应返回errno(负值),且驱动程序应将硬件状态恢复到此函数被调用之前的状态。该函数被调用时,硬件发送和接收功能已被禁用。

5.5. enable_rx

void (*enable_rx)(aos_tty_t *tty);

enable_rx回调函数在使能接收功能时被调用,驱动程序应在该函数中使能硬件的接收功能。该函数无需等待有数据被接收到再返回,而是应该使能接收中断(本设备级别而非中断控制器级别)并立即返回,在硬件中断处理程序中处理后续接收工作。

size_t aos_tty_rx_buffer_produce(aos_tty_t *tty, const void *buf, size_t count);

在中断处理程序中使用aos_tty_rx_buffer_produce将硬件接收到的数据送入设备软件核心层。参数count为硬件接收到的字节数目。驱动程序应在调用该函数之前从FIFO或DMA获取数据并存放到buf指向的空间。返回值为实际送入设备软件核心层的字节数目,在设备软件接收缓冲区已满的情况下返回值会小于countaos_tty_rx_buffer_produce应在关闭本地CPU中断且tty->lock加锁的环境下调用。例如:

void rx_irq_handler(tty_abc_t *tty_abc)
{
aos_tty_t *tty = &tty_abc->tty;
uint8_t fifo_data[RX_FIFO_SIZE];
size_t rx_count;
flags = aos_spin_lock_irqsave(&tty->lock);
if (!is_rx_irq_en(tty_abc->reg_base) || !is_rx_ready(tty_abc->reg_base)) {
aos_spin_unlock_irqrestore(&tty->lock, flags);
return;
}
rx_count = get_rx_fifo_data(tty_abc->reg_base, fifo_data);
(void)aos_tty_rx_buffer_produce(tty, fifo_data, rx_count);
aos_spin_unlock_irqrestore(&tty->lock, flags);
}
void aos_spin_unlock_irqrestore(aos_spinlock_t *spinlock, aos_irqsave_t flag)
aos_irqsave_t aos_spin_lock_irqsave(aos_spinlock_t *spinlock)
long aos_irqsave_t
Definition: kernel.h:200

5.6. disable_rx

void (*disable_rx)(aos_tty_t *tty);

enable_rx回调函数在禁用接收功能时被调用,驱动程序应在该函数中禁用硬件的接收功能。此时FIFO中已接收的数据或DMA正在接收的数据可直接丢弃。

5.7. start_tx

void (*start_tx)(aos_tty_t *tty);

start_tx回调函数在设备软件发送缓冲区由空变为非空时被调用,驱动程序应在该函数中将待发送数据送入FIFO或DMA。该函数无需等待数据全部被硬件发出再返回,而是应该使能发送中断(本设备级别而非中断控制器级别)并立即返回,在硬件中断处理程序中处理后续发送工作。注意start_tx回调函数在关闭本地CPU中断且tty->lock加锁的环境下被调用,请不要在该函数中进行任务调度或执行长耗时的工作。

size_t aos_tty_tx_buffer_consume(aos_tty_t *tty, void *buf, size_t count);

start_tx回调函数或中断处理程序中使用aos_tty_tx_buffer_consume从设备软件核心层获取待发送数据。参数count为硬件发送最大字节数,例如FIFO深度或DMA最大长度。待发送数据将被复制到buf指向的空间,驱动程序应将这些数据交给FIFO或DMA。返回值为实际从设备软件核心层获取的字节数目;返回0时说明设备软件发送缓冲区为空,驱动程序应禁用发送中断(本设备级别而非中断控制器级别)。aos_tty_tx_buffer_consume应在关闭本地CPU中断且tty->lock加锁的环境下调用。例如:

void tx_irq_handler(tty_abc_t *tty_abc)
{
aos_tty_t *tty = &tty_abc->tty;
uint8_t fifo_data[TX_FIFO_SIZE];
size_t tx_count;
flags = aos_spin_lock_irqsave(&tty->lock);
if (!is_tx_irq_en(tty_abc->reg_base) || !is_tx_empty(tty_abc->reg_base)) {
aos_spin_unlock_irqrestore(&tty->lock, flags);
return;
}
tx_count = aos_tty_tx_buffer_consume(tty, fifo_data, TX_FIFO_SIZE);
if (tx_count > 0)
set_tx_fifo_data(tty_abc->reg_base, fifo_data, tx_count);
else
disable_tx_irq(tty_abc->reg_base);
aos_spin_unlock_irqrestore(&tty->lock, flags);
}

5.8. stop_tx

void (*stop_tx)(aos_tty_t *tty);

disable_tx回调函数在中止发送时被调用,驱动程序应在该函数中中止硬件的发送流程。若此时FIFO中有待发送的数据或DMA正在发送数据,应先等待这些数据发送完成。