入門級 Mouse Linux Kernel Driver

每當說起 Linux Kernel Driver 入門,就不免提到如何寫個 Hello World 級的 Module,這樣的第一支程式,除了可供 Linux Kernel 動態載入和卸載,似乎是一點用處也沒有。與一般應用程式不同,開發 Linux Driver 最大的門檻不在於如何撰寫出 Module,而是如何設計系統架構與硬體兩者間的橋樑。其中懂得如何控制和結合 Kernel 內各種機制更是重點,最複雜的莫過於此。

這邊有個 Mouse Kernel Driver,會在 Kernel 上新增一個虛擬滑鼠裝置,然後使用者可從 sysfs 控制該虛擬滑鼠(virmouse.c):
/*
 * A Virtual Mouse Driver to send fake events from userspace.
 *
 * Written by Fred Chien <fred@ullab.org>
 *
 */
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <linux/input.h>
#include <linux/platform_device.h>

struct input_dev *virmouse_input_dev;
static struct platform_device *virmouse_dev; /* Device structure */

/* Sysfs method to input simulated coordinates */
static ssize_t write_virmouse(struct device *dev,
                              struct device_attribute *attr,
                              const char *buffer, size_t count)
{
        int x, y, key;

        /* parsing input data */
        sscanf(buffer, "%d%d%d", &x, &y, &key);

        /* Report relative coordinates */
        input_report_rel(virmouse_input_dev, REL_X, x);
        input_report_rel(virmouse_input_dev, REL_Y, y);

        printk ("virmouse_event: X:%d Y:%d %d\n", x, y, key);

        /* Report key event */
        if (key>0) {
                if (key==1)
                        input_report_key(virmouse_input_dev, BTN_LEFT, 1);
                else if (key==2)
                        input_report_key(virmouse_input_dev, BTN_MIDDLE, 1);
                else
                        input_report_key(virmouse_input_dev, BTN_RIGHT, 1);
        }

        input_sync(virmouse_input_dev);

        return count;

}

/* Attach the sysfs write method */
DEVICE_ATTR(vmevent, 0644, NULL, write_virmouse);

/* Attribute Descriptor */
static struct attribute *virmouse_attrs[] = {
        &dev_attr_vmevent.attr,
        NULL
};

/* Attribute group */
static struct attribute_group virmouse_attr_group = {
        .attrs = virmouse_attrs,
};

/* Driver Initializing */
int __init virmouse_init(void)
{
        /* Register a platform device */
        virmouse_dev = platform_device_register_simple("virmouse", -1, NULL, 0);
        if (IS_ERR(virmouse_dev)){
                printk ("virmouse_init: error\n");
                return PTR_ERR(virmouse_dev);
        }

        /* Create a sysfs node to read simulated coordinates */
        sysfs_create_group(&virmouse_dev->dev.kobj, &virmouse_attr_group);

        /* Allocate an input device data structure */
        virmouse_input_dev = input_allocate_device();
        if (!virmouse_input_dev) {
                printk("Bad input_allocate_device()\n");
                return -ENOMEM;
        }

        /* Announce that the virtual mouse will generate relative coordinates */
        set_bit(EV_REL, virmouse_input_dev->evbit);
        set_bit(REL_X, virmouse_input_dev->relbit);
        set_bit(REL_Y, virmouse_input_dev->relbit);
        set_bit(REL_WHEEL, virmouse_input_dev->relbit);


        /* Announce key event */
        set_bit(EV_KEY, virmouse_input_dev->evbit);
        set_bit(BTN_LEFT, virmouse_input_dev->keybit);
        set_bit(BTN_MIDDLE, virmouse_input_dev->keybit);
        set_bit(BTN_RIGHT, virmouse_input_dev->keybit);

        /* Register with the input subsystem */
        input_register_device(virmouse_input_dev);

        /* print messages in the dmesg */
        printk("Virtual Mouse Driver Initialized.\n");

        return 0;
}

/* Driver Uninitializing */
void virmouse_uninit(void)
{
        /* Unregister from the input subsystem */
        input_unregister_device(virmouse_input_dev);

        /* Remove sysfs node */
        sysfs_remove_group(&virmouse_dev->dev.kobj, &virmouse_attr_group);

        /* Unregister driver */
        platform_device_unregister(virmouse_dev);

        return;
}

module_init(virmouse_init);
module_exit(virmouse_uninit);

MODULE_AUTHOR("Fred Chien <fred@ullab.org>");
MODULE_DESCRIPTION("Virtual Mouse Driver");
MODULE_LICENSE("GPL");

然後建立 Makefile:
obj-m += virmouse.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)

default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        @rm -fr *.ko *.o


編譯(需要安裝 Kernel header):
$ make

載入:
$ sudo insmod virmouse.ko

測試:
# 先切換成 root
$ sudo su -
# 滑鼠 X軸移動 168,Y軸移動 68,0 代表純移動不點擊
$ echo "168 68 0" > /sys/devices/platform/virmouse/vmevent

此 Driver 會先註冊成 evdev input 的滑鼠裝置,然後在 sysfs 並建立 group 和 vmevent 檔,Userspace 下的應用程式可以發送命令到 vmevent 使滑鼠移動或點擊左右中鍵。

後記:

此程式極為簡單,因此省略程式碼的說明,讀者直接看 source code 應該就能明瞭。

留言

  1. 請問一下
    他的點擊是跟目前的螢幕上的滑鼠分開囉?
    我下了一些命令但似乎
    螢幕上的滑鼠沒有反應

    [403064.769199] virmouse_event: X:168 Y:68 0
    [403077.313221] virmouse_event: X:168 Y:68 0
    [403137.784216] virmouse_event: X:0 Y:0 1
    [403209.072702] virmouse_event: X:0 Y:0 2
    [403213.264967] virmouse_event: X:0 Y:0 1
    [403228.372760] virmouse_event: X:0 Y:0 1
    [403230.220670] virmouse_event: X:0 Y:0 1
    [403234.157705] virmouse_event: X:0 Y:0 0

    回覆刪除
  2. 好像懂了

    cat /dev/input/mouse2

    就可以看到他的行為

    如果你有動作的話

    很實用的範例!

    回覆刪除
  3. 不好意思喔~想問一下

    我echo " 10 10 3" 不是應該按右鍵嗎?

    但是他好像沒有反應?

    另外, 我好像開不了 dev/input/mouse2 ??

    回覆刪除
  4. 應該是要先 ls /dev/input 看看新增的是那個mouse!然後再使用 cat /dev/input/mouse1(or 2.......)
    我是的 /dev/input/mouse1

    回覆刪除
  5. 可以請問您是用ubuntu哪一個版本嗎@@?

    因為我的input.h檔案裡面找不到"input_report_rel(virmouse_input_dev, REL_X, x);"這個函式

    回覆刪除
  6. 你可能忘了裝 linux kernel header 相關套件吧 :-)

    回覆刪除
  7. 可以麻煩前輩告訴我妳裝了哪些套件嗎><?

    回覆刪除
  8. 前輩您好,想請問你在 /usr/include/linux路徑下的input.h檔裡面有input_report_rel這個函式嗎?

    還是說您是另外安裝其他版本的linux kernel header呢?

    回覆刪除
    回覆
    1. 我是直接裝系統上 Kernel Header 相關套件,有時因需求會去下載特定版本的完整 Kernel Source Code 回來。

      刪除
  9. 因為我是用libusb函式庫寫一個擷取滑鼠Y軸的移動量,並且用gcc編譯
    設備都抓的到,但是當我要加上input.h裡面的input_report_rel擷取Y軸卻發生/usr/include/linux沒有此函式
    所以換個方式編譯寫了一個makefile並把路徑設定到新的kernel header,但是卻又發生找不到我安裝的libusb套件的include檔
    請問前輩如果我堅持要用libusb函式庫開發的話,是否可以建議我該用哪種方式擷取Y軸的移動量?
    麻煩前輩指點一下><

    回覆刪除
    回覆
    1. libusb 是在 user space 下的 Library,而 input_report_rel 是在 kernel 內部使用的,兩者不可混用。:-)

      如果你的設備已經被 kernel 認到,並視為 Input Device,你可以從 /dev/input/* 去直接讀取回傳值。

      刪除
  10. 感謝前輩的指導!!!
    我用fopen打開/dev/input/event5路徑的檔案(就是滑鼠)
    然後再用fread讀取這個檔案,並且用printf順利擷取到滑鼠移動的值XD

    但是我只要輕輕移動一下他的數字就更新的好快(請問這是光學滑鼠的關係嗎?如果是的話請問材料行有類似的USB設備可以取代嗎?)
    而且數字的值好大(請問前輩該怎麼排解這個問題呢?)

    回覆刪除
  11. 附上小弟執行完的截圖與程式碼...http://i.imgur.com/ezGvv.png

    void parse_mouse(char *b) {
    lb=(b[0]&1)>0;
    rb=(b[0]&2)>0;
    mb=(b[0]&4)>0;
    xd=b[1];
    yd=b[2];
    }

    FILE *cou;
    cou = fopen("/dev/input/event5","r"); //打開滑鼠設備
    char b[3]; //buffer size
    while(1)
    {
    fread(b,sizeof(char),3,cou);
    parse_mouse(b);
    printf("out:%d\n",yd);
    }

    很抱歉因為求助無門,只好厚臉皮的來向前輩請教了T T

    回覆刪除
    回覆
    1. 滑鼠裝置給你的是『相對位移』的連續數值,所以有正有負,這樣是正常的。

      而且從你的圖上來看,看起來數值也是在合理範圍。

      刪除
  12. 但是我只printf Y軸的值

    怎麼連我移動X軸的值也跑出來了...囧

    回覆刪除
  13. Fred, can i ask you for help? I use your example, which as i guess is based on Essential Linux Device Drivers books example (chapter 7). I think the code is ok, - i can catch packets generated by my event interface, but seems that gpm doesen't react, and i don't know why ... i created this topic http://stackoverflow.com/questions/16482260/using-linux-virtual-mouse-driver on stackoverflow according to this problem, and figure out that there maybe a problem with X11 configuration ... Thanks.

    回覆刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(二)

淺談 USB 通訊架構之定義(一)

Reverse SSH Tunnel 反向打洞實錄