📄 mousedrivers.tmpl
字号:
<para> We can fill in the write handler at this point as the write function for our mouse simply declines to allow writes: </para> <programlisting>static ssize_t write_mouse(struct file *file, const char *buffer, size_t count, loff_t *ppos){ return -EINVAL;} </programlisting> <para> This is pretty much self-explanatory. Whenever you write you get told it was an invalid function. </para> <para> To make the poll and read functions work we have to consider how we handle the mouse interrupt. </para> <programlisting>static struct wait_queue *mouse_wait;static spinlock_t mouse_lock = SPIN_LOCK_UNLOCKED;static void ourmouse_interrupt(int irq, void *dev_id, struct pt_regs *regs){ char delta_x; char delta_y; unsigned char new_buttons; delta_x = inb(OURMOUSE_BASE); delta_y = inb(OURMOUSE_BASE+1); new_buttons = inb(OURMOUSE_BASE+2); if(delta_x || delta_y || new_buttons != mouse_buttons) { /* Something happened */ spin_lock(&mouse_lock); mouse_event = 1; mouse_dx += delta_x; mouse_dy += delta_y; mouse_buttons = new_buttons; spin_unlock(&mouse_lock); wake_up_interruptible(&mouse_wait); }} </programlisting> <para> The interrupt handler reads the mouse status. The next thing we do is to check whether something has changed. If the mouse was smart it would only interrupt us if something had changed, but let's assume our mouse is stupid as most mice actually tend to be. </para> <para> If the mouse has changed we need to update the status variables. What we don't want is the mouse functions reading these variables to read them during a change. We add a spinlock that protects these variables while we play with them. </para> <para> If a change has occurred we also need to wake sleeping processes, so we add a wakeup call and a <structname>wait_queue</structname> to use when we wish to await a mouse event. </para> <para> Now we have the wait queue we can implement the poll function for the mouse relatively easily: </para> <programlisting>static unsigned int mouse_poll(struct file *file, poll_table *wait){ poll_wait(file, &mouse_wait, wait); if(mouse_event) return POLLIN | POLLRDNORM; return 0;} </programlisting> <para> This is fairly standard poll code. First we add the wait queue to the list of queues we want to monitor for an event. Secondly we check if an event has occurred. We only have one kind of event - the <varname>mouse_event</varname> flag tells us that something happened. We know that this something can only be mouse data. We return the flags indicating input and normal reading will succeed. </para> <para> You may be wondering what happens if the function returns saying 'no event yet'. In this case the wake up from the wait queue we added to the poll table will cause the function to be called again. Eventually we will be woken up and have an event ready. At this point the <function>poll</function> call will exit back to the user. </para> <para> After the poll completes the user will want to read the data. We now need to think about how our <function>mouse_read</function> function will work: </para> <programlisting>static ssize_t mouse_read(struct file *file, char *buffer, size_t count, loff_t *pos){ int dx, dy; unsigned char button; unsigned long flags; int n; if(count<3) return -EINVAL; /* * Wait for an event */ while(!mouse_event) { if(file->f_flags&O_NDELAY) return -EAGAIN; interruptible_sleep_on(&mouse_wait); if(signal_pending(current)) return -ERESTARTSYS; } </programlisting> <para> We start by validating that the user is reading enough data. We could handle partial reads if we wanted but it isn't terribly useful and the mouse drivers don't bother to try. </para> <para> Next we wait for an event to occur. The loop is fairly standard event waiting in Linux. Having checked that the event has not yet occurred, we then check if an event is pending and if not we need to sleep. </para> <para> A user process can set the <constant>O_NDELAY</constant> flag on a file to indicate that it wishes to be told immediately if no event is pending. We check this and give the appropriate error if so. </para> <para> Next we sleep until the mouse or a signal awakens us. A signal will awaken us as we have used <function>wakeup_interruptible</function>. This is important as it means a user can kill processes waiting for the mouse - clearly a desirable property. If we are interrupted we exit the call and the kernel will then process signals and maybe restart the call again - from the beginning. </para> <para> This code contains a classic Linux bug. All will be revealed later in this article as well as explanations for how to avoid it. </para> <programlisting> /* Grab the event */ spinlock_irqsave(&mouse_lock, flags); dx = mouse_dx; dy = mouse_dy; button = mouse_buttons; if(dx<=-127) dx=-127; if(dx>=127) dx=127; if(dy<=-127) dy=-127; if(dy>=127) dy=127; mouse_dx -= dx; mouse_dy -= dy; if(mouse_dx == 0 && mouse_dy == 0) mouse_event = 0; spin_unlock_irqrestore(&mouse_lock, flags); </programlisting> <para> This is the next stage. Having established that there is an event going, we capture it. To be sure that the event is not being updated as we capture it we also take the spinlock and thus prevent parallel updates. Note here we use <function>spinlock_irqsave</function>. We need to disable interrupts on the local processor otherwise bad things will happen. </para> <para> What will occur is that we take the spinlock. While we hold the lock an interrupt will occur. At this point our interrupt handler will try and take the spinlock. It will sit in a loop waiting for the read routine to release the lock. However because we are sitting in a loop in the interrupt handler we will never release the lock. The machine hangs and the user gets upset. </para> <para> By blocking the interrupt on this processor we ensure that the lock holder will always give the lock back without deadlocking. </para> <para> There is a little cleverness in the reporting mechanism too. We can only report a move of 127 per read. We don't however want to lose information by throwing away further movement. Instead we keep returning as much information as possible. Each time we return a report we remove the amount from the pending movement in <varname>mouse_dx</varname> and <varname>mouse_dy</varname>. Eventually when these counts hit zero we clear the <varname>mouse_event</varname> flag as there is nothing else left to report. </para> <programlisting> if(put_user(button|0x80, buffer)) return -EFAULT; if(put_user((char)dx, buffer+1)) return -EFAULT; if(put_user((char)dy, buffer+2)) return -EFAULT; for(n=3; n < count; n++) if(put_user(0x00, buffer+n)) return -EFAULT; return count;} </programlisting> <para> Finally we must put the results in the user supplied buffer. We cannot do this while holding the lock as a write to user memory may sleep. For example the user memory may be residing on disk at this instant. Thus we did our computation beforehand and now copy the data. Each <function>put_user call</function> is filling in one byte of the buffer. If it returns an error we inform the program that it passed us an invalid buffer and abort. </para> <para> Having written the data we blank the rest of the buffer that was read and report the read as being successful. </para> </chapter> <chapter id="debugging"> <title>Debugging the mouse driver</title> <para> We now have an almost perfectly usable mouse driver. If you were to actually try and use it however you would eventually find a couple of problems with it. A few programs will also not work with as it does not yet support asynchronous I/O. </para> <para> First let us look at the bugs. The most obvious one isn't really a driver bug but a failure to consider the consequences. Imagine you bumped the mouse hard by accident and sent it skittering across the desk. The mouse interrupt routine will add up all that movement and report it in steps of 127 until it has reported all of it. Clearly there is a point beyond which mouse movement isn't worth reporting. We need to add this as a limit to the interrupt handler: </para> <programlisting>static void ourmouse_interrupt(int irq, void *dev_id, struct pt_regs *regs){ char delta_x; char delta_y; unsigned char new_buttons; delta_x = inb(OURMOUSE_BASE); delta_y = inb(OURMOUSE_BASE+1); new_buttons = inb(OURMOUSE_BASE+2); if(delta_x || delta_y || new_buttons != mouse_buttons) { /* Something happened */ spin_lock(&mouse_lock); mouse_event = 1; mouse_dx += delta_x; mouse_dy += delta_y; if(mouse_dx < -4096) mouse_dx = -4096; if(mouse_dx > 4096) mouse_dx = 4096; if(mouse_dy < -4096) mouse_dy = -4096; if(mouse_dy > 4096) mouse_dy = 4096; mouse_buttons = new_buttons; spin_unlock(&mouse_lock); wake_up_interruptible(&mouse_wait); }} </programlisting> <para> By adding these checks we limit the range of accumulated movement to something sensible. </para> <para> The second bug is a bit more subtle, and that is perhaps why this is such a common mistake. Remember, I said the waiting loop for the read handler had a bug in it. Think about what happens when we execute: </para> <programlisting> while(!mouse_event) { </programlisting> <para> and an interrupt occurs at this point here. This causes a mouse movement and wakes up the queue. </para> <programlisting> interruptible_sleep_on(&mouse_wait); </programlisting> <para> Now we sleep on the queue. We missed the wake up and the application will not see an event until the next mouse event occurs. This will lead to just the odd instance when a mouse button gets delayed. The consequences to the user will probably be almost undetectable with a mouse driver. With other drivers this bug could be a lot more severe. </para> <para> There are two ways to solve this. The first is to disable interrupts during the testing and the sleep. This works because when a task sleeps it ceases to disable interrupts, and when it resumes it disables them again. Our code thus becomes: </para>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -