LINUX-The Serial Driver Layer



The Serial Driver Layer

Registering a Serial Driver
--------------------------------
序列層要求你的驅動程式做兩件事情
1.註冊驅動程式它自己
2.註冊個人的序列埠 當它們被系統找到(經由PCI的列舉或其他裝置搜尋)

若要註冊自己的驅動程式請呼叫 uart_register_driver() 並傳入 struct uart_driver 的指標
這個函式取得 uart_driver 的資訊去初始狀態 並在uart core layer註冊一個驅動程式
註冊完後可在 /proc/tty/driver 出現相關資料

int uart_register_driver(struct uart_driver *drv)


下面是必須要提供的參數

struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;


下面是 uart_driver 結構內的參數/include/linux/serial_core.h
struct uart_driver {
struct module *owner;
int normal_major;
const char *normal_name;
struct tty_driver *normal_driver;
int callout_major;
const char *callout_name;
struct tty_driver *callout_driver;
struct tty_struct **table;
struct termios **termios;
struct termios **termios_locked;
int minor;
int nr;
struct console *cons;
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
};


owner 是序列驅動程式的模組擁有者的指標,通常設定為一個巨集 THIS_MODULE 表示
driver_name 是指向這個驅動程式描述的指標通常與 dev_name 欄位相同. 然而當描述 dev_name 欄位時,若是 devfs 則必須加一個計數值, 如 ``%d'' 必須加在這個欄位最後一個字元. 這是因為 devfs 會建立裝置的 devfs節點.

如下例, 在 amba.c 驅動程式設定 driver_name 跟 dev_name 欄位:
.driver_name = "ttyAM",
#ifdef CONFIG_DEVFS_FS
.dev_name = "ttyAM%d",
#else
.dev_name = "ttyAM",
#endif

major 與 minor 欄位表示驅動程式的 major 號碼跟起始的minor號碼.
nr 表示這個驅動程式的序列埠支援的最大數目.
cons 是一個指向結構struct console的指標 ,若不支援serial console 則本欄位設為 NULL.

Registering a Serial Port
現在序列驅動程式已經完成serial driver layer的註冊
每一個序列埠需要呼叫uart_add_one_port()去個別的註冊
這個函式取得一個由uart_register_driver跟uart_port所傳來的 uart_driver指標

uart_port 結構定義如下/include/linux/serial_core.h

struct uart_port {
spinlock_t lock; /* port lock */
unsigned int iobase; /* in/out[bwl] */
char *membase; /* read/write[bwl] */
unsigned int irq; /* irq number */
unsigned int uartclk; /* base uart clock */
unsigned char fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
unsigned int read_status_mask; /* driver specific */
unsigned int ignore_status_mask; /* driver specific */
struct uart_info *info; /* pointer to parent info */
struct uart_icount icount; /* statistics */
struct console *cons; /* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
unsigned long sysrq; /* sysrq timeout */
#endif
unsigned int flags;
unsigned int mctrl; /* current modem ctrl settings */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port type */
struct uart_ops *ops;
unsigned int line; /* port index */
unsigned long mapbase; /* for ioremap */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char unused[3];
};





低階序列硬體驅動程式
--------------------------------

低階序列硬體驅動程式是由所支援的埠資料(uart_port)跟一個控制方法(uart_ops)給core serial driver
它也可由埠支援中斷操作跟console

console支援
---------------

serial_core提供相對應的函式包括可認到正確的埠架構(uart_get_console)跟命令列解碼(uart_parse_options)


Locking
-------

一般而言,除了中斷功能外,全部的鎖都由核心驅動.這表示低階的硬體驅動程式必須去操作鎖(使用info->lock)
若操作在中斷期間則只需使用 spin_lock() 跟 spin_unlock()


uart_ops
--------

uart_ops結構是介於serial_core與硬體特定驅動程式主要的介面.它包含全部的硬體的控制方法

ops欄位 是結構 struct uart_ops
定義如下

struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *,unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *, unsigned int tty_stop);
void (*start_tx)(struct uart_port *, unsigned int tty_start);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*change_speed)(struct uart_port *, unsigned int cflag,unsigned int iflag, unsigned int quot);
void (*pm)(struct uart_port *, unsigned int state,unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
/*
* Return a string describing the port type
*/
const char *(*type)(struct uart_port *);
/*
* Release IO and memory resources used by
* the port. This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *);
/*
* Request IO and memory resources used by the
* port. This includes iomapping the port if
* necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *,struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int,unsigned long);
};


tx_empty(port)
若是空的,它將返回 TIOCSER_TEMT ,其他情形則返回0
若埠不支援這個功能則應返回TIOCSER_TEMT

set_mctrl(port, mctrl)
適當的mctrl 控制BIT為
- TIOCM_RTS RTS 信號.
- TIOCM_DTR DTR 信號.
- TIOCM_OUT1 OUT1 信號.
- TIOCM_OUT2 OUT2 信號.
若相關的BIT被設定則信號將被驅動,若相關的BIT被清除則信號也相被除能

get_mctrl(port)
返回 mctrl 現在的狀況
- TIOCM_DCD DCD 狀態
- TIOCM_CTS CTS 狀態
- TIOCM_DSR DSR 狀態
- TIOCM_RI RI 狀態
若port沒有支援 CTS, DCD or DSR, 則驅動程式應該指示這些信號永遠是動作的.

stop_tx(port,from_tty)
停止傳送字元
這表示CTS變的不活動或tty layer指示我們要去停止傳送

start_tx(port,nonempty,from_tty)
開始傳送字元

stop_rx(port)
停止接收字元

enable_ms(port)
致能modem狀態中斷

break_ctl(port,ctl)
傳送一個break 信號.
若 ctl 不為零, 則 break 信號應該被傳送.
當 ctl 為零時break 信號就應該被停止傳送

startup(port,info)
抓取任何中斷資源跟初始低階驅動程式狀態並致能port

shutdown(port,info)
與startup函式功能相反,關閉port及任何有影響的break條件,而所有startup函式所配置的資源全部釋放

change_speed(port,cflag,iflag,quot)
改變這個埠的參數,包括字元的長度,同位元,停止位元.
更新 read_status_mask 跟 ignore_status_mas


相關的 cflag 控制模式旗標為
CSIZE - word size
CSTOPB - 2 個停止位元
PARENB - 致能同位元
PARODD - 奇同位 (when PARENB is in force)
CREAD - 致能接收字元 (若沒設定 仍然可以接收字元但會丟棄)
CRTSCTS - 致能 CTS 狀態改變報告
CLOCAL - 若沒設定 致能modem 狀態改變報告.

相關的 iflag 輸入模式旗標為:
INPCK - 致能框架與同位元錯誤事件給 TTY layer.
BRKINT
PARMRK - 這兩個都是致能 brea 事件給 TTY layer.
IGNPAR - 忽視同位元跟框架錯誤
IGNBRK - 忽視break錯誤 若 IGNPAR 也被設定, 則忽視溢位錯誤 (overrun errors).

在 iflag 輸入模式旗標 設定與同位元錯誤有不同的影響:
Parity error INPCK IGNPAR
None n/a n/a 接收字元
Yes n/a 0 丟棄字元
Yes 0 1 接收字元, 註記 TTY_NORMAL
Yes 1 1 接收字元, 註記 TTY_PARITY

pm(port,state,oldstate)
set_wake(port,state)
若你的硬體平台有支援電源管理 使用這些函式去掌控硬體電源管理
'state' 定義如 ACPI D0-D3
'oldstate' 指示之前的狀態
基本上 D0表示全開 D3表示全關


下面這些函式被使用來抓取任何有關資源

type(port)
返回序列埠的描述 被使用在 /proc/tty/driver/ 跟 boot 起來時發現到這個埠所出現的訊息

release_port(port)
釋放這個port有關的記憶體與IO區域資源

request_port(port)
要求這個port有關的記憶體與IO區域資源
若要求失敗則應該沒有任何資料有被配置並返回 -EBUSY

config_port(port,type)
'type'種類
UART_CONFIG_TYPE 表示不要求偵測跟辨別.當 型態有被找出則 port->type 應被設定 若沒被找出則設為 PORT_UNKNOWN
UART_CONFIG_IRQ 表示在中斷信號下自動安裝,應該被探測在標準的核心自動探測技術,(這不是必須的)


verify_port(port,serinfo)
用來確認序列埠設定是否合法 跟 當使用者產生 ioctl 的 TIOCSSERIAL 時
Verify the new serial port information contained within serinfo is
suitable for this port type.

ioctl(port,cmd,arg)
執行任何 port 特定的 IOCTLs.
IOCTL 必須只能使用在 <asm/ioctl.h> 的定義


Other notes
-----------

為了將來的擴充性
可以建立自己個人的uart_port

struct my_port {
struct uart_port port;
int my_stuff;
};


Where Is the Data?
--------------------------------
你可能注意到這裡沒有函式是去做傳跟收資料
使用者傳資料時藉由serial driver layer,經由tty layer 呼叫 write() 去配置環狀緩衝區
然後由UART driver取得這些資料並從這個埠送出
通常每次UART中斷發生 若有資料要去傳送則驅動程式去確認環狀緩衝區

TX範例如下

static void tiny_tx_chars(struct uart_port *port)
{
struct circ_buf *xmit = &port->info->xmit;
int count;
if (port->x_char) {
/* send port->x_char out the port here */
UART_SEND_DATA(port->x_char);
port->icount.tx++;
port->x_char = 0;
return;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
tiny_stop_tx(port, 0);
return;
}
count = port->fifosize >> 1;
do {
/* send xmit->buf[xmit->tail] out the port here */
UART_SEND_DATA(xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
if (uart_circ_empty(xmit))
break;
} while (--count > 0);
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_event(port, EVT_WRITE_WAKEUP);
if (uart_circ_empty(xmit))
tiny_stop_tx(port, 0);
}


函式一開始由x_char來指示是否有現在資料要傳。
如果有資料,開始傳送且累計計數值後返回。
如果沒有,查看環狀緩衝區是否還有資料和埠現在是否已經停止。
若成立,我們開始從環狀緩衝區中取出字元並且把它們送到UART。
在這個例子裡e,我們最多只送出FIFO大小的一半,這是為了可以平均的送出字元。
送完字元後,判斷環狀緩衝區是否可接受更多字元。
若可以呼叫uart_event()傳入EVT_WRITE_WAKEUP 喚醒參數,通知序列核心去告知用戶端,我們能接受更多的數據。
這些收傳動作都是在中斷內發生。


UART中斷範例
/*
* This is the serial driver's interrupt routine.
*/
static void fic8120_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct fic8120_port *up = (struct fic8120_port *) dev_id;
unsigned char lsr;


DBG("%s(): ", __FUNCTION__);
spin_lock(&up->port.lock);

lsr = serial_in(up, FIC8120_LSR);
if (lsr & FIC8120_LSR_DR)
fic8120_rx_chars(up, &lsr, regs);

fic8120_check_mstatus(up);

if (lsr & FIC8120_LSR_THRE)
fic8120_tx_chars(up);

spin_unlock(&up->port.lock);
}


arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()