Date: Tue, 20 Sep 2005 11:40:30 -0400 From: John Baldwin <jhb@FreeBSD.org> To: freebsd-drivers@freebsd.org Cc: prichardson@lincoln.ac.uk Subject: Re: Newbie Device Driver writer... Message-ID: <200509201140.31329.jhb@FreeBSD.org> In-Reply-To: <20050919.170559.68164164.imp@bsdimp.com> References: <139F6E4BC4C585478FA84D02D90CC1CA04C5102C@aexcmb02.network.uni> <20050919.170559.68164164.imp@bsdimp.com>
next in thread | previous in thread | raw e-mail | index | archive | help
On Monday 19 September 2005 07:05 pm, M. Warner Losh wrote: > In message: <139F6E4BC4C585478FA84D02D90CC1CA04C5102C@aexcmb02.network.uni> > > "Phil Richardson" <prichardson@lincoln.ac.uk> writes: > : Im rather new to device driver writing under FreeBSD (in particular > : Im using 5.4). I have a PCI based IO card (4x32bit TTL presentation) > : and have written a simplistic driver thus far (basic handling is > : done completely through IOCTL calls - but it works). > > Sounds like an interesting device. > > : However now I have started to think about using the device in a real > : way I realise it would be nice to implement asynchronous use. Maybe > : not the right term (forgive me) - but in essence I would really like > : to utilise this device along with other devices (such as a socket or > : tty) and use the Select() call to determine whats happening to who, > : and when. > > Event driven programming is what it sounds like. > > : However - I cannot find any reference anywhere within source code > : (that I can recognise at any rate) within existing device drivers > : (say sio.c) that suggest how you link a device driver to the > : select() call itself (I was expecting to find something like > : sio_select, much as sio_read, sio_write and sio_ioctl for example - > : which shows my inexperience and definate lack of understanding). > > sio is a bad driver to look at, since it move much of its > functionality into the tty layer, obscuring these simple things. > > : Anyone care to give some advice on how the kernel select() call can > : be linked into a home-made driver? A pointer to some documentation > : (dare I say that - documentation is a bit thin on device drivers so > : ive found...) would be a help. > > You need to implment a d_poll_t function for your driver. > > : Trusting someone somewhere might have a pointer or clue out there..... > > I just started to take a look at our man pages, and realized that some > work is needed to ferret out the information. > > First, you'll need to read up on the configuration phase of your > device's life cycle. This is the traditional probe and attach > routines. You can find good infromation about these in the various > bus and device man pages (DEVICE_PROBE, DEVICE_ATTACH, > bus_alloc_resource, etc). > > In your attach routine, you'll need to make the device visible to the > filesystem. make_dev will do this. One of the arguments to make_dev > is the cdevsw for your device. cdevsw is a structure that contains > poiners to functions that get called for your device when the user > interacts with it. I didn't see a man page for, so I'll give a > quickie add-hoc one here. > > struct cdevsw { > int d_version; > u_int d_flags; > const char *d_name; > d_open_t *d_open; > d_fdopen_t *d_fdopen; > d_close_t *d_close; > d_read_t *d_read; > d_write_t *d_write; > d_ioctl_t *d_ioctl; > d_poll_t *d_poll; > d_mmap_t *d_mmap; > d_strategy_t *d_strategy; > dumper_t *d_dump; > d_kqfilter_t *d_kqfilter; > d_purge_t *d_purge; > d_spare2_t *d_spare2; > uid_t d_uid; > gid_t d_gid; > mode_t d_mode; > const char *d_kind; > > /* These fields should not be messed with by drivers */ > LIST_ENTRY(cdevsw) d_list; > LIST_HEAD(, cdev) d_devs; > int d_spare3; > struct cdevsw *d_gianttrick; > }; > > d_version should be set to D_VERSION. > > d_flags should be one or more of the following: > /* > * Flags for d_flags which the drivers can set. > */ > #define D_MEMDISK 0x00010000 /* memory type disk */ > #define D_TRACKCLOSE 0x00080000 /* track all closes */ > #define D_MMAP_ANON 0x00100000 /* special treatment in vm_mmap.c */ > #define D_PSEUDO 0x00200000 /* make_dev() can return NULL */ > #define D_NEEDGIANT 0x00400000 /* driver want Giant */ > > Usually, D_NEEDGIANT and D_TRACKCLOSE are the only flags you need. > > >From the sounds of your device, it is unlikely to need anything else. > > d_name is the name of the device. > > d_open is called when the file is opened. You'll almost certainly > want to create one of these. > > d_close is called when the last reference to the device goes away > (unless D_TRACKCLOSE is called, in which case each close causes a > call to d_close). > > d_read and d_write are called when the user calls read(2) and write(2) > respectively. > > d_ioctl is used for device control. > > d_poll is used to indicate when the device has data. > > The rest aren't relevant for this discussion. > > cdevsw is usually initialized like so: > > static struct cdevsw crd_cdevsw = { > .d_version = D_VERSION, > .d_flags = D_NEEDGIANT, > .d_open = fooopen, > .d_close = fooclose, > .d_read = fooread, > .d_write = foowrite, > .d_ioctl = fooioctl, > .d_poll = foopoll, > .d_name = "foo", > }; > > Most of the routines are straight forward, so I'll not comment on them > here (feel free to ask questions, however). > > d_poll is coded as follows: > > static int > foopoll(struct cdev *dev, int events, d_thread_t *td) > { > int revents = 0; > int s; > struct softc *sc = dev->si_drv1; > > if (events & (POLLIN | POLLRDNORM)) > revents |= events & (POLLIN | POLLRDNORM); > > if (events & (POLLOUT | POLLWRNORM)) > revents |= events & (POLLIN | POLLRDNORM); > > /* Read polling */ > if (events & POLLRDBAND) > if (### test that there's more data ###) > revents |= POLLRDBAND; > > if (revents == 0) > selrecord(td, &slt->selp); > > return (revents); > } > > then in your read routine, you'd have something like: > > static int > fooread(struct cdev *dev, struct uio *uio, int ioflag) > { > struct softc *sc = dev->si_drv1; > > > if !(### test that there's some data ###) && non-blocking > return EAGAIN; > > while (### test that there's some data ###) && room in buffer > copyout data > if (!non-blocking) > sleep for more data > else > break; > } You'll also need to call selwakeup() in your interrupt routine when data becomes available to wake threads that have blocked in select(). -- John Baldwin <jhb@FreeBSD.org> <>< http://www.FreeBSD.org/~jhb/ "Power Users Use the Power to Serve" = http://www.FreeBSD.org
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200509201140.31329.jhb>