File:  [DragonFly] / src / sys / dev / video / gsc / gsc.c
Revision 1.8: download - view: text, annotated - select for diffs
Thu May 13 23:49:22 2004 UTC (10 years, 6 months ago) by dillon
Branches: MAIN
CVS tags: HEAD
device switch 1/many: Remove d_autoq, add d_clone (where d_autoq was).

d_autoq was used to allow the device port dispatch to mix old-style synchronous
calls with new style messaging calls within a particular device.  It was never
used for that purpose.

d_clone will be more fully implemented as work continues.  We are going to
install d_port in the dev_t (struct specinfo) structure itself and d_clone
will be needed to allow devices to 'revector' the port on a minor-number
by minor-number basis, in particular allowing minor numbers to be directly
dispatched to distinct threads.  This is something we will be needing later
on.

/* gsc.c - device driver for handy scanners
 *
 * Current version supports:
 *
 * 	- Genius GS-4500
 *
 * Copyright (c) 1995 Gunther Schadow.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Gunther Schadow.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $FreeBSD: src/sys/i386/isa/gsc.c,v 1.35.2.1 2000/08/08 19:49:53 peter Exp $
 * $DragonFly: src/sys/dev/video/gsc/gsc.c,v 1.8 2004/05/13 23:49:22 dillon Exp $
 *
 */

#include "use_gsc.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/uio.h>

#include <machine/gsc.h>

#include <bus/isa/i386/isa.h>
#include <bus/isa/i386/isa_device.h>
#include "gscreg.h"

/***********************************************************************
 *
 * CONSTANTS & DEFINES
 *
 ***********************************************************************/

#define PROBE_FAIL    0
#define PROBE_SUCCESS IO_GSCSIZE
#define ATTACH_FAIL   0
#define ATTACH_SUCCESS 1
#define SUCCESS       0
#define FAIL         -1
#define INVALID       FAIL

#define DMA1_READY  0x08

#ifdef GSCDEBUG
#define lprintf(args)						\
		do {						\
			if (scu->flags & FLAG_DEBUG)		\
				printf args;			\
		} while (0)
#else
#define lprintf(args)
#endif

#define TIMEOUT (hz*15)  /* timeout while reading a buffer - default value */
#define LONG    (hz/60)  /* timesteps while reading a buffer */

/***********************************************************************
 *
 * LAYOUT OF THE MINOR NUMBER
 *
 ***********************************************************************/

#define UNIT_MASK 0xc0    /* unit gsc0 .. gsc3 */
#define UNIT(x)   (x >> 6)
#define DBUG_MASK 0x20
#define FRMT_MASK 0x18    /* output format */
#define FRMT_RAW  0x00    /* output bits as read from scanner */
#define FRMT_GRAY 0x10    /* output graymap (not implemented yet) */
#define FRMT_PBM  0x08    /* output pbm format */
#define FRMT_PGM  0x18

/***********************************************************************
 *
 * THE GEMOMETRY TABLE
 *
 ***********************************************************************/

#define GEOMTAB_SIZE 7

static const struct gsc_geom {
  int dpi;     /* dots per inch */
  int dpl;     /* dots per line */
  int g_res;   /* get resolution value (status flag) */
  int s_res;   /* set resolution value (control register) */
} geomtab[GEOMTAB_SIZE] = {
  { 100,  424, GSC_RES_100, GSC_CNT_424},
  { 200,  840, GSC_RES_200, GSC_CNT_840},
  { 300, 1264, GSC_RES_300, GSC_CNT_1264},
  { 400, 1648, GSC_RES_400, GSC_CNT_1648},
  {  -1, 1696,          -1, GSC_CNT_1696},
  {  -2, 2644,          -2, GSC_CNT_2544},
  {  -3, 3648,          -3, GSC_CNT_3648},
};

#define NEW_GEOM { INVALID, INVALID, INVALID, INVALID }

/***********************************************************************
 *
 * THE TABLE OF UNITS
 *
 ***********************************************************************/

struct _sbuf {
  size_t  size;
  size_t  poi;
  char   *base;
};

struct gsc_unit {
  int channel;            /* DMA channel */
  int data;               /* - video port */
  int stat;               /* - status port */
  int ctrl;               /* - control port */
  int clrp;               /* - clear port */
  int flags;
#define ATTACHED 0x01
#define OPEN     0x02
#define READING  0x04
#define EOF      0x08
#define FLAG_DEBUG  0x10
#define PBM_MODE 0x20
  int     geometry;       /* resolution as geomtab index */
  int     blen;           /* length of buffer in lines */
  int     btime;          /* timeout of buffer in seconds/hz */
  struct  _sbuf sbuf;
  char    ctrl_byte;      /* the byte actually written to ctrl port */
  int     height;         /* height, for pnm modes */
  size_t  bcount;         /* bytes to read, for pnm modes */
  struct  _sbuf hbuf;     /* buffer for pnm header data */
};

static struct gsc_unit unittab[NGSC];

/* I could not find a reasonable buffer size limit other than by
 * experiments. MAXPHYS is obviously too much, while DEV_BSIZE and
 * PAGE_SIZE are really too small. There must be something wrong
 * with isa_dmastart/isa_dmarangecheck HELP!!!
 */
#define MAX_BUFSIZE 0x3000
#define DEFAULT_BLEN 59

/***********************************************************************
 *
 * THE PER-DRIVER RECORD FOR ISA.C
 *
 ***********************************************************************/

static	int gscprobe (struct isa_device *isdp);
static	int gscattach(struct isa_device *isdp);

struct isa_driver gscdriver = { gscprobe, gscattach, "gsc" };

static	d_open_t	gscopen;
static	d_close_t	gscclose;
static	d_read_t	gscread;
static	d_ioctl_t	gscioctl;

#define CDEV_MAJOR 47
static struct cdevsw gsc_cdevsw = {
	/* name */	"gsc",
	/* maj */	CDEV_MAJOR,
	/* flags */	0,
	/* port */	NULL,
	/* clone */	NULL,

	/* open */	gscopen,
	/* close */	gscclose,
	/* read */	gscread,
	/* write */	nowrite,
	/* ioctl */	gscioctl,
	/* poll */	nopoll,
	/* mmap */	nommap,
	/* strategy */	nostrategy,
	/* dump */	nodump,
	/* psize */	nopsize
};


/***********************************************************************
 *
 * LOCALLY USED SUBROUTINES
 *
 ***********************************************************************/

/***********************************************************************
 *
 * lookup_geometry -- lookup a record in the geometry table by pattern
 *
 * The caller supplies a geometry record pattern, where INVALID
 * matches anything. Returns the index in the table or INVALID if
 * lookup fails.
 */

static int
lookup_geometry(struct gsc_geom geom, const struct gsc_unit *scu)
{
  struct gsc_geom tab;
  int i;

  for(i=0; i<GEOMTAB_SIZE; i++)
    {
      tab = geomtab[i];

      if ( ( ( geom.dpi   != INVALID ) && ( tab.dpi   == geom.dpi   ) ) ||
	   ( ( geom.dpl   != INVALID ) && ( tab.dpl   == geom.dpl   ) ) ||
	   ( ( geom.g_res != INVALID ) && ( tab.g_res == geom.g_res ) ) ||
	   ( ( geom.s_res != INVALID ) && ( tab.s_res == geom.s_res ) ) )
	{
	  lprintf(("gsc.lookup_geometry: "
		 "geometry lookup found: %ddpi, %ddpl\n",
		 tab.dpi, tab.dpl));
	  return i;
	}
    }

  lprintf(("gsc.lookup_geometry: "
	 "geometry lookup failed on {%d, %d, 0x%02x, 0x%02x}\n",
	 geom.dpi, geom.dpl, geom.g_res, geom.s_res));

  return INVALID;
}

/***********************************************************************
 *
 * get_geometry -- read geometry from status port
 *
 * Returns the index into geometry table or INVALID if it fails to
 * either read the status byte or lookup the record.
 */

static int
get_geometry(const struct gsc_unit *scu)
{
  struct gsc_geom geom = NEW_GEOM;

  lprintf(("gsc.get_geometry: get geometry at 0x%03x\n", scu->stat));

  if ( ( geom.g_res = inb(scu->stat) ) == FAIL )
    return INVALID;

  geom.g_res &= GSC_RES_MASK;

  return lookup_geometry(geom, scu);
}

/***********************************************************************
 *
 * buffer_allocate -- allocate/reallocate a buffer
 * Now just checks that the preallocated buffer is large enough.
 */

static int
buffer_allocate(struct gsc_unit *scu)
{
  size_t size;

  size = scu->blen * geomtab[scu->geometry].dpl / 8;

  lprintf(("gsc.buffer_allocate: need 0x%x bytes\n", size));

  if ( size > MAX_BUFSIZE )
    {
      lprintf(("gsc.buffer_allocate: 0x%x bytes are too much\n", size));
      return ENOMEM;
    }

  scu->sbuf.size = size;
  scu->sbuf.poi  = size;

  lprintf(("gsc.buffer_allocate: ok\n"));

  return SUCCESS;
}

/***********************************************************************
 *
 * buffer_read -- scan a buffer
 */

static int
buffer_read(struct gsc_unit *scu)
{
  int stb;
  int res = SUCCESS;
  int chan_bit;
  char *p;
  int sps;
  int delay;

  lprintf(("gsc.buffer_read: begin\n"));

  if (scu->ctrl_byte == INVALID)
    {
      lprintf(("gsc.buffer_read: invalid ctrl_byte\n"));
      return EIO;
    }

  sps=splbio();

  outb( scu->ctrl, scu->ctrl_byte | GSC_POWER_ON );
  outb( scu->clrp, 0 );
  stb = inb( scu->stat );

  isa_dmastart(ISADMA_READ, scu->sbuf.base, scu->sbuf.size, scu->channel);

  chan_bit = 0x01 << scu->channel;

  for(delay=0; !(inb(DMA1_READY) & 0x01 << scu->channel); delay += LONG)
    {
      if(delay >= scu->btime)
	{
	  splx(sps);
	  lprintf(("gsc.buffer_read: timeout\n"));
	  res = EWOULDBLOCK;
	  break;
	}
      res = tsleep((caddr_t)scu, PCATCH, "gscread", LONG);
      if ( ( res == 0 ) || ( res == EWOULDBLOCK ) )
	res = SUCCESS;
      else
	break;
    }
  splx(sps);
  isa_dmadone(ISADMA_READ, scu->sbuf.base, scu->sbuf.size, scu->channel);
  outb( scu->clrp, 0 );

  if(res != SUCCESS)
    {
      lprintf(("gsc.buffer_read: aborted with %d\n", res));
      return res;
    }

  lprintf(("gsc.buffer_read: invert buffer\n"));
  for(p = scu->sbuf.base + scu->sbuf.size - 1; p >= scu->sbuf.base; p--)
    *p = ~*p;

  scu->sbuf.poi = 0;
  lprintf(("gsc.buffer_read: ok\n"));
  return SUCCESS;
}

/***********************************************************************
 *
 * the main functions
 *
 ***********************************************************************/

/***********************************************************************
 *
 * gscprobe
 *
 * read status port and check for proper configuration:
 *  - if address group matches (status byte has reasonable value)
 *  - if DMA channel matches   (status byte has correct value)
 */

static int
gscprobe (struct isa_device *isdp)
{
  int unit = isdp->id_unit;
  struct gsc_unit *scu = unittab + unit;
  int stb;
  struct gsc_geom geom = NEW_GEOM;
  static int once;

  if (!once++)
	cdevsw_add(&gsc_cdevsw);

  scu->flags = FLAG_DEBUG;

  lprintf(("gsc%d.probe "
	 "on iobase 0x%03x, irq %d, drq %d, addr %p, size %d\n",
	 unit,
	 isdp->id_iobase,
	 isdp->id_irq,
	 isdp->id_drq,
	 isdp->id_maddr,
	 isdp->id_msize));

  if ( isdp->id_iobase < 0 )
    {
      lprintf(("gsc%d.probe: no iobase given\n", unit));
      return PROBE_FAIL;
    }

  stb = inb( GSC_STAT(isdp->id_iobase) );
  if (stb == FAIL)
    {
      lprintf(("gsc%d.probe: get status byte failed\n", unit));
      return PROBE_FAIL;
    }

  scu->data = GSC_DATA(isdp->id_iobase);
  scu->stat = GSC_STAT(isdp->id_iobase);
  scu->ctrl = GSC_CTRL(isdp->id_iobase);
  scu->clrp = GSC_CLRP(isdp->id_iobase);

  outb(scu->clrp,stb);
  stb = inb(scu->stat);

  switch(stb & GSC_CNF_MASK) {
  case GSC_CNF_DMA1:
    lprintf(("gsc%d.probe: DMA 1\n", unit));
    scu->channel = 1;
    break;

  case GSC_CNF_DMA3:
    lprintf(("gsc%d.probe: DMA 3\n", unit));
    scu->channel = 3;
    break;

  case GSC_CNF_IRQ3:
    lprintf(("gsc%d.probe: IRQ 3\n", unit));
    goto probe_noirq;
  case GSC_CNF_IRQ5:
    lprintf(("gsc%d.probe: IRQ 5\n", unit));
  probe_noirq:
    lprintf(("gsc%d.probe: sorry, can't use IRQ yet\n", unit));
    return PROBE_FAIL;
  default:
    lprintf(("gsc%d.probe: invalid status byte 0x%02x\n", unit, (u_char) stb));
    return PROBE_FAIL;
  }

  if (isdp->id_drq < 0)
    isdp->id_drq = scu->channel;
  if (scu->channel != isdp->id_drq)
    {
      lprintf(("gsc%d.probe: drq mismatch: config: %d; hardware: %d\n",
	      unit, isdp->id_drq, scu->channel));
      return PROBE_FAIL;
    }

  geom.g_res = stb & GSC_RES_MASK;
  scu->geometry = lookup_geometry(geom, scu);
  if (scu->geometry == INVALID)
    {
      lprintf(("gsc%d.probe: geometry lookup failed\n", unit));
      return PROBE_FAIL;
    }
  else
    {
      scu->ctrl_byte = geomtab[scu->geometry].s_res;
      outb(scu->ctrl, scu->ctrl_byte | GSC_POWER_ON);

      lprintf(("gsc%d.probe: status 0x%02x, %ddpi\n",
	     unit, stb, geomtab[scu->geometry].dpi));

      outb(scu->ctrl, scu->ctrl_byte & ~GSC_POWER_ON);
    }

  lprintf(("gsc%d.probe: ok\n", unit));

  scu->flags &= ~FLAG_DEBUG;

  return PROBE_SUCCESS;
}

/***********************************************************************
 *
 * gscattach
 *
 * finish initialization of unit structure
 * get geometry value
 */

static int
gscattach(struct isa_device *isdp)
{
  int unit = isdp->id_unit;
  struct gsc_unit *scu = unittab + unit;

  scu->flags |= FLAG_DEBUG;

  lprintf(("gsc%d.attach: "
	 "iobase 0x%03x, irq %d, drq %d, addr %p, size %d\n",
	 unit,
	 isdp->id_iobase,
	 isdp->id_irq,
	 isdp->id_drq,
	 isdp->id_maddr,
	 isdp->id_msize));

  printf("gsc%d: GeniScan GS-4500 at %ddpi\n",
	 unit, geomtab[scu->geometry].dpi);

  /*
   * Initialize buffer structure.
   * XXX this must be done early to give a good chance of getting a
   * contiguous buffer.  This wastes memory.
   */
  scu->sbuf.base = contigmalloc((unsigned long)MAX_BUFSIZE, M_DEVBUF, M_NOWAIT,
				0ul, 0xfffffful, 1ul, 0x10000ul);
  if ( scu->sbuf.base == NULL )
    {
      lprintf(("gsc%d.attach: buffer allocation failed\n", unit));
      return ATTACH_FAIL;	/* XXX attach must not fail */
    }
  scu->sbuf.size = INVALID;
  scu->sbuf.poi  = INVALID;

  scu->blen = DEFAULT_BLEN;
  scu->btime = TIMEOUT;

  scu->flags |= ATTACHED;
  lprintf(("gsc%d.attach: ok\n", unit));
  scu->flags &= ~FLAG_DEBUG;
#define GSC_UID 0
#define GSC_GID 13
  make_dev(&gsc_cdevsw, unit<<6, GSC_UID, GSC_GID, 0666, "gsc%d", unit);
  make_dev(&gsc_cdevsw, ((unit<<6) + FRMT_PBM),
     GSC_UID,  GSC_GID, 0666, "gsc%dp", unit);
  make_dev(&gsc_cdevsw, ((unit<<6) + DBUG_MASK),
     GSC_UID,  GSC_GID, 0666, "gsc%dd", unit);
  make_dev(&gsc_cdevsw, ((unit<<6) + DBUG_MASK+FRMT_PBM),
     GSC_UID,  GSC_GID, 0666, "gsc%dpd", unit);

  return ATTACH_SUCCESS;
}

/***********************************************************************
 *
 * gscopen
 *
 * set open flag
 * set modes according to minor number
 * don't switch scanner on, wait until first read ioctls go before
 */

static	int
gscopen  (dev_t dev, int flags, int fmt, struct thread *td)
{
  struct gsc_unit *scu;
  int unit;

  unit = UNIT(minor(dev)) & UNIT_MASK;
  if ( unit >= NGSC )
    {
#ifdef GSCDEBUG
      /* XXX lprintf isn't valid here since there is no scu. */
      printf("gsc%d.open: unconfigured unit number (max %d)\n", unit, NGSC);
#endif
      return ENXIO;
    }
  scu = unittab + unit;
  if ( !( scu->flags & ATTACHED ) )
    {
      lprintf(("gsc%d.open: unit was not attached successfully 0x%04x\n",
	     unit, scu->flags));
      return ENXIO;
    }

  if ( minor(dev) & DBUG_MASK )
    scu->flags |= FLAG_DEBUG;
  else
    scu->flags &= ~FLAG_DEBUG;

  switch(minor(dev) & FRMT_MASK) {
  case FRMT_PBM:
    scu->flags |= PBM_MODE;
    lprintf(("gsc%d.open: pbm mode\n", unit));
    break;
  case FRMT_RAW:
    lprintf(("gsc%d.open: raw mode\n", unit));
    scu->flags &= ~PBM_MODE;
    break;
  default:
    lprintf(("gsc%d.open: gray maps are not yet supported", unit));
    return ENXIO;
  }

  lprintf(("gsc%d.open: minor %d\n",
	 unit, minor(dev)));

  if ( scu->flags & OPEN )
    {
      lprintf(("gsc%d.open: already open", unit));
      return EBUSY;
    }

  if (isa_dma_acquire(scu->channel))
      return(EBUSY);

  scu->flags |= OPEN;

  return SUCCESS;
}

/***********************************************************************
 *
 * gscclose
 *
 * turn off scanner
 * release the buffer
 */

static	int
gscclose (dev_t dev, int flags, int fmt, struct thread *td)
{
  int unit = UNIT(minor(dev));
  struct gsc_unit *scu = unittab + unit;

  lprintf(("gsc%d.close: minor %d\n",
	 unit, minor(dev)));

  if ( unit >= NGSC || !( scu->flags & ATTACHED ) )
    {
      lprintf(("gsc%d.read: unit was not attached successfully 0x%04x\n",
	     unit, scu->flags));
      return ENXIO;
    }

  outb(scu->ctrl, scu->ctrl_byte & ~GSC_POWER_ON);

  scu->sbuf.size = INVALID;
  scu->sbuf.poi  = INVALID;

  isa_dma_release(scu->channel);

  scu->flags &= ~(FLAG_DEBUG | OPEN | READING);

  return SUCCESS;
}

/***********************************************************************
 *
 * gscread
 */

static	int
gscread  (dev_t dev, struct uio *uio, int ioflag)
{
  int unit = UNIT(minor(dev));
  struct gsc_unit *scu = unittab + unit;
  size_t nbytes;
  int res;

  lprintf(("gsc%d.read: minor %d\n", unit, minor(dev)));

  if ( unit >= NGSC || !( scu->flags & ATTACHED ) )
    {
      lprintf(("gsc%d.read: unit was not attached successfully 0x%04x\n",
	     unit, scu->flags));
      return ENXIO;
    }

  if ( !(scu->flags & READING) )
    {
      res = buffer_allocate(scu);
      if ( res == SUCCESS )
	scu->flags |= READING;
      else
	return res;

      scu->ctrl_byte = geomtab[scu->geometry].s_res;

      /* initialize for pbm mode */
      if ( scu->flags & PBM_MODE )
	{
	  char *p;
	  int width = geomtab[scu->geometry].dpl;

	  sprintf(scu->sbuf.base,"P4 %d %d\n", width, scu->height);
	  scu->bcount = scu->height * width / 8;

	  lprintf(("gsc%d.read: initializing pbm mode: `%s', bcount: 0x%x\n",
		  unit, scu->sbuf.base, scu->bcount));

	  /* move header to end of sbuf */
	  for(p=scu->sbuf.base; *p; p++);
	  while(--p >= scu->sbuf.base)
	    {
	      *(char *)(scu->sbuf.base + --scu->sbuf.poi) = *p;
	      scu->bcount++;
	    }
	}
    }

  lprintf(("gsc%d.read(before buffer_read): "
	  "size 0x%x, pointer 0x%x, bcount 0x%x, ok\n",
	  unit, scu->sbuf.size, scu->sbuf.poi, scu->bcount));

  if ( scu->sbuf.poi == scu->sbuf.size )
    if ( (res = buffer_read(scu)) != SUCCESS )
      return res;

  lprintf(("gsc%d.read(after buffer_read): "
	  "size 0x%x, pointer 0x%x, bcount 0x%x, ok\n",
	  unit, scu->sbuf.size, scu->sbuf.poi, scu->bcount));

  nbytes = MIN( uio->uio_resid, scu->sbuf.size - scu->sbuf.poi );

  if ( (scu->flags & PBM_MODE) )
    nbytes = MIN( nbytes, scu->bcount );

  lprintf(("gsc%d.read: transferring 0x%x bytes", unit, nbytes));

  res = uiomove(scu->sbuf.base + scu->sbuf.poi, nbytes, uio);
  if ( res != SUCCESS )
    {
      lprintf(("gsc%d.read: uiomove failed %d", unit, res));
      return res;
    }

  scu->sbuf.poi += nbytes;
  if ( scu->flags & PBM_MODE ) scu->bcount -= nbytes;

  lprintf(("gsc%d.read: size 0x%x, pointer 0x%x, bcount 0x%x, ok\n",
	  unit, scu->sbuf.size, scu->sbuf.poi, scu->bcount));

  return SUCCESS;
}

/***********************************************************************
 *
 * gscioctl
 *
 */

static	int
gscioctl (dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
{
  int unit = UNIT(minor(dev));
  struct gsc_unit *scu = unittab + unit;

  lprintf(("gsc%d.ioctl: minor %d\n",
	 unit, minor(dev)));

  if ( unit >= NGSC || !( scu->flags & ATTACHED ) )
    {
      lprintf(("gsc%d.ioctl: unit was not attached successfully 0x%04x\n",
	     unit, scu->flags));
      return ENXIO;
    }

  switch(cmd) {
  case GSC_SRESSW:
    lprintf(("gsc%d.ioctl:GSC_SRESSW\n", unit));
    if ( scu->flags & READING )
      {
	lprintf(("gsc%d:ioctl on already reading unit\n", unit));
	return EBUSY;
      }
    scu->geometry = get_geometry(scu);
    return SUCCESS;
  case GSC_GRES:
    *(int *)data=geomtab[scu->geometry].dpi;
    lprintf(("gsc%d.ioctl:GSC_GRES %ddpi\n", unit, *(int *)data));
    return SUCCESS;
  case GSC_GWIDTH:
    *(int *)data=geomtab[scu->geometry].dpl;
    lprintf(("gsc%d.ioctl:GSC_GWIDTH %d\n", unit, *(int *)data));
    return SUCCESS;
  case GSC_SRES:
  case GSC_SWIDTH:
    lprintf(("gsc%d.ioctl:GSC_SRES or GSC_SWIDTH %d\n",
	   unit, *(int *)data));
    { int g;
      struct gsc_geom geom = NEW_GEOM;
      if ( cmd == GSC_SRES )
	geom.dpi = *(int *)data;
      else
	geom.dpl = *(int *)data;
      if ( ( g = lookup_geometry(geom, scu) ) == INVALID )
	return EINVAL;
      scu->geometry = g;
      return SUCCESS;
    }
  case GSC_GHEIGHT:
    *(int *)data=scu->height;
    lprintf(("gsc%d.ioctl:GSC_GHEIGHT %d\n", unit, *(int *)data));
    return SUCCESS;
  case GSC_SHEIGHT:
    lprintf(("gsc%d.ioctl:GSC_SHEIGHT %d\n", unit, *(int *)data));
    if ( scu->flags & READING )
      {
	lprintf(("gsc%d:ioctl on already reading unit\n", unit));
	return EBUSY;
      }
    scu->height=*(int *)data;
    return SUCCESS;
  case GSC_GBLEN:
    *(int *)data=scu->blen;
    lprintf(("gsc%d.ioctl:GSC_GBLEN %d\n", unit, *(int *)data));
    return SUCCESS;
  case GSC_SBLEN:
    lprintf(("gsc%d.ioctl:GSC_SBLEN %d\n", unit, *(int *)data));
    if (*(int *)data * geomtab[scu->geometry].dpl / 8 > MAX_BUFSIZE)
      {
	lprintf(("gsc%d:ioctl buffer size too high\n", unit));
	return ENOMEM;
      }
    scu->blen=*(int *)data;
    return SUCCESS;
  case GSC_GBTIME:
    *(int *)data = scu->btime / hz;
    lprintf(("gsc%d.ioctl:GSC_GBTIME %d\n", unit, *(int *)data));
    return SUCCESS;
  case GSC_SBTIME:
    scu->btime = *(int *)data * hz;
    lprintf(("gsc%d.ioctl:GSC_SBTIME %d\n", unit, *(int *)data));
    return SUCCESS;
  default: return ENOTTY;
  }
}