日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

嵌入式linux 控制台 驱动,控制台驱动是linux重要的设备驱动之一

發布時間:2023/12/29 linux 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 嵌入式linux 控制台 驱动,控制台驱动是linux重要的设备驱动之一 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一:前言

我們在之前分析過input子系統和tty設備驅動架構.今天需要將兩者結合起來.看看linux中的控制臺是怎么樣實現的.

二:控制臺驅動的初始化

之前在分析tty驅動架構的時候曾分析到.主設備為4,次設備為0的設備節點,即/dev/tty0為當前的控制終端.

有tty_init()中,有以下代碼段:

static int __init tty_init(void)

{

……

……

#ifdef CONFIG_VT

cdev_init(&vc0_cdev, &console_fops);

if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||

register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)

panic("Couldn't register /dev/tty0 driver/n");

device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), "tty0");

vty_init();

#endif

return 0;

}

CONFIG_VT:是指配置虛擬終端.即我們所說的控制臺.在此可以看到TTY_MAJOR(4),0對應的設備節點操作集為console_fops.

繼續跟進vty_init()

int __init vty_init(void)

{

vcs_init();

console_driver = alloc_tty_driver(MAX_NR_CONSOLES);

if (!console_driver)

panic("Couldn't allocate console driver/n");

console_driver->owner = THIS_MODULE;

console_driver->name = "tty";

console_driver->name_base = 1;

console_driver->major = TTY_MAJOR;

console_driver->minor_start = 1;

console_driver->type = TTY_DRIVER_TYPE_CONSOLE;

console_driver->init_termios = tty_std_termios;

console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;

tty_set_operaTIons(console_driver, &con_ops);

if (tty_register_driver(console_driver))

panic("Couldn't register console driver/n");

kbd_init();

console_map_init();

#ifdef CONFIG_PROM_CONSOLE

prom_con_init();

#endif

#ifdef CONFIG_MDA_CONSOLE

mda_console_init();

#endif

return 0;

}

經過我們之前的tty驅動架構分析,這段代碼看起來就比較簡單了,它就是注冊了一個tty驅動.這個驅動對應的操作集是位于con_ops里面的.

仔細看.在之后還會調用kbd_init().顧名思義,這個是一個有關鍵盤的初始化.控制終端跟鍵盤有什么關系呢?在之前分析tty的時候,曾提到過,. 對于控制臺而言,它的輸入設備是鍵盤鼠標,它的輸出設備是當前顯示器.這兩者是怎么關聯起來的呢?不著急.請看下面的分析.三:控制臺的open操作

在前面分析了,對應console的操作集為con_ops.定義如下:

staTIc const struct file_operaTIons console_fops = {

.llseek??????????????? = no_llseek,

.read?????????????????? = tty_read,

.write????????????????? = redirected_tty_write,

.poll?????????? = tty_poll,

.ioctl????????? = tty_ioctl,

.compat_ioctl??? = tty_compat_ioctl,

.open????????????????? = tty_open,

.release??? = tty_release,

.fasync?????????????? = tty_fasync,

};

里面的函數指針值我們都不陌生了,在之前分析的tty驅動中已經分析過了.

結合前面的tty驅動分析.我們知道在open的時候,會調用ldisc的open和tty_driver.open.

對于ldisc默認是tty_ldiscs[0].我們來看下它的具體賦值.

console_init():

void __init console_init(void)

{

initcall_t *call;

/* Setup the default TTY line discipline. */

(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

/*

* set up the console device so that later boot sequences can

* inform about problems etc..

*/

call = __con_initcall_start;

while (call < __con_initcall_end) {

(*call)();

call++;

}

}

在這里,通過tty_register_ldisc.將tty_ldisc_N_TTY注冊為了第N_TTY項.即第1項. tty_ldisc_N_TTY定義如下:

struct tty_ldisc tty_ldisc_N_TTY = {

.magic?????????? = TTY_LDISC_MAGIC,

.name??????????? = "n_tty",

.open??????????? = n_tty_open,

.close?????????? = n_tty_close,

.flush_buffer??? = n_tty_flush_buffer,

.chars_in_buffer = n_tty_chars_in_buffer,

.read??????????? = read_chan,

.write?????????? = write_chan,

.ioctl?????????? = n_tty_ioctl,

.set_termios???? = n_tty_set_termios,

.poll??????????? = normal_poll,

.receive_buf???? = n_tty_receive_buf,

.write_wakeup??? = n_tty_write_wakeup

}

對應的open操作為n_tty_open:

staTIc int n_tty_open(struct tty_struct *tty)

{

if (!tty)

return -EINVAL;

/* This one is ugly. Currently a malloc failure here can panic */

if (!tty->read_buf) {

tty->read_buf = alloc_buf();

if (!tty->read_buf)

return -ENOMEM;

}memset(tty->read_buf, 0, N_TTY_BUF_SIZE);

reset_._flags(tty);

tty->column = 0;

n_tty_set_termios(tty, NULL);

tty->minimum_to_wake = 1;

tty->closing = 0;

return 0;

}

它為tty->read_buf分配內存.這個buffer空間大小為N_TTY_BUF_SIZE.read_buf實際上就是從按鍵的緩存區.然后調用reset_flags()來初始化tty中的一些字段:

static void reset_buffer_flags(struct tty_struct *tty)

{

unsigned long flags;

spin_lock_irqsave(&tty->read_lock, flags);

tty->read_head = tty->read_tail = tty->read_cnt = 0;

spin_unlock_irqrestore(&tty->read_lock, flags);

tty->canon_head = tty->canon_data = tty->erasing = 0;

memset(&tty->read_flags, 0, sizeof tty->read_flags);

n_tty_set_room(tty);

check_unthrottle(tty);

}

這里比較簡,不再詳細分析.在這里要注意幾個tty成員的含義:

Tty->read_head, tty->read_tail , tty->read_cnt分別代表read_buf中數據的寫入位置,讀取位置和數據總數.read_buf是一個環形緩存區.

n_tty_set_room()是設備read_buf中的可用緩存區

check_unthrottle():是用來判斷是否需要打開”閥門”,允許輸入數據流入

對于console tty_driver對應的open函數如下示:

static int con_open(struct tty_struct *tty, struct file *filp)

{

unsigned int currcons = tty->index;

int ret = 0;

acquire_console_sem();

if (tty->driver_data == NULL) {

ret = vc_allocate(currcons);

if (ret == 0) {

struct vc_data *vc = vc_cons[currcons].d;

tty->driver_data = vc;

vc->vc_tty = tty;

if (!tty->winsize.ws_row && !tty->winsize.ws_col) {

tty->winsize.ws_row = vc_cons[currcons].d->vc_rows;

tty->winsize.ws_col = vc_cons[currcons].d->vc_cols;

}

release_console_sem();

vcs_make_sysfs(tty);

return ret;

}

}

release_console_sem();

return ret;

}

tty->index表示的是tty_driver所對示的設備節點序號.在這里也就是控制臺的序列.用alt+fn就可以切換控制終端.

在這里,它主要為vc_cons[ ]數組中的對應項賦值.并將tty和vc建立關聯.

四:控制臺的read操作

從tty驅動架構中分析可得到,最終的read操作會轉入到ldsic->read中進行.

相應tty_ldisc_N_TTY的read操作如下.這個函數代碼較長,分段分析如下:

static ssize_t read_chan(struct tty_struct *tty, struct file *file,

unsigned char __user *buf, size_t nr)

{

unsigned char __user *b = buf;

DECLARE_WAITQUEUE(wait, current);

int c;

int minimum, time;

ssize_t retval = 0;

ssize_t size;

long timeout;

unsigned long flags;

do_it_again:

if (!tty->read_buf) {

printk(KERN_ERR "n_tty_read_chan: read_buf == NULL?!?/n");

return -EIO;

}

c = job_control(tty, file);

if (c < 0)

return c;

minimum = time = 0;

timeout = MAX_SCHEDULE_TIMEOUT;if (!tty->icanon) {

time = (HZ / 10) * TIME_CHAR(tty);

minimum = MIN_CHAR(tty);

if (minimum) {

if (time)

tty->minimum_to_wake = 1;

else if (!waitqueue_active(&tty->read_wait) ||

(tty->minimum_to_wake > minimum))

tty->minimum_to_wake = minimum;

} else {

timeout = 0;

if (time) {

timeout = time;

time = 0;

}

tty->minimum_to_wake = minimum = 1;

}

}

首先,檢查read操作的合法性,read_buf是否已經建立.然后再根據操作的類型來設置tty-> minimum_to_wake.這個成員的含義即為: 如果讀進程在因數據不足而睡眠的情況下,數據到達并超過了minimum_to_wake.就將這個讀進程喚醒.具體的喚醒過程我們在遇到的時候再進行分析.

/*

*????? Internal serialization of reads.

*/

//不允許阻塞

if (file->f_flags & O_NONBLOCK) {

if (!mutex_trylock(&tty->atomic_read_lock))

return -EAGAIN;

} else {

if (mutex_lock_interruptible(&tty->atomic_read_lock))

return -ERESTARTSYS;

}

add_wait_queue(&tty->read_wait, &wait);

在不允許睡眠的情況下,調用mutex_trylock()去獲得鎖.如果鎖被占用,馬上返回.否則用可中斷的方式去獲取鎖,如果取鎖錯誤,返回失敗.如果取鎖成功,將進程加至等待隊列.在沒有數據可讀的情況下,直接睡眠.如果有數據可讀,將其移出等待隊列即可.

while (nr) {

/* First test for status change. */

if (tty->packet && tty->link->ctrl_status) {

unsigned char cs;

if (b != buf)

break;

cs = tty->link->ctrl_status;

tty->link->ctrl_status = 0;

if (tty_put_user(tty, cs, b++)) {

retval = -EFAULT;

b--;

break;

}

nr--;

break;

}

接下來就是一個漫長的while循環,用來讀取數據,一直到數據取滿為止.如果tty->packet被置為1.即為信包模式,通常用在偽終端設備. 如果tty->link->ctrl_status有數據.則說明如果鏈路狀態發生改變,需要提交此信息.在這種情況下,將其直接copy到用戶空間即可.

/* This statement must be first before checking for input

so that any interrupt will set the state back to

TASK_RUNNING. */

set_current_state(TASK_INTERRUPTIBLE);

if (((minimum - (b - buf)) < tty->minimum_to_wake) &&

((minimum - (b - buf)) >= 1))

tty->minimum_to_wake = (minimum - (b - buf));

if (!input_available_p(tty, 0)) {

if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {

retval = -EIO;

break;

}

if (tty_hung_up_p(file))

break;

if (!timeout)

break;

if (file->f_flags & O_NONBLOCK) {

retval = -EAGAIN;

break;

}

if (signal_pending(current)) {

retval = -ERESTARTSYS;

break;

}

n_tty_set_room(tty);

timeout = schedule_timeout(timeout);

continue;

}

__set_current_state(TASK_RUNNING);

先將進程設為TASK_INTERRUPTIBLE狀態.再調用input_available_p()來判斷可數據供讀取.如果沒有.則進程睡眠.如果有數據,則將進程狀態設為TASK_RUNNING.在終端接收數據的處理過程中,有兩種方式,一種是規范模式.一種是原始模式.在規范模式下,終端需要對數據里面的一些特殊字符做處理.在原始模式下.終端不會對接收到的數據做任何的處理.在這里input_available_p()在判斷是否有數據可讀也分兩種情況進行,對于規范模式,看是否有已經轉換好的數據,對于原始模式,判斷接收的信息總數

/* Deal with packet mode. */

//packet模式`忽略

if (tty->packet && b == buf) {

if (tty_put_user(tty, TIOCPKT_DATA, b++)) {

retval = -EFAULT;

b--;

break;

}

nr--;

}

if (tty->icanon) {

/* N.B. avoid overrun if nr == 0 */

while (nr && tty->read_cnt) {

int eol;

eol = test_and_clear_bit(tty->read_tail,

tty->read_flags);

c = tty->read_buf[tty->read_tail];

spin_lock_irqsave(&tty->read_lock, flags);

tty->read_tail = ((tty->read_tail+1) &

(N_TTY_BUF_SIZE-1));

tty->read_cnt--;

if (eol) {

/* this test should be redundant:

* we shouldn't be reading data if

* canon_data is 0

*/

if (--tty->canon_data < 0)

tty->canon_data = 0;

}

spin_unlock_irqrestore(&tty->read_lock, flags);

//如果沒有到結束字符,將字符copy到數據空間

//__DISABLED_CHAR是不需要copy到用戶空間的

if (!eol || (c != __DISABLED_CHAR)) {

if (tty_put_user(tty, c, b++)) {

retval = -EFAULT;

b--;

break;

}

nr--;

}

if (eol) {

//如果遇到行結束符.就可以退出了

tty_audit_push(tty);

break;

}

}

if (retval)

break;

} else {

//非加工模式,直接copy

int uncopied;

//環形緩存,copy兩次

uncopied = copy_from_read_buf(tty, &b, &nr);

uncopied += copy_from_read_buf(tty, &b, &nr);

if (uncopied) {

retval = -EFAULT;

break;

}

}

總結

以上是生活随笔為你收集整理的嵌入式linux 控制台 驱动,控制台驱动是linux重要的设备驱动之一的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。