下面分析一下自己实现的光传感器的驱动。先附上代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define EVENT_TYPE_LIGHT ABS_MISC
#define LIGHTSENSOR_NAME "lightsensor"
#define S5PV210_SENSOR_CHANNEL 2
#define INIT_POLL_INTERVAL 10000
#define sensor_dbg(format, arg...) do { \
printk("%s : " format , __func__, ## arg) ;\
} while (0)
static DEFINE_MUTEX(sensor_mutex);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Light Sensor Driver");
struct lightsensor_data {
struct input_polled_dev *input_poll_dev;
atomic_t enabled;
int light_value;
};
static struct lightsensor_data *sensor_data;
static int poll_cnt = 0;
static int lightsensor_set_value(struct lightsensor_data *sensor, int value)
{
mutex_lock(&sensor_mutex);
sensor->light_value = value;
mutex_unlock(&sensor_mutex);
return 0;
}
static int lightsensor_get_value(struct lightsensor_data *sensor)
{
printk("%s %d\n", __func__, sensor->light_value);
int ret;
mutex_lock(&sensor_mutex);
ret = sensor->light_value ;
mutex_unlock(&sensor_mutex);
return ret;
}
void lightsensor_input_poll_func(struct input_polled_dev *input)
{
struct lightsensor_data *sensor = input->private;
u32 enabled = atomic_read(&sensor->enabled);
printk("%s, sensor %p enabled: %u, light_value: %d poll_rate: %d \n", __func__,
sensor, enabled, sensor->light_value, sensor->input_poll_dev->poll_interval);
if(enalbed)
{
// int value = mini2440_adc_get_light();
int value = poll_cnt++;
input_event(input->input, EV_FF, EVENT_TYPE_LIGHT, value);
input_sync(input->input);
sensor_dbg("input_report\n");
// lightsensor_set_value( sensor, value);
} //else
// sensor_dbg("input_not report\n");
}
static int lightsensor_enable(struct lightsensor_data *sensor)
{
// open the input dev
struct input_dev* input = sensor->input_poll_dev->input;
atomic_cmpxchg(&sensor->enabled, 0, 1);
return 0;
}
static int lightsensor_disable(struct lightsensor_data *sensor)
{
// close the input dev
struct input_dev* input = sensor->input_poll_dev->input;
atomic_cmpxchg(&sensor->enabled, 1, 0) ;
return 0;
}
static ssize_t attr_polling_rate_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned int val;
struct lightsensor_data *sensor = sensor_data;
mutex_lock(&sensor_mutex);
val = sensor->input_poll_dev->poll_interval;
mutex_unlock(&sensor_mutex);
return sprintf(buf, "%u\n", val);
}
static ssize_t attr_polling_rate_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct lightsensor_data *sensor = sensor_data;
long interval_ms;
if (strict_strtol(buf, 10, &interval_ms))
return -EINVAL;
if (!interval_ms)
return -EINVAL;
mutex_lock(&sensor_mutex);
sensor->input_poll_dev->poll_interval = interval_ms;
mutex_unlock(&sensor_mutex);
return size;
}
static ssize_t attr_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lightsensor_data *sensor = sensor_data;
u32 val = atomic_read(&sensor->enabled);
return sprintf(buf, "%d\n", val);
}
static ssize_t attr_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct lightsensor_data *sensor = sensor_data;
unsigned long val;
if (strict_strtoul(buf, 10, &val))
return -EINVAL;
if (val)
lightsensor_enable(sensor);
else
lightsensor_disable(sensor);
return size;
}
static ssize_t attr_light_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
//input_get_drvdata is obidient here, because input_get_drvdata = sensor_data->input_poll_dev
struct lightsensor_data *sensor = sensor_data;
int val = lightsensor_get_value(sensor);
printk("%s: sensor_data %p, sensor %p val %d\n", __func__, sensor_data, sensor, val);
return sprintf(buf, "%d\n", val);
}
static struct device_attribute attributes[] = {
__ATTR(pollrate_ms, 0666, attr_polling_rate_show, attr_polling_rate_store),
__ATTR(enable_device, 0666, attr_enable_show, attr_enable_store),
__ATTR(light_value, 0444, attr_light_value_show, NULL),
};
static int create_sysfs_interfaces(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(attributes); i++)
if (device_create_file(dev, attributes + i))
goto error;
return 0;
error:
printk("%s error, i = %d\n", __func__, i );
for ( ; i >= 0; i--)
device_remove_file(dev, attributes + i);
dev_err(dev, "%s:Unable to create interface\n", __func__);
return -1;
}
static int remove_sysfs_interfaces(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(attributes); i++)
device_remove_file(dev, attributes + i);
return 0;
}
static int __init lightsensor_init(void)
{
int ret = -1;
struct input_dev *input;
sensor_data= kmalloc(sizeof(struct lightsensor_data), GFP_KERNEL) ;
if(!sensor_data) {
printk("kmalloc\n");
goto kmalloc_error;
}
sensor_data->input_poll_dev = input_allocate_polled_device();
if(! sensor_data->input_poll_dev) {
sensor_dbg("error\n");
goto alloc_error;
}
sensor_data->input_poll_dev->private = sensor_data;
sensor_data->input_poll_dev->poll = lightsensor_input_poll_func;
sensor_data->input_poll_dev->poll_interval = INIT_POLL_INTERVAL;
input = sensor_data->input_poll_dev->input;
input->name = LIGHTSENSOR_NAME;
//input_set_drvdata(sensor_data->input_poll_dev->input, sensor_data);
set_bit(EV_FF, input->evbit);
ret = input_register_polled_device(sensor_data->input_poll_dev);
if(ret) {
printk("input_register_polled_device error\n");
return ret;
}
sensor_dbg("\n");
atomic_set(&sensor_data->enabled, 0);
sensor_data->light_value = 88;
printk("%s sensor->light_value:%d \n ", __func__, sensor_data->light_value);
if ( create_sysfs_interfaces(&input->dev) ) {
printk("create_sysfs_interface failed\n");
goto sys_error;
}
return ret;
sys_error:
input_unregister_polled_device(sensor_data->input_poll_dev);
alloc_error:
kfree(sensor_data);
kmalloc_error:
return ret;
}
static void __exit lightsensor_exit(void)
{
remove_sysfs_interfaces( &sensor_data->input_poll_dev->input->dev);
input_unregister_polled_device(sensor_data->input_poll_dev);
input_free_polled_device(sensor_data->input_poll_dev);
kfree(sensor_data);
}
module_init(lightsensor_init);
module_exit(lightsensor_exit);
我将这个设备实现成了一个input_poll_dev,这个设备的具体实现是在driver/input/input-polldev.c中的。我们这里是用了一个它的模型。因为 我们的光传感器要做的事情就是周期性的向应用程序汇报光的值。 而input-polldev正好实现了这个功能,具体到我们的光传感器主要就是要实现Poll这个函数。为了实现控制这个光传感器,另外增加了sysfs中的接口enable、 pollrate 、light_value。可以看到利用Inut子系统,我们在写驱动时候就非常的方便。
对应的在用户态的 应用程序如下:
#include
#include
#include
#include
#include
#include
#include
#define INPUTFILE "/dev/input/event0"
int gotdata=0;
void sighandler(int signo)
{
if (signo==SIGIO)
gotdata++;
return;
}
char buffer[4096];
int main(int argc, char **argv)
{
int count;
struct sigaction action;
struct input_event event;
int fd = open(INPUTFILE, O_RDONLY);
memset(&action, 0, sizeof(action));
action.sa_handler = sighandler;
action.sa_flags = 0;
sigaction(SIGIO, &action, NULL);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC);
while(1) {
/* this only returns if a signal arrives */
sleep(86400); /* one day */
if (!gotdata)
continue;
count=read(fd, &event, sizeof(event));
printf("event: %d %d %d\n", event.type, event.code, event.value);
/* buggy: if avail data is more than 4kbytes... */
gotdata=0;
}
}
刚开始看这个input子系统时,知道驱动report了event,但是不知道该去哪里读,自己研究半天。终于找到了对应的可操作的文件就是 /dev/input/inputX这些文件。其中/dev/input/是在/dev系统里面创建了一个目录,应该也是利用device_create接口创建的,但是目前自己还不是很明确在/dev下面创建directory的方法。明天再继续看。
这样利用input子系统,一个光传感器的驱动就写好了。代码量很少。
另外,在input子系统中, 事件的汇报采用的是异步的机制,也就是说当report event时,也会向应用程序发送SIGIO信号,因此只要在应用程序中将file设置成异步模式,就可以读到SIGNO信号,然后在中断程序中读取信号,也是可以的。因为event都是不可预知的,所以采用异步的方式更加科学。
记录一下,我在写这个驱动时候,犯的错误,自己调试了很长时间。
1. 首先,我在写lightsensor.c时,给input_dev注册了自己的read\write 函数。 就像注册poll函数一样。
sensor_data->input_poll_dev->read = lightsensor_input_read_func;但是这些函数总是不被调用。 后来发现是,在调用input_register_polled_device(sensor_data->input_poll_dev)时,这里会注册新的open\read\write函数,在open的时候创建delayd work queue。所以我们在用input_poll_dev时,自己只需要实现poll函数就好了,如果注册了其他函数,并替换掉了inpu_poll_dev本身的函数,就会导致这个设备不能正常的工作。
2. 关于sysfs 的错误,这个错误自己也调试了挺长时间。其实就是由于input_get_drvdata和input_set_drvdata的使用有误造成的。我在lightsensor.c 中,设置了dev的private, input_set_drvdata(sensor_data->input_poll_dev->input, sensor_data); 然后在 sysfs的 static ssize_t attr_polling_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) 函数中调用input_get_drvdata,但是得到的指针指向的不是sensor_data。 我以为是sysfs的使用错误,调试了很久。结果发现是在input_poll_dev中 也使用了这个函数对,将这个指针指向了别的地方。 所以,这就更说明了一点,在我们基于input_poll_dev实现的设备中,不能利用input_poll_dev中使用的input_dev的方法。 编程时需要非常的注意,否则就会因为小错调试很长时间。
明天继续分析INPUT子系统中几个重要的结构体。