Newbie Device Driver writer...
M. Warner Losh
imp at bsdimp.com
Mon Sep 19 16:06:33 PDT 2005
In message: <139F6E4BC4C585478FA84D02D90CC1CA04C5102C at aexcmb02.network.uni>
"Phil Richardson" <prichardson at 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;
}
Warner
More information about the freebsd-drivers
mailing list