Skip site navigation (1)Skip section navigation (2)
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>