From: Adrian M. <ad...@mc...> - 2002-02-23 01:00:30
|
I was asked to contribute a HOWTO type document on this... here is my first effort: TASKLETS IN THE PURU PURU DRIVER Notes for developers Copyright, Adrian McMenamin, 2002 ad...@mc... The author's moral rights have been asserted. 0 THE HARDWARE 0.1 PURU PURU Puru Puru is Sega's name for their rumble pack. This pack can be attached to a Dreamcast controller and when activated will cause the controller to shake (rumble). 0.2 MAPLE Maple is Sega's name for their USB-like bus. Devices are chained together and connected to the DC through the four connection ports. Keyboards, mouses, controllers, rumble packs and microphones are all Maple devices. For more details on this please see: http://mc.pp.se/dc/maplebus.html 1 THE DRIVER 1.1 FF Interface There is a Linux 'force feedback' interface. The rumble pack is a force feedback device. The interface is currently (February 2002) in rapid transition and it's best to check the latest kernel documentation at: linux/Documentation/input/ff.txt 1.1.1 Storing an effect The user application can store a standard effect in the driver memory space (eg a 2 second rumble for an explosion). Once stored such an effect is (in theory) available to all running applications on the DC. Code that does this: static int puru_ioctl(struct inode *inode, struct file *filip, unsigned int cmd, unsigned long arg) { ..... case EVIOCSFF: ..... ff_upload = kmalloc(sizeof(struct ff_effect), GFP_KERNEL); if (ff_upload == NULL) return -ENOMEM; copy_from_user(ff_upload, (void *) arg, sizeof(struct ff_effect)); /* Now get the length */ ff_time = ff_upload->replay; new_effect = kmalloc(sizeof(struct dc_puru_effect), GFP_KERNEL); if (new_effect == NULL) { kfree(ff_upload); return -ENOMEM; } new_effect->length_of_time = ff_time.length; new_effect->sid = effect_number++; list_add_tail(&new_effect->list, &effects_list); /* report the effect number back to the caller */ ff_upload->id = new_effect->sid; copy_to_user(arg, ff_upload, sizeof(struct ff_effect)); kfree(ff_upload); return 0; 1.1.2 Playing an effect The user application plays an effect by opening the relevant device file (eg /dev/input/event3) and 'writing' an input event to the file... static ssize_t puru_play(struct file *file, const char *buffer, size_t count, loff_t * pos) { ........ struct input_event *play = kmalloc(sizeof(struct input_event), GFP_KERNEL); ........ copy_from_user(play, buffer, sizeof(struct input_event)); ........ u16 find_id = play->code; struct dc_puru_effect *entry; for (ptr = effects_list.next; ptr != &effects_list; ptr = ptr->next) { entry = list_entry(ptr, struct dc_puru_effect, list); if (entry->sid == find_id) { play_puru(puru); /* Set up the timing measurements */ atomic_set(&puru->active, 1); puru->timeleft = entry->length_of_time; kfree(play); return 0; } } 1,2 Timing the effect The driver needs to time the length of the rumble without blocking - ie the DC needs to get on with other things and not just wait for the clock to run down the required length of time. It does this by hooking the vblank interrupt - called 60 times a second to enable a display update. 1.2.1 Hooking the interrupt This is actually done deep down in the maple driver mecahnism itself. But the Puru Puru driver registers that it wishes to be called by the maple interrupt handler - along with the other maple devices also hooked up. 1.2.2 The interrupt handler This is this code... void dc_purupuru_wake(struct maple_driver_data *data) { if (last_jiffies == -1) { last_jiffies = jiffies; return; } tasklet_schedule(&switchoff_tasklet); } The first bit only operates once in the driver's history and initialises a variable used to determine elapsed time. The second bit schedules a tasklet to run. 2. TASKLETS 2.1 URL to check http://www.xml.com/ldd/chapter/book/ This will tell you far more than I can! 2.2 What's a tasklet then? As the book says... Tasklets and Bottom-Half Processing "One of the main problems with interrupt handling is how to perform longish tasks within a handler. Often a substantial amount of work must be done in response to a device interrupt, but interrupt handlers need to finish up quickly and not keep interrupts blocked for long. These two needs (work and speed) conflict with each other, leaving the driver writer in a bit of a bind. "Linux (along with many other systems) resolves this problem by splitting the interrupt handler into two halves. The so-called top half is the routine that actually responds to the interrupt -- the one you register with request_irq. The bottom half is a routine that is scheduled by the top half to be executed later, at a safer time. ..... "Tasklets were introduced late in the 2.3 development series; they are now the preferred way to do bottom-half processing," (This and subsequent excerpts from "Linux Device Drivers" by Alassandro Rubini and Jonathan Corbet, published by O'Reilly) 2.3 Declaring a tasklet This is quite simple ... again, from the book: "Software support for tasklets is part of <linux/interrupt.h>, and the tasklet itself must be declared with one of the following: "DECLARE_TASKLET(name, function, data); "Declares a tasklet with the given name; when the tasklet is to be executed (as described later), the given function is called with the (unsigned long) data value. "DECLARE_TASKLET_DISABLED(name, function, data); "Declares a tasklet as before, but its initial state is "disabled,'' meaning that it can be scheduled but will not be executed until enabled at some future time. ..... "When your driver wants to schedule a tasklet to run, it calls tasklet_schedule: "tasklet_schedule(&jiq_tasklet);" 3. TASKLETS IN PURU PURU 3.1 Declaration DECLARE_TASKLET(switchoff_tasklet, switchoff_puru, (unsigned long) 0); 3.2 Tasklet void switchoff_puru(unsigned long x) { int gap; if (time_before(jiffies, last_jiffies)) { gap = ((jiffies + (0xffffffff - last_jiffies)) * 1000) / HZ; } else gap = ((jiffies - last_jiffies) * 1000) / HZ; last_jiffies = jiffies; /* which Puru Puru packs are active? */ struct dc_purupuru *select_me; struct list_head *ptr; for (ptr = devices_list.next; ptr != &devices_list; ptr = ptr->next) { select_me = list_entry(ptr, struct dc_purupuru, list); if (atomic_read(&select_me->active) == 1) { select_me->timeleft -= gap; if (select_me->timeleft <= 0) { atomic_set(&select_me->active, 0); halt_puru(select_me); } } } return; } This is scheduled by the vblank routine shown above...(this counts down the milliseconds left before the rumble pack effect has used it up it's allotted time). 3.3 Enabling and disabling the tasklet The tasklet should not be scheduled when no puru puru device is installed - so this code disbales the tasklet as soon as the driver is initialised: static int __init dc_purupuru_init(void) { maple_register_driver(&dc_purupuru_driver); printk("Puru Puru Pack driver for Dreamcast Linux version 0.1\n"); tasklet_disable(&switchoff_tasklet); return 0; } The tasklet is then enabled once a puru pack has been plugged in: static struct input_handle *purupuru_connect(struct input_handler *handler, struct input_dev *dev) { struct input_handle *handle; /* Is this a Puru Puru device? */ if (dev->open != dc_puru_open) return NULL; if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL))) return NULL; memset(handle, 0, sizeof(struct input_handle)); handle->dev = dev; handle->handler = handler; input_open_device(handle); printk(KERN_INFO "purupuru.c: adding Puru Puru Pack input%d\n", dev->number); tasklet_enable(&switchoff_tasklet); handler->minor = PURU_MINOR_BASE + dev->number; return handle; } ============================================================= |