Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 12 Jun 2002 12:25:06 -0400 (EDT)
From:      Andrew Gallatin <gallatin@cs.duke.edu>
To:        Maksim Yevmenkin <m_evmenkin@yahoo.com>
Cc:        freebsd-current@freebsd.org
Subject:   Re: Device cloning
Message-ID:  <15623.30178.839020.170020@grasshopper.cs.duke.edu>

next in thread | raw e-mail | index | archive | help

About 1 year ago, I ported a proprietary linux driver to FreeBSD 4.x.
I needed to support linux binaries which use device cloning.  I came
up with the following hack. The Linux driver code I ported from is
under NDA, but I feel safe in posting this with the names obscured.

The just of it is that on open, I create a new vnode and struct file
which holds a pointer to a unique (per-instance) data structure.  This
allows me to track the private data associated with each open.

Its ugly, but it works for what this driver needs to do.  Perhaps
something like this might work for you.

Cheers,

Drew



/*
 * Open method, corresponds to most of XXXNicOpen.  Allocates an owner
 * id and a private data structure.
 *
 * Each time FOO is opened, a new file and vnode is allocated to it.
 */
static int 
foo_open(dev_t kdev, int oflags, int devtype, struct proc *p)
{
	int priv_id = -1;
	foo_private *priv = NULL;
	int num, error;
	int fd;
	struct file *fp;
	struct vnode *vn = NULL, *vd = NULL;

	if (p->p_dupfd >= 0)
	  return ENODEV;

	num = 0;	/* xxx one NIC only */

	error = foo_open_connection(num, (void **)&priv, &priv_id,
				oflags, NULL);
	if (error)
		return error;

	priv->p = p;	/* process */

	if ((error = falloc(p, &fp, &fd)) != 0)
		return error; 	/* XXX leaks priv */

	vd = SLIST_FIRST(&kdev->si_hlist);

	/* 
	 * Conjure up our own vnode out of thin air.  We need the
	 * vnode so that we can stash a pointer to the per-connection
	 * priv struct for use in open/close/ioctl and mmap.  This is
	 * tricky, because we need make it look enough like the device
	 * vnode so that VOP_GETATTR() works on the slave vnode in mmap()
	 */

	if ((error = getnewvnode(VT_NON, (struct mount *)0, 
		 vd->v_op, &vn)))
		return error;  /* XXX leaks fp & priv */

	vn->v_type = VCHR;
	
	/* XXXX really should clone v_vdata & not copy pointer */
	vn->v_data = vd->v_data;	/* for VTOI in ufs_getattr() */
	insmntque(vn, vd->v_mount);	/* for VFSTOUFS in ufs_getattr() */
	/* allocate our own rdev & save the uniq priv */
	vn->v_rdev = malloc(sizeof(struct specinfo), M_DEVBUF, M_WAITOK);
	bcopy(vd->v_rdev, vn->v_rdev, sizeof(struct specinfo));
	vn->v_rdev->si_drv2 = (void *)priv;

	fp->f_data = (caddr_t)vn;
	fp->f_flag = FREAD|FWRITE;
	fp->f_ops = &foo_fileops;
	fp->f_type = DTYPE_VNODE;	/* so that we can mmap */

	/*
	 * Save the dup fd in the proc structure then return the
	 * special error code (ENXIO) which causes magic things to
	 * happen in vn_open.  The whole concept is, well, hmmm.
	 */
        p->p_dupfd = fd;
        return ENXIO;
}


/*
 * File operations on FOO device instances
 *
 * Each FOO instance has a separate file, vnode, devnode, and specinfo
 * structures associated with it (see foo_open).
 * Private device data are stored with the specinfo (si_drv2)
 */

#include <sys/file.h>
#include <sys/stat.h>

int     foo_fileread __P((struct file *fp, struct uio *uio, struct ucred *cred,
            int flags, struct proc *p));
int     foo_filewrite __P((struct file *fp, struct uio *uio, struct ucred *cred,
            int flags, struct proc *p));
int     foo_fileclose __P((struct file *fp, struct proc *p));
int     foo_fileioctl __P((struct file *fp, u_long cmd, caddr_t data,
            struct proc *p));
int     foo_filepoll __P((struct file *fp, int events, struct ucred *cred,
            struct proc *p));
int     foo_filestat __P((struct file *fp, struct stat *ub, struct proc *p));

int
foo_fileread(fp, uio, cred, flags, p)
        struct file *fp;
        struct uio *uio;
        struct ucred *cred;
        struct proc *p;
        int flags;
{
	return 0;
}

int
foo_filewrite(fp, uio, cred, flags, p)
        struct file *fp;
        struct uio *uio;
        struct ucred *cred;
        struct proc *p;
        int flags;
{
	return 0;
}

int
foo_fileioctl(fp, cmd, data, p)
        struct file *fp;
        u_long cmd;
        register caddr_t data;
        struct proc *p;
{
	struct vnode *vn;
	struct specinfo *si;
	foo_private *priv;
	struct foo_softc *sc;
	int val = 0;

	vn = (struct vnode *)fp->f_data;
	si = vn->v_rdev;
	priv = si->si_drv2;
	sc = priv->sc;

	switch (cmd) {
<...>
	default:
		return -EINVAL;
	}

	return val;
}

int
foo_filepoll(fp, events, cred, p)
        struct file *fp;
        int events;
        struct ucred *cred;
        struct proc *p;
{
	return EOPNOTSUPP;
}

int
foo_filestat(fp, ub, p)
        struct file *fp;
        struct stat *ub;
        struct proc *p;
{
	return EOPNOTSUPP;
}

void insmntque __P((struct vnode *vp, struct mount *mp));

/*
 * It is essential that foo_do_close_connection is called each
 * time a process calls close on the open device fd.
 *
 * Normally, if a device is opened more than once, the higher level
 * close will just decrement the vnode reference count and will
 * only call the device close if the reference count is zero
 */
int
foo_fileclose(fp, p)
        struct file *fp;
        struct proc *p;
{
	struct vnode *vn;
	struct specinfo *si;
	foo_private *priv;
	struct foo_softc *sc;
	struct specinfo *rdev;

	vn = (struct vnode *)fp->f_data;
	si = vn->v_rdev;
	priv = si->si_drv2;
	sc = priv->sc;
	foo_do_close_connection(priv, 0);

	/* remove from mountpt queue */
	insmntque(vn, NULL);
	/* replace the vnode ops so that ufs doesn't try to reclaim
	   anything */
	vn->v_op = spec_vnodeop_p;
	/* vn->v_data = NULL;*/ /* XXX do we want to do this? */
	rdev = vn->v_rdev;
	vn->v_type = VNON; /* don't want to freedev() in vgonel()*/
	vgone(vn);
	/* free our private rdev */
	free(rdev, M_DEVBUF);

	return 0;
}

static struct fileops foo_fileops = {
	foo_fileread, foo_filewrite, foo_fileioctl, foo_filepoll,
	0/*kqfilter*/, foo_filestat, foo_fileclose
};


static int
foo_mmap(dev_t kdev, vm_offset_t offset, int prot)
{
        foo_private *priv;
        struct foo_softc *sc;
        volatile vm_offset_t paddr;

        priv = kdev->si_drv2;
        sc = priv->sc;

        if ((offset < 0) || (offset > 16*1024 + 4096))
                return -1;      /* probably a user error */

        if (offset < 16*1024)   /* doorbells, 16K */
                paddr = rman_get_start(sc->bus_addrs[2]) +
                                16*1024*priv->owner_id + offset;
        else    /* flags, one 4K page */
                paddr = vtophys((vm_offset_t)priv->vi_flagmem);

        return atop(paddr);
}


#include "linux_bar_ioctl.h"
#include <sys/file.h>
#include <sys/sysproto.h>
#include <machine/../linux/linux.h>
#include <machine/../linux/linux_proto.h>

#if (__FreeBSD_version >= 430000)
#include <compat/linux/linux_ioctl.h>
MODULE_DEPEND(bar, linux, 1, 1, 1);
#else
#include <machine/../linux/linux_ioctl.h>
#endif

#define BAR_LINUX_IOCTL_MIN  0x4700
#define BAR_LINUX_IOCTL_MAX  0x47ff

static linux_ioctl_function_t bar_linux_ioctl;
static struct linux_ioctl_handler bar_handler = {bar_linux_ioctl, BAR_LINUX_IOCTL_MIN, BAR_LINUX_IOCTL_MAX};

SYSINIT  (bar_register,   SI_SUB_KLD, SI_ORDER_MIDDLE, linux_ioctl_register_handler, &bar_handler);
SYSUNINIT(bar_unregister, SI_SUB_KLD, SI_ORDER_MIDDLE, linux_ioctl_unregister_handler, &bar_handler);


static int
bar_linux_ioctl(struct proc *p, struct linux_ioctl_args *args)
{
    switch (args->cmd) {
    case LINUX_FOO_IOC_TESTINT:
	    args->cmd = FOO_IOC_TESTINT;
	    break;
<...>

    }


    /*
     * Pass the ioctl off to our standard handler.
     */
    return(ioctl(p, (struct ioctl_args *)args));
}


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?15623.30178.839020.170020>