Linux Driver Example: virmouse

我很喜歡在網路上找一些簡單的程式範例,一來可以幫助自己學習,二來以後要拿來用比較方便。找範例程式的最高指導原則是:簡單!一個程式最好只說明一樣東西,而且不需要加上太多的廢話說明就可以看懂,這樣以後拿來用比較快,不用在花太多時間去重新想這個程式在做什麼。今天要放上的程式範例是一個 Linux Driver 的範例,網路上大部分的範例都是「Hello World」,簡單,但這樣的範例幾乎不具備任何參考價值。今天在網路上看到 Fred 所撰寫的一個 virtual mouse 的範例,所以就紀錄在這裡。但因為在下功力太差,所以之後會在加上一些自己的說明。


/*
 * A Virtual Mouse Driver to send fake events from userspace.
 *
 * Written by Fred Chien <fred@ullab.org>
 * Modified by Neokent.
 *      1. Change some coding styles.
 *      2. Add some comments for myself.
 *
 */
 
#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:this macro is used to declare a variable with name dev_attr_##_name.
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

可以透過 echo "168 68 0" > /sys/devices/platform/virmouse/vmevent 進行操作。

這是一個簡單的程式,但我還是有幾個地方看不懂:第一:明明是一個簡單的 virtual mouse device,為什麼要宣告 input_dev 和 platform_device?;第二:vmevent 和 dev_attr_vmevent 是哪來的?

先回答第二個問題,答案就在 DEVICE_ATTR 這個 MACRO 裡面。來看看它的定義(linux/device.h):

#define DEVICE_ATTR(_name, _mode, _show, _store) \
      struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

很明顯的可以看出來 DEVICE_ATTR 有作一個宣告的動作。至於第一個問題,「感覺上」,platform_device 製作了一個虛擬的滑鼠(可以看作一個跟User Space 的 IO 介面),之後在透過 input_sync 把 event 同步到 input_device 上面,也就是真正對 kernel 的介面。

應該 ... 是這樣吧 ...

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP