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