linux驅動篇-Input-button

Input-button

本篇是linux下input子系統下的按鍵(button/key)驅動,一起來動手吧。下面的話,老鳥可以跳過了直接從《需求描述》章節看起,新手可以試着看看。

 

前言

在嵌入式行業,有很多從業者。我們工作的主旋律是拿開源代碼,拿廠家代碼,完成產品的功能,提升產品的性能,進而解決各種各樣的問題。或者是維護一個模塊或方向,一搞就是好幾年。

時間長了,中年潤髮現我們對從零開始編寫驅動、應用、算法、系統、協議、文件系統等缺乏經驗。沒有該有的廣度和深度。中年潤也是這樣,工作了很多年,都是針對某個問題點修修補補或者某個模塊的局部刪刪改改。很少有機會去獨自從零開始編寫一整套完整的代碼。

當然,這種現狀對於企業來說是比較正常的,可以降低風險。但是對於員工本身,如果缺乏必要的規劃,很容易工作多年卻還是停留在單點的層面,而喪失了提升到較高層面的機會。隨着時間的增長很容易喪失競爭力。

另外,根據中年潤的經驗,絕大多數公司對於0-5年經驗從業者的定位主要是積極的問題解決者。而對於5-10經驗從業者的定位主要是積極的系統規劃者和引領者。在這種行業規則下,中年潤認爲,每個從業者都應該問自己一句,“5年後,我是否具備系統化把控軟件的能力呢?”。

當前的這種行業現狀,如果我們不做出一點改變,是沒有辦法突破的。有些東西,僅僅知道是不夠的,還需要深思熟慮的思考和必要的訓練,簡單來說就是要知行合一。

 

也許有讀者會有疑惑?這不就是重複造輪子麼?我們確實是在重複造輪子,因爲別人會造輪子那是別人的能力,我們自己會造輪子是我們自己的能力。在行業中,有太多的定製化需求是因爲輪子本身有原生性缺陷,我們無法直接使用,或者需要對其進行改進,或者需要抽取開源代碼的主體思想和框架,根據公司的需要定製自己的各項功能。設想,如果我們具備這種能力,必然會促使我們在行業中脫穎而出,而不是工作很多年一直在底層搬磚。底層搬磚沒什麼不好,問題是當有更廉價更激情的勞動力湧進來的時候,我們這些老的搬磚民工也就失去了價值。我們不會天天重複造輪子,我們需要通過造幾個輪子使得自己具備造輪子的能力,從而更好的適應這個環境,適應這個世界。

 

針對當前行業現狀,中年潤經過深思熟慮,想爲大家做點實實在在的事情,希望能夠幫助大家在鞏固基礎的同時提升系統化把控軟件的能力。當然,中年潤的水平也有限,有些觀點也只是一家之談,希望大家獨立思考,謹慎採用,如果寫的有錯誤或者不對的地方還請讀者們批評斧正,我們一起共同進步。

在這裏簡單介紹下中年潤,中年潤現在就職於一家大型國際化公司,工作經驗6年,碩士畢業。曾經擔任過組內的項目主管,項目經理,也曾經組建過新團隊,帶領大家衝鋒陷陣。在工作中,有做的不錯的地方,也有失誤的地方,有激情的時刻,也有失落的時刻。現在偏安一隅,專心搞技術,目前個人規劃的技術方向是嵌入式和AI基礎設施建設,以及嵌入式和AI的融合發展。

 

最後,說了這麼多,中年潤希望,在未來的日子裏和未知的領域裏,你我同行,爲我們的美好生活而努力奮鬥。

 

總體目標

本篇文章的目標是介紹如何利用linux下的input子系統自頂向下從零編寫按鍵(button/key)驅動。着力從總體思路,需求端,分析端,實現端,詳盡描述一個完整需求的開發流程,是中年潤多年經驗的提煉,希望讀者能夠有所收穫。最後的實戰目標,請讀者儘量完成,這樣讀者才能形成自己的思路。

本示例採用arm920架構,天祥電子生產的tx2440a開發板,核心爲三星的s3c2440。Linux版本爲2.6.31,是已經移植好的版本。編譯器爲arm920t-eabi-4.1.2.tar。

 

總體思路

總體思路是嚴格遵循需求的開發流程來,不遺漏任何思考環節。讀者在閱讀時請先跟中年潤的思路走一遍,然後再拋棄中年潤的思路,按照自己的思路走一遍,如果遇到困難請先自己思考,實在不會再來參考中年潤的思路和實現。

 

中年潤在寫代碼的的總體思路如下:

需求描述—能夠詳細完整的描述一個需求。

需求分析—根據需求描述,提取可供實現的功能,需要有定量或者定性的指標。(從宏觀上確定需要什麼功能)。

需求分解—根據需求分析,考慮要實現需求所需要做的工作(根據宏觀確定的功能,拆分成小的可單獨實現的功能)。

編寫思路—根據需求分解從總體上描述應該如何編寫代碼,(解決怎麼在宏觀上實現)。

詳細步驟—根據編寫思路,落實具體步驟,(解決怎麼在微觀上實現)。

編寫框架—根據編寫思路,實現總體框架(實現編寫思路里主體框架,細節內容留在具體代碼裏編寫)。

具體代碼—根據編寫框架,編寫每一個函數裏所需要實現的小功能,主要是實現驅動代碼,測試代碼。

Makefile—用來編譯驅動代碼。

目錄結構—用來說明當完成編碼後的結果。

測試步驟—說明如何對驅動進行測試,主要是加載驅動模塊,執行測試代碼。

執行結果—觀察執行結果是否符合預期。

結果總結—回顧本節的思路,知識點,api,結構體。

實戰目標—說明如何根據本文檔訓練。

請大家儘量按照自頂向下的學習思路來學習和實戰,因爲我們所有工作的動力都是我們心中的需求。這些步驟僅僅是我們達到目標所要走過的路。目錄起到提綱挈領的重要作用,寫的時候要實時看下提綱,看有沒有偏離自己的方向。

 

需求描述

使用input子系統接口,編寫一個按鍵驅動,能夠讓四個按鍵分別表達L,S,ENTER(回車換行),LEFTSHIFT(左shift)。

 

需求分析

根據《需求描述》,從宏觀上提取可供實現的功能,我們需要做以下幾件工作。

1需要使用input子系統接口

2需要能夠檢測按鍵的狀態來判斷是哪個按鍵按下或者鬆開

3需要通過四個按鍵的按下來上報L,S,ENTER,LEFTSHIFT4個事件

 

需求分解

根據《需求分析》的結果,將宏觀確定的功能拆分成小的可單獨實現的功能,我們需要做以下幾件工作。

1需要註冊一個input dev結構

2需要註冊檢測按鍵狀態的中斷函數

3需要在按下時,利用input api將按鍵的事件上報

4按鍵按下時一般會有抖動,需要通過定時器來延時消抖

 

編寫思路

編寫思路主要用來搭建代碼框架,解決在宏觀上如何用代碼實現驅動的功能。

input驅動的核心是註冊input設備,並註冊檢測按鍵狀態的中斷處理函數。

 

0搭建基礎框架

0.1編寫代碼框架,頭文件,修飾出口函數,修飾入口函數,聲明LICENSE

0.2構造和定義基礎數據結構

0.2.1構造引腳描述符數據結構

0.2.2重新定義引腳號碼

0.2.3定義引腳描述符

 

在入口函數中所做的工作如下

1入口函數

1.1 分配一個input_dev 結構體

1.2 設置

1.2.1 設置能產生哪類事件

1.2.2 設置能產生這類事件的哪些事件: L,S,ENTER,LEFTSHIT

1.3 註冊input_dev

1.4 硬件相關操作

1.4.1 註冊一個定時器

1.4.2 註冊中斷及中斷處理函數

 

在出口函數中所作的工作如下

2出口函數

2.1 釋放中斷

2.2 刪除定時器

2.3 卸載input設備

2.4 釋放input設備

 

詳細步驟

詳細步驟主要用來在代碼框架裏填充必要的細節代碼,解決在微觀上如何用代碼實現驅動各個小功能。

Input按鍵驅動的總體核心是註冊input設備,並註冊按鍵中斷處理函數。

 

0搭建基礎框架

0.1編寫代碼框架,頭文件,修飾出口函數,修飾入口函數,聲明LICENSE

0.2構造和定義基礎數據結構

0.2.1構造引腳描述符數據結構

0.2.2重新定義引腳號碼,就是GPIO口

0.2.3定義引腳描述符,定義按鍵所需要的資源

 

在入口函數中所做的工作如下:

1入口函數

1.1 分配一個input_dev 結構體

1.2 設置

1.2.1 設置能產生哪類事件

1.2.2 設置能產生這類事件的哪些事件: L,S,ENTER,LEFTSHIT

1.3 註冊input_dev

1.4 硬件相關操作

1.4.1 註冊一個定時器

1.4.1.1編寫定時器處理函數

1.4.2 註冊中斷及中斷處理函數

1.4.2.1編寫中斷處理函數

 

在出口函數中所作的工作如下

2出口函數

2.1 釋放中斷

2.2 刪除定時器

2.3 卸載input設備

2.4 釋放input設備

 

編寫框架

根據編寫思路,實現總體框架(實現編寫思路里主體框架,細節內容留在具體代碼裏編寫)。

 

 /* 本文件名字爲input_button_skel.c*/
/* 本文件是依照input 驅動<編寫思路>章節編寫,本文件
  * 的目的是編寫代碼框架,不做具體細節的編寫
  */
/* 本頭文件是linux2.6.31內核所提供的,其他版本按需調整 */
 
/* 0.1編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>
#include <linux/input.h>
/* 本文件的編寫思路如下,請參考中年潤所寫的input驅動文檔 */
/* 需求分析 --- 需求分解 --- 編寫框架*/
 
/* 0.2構造和定義基礎數據結構 */
/* 0.2.1構造引腳描述符數據結構 */
/* 引腳描述符 */
struct pin_desc{
       int irqnum;
       char *name;
       int pin;
       int key_val;
};
 
/*按引腳的不同定義不同的值,在中斷中進行處理
*要自己重新定義下面幾個宏,配合s3c2410_gpio_getpin函數使用
*/
/* 0.2.2重新定義引腳號碼 */
#define S3C2410_GPF4 S3C2410_GPF(4)
#define S3C2410_GPF5 S3C2410_GPF(5)
#define S3C2410_GPF6 S3C2410_GPF(6)
#define S3C2410_GPF7 S3C2410_GPF(7)
 
static struct timer_list button_timer;
static struct pin_desc *irq_pindesc;
static struct input_dev * button_inputdev;
 
/* 0.2.3定義引腳描述符 */
/* 定義按鍵所需要的中斷號,名字,gpio引腳,按鍵值*/
struct pin_desc pin_desc[4] =
{
 
};
 
/* 定時器函數 */
void button_timer_func(unsigned long data)
{
 
}
 
/* 按鍵中斷函數 */
irqreturn_t button_irq(int irq, void * devid)
{
       return IRQ_RETVAL(IRQ_HANDLED);
}
 
/* 1入口函數 */
static int my_button_init(void)
{
       int ret = 0;
       int i = 0;
       /* 1.1 分配一個input_dev 結構體 */
       button_inputdev = input_allocate_device();
       /* 1.2 設置*/
       /* 1.2.1 設置能產生哪類事件 EV_KEY*/
       set_bit(EV_KEY,button_inputdev->evbit);
       set_bit(EV_REP,button_inputdev->evbit);
       /* 1.2.2 設置能產生這類事件的哪些事件: L,S,ENTER,LEFTSHIFT */
       set_bit(KEY_L,button_inputdev->keybit);
       set_bit(KEY_S,button_inputdev->keybit);
       set_bit(KEY_ENTER,button_inputdev->keybit);
       set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
       /* 1.3 註冊input_dev */
       ret = input_register_device(button_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
       /* 1.4 硬件相關操作 */
       /* 1.4.1 定時器相關操作 */
       init_timer(&button_timer);
       //buttons_timer.expires  = 0;
       button_timer.function = button_timer_func;
       add_timer(&button_timer);
       /* 1.4.2 註冊中斷及中斷處理函數 */
       for (i = 0; i < 4; i ++){ 
              ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);
              if (ret != 0)
              {
                     printk("request button irq failed error code %d\n",ret);
              }
       }
 
       return ret;
}
 
/* 2出口函數 */
static void my_button_exit(void)
{
       int i = 0;
       /* 2.1 釋放中斷 */
       for (i = 0; i < 4; i ++){
              free_irq(pin_desc[i].irqnum,&pin_desc[i]);
       }
       /* 2.2 刪除定時器 */
       del_timer(&button_timer);
       /* 2.3 卸載input設備 */
       input_unregister_device(button_inputdev);
       /* 2.4 釋放input設備 */
       input_free_device(button_inputdev);
       return;
}
 
/* 0.1編寫代碼框架,頭文件,修飾出口函數,修飾入口函數,聲明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

 

驅動代碼

/* 本文件名字爲input_button.c*/
/* 本文件是依照input 驅動<詳細步驟>章節編寫,本文件
  * 的目的是編寫具體代碼,不介紹框架
  */
/* 本頭文件是linux2.6.31內核所提供的,其他版本按需調整 */
 
/* 0.1編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>
#include <linux/input.h>
/* 本文件的編寫思路如下,請參考中年潤所寫的input驅動文檔 */
/* 需求分析 --- 需求分解 --- 編寫框架--編寫代碼*/
 
/* 0.2構造和定義基礎數據結構 */
/* 0.2.1構造引腳描述符數據結構 */
/* 引腳描述符 */
struct pin_desc{
       int irqnum;
       char *name;
       int pin;
       int key_val;
};
 
/*按引腳的不同定義不同的值,在中斷中進行處理
*要自己重新定義下面幾個宏,配合s3c2410_gpio_getpin函數使用
*/
/* 0.2.2重新定義引腳號碼,就是GPIO口 */
#define S3C2410_GPF4 S3C2410_GPF(4)
#define S3C2410_GPF5 S3C2410_GPF(5)
#define S3C2410_GPF6 S3C2410_GPF(6)
#define S3C2410_GPF7 S3C2410_GPF(7)
 
static struct timer_list button_timer;
static struct pin_desc *irq_pindesc;
static struct input_dev * button_inputdev;
 
/* 0.2.3定義引腳描述符,定義按鍵所需要的資源 */
/* 定義按鍵所需要的中斷號,名字,gpio引腳,按鍵值*/
struct pin_desc pin_desc[4] =
{
       {IRQ_EINT4,"S1",S3C2410_GPF4,KEY_L},
       {IRQ_EINT5,"S2",S3C2410_GPF5,KEY_S},
 
       {IRQ_EINT6,"S3",S3C2410_GPF6,KEY_ENTER},
       {IRQ_EINT7,"S4",S3C2410_GPF7,KEY_LEFTSHIFT},
};
 
/* 1.4.1.1編寫定時器處理函數 */
/* 定時器函數 */
void button_timer_func(unsigned long data)
{
       int pin_val;
       struct pin_desc * pin_readed;
       pin_readed = irq_pindesc;
       if (!pin_readed)
              return;
      
       /*獲取某個引腳是高還是低,需要配合上面定義的宏使用*/
       pin_val = s3c2410_gpio_getpin(pin_readed->pin);
       if (pin_val) /*鬆開是高電平*/
       {
              /*上報事件,0表示鬆開,最後一個參數: 0-鬆開, 1-按下*/
              input_event(button_inputdev, EV_KEY, pin_readed->key_val, 0);
       }
       else        /* 按下爲低電平*/
       {
              /*上報事件,1表示按下,最後一個參數: 0-鬆開, 1-按下*/
              input_event(button_inputdev, EV_KEY, pin_readed->key_val, 1);
       }
       /* 上報同步事件*/
       input_sync(button_inputdev);     
 
}
 
/* 1.4.2.1編寫中斷處理函數 */
/* 按鍵中斷函數 */
irqreturn_t button_irq(int irq, void * devid)
{
       /*記錄傳入的參數值,利用定時器延時,10ms後調用定時器中斷處理函數
       *如果有按鍵抖動會多次進入按鍵中斷函數,會多次修改調用定時器中斷
       *的時機,最後的效果就是隻調用了一次定時器中斷處理函數
       *HZ代表1s,HZ/100是10ms
       *mod_timer第二個參數應該是jiffies+你想要延時的時間
       */
       irq_pindesc = (struct pin_desc *)devid;
       mod_timer(&button_timer,jiffies + HZ / 100);
       return IRQ_RETVAL(IRQ_HANDLED);
}
 
/* 1入口函數 */
static int my_button_init(void)
{
       int ret = 0;
       int i = 0;
       /* 1.1 分配一個input_dev 結構體 */
       button_inputdev = input_allocate_device();
       /* 1.2 設置*/
       /* 1.2.1 設置能產生哪類事件 EV_KEY*/
       set_bit(EV_KEY,button_inputdev->evbit);
       set_bit(EV_REP,button_inputdev->evbit);
       /* 1.2.2 設置能產生這類事件的哪些事件: L,S,ENTER,LEFTSHIFT */
       set_bit(KEY_L,button_inputdev->keybit);
       set_bit(KEY_S,button_inputdev->keybit);
       set_bit(KEY_ENTER,button_inputdev->keybit);
       set_bit(KEY_LEFTSHIFT,button_inputdev->keybit);
       /* 1.3 註冊input_dev */
       ret = input_register_device(button_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
       /* 1.4 硬件相關操作 */
       /* 1.4.1 定時器相關操作 */
       init_timer(&button_timer);
       //buttons_timer.expires  = 0;
       button_timer.function = button_timer_func;
       add_timer(&button_timer);
       /* 1.4.2 註冊中斷及中斷處理函數 */
       for (i = 0; i < 4; i ++){ 
              ret = request_irq(pin_desc[i].irqnum, button_irq, IRQ_TYPE_EDGE_BOTH, pin_desc[i].name,(void *)&pin_desc[i]);           
              if (ret != 0)
              {
                     printk("request button irq failed error code %d\n",ret);
              }
       }
 
       return ret;
}
 
/* 2出口函數 */
static void my_button_exit(void)
{
       int i = 0;
       /* 2.1 釋放中斷 */
       for (i = 0; i < 4; i ++){
              free_irq(pin_desc[i].irqnum,&pin_desc[i]);
       }
       /* 2.2 刪除定時器 */
       del_timer(&button_timer);
       /* 2.3 卸載input設備 */
       input_unregister_device(button_inputdev);
       /* 2.4 釋放input設備 */
       input_free_device(button_inputdev);
       return;
}
 
/* 0.1編寫代碼框架,頭文件,修飾出口函數,修飾入口函數,聲明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

 

測試代碼

本示例無需測試代碼,可以通過相關命令測試。

 

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
all:
       make -C $(KERN_DIR) M=`pwd` modules
clean:
       make -C $(KERN_DIR) M=`pwd` modules clean
       rm -rf modules.order
obj-m += input_button.o

如果名字不同,請更換obj-m後面.o的名字。

 

目錄結構

代碼編寫完成的目錄結構如下所示。直接執行make即可生成.ko文件。

.
├── input_button.c
├── input_button_skel.c
└── Makefile

 

測試步驟

0 在linux下的makefile +180行處配置好arch爲arm,cross_compile爲arm-linux-(arm-angstrom-linux-gnueabi-)

1 在menuconfig中配置好內核源碼的目標系統爲s3c2440

2 在pc上將驅動程序編譯生成.ko,命令:make

3 掛載nfs,這樣就可以在開發板上看到pc端的.ko文件和測試文件

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

4 insmod input_button.ko

input: Unspecified device as /class/input/input0

5 cat /dev/tty1

6依次按下四個按鍵,觀察現象

 

執行結果

[root@TX2440A 8input-button]# cat /dev/tty1

lllllss

ls

ls

LS

ls

LS

llllllllllllllllll

ssssssssssss

單次按下,會有輸出l,s,ls,配合板子的SHIFT鍵,會有輸出L,S,LS

按着不動,會重複輸出l或者s

 

[root@TX2440A 8input-button]# ls /dev/event*

/dev/event0

event0代表着input設備

 

結果總結

在本篇文章中,中年潤跟讀者分享了input按鍵驅動的編寫思路和方法,其中貫穿始終的有幾個函數和關鍵數據結構。它們分別是:

struct input_dev

struct timer_list

s3c2410_gpio_getpin

input_event

input_sync

input_allocate_device

set_bit

input_register_device

init_timer

request_irq

free_irq

del_timer

input_unregister_device

input_free_device

宏S3C2410_GPF()

請讀者盡力去了解這些函數的作用,入參,返回值。

 

實戰目標

1請讀者根據《需求描述》章節,獨立編寫需求分析和需求分解。

2請讀者根據需求分析和需求分解,獨立編寫編寫思路和詳細步驟。

3請讀者根據編寫思路,獨立寫出編寫框架。

4請讀者根據詳細步驟,獨立編寫驅動代碼和測試代碼。

5請讀者根據《Makefile》章節,獨立編寫Makefile。

6請讀者根據《測試步驟》章節,獨立進行測試。

7請讀者拋開上述練習,自頂向下從零開始再編寫一遍驅動代碼,測試代碼,makefile

8如果無法獨立寫出7,請重複練習1-6,直到能獨立寫出7。

 

參考資料

《linux設備驅動開發祥解》

《TX2440開發手冊及代碼》

《韋東山嵌入式教程》

《魚樹驅動筆記》

《s3c2440a》芯片手冊英文版和中文版

 

 

致謝

感謝在嵌入式領域深耕多年的前輩,感謝中年潤的家人,感謝讀者。沒有前輩們的開拓,我輩也不能站在巨人的肩膀上看世界;沒有家人的鼎力支持,我們也不能集中精力完成自己的工作;沒有讀者的關注和支持,我們也沒有充足的動力來編寫和完善文章。看完中年潤的文章,希望讀者學到的不僅僅是如何編寫代碼,更進一步能夠學到一種思路和一種方法。

 

爲了省去驅動開發者蒐集各種資料來寫驅動,中年潤後續有計劃按照本模板編寫linux下的常見驅動,敬請讀者關注。

 

聯繫方式

微信羣:見文章最底部,因微信羣有效期只有7天,感興趣的同學可以加下。微信羣裏主要是爲初學者答疑解惑,也可以進行技術和非技術的交流。如果微信羣失效了,大家可以加qq羣,我會在qq羣裏分享微信羣的二維碼。同時也歡迎和中年潤志同道合的中年人尤其是中年碼農的加入。

微信訂閱號:自頂向下學嵌入式

公衆號微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客網址:https://blog.csdn.net/chichi123137

QQ郵箱:[email protected]

QQ羣:766756075

更多原創文章請關注微信公衆號。另外,中年潤還代理銷售韋東山老師的視頻教程,歡迎讀者諮詢。在中年潤這裏購買了韋東山老師的視頻教程,除了能得到韋東山官方的技術支持外,還能獲得中年潤細緻入微的技術和非技術的支持和幫助。歡迎大家選購哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章