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);
調用注冊函數之前,BSP開發者應自行分配一個aos_tty_t
類型或其派生類型的變量并對包含的如下變量賦值:
dev.id
:uint32_t
類型,表示該設備的ID。不同TTY設備不能擁有相同的ID。ops
:const 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;
flags
:uint32_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
:表示使能校驗位。PARODD
:PARENB
有效時,若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
指向的空間。返回值為實際送入設備軟件核心層的字節數目,在設備軟件接收緩沖區已滿的情況下返回值會小于count
。aos_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;
aos_irqsave_t flags;
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);
}
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;
aos_irqsave_t flags;
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正在發送數據,應先等待這些數據發送完成。