/* FBADASD.C    (c) Copyright Roger Bowler, 1999-2009                */
/*              ESA/390 FBA Direct Access Storage Device Handler     */

// $Id: fbadasd.c 5460 2009-09-15 07:47:32Z hsg001 $

/*-------------------------------------------------------------------*/
/* This module contains device handling functions for emulated       */
/* fixed block architecture direct access storage devices.           */
/*-------------------------------------------------------------------*/

/*-------------------------------------------------------------------*/
/* Additional credits:                                               */
/*      0671 device support by Jay Maynard                           */
/*-------------------------------------------------------------------*/

// $Log$
// Revision 1.49  2009/01/23 11:53:48  bernard
// copyright notice
//
// Revision 1.48  2007/11/21 22:54:14  fish
// Use new BEGIN_DEVICE_CLASS_QUERY macro
//
// Revision 1.47  2007/06/23 00:04:09  ivan
// Update copyright notices to include current year (2007)
//
// Revision 1.46  2007/06/06 02:55:39  ivan
// Allow Byte 0 Bit 3 of FBA Locate CCW to be non-zero
//
// Revision 1.45  2007/03/15 20:57:55  gsmith
// Fix fba when the fba device is > 4G
//
// Revision 1.44  2006/12/08 09:43:20  jj
// Add CVS message log
//

#include "hstdinc.h"

#define _FBADASD_C_
#define _HDASD_DLL_

#include "hercules.h"
#include "dasdblks.h"  // (need #define DEFAULT_FBA_TYPE)
#include "sr.h"

/*-------------------------------------------------------------------*/
/* Bit definitions for Define Extent file mask                       */
/*-------------------------------------------------------------------*/
#define FBAMASK_CTL             0xC0    /* Operation control bits... */
#define FBAMASK_CTL_INHFMT      0x00    /* ...inhibit format writes  */
#define FBAMASK_CTL_INHWRT      0x40    /* ...inhibit all writes     */
#define FBAMASK_CTL_ALLWRT      0xC0    /* ...allow all writes       */
#define FBAMASK_CTL_RESV        0x80    /* ...reserved bit setting   */
#define FBAMASK_CE              0x08    /* CE field extent           */
#define FBAMASK_DIAG            0x04    /* Permit diagnostic command */
#define FBAMASK_RESV            0x33    /* Reserved bits - must be 0 */

/*-------------------------------------------------------------------*/
/* Bit definitions for Locate operation byte                         */
/*-------------------------------------------------------------------*/
#define FBAOPER_RESV            0xE0    /* Reserved bits - must be 0 */
                                        /* Note : Bit 3 seems to be  */
                                        /* The "suppress" bit but is */
                                        /* apparently ignored        */
                                        /* It is therefore valid but */
                                        /* not used.                 */
#define FBAOPER_CODE            0x0F    /* Operation code bits...    */
#define FBAOPER_WRITE           0x01    /* ...write data             */
#define FBAOPER_READREP         0x02    /* ...read replicated data   */
#define FBAOPER_FMTDEFC         0x04    /* ...format defective block */
#define FBAOPER_WRTVRFY         0x05    /* ...write data and verify  */
#define FBAOPER_READ            0x06    /* ...read data              */

#define FBA_BLKGRP_SIZE  (120 * 512)    /* Size of block group       */

/*-------------------------------------------------------------------*/
/* Initialize the device handler                                     */
/*-------------------------------------------------------------------*/
int fbadasd_init_handler ( DEVBLK *dev, int argc, char *argv[] )
{
int     rc;                             /* Return code               */
struct  stat statbuf;                   /* File information          */
int     startblk;                       /* Device origin block number*/
int     numblks;                        /* Device block count        */
BYTE    c;                              /* Character work area       */
int     cfba = 0;                       /* 1 = Compressed fba        */
int     i;                              /* Loop index                */
CKDDASD_DEVHDR  devhdr;                 /* Device header             */
CCKDDASD_DEVHDR cdevhdr;                /* Compressed device header  */
char    pathname[MAX_PATH];             /* file path in host format  */

    if (!dev->typname || !sscanf(dev->typname,"%hx",&(dev->devtype)))
        dev->devtype = DEFAULT_FBA_TYPE;

    /* The first argument is the file name */
    if (argc == 0 || strlen(argv[0]) > sizeof(dev->filename)-1)
    {
        logmsg (_("HHCDA056E File name missing or invalid\n"));
        return -1;
    }

    /* Save the file name in the device block */
    strcpy (dev->filename, argv[0]);

    /* Device is shareable */
    dev->shared = 1;

    /* Check for possible remote device */
    hostpath(pathname, dev->filename, sizeof(pathname));
    if (stat(pathname, &statbuf) < 0)
    {
        rc = shared_fba_init ( dev, argc, argv);
        if (rc < 0)
        {
            logmsg (_("HHCDA057E %4.4X:File not found or invalid\n"),
                    dev->devnum);
            return -1;
        }
        else
            return rc;
    }

    /* Open the device file */
    hostpath(pathname, dev->filename, sizeof(pathname));
    dev->fd = open (pathname, O_RDWR|O_BINARY);
    if (dev->fd < 0)
    {
        dev->fd = open (pathname, O_RDONLY|O_BINARY);
        if (dev->fd < 0)
        {
            logmsg (_("HHCDA058E File %s open error: %s\n"),
                    dev->filename, strerror(errno));
            return -1;
        }
    }

    /* Read the first block to see if it's compressed */
    rc = read (dev->fd, &devhdr, CKDDASD_DEVHDR_SIZE);
    if (rc < (int)CKDDASD_DEVHDR_SIZE)
    {
        /* Handle read error condition */
        if (rc < 0)
            logmsg (_("HHCDA059E Read error in file %s: %s\n"),
                    dev->filename, strerror(errno));
        else
            logmsg (_("HHCDA060E Unexpected end of file in %s\n"),
                    dev->filename);
        close (dev->fd);
        dev->fd = -1;
        return -1;
    }

    /* Processing for compressed fba dasd */
    if (memcmp (&devhdr.devid, "FBA_C370", 8) == 0)
    {
        cfba = 1;

        /* Read the compressed device header */
        rc = read (dev->fd, &cdevhdr, CCKDDASD_DEVHDR_SIZE);
        if (rc < (int)CKDDASD_DEVHDR_SIZE)
        {
            /* Handle read error condition */
            if (rc < 0)
                logmsg (_("HHCDA061E Read error in file %s: %s\n"),
                        dev->filename, strerror(errno));
            else
                logmsg (_("HHCDA062E Unexpected end of file in %s\n"),
                        dev->filename);
            close (dev->fd);
            dev->fd = -1;
            return -1;
        }

        /* Set block size, device origin, and device size in blocks */
        dev->fbablksiz = 512;
        dev->fbaorigin = 0;
        dev->fbanumblk = ((U32)(cdevhdr.cyls[3]) << 24)
                       | ((U32)(cdevhdr.cyls[2]) << 16)
                       | ((U32)(cdevhdr.cyls[1]) << 8)
                       |  (U32)(cdevhdr.cyls[0]);

        /* Default to synchronous I/O */
//FIXME: syncio is reported to be broken for fba
//      dev->syncio = 1;

        /* process the remaining arguments */
        for (i = 1; i < argc; i++)
        {
            if (strlen (argv[i]) > 3
             && memcmp ("sf=", argv[i], 3) == 0)
            {
                if (strlen(argv[i]+3) < 256)
                    dev->dasdsfn=strdup (argv[i]+3);
                    if (dev->dasdsfn)
                    {
                    /* Set the pointer to the suffix character */
                        dev->dasdsfx = strrchr (dev->dasdsfn, '/');
                        if (dev->dasdsfx == NULL)
                            dev->dasdsfx = dev->dasdsfn + 1;
                        dev->dasdsfx = strchr (dev->dasdsfx, '.');
                        if (dev->dasdsfx == NULL)
                            dev->dasdsfx = dev->dasdsfn + strlen(dev->dasdsfn);
                        dev->dasdsfx--;
                    }
                continue;
            }
            if (strcasecmp ("nosyncio", argv[i]) == 0
             || strcasecmp ("nosyio",   argv[i]) == 0)
            {
                dev->syncio = 0;
                continue;
            }
            if (strcasecmp ("syncio", argv[i]) == 0
             || strcasecmp ("syio",   argv[i]) == 0)
            {
                dev->syncio = 1;
                continue;
            }

            logmsg (_("HHCDA063E parameter %d is invalid: %s\n"),
                    i + 1, argv[i]);
            return -1;
        }
    }

    /* Processing for regular fba dasd */
    else
    {
        /* Determine the device size */
        rc = fstat (dev->fd, &statbuf);
        if (rc < 0)
        {
            logmsg (_("HHCDA064E File %s fstat error: %s\n"),
                    dev->filename, strerror(errno));
            close (dev->fd);
            dev->fd = -1;
            return -1;
        }
#if defined(OPTION_FBA_BLKDEVICE) && defined(BLKGETSIZE)
        if(S_ISBLK(statbuf.st_mode))
        {
            rc=ioctl(dev->fd,BLKGETSIZE,&statbuf.st_size);
            if(rc<0)
            {
                logmsg (_("HHCDA082E File %s IOCTL BLKGETSIZE error: %s\n"),
                        dev->filename, strerror(errno));
                close (dev->fd);
                dev->fd = -1;
                return -1;
            }
            dev->fbablksiz = 512;
            dev->fbaorigin = 0;
            dev->fbanumblk = statbuf.st_size;
            logmsg("REAL FBA Opened\n");
        }
        else
#endif // defined(OPTION_FBA_BLKDEVICE) && defined(BLKGETSIZE)
        {
            /* Set block size, device origin, and device size in blocks */
            dev->fbablksiz = 512;
            dev->fbaorigin = 0;
            dev->fbanumblk = statbuf.st_size / dev->fbablksiz;
        }

        /* The second argument is the device origin block number */
        if (argc >= 2)
        {
            if (sscanf(argv[1], "%u%c", &startblk, &c) != 1
             || startblk >= dev->fbanumblk)
            {
                logmsg (_("HHCDA065E Invalid device origin block number %s\n"),
                        argv[1]);
                close (dev->fd);
                dev->fd = -1;
                return -1;
            }
            dev->fbaorigin = (off_t)startblk;
            dev->fbanumblk -= startblk;
        }

        /* The third argument is the device block count */
        if (argc >= 3 && strcmp(argv[2],"*") != 0)
        {
            if (sscanf(argv[2], "%u%c", &numblks, &c) != 1
             || numblks > dev->fbanumblk)
            {
                logmsg (_("HHCDA066E Invalid device block count %s\n"),
                        argv[2]);
                close (dev->fd);
                dev->fd = -1;
                return -1;
            }
            dev->fbanumblk = numblks;
        }
    }
    dev->fbaend = (dev->fbaorigin + dev->fbanumblk) * dev->fbablksiz;

    logmsg (_("HHCDA067I %s origin=%lld blks=%d\n"),
            dev->filename, (long long)dev->fbaorigin, dev->fbanumblk);

    /* Set number of sense bytes */
    dev->numsense = 24;

    /* Locate the FBA dasd table entry */
    dev->fbatab = dasd_lookup (DASD_FBADEV, NULL, dev->devtype, dev->fbanumblk);
    if (dev->fbatab == NULL)
    {
        logmsg (_("HHCDA068E %4.4X device type %4.4X not found in dasd table\n"),
                dev->devnum, dev->devtype);
        close (dev->fd);
        dev->fd = -1;
        return -1;
    }

    /* Build the devid area */
    dev->numdevid = dasd_build_fba_devid (dev->fbatab,(BYTE *)&dev->devid);

    /* Build the devchar area */
    dev->numdevchar = dasd_build_fba_devchar (dev->fbatab,
                                 (BYTE *)&dev->devchar,dev->fbanumblk);

    /* Initialize current blkgrp and cache entry */
    dev->bufcur = dev->cache = -1;

    /* Activate I/O tracing */
//  dev->ccwtrace = 1;

    /* Call the compressed init handler if compressed fba */
    if (cfba)
        return cckddasd_init_handler (dev, argc, argv);

    return 0;
} /* end function fbadasd_init_handler */

/*-------------------------------------------------------------------*/
/* Query the device definition                                       */
/*-------------------------------------------------------------------*/
void fbadasd_query_device (DEVBLK *dev, char **class,
                int buflen, char *buffer)
{

    BEGIN_DEVICE_CLASS_QUERY( "DASD", dev, class, buflen, buffer );

    snprintf (buffer, buflen, "%s [%lld,%d]",
            dev->filename,
            (long long)dev->fbaorigin, dev->fbanumblk);

} /* end function fbadasd_query_device */

/*-------------------------------------------------------------------*/
/* Calculate length of an FBA block group                            */
/*-------------------------------------------------------------------*/
static int fba_blkgrp_len (DEVBLK *dev, int blkgrp)
{
off_t   offset;                         /* Offset of block group     */

    offset = blkgrp * FBA_BLKGRP_SIZE;
    if (dev->fbaend - offset < FBA_BLKGRP_SIZE)
        return (int)(dev->fbaend - offset);
    else
        return FBA_BLKGRP_SIZE;
}

/*-------------------------------------------------------------------*/
/* Read fba block(s)                                                 */
/*-------------------------------------------------------------------*/
static int fba_read (DEVBLK *dev, BYTE *buf, int len, BYTE *unitstat)
{
int     rc;                             /* Return code               */
int     blkgrp;                         /* Block group number        */
int     blklen;                         /* Length left in block group*/
int     off;                            /* Device buffer offset      */
int     bufoff;                         /* Buffer offset             */
int     copylen;                        /* Length left to copy       */

    /* Command reject if referencing outside the volume */
    if (dev->fbarba < dev->fbaorigin * dev->fbablksiz
     || dev->fbarba + len > dev->fbaend)
    {
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        return -1;
    }

    /* Read the block group */
    blkgrp = dev->fbarba / FBA_BLKGRP_SIZE;
    rc = (dev->hnd->read) (dev, blkgrp, unitstat);
    if (rc < 0)
        return -1;

    off = dev->fbarba % FBA_BLKGRP_SIZE;
    blklen = dev->buflen - off;

    /* Initialize target buffer offset and length to copy */
    bufoff = 0;
    copylen = len;

    /* Access multiple block groups asynchronously */
    if (dev->syncio_active && copylen > blklen)
    {
        dev->syncio_retry = 1;
        return -1;
    }

    /* Copy from the device buffer to the target buffer */
    while (copylen > 0)
    {
        int len = copylen < blklen ? copylen : blklen;

        /* Copy to the target buffer */
        if (buf) memcpy (buf + bufoff, dev->buf + off, len);

        /* Update offsets and lengths */
        bufoff += len;
        copylen -= blklen;

        /* Read the next block group if still more to copy */
        if (copylen > 0)
        {
            blkgrp++;
            off = 0;
            rc = (dev->hnd->read) (dev, blkgrp, unitstat);
            if (rc < 0)
                return -1;
            blklen = fba_blkgrp_len (dev, blkgrp);
        }
    }

    /* Update the rba */
    dev->fbarba += len;

    return len;
} /* end function fba_read */

/*-------------------------------------------------------------------*/
/* Write fba block(s)                                                */
/*-------------------------------------------------------------------*/
static int fba_write (DEVBLK *dev, BYTE *buf, int len, BYTE *unitstat)
{
int     rc;                             /* Return code               */
int     blkgrp;                         /* Block group number        */
int     blklen;                         /* Length left in block group*/
int     off;                            /* Target buffer offset      */
int     bufoff;                         /* Source buffer offset      */
int     copylen;                        /* Length left to copy       */

    /* Command reject if referencing outside the volume */
    if (dev->fbarba < dev->fbaorigin * dev->fbablksiz
     || dev->fbarba + len > dev->fbaend)
    {
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        return -1;
    }

    /* Read the block group */
    blkgrp = dev->fbarba / FBA_BLKGRP_SIZE;
    rc = (dev->hnd->read) (dev, blkgrp, unitstat);
    if (rc < 0)
        return -1;

    off = dev->fbarba % FBA_BLKGRP_SIZE;
    blklen = dev->buflen - off;

    /* Initialize source buffer offset and length to copy */
    bufoff = 0;
    copylen = len;

    /* Access multiple block groups asynchronously */
    if (dev->syncio_active && copylen > blklen)
    {
        dev->syncio_retry = 1;
        return -1;
    }

    /* Copy to the device buffer from the target buffer */
    while (copylen > 0)
    {
        int len = copylen < blklen ? copylen : blklen;

        /* Write to the block group */
        rc = (dev->hnd->write) (dev, blkgrp, off, buf + bufoff,
                                len, unitstat);
        if (rc < 0)
            return -1;

        /* Update offsets and lengths */
        bufoff += len;
        copylen -= len;
        blkgrp++;
        off = 0;
        blklen = fba_blkgrp_len (dev, blkgrp);
    }

    /* Update the rba */
    dev->fbarba += len;

    return len;
} /* end function fba_write */

/*-------------------------------------------------------------------*/
/* FBA read block group exit                                         */
/*-------------------------------------------------------------------*/
static
int fbadasd_read_blkgrp (DEVBLK *dev, int blkgrp, BYTE *unitstat)
{
int             rc;                     /* Return code               */
int             i, o;                   /* Cache indexes             */
int             len;                    /* Length to read            */
off_t           offset;                 /* File offsets              */

    /* Return if reading the same block group */
    if (blkgrp >= 0 && blkgrp == dev->bufcur)
        return 0;

    /* Write the previous block group if modified */
    if (dev->bufupd)
    {
        /* Retry if synchronous I/O */
        if (dev->syncio_active)
        {
            dev->syncio_retry = 1;
            return -1;
        }

        dev->bufupd = 0;

        /* Seek to the old block group offset */
        offset = (off_t)(((S64)dev->bufcur * FBA_BLKGRP_SIZE) + dev->bufupdlo);
        offset = lseek (dev->fd, offset, SEEK_SET);
        if (offset < 0)
        {
            /* Handle seek error condition */
            logmsg (_("HHCDA069E error writing blkgrp %d: lseek error: %s\n"),
                    dev->bufcur, strerror(errno));
            dev->sense[0] = SENSE_EC;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            cache_lock(CACHE_DEVBUF);
            cache_setflag(CACHE_DEVBUF, dev->cache, ~FBA_CACHE_ACTIVE, 0);
            cache_unlock(CACHE_DEVBUF);
            dev->bufupdlo = dev->bufupdhi = 0;
            dev->bufcur = dev->cache = -1;
            return -1;
        }

        /* Write the portion of the block group that was modified */
        rc = write (dev->fd, dev->buf + dev->bufupdlo,
                    dev->bufupdhi - dev->bufupdlo);
        if (rc < dev->bufupdhi - dev->bufupdlo)
        {
            /* Handle write error condition */
            logmsg (_("HHCDA070E error writing blkgrp %d: write error: %s\n"),
                    dev->bufcur, strerror(errno));
            dev->sense[0] = SENSE_EC;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            cache_lock(CACHE_DEVBUF);
            cache_setflag(CACHE_DEVBUF, dev->cache, ~FBA_CACHE_ACTIVE, 0);
            cache_unlock(CACHE_DEVBUF);
            dev->bufupdlo = dev->bufupdhi = 0;
            dev->bufcur = dev->cache = -1;
            return -1;
        }

        dev->bufupdlo = dev->bufupdhi = 0;
    }

    cache_lock (CACHE_DEVBUF);

    /* Make the previous cache entry inactive */
    if (dev->cache >= 0)
        cache_setflag(CACHE_DEVBUF, dev->cache, ~FBA_CACHE_ACTIVE, 0);
    dev->bufcur = dev->cache = -1;

    /* Return on special case when called by the close handler */
    if (blkgrp < 0)
    {
        cache_unlock (CACHE_DEVBUF);
        return 0;
    }

fba_read_blkgrp_retry:

    /* Search the cache */
    i = cache_lookup (CACHE_DEVBUF, FBA_CACHE_SETKEY(dev->devnum, blkgrp), &o);

    /* Cache hit */
    if (i >= 0)
    {
        cache_setflag(CACHE_DEVBUF, i, ~0, FBA_CACHE_ACTIVE);
        cache_setage(CACHE_DEVBUF, i);
        cache_unlock(CACHE_DEVBUF);

        logdevtr (dev, _("HHCDA071I read blkgrp %d cache hit, using cache[%d]\n"),
                  blkgrp, i);

        dev->cachehits++;
        dev->cache = i;
        dev->buf = cache_getbuf(CACHE_DEVBUF, dev->cache, 0);
        dev->bufcur = blkgrp;
        dev->bufoff = 0;
        dev->bufoffhi = fba_blkgrp_len (dev, blkgrp);
        dev->buflen = fba_blkgrp_len (dev, blkgrp);
        dev->bufsize = cache_getlen(CACHE_DEVBUF, dev->cache);
        return 0;
    }

    /* Retry if synchronous I/O */
    if (dev->syncio_active)
    {
        cache_unlock(CACHE_DEVBUF);
        dev->syncio_retry = 1;
        return -1;
    }

    /* Wait if no available cache entry */
    if (o < 0)
    {
        logdevtr (dev, _("HHCDA072I read blkgrp %d no available cache entry, waiting\n"),
                  blkgrp);
        dev->cachewaits++;
        cache_wait(CACHE_DEVBUF);
        goto fba_read_blkgrp_retry;
    }

    /* Cache miss */
    logdevtr (dev, _("HHCDA073I read blkgrp %d cache miss, using cache[%d]\n"),
              blkgrp, o);

    dev->cachemisses++;

    /* Make this cache entry active */
    cache_setkey (CACHE_DEVBUF, o, FBA_CACHE_SETKEY(dev->devnum, blkgrp));
    cache_setflag(CACHE_DEVBUF, o, 0, FBA_CACHE_ACTIVE|DEVBUF_TYPE_FBA);
    cache_setage (CACHE_DEVBUF, o);
    dev->buf = cache_getbuf(CACHE_DEVBUF, o, FBA_BLKGRP_SIZE);
    cache_unlock (CACHE_DEVBUF);

    /* Get offset and length */
    offset = (off_t)((S64)blkgrp * FBA_BLKGRP_SIZE);
    len = fba_blkgrp_len (dev, blkgrp);

    logdevtr (dev, _("HHCDA074I read blkgrp %d offset %" I64_FMT "d len %d\n"),
              blkgrp, (long long)offset, fba_blkgrp_len(dev, blkgrp));

    /* Seek to the block group offset */
    offset = lseek (dev->fd, offset, SEEK_SET);
    if (offset < 0)
    {
        /* Handle seek error condition */
        logmsg (_("HHCDA075E error reading blkgrp %d: lseek error: %s\n"),
                blkgrp, strerror(errno));
        dev->sense[0] = SENSE_EC;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        cache_lock(CACHE_DEVBUF);
        cache_release(CACHE_DEVBUF, o, 0);
        cache_unlock(CACHE_DEVBUF);
        return -1;
    }

    /* Read the block group */
    rc = read (dev->fd, dev->buf, len);
    if (rc < len)
    {
        /* Handle read error condition */
        logmsg (_("HHCDA076E error reading blkgrp %d: read error: %s\n"),
           blkgrp, rc < 0 ? strerror(errno) : "end of file");
        dev->sense[0] = SENSE_EC;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        cache_lock(CACHE_DEVBUF);
        cache_release(CACHE_DEVBUF, o, 0);
        cache_unlock(CACHE_DEVBUF);
        return -1;
    }

    dev->cache = o;
    dev->buf = cache_getbuf(CACHE_DEVBUF, dev->cache, 0);
    dev->bufcur = blkgrp;
    dev->bufoff = 0;
    dev->bufoffhi = fba_blkgrp_len (dev, blkgrp);
    dev->buflen = fba_blkgrp_len (dev, blkgrp);
    dev->bufsize = cache_getlen(CACHE_DEVBUF, dev->cache);

    return 0;

} /* end function fbadasd_read_blkgrp */

/*-------------------------------------------------------------------*/
/* FBA update block group exit                                       */
/*-------------------------------------------------------------------*/
static
int fbadasd_update_blkgrp (DEVBLK *dev, int blkgrp, int off,
                          BYTE *buf, int len, BYTE *unitstat)
{
int             rc;                     /* Return code               */

    /* Read the block group */
    if (blkgrp != dev->bufcur)
    {
        rc = (dev->hnd->read) (dev, blkgrp, unitstat);
        if (rc < 0)
        {
            dev->bufcur = dev->cache = -1;
            return -1;
        }
    }

    /* Copy to the device buffer */
    if (buf) memcpy (dev->buf + off, buf, len);

    /* Update high/low offsets */
    if (!dev->bufupd || off < dev->bufupdlo)
        dev->bufupdlo = off;
    if (off + len > dev-> bufupdhi)
        dev->bufupdhi = off + len;

    /* Indicate block group has been modified */
    if (!dev->bufupd)
    {
        dev->bufupd = 1;
        shared_update_notify (dev, blkgrp);
    }

    return len;
} /* end function fbadasd_update_blkgrp */

/*-------------------------------------------------------------------*/
/* Channel program end exit                                          */
/*-------------------------------------------------------------------*/
static
void fbadasd_end (DEVBLK *dev)
{
BYTE            unitstat;

    /* Forces updated buffer to be written */
    fbadasd_read_blkgrp (dev, -1, &unitstat);
}

/*-------------------------------------------------------------------*/
/* Release cache entries                                             */
/*-------------------------------------------------------------------*/
int fbadasd_purge_cache (int *answer, int ix, int i, void *data)
{
U16             devnum;                 /* Cached device number      */
int             blkgrp;                 /* Cached block group        */
DEVBLK         *dev = data;             /* -> device block           */

    UNREFERENCED(answer);
    FBA_CACHE_GETKEY(i, devnum, blkgrp);
    if (dev->devnum == devnum)
        cache_release (ix, i, CACHE_FREEBUF);
    return 0;
}

/*-------------------------------------------------------------------*/
/* Close the device                                                  */
/*-------------------------------------------------------------------*/
int fbadasd_close_device ( DEVBLK *dev )
{
BYTE            unitstat;

    /* Forces updated buffer to be written */
    fbadasd_read_blkgrp (dev, -1, &unitstat);

    /* Free the cache */
    cache_lock(CACHE_DEVBUF);
    cache_scan(CACHE_DEVBUF, fbadasd_purge_cache, dev);
    cache_unlock(CACHE_DEVBUF);

    /* Close the device file */
    close (dev->fd);
    dev->fd = -1;

    return 0;
} /* end function fbadasd_close_device */

/*-------------------------------------------------------------------*/
/* Return used blocks                                                */
/*-------------------------------------------------------------------*/
static
int fbadasd_used (DEVBLK *dev)
{
    return dev->fbanumblk;
}

/*-------------------------------------------------------------------*/
/* Execute a Channel Command Word                                    */
/*-------------------------------------------------------------------*/
void fbadasd_execute_ccw ( DEVBLK *dev, BYTE code, BYTE flags,
        BYTE chained, U16 count, BYTE prevcode, int ccwseq,
        BYTE *iobuf, BYTE *more, BYTE *unitstat, U16 *residual )
{
int     rc;                             /* Return code               */
int     num;                            /* Number of bytes to move   */
BYTE    hexzeroes[512];                 /* Bytes for zero fill       */
int     rem;                            /* Byte count for zero fill  */
int     repcnt;                         /* Replication count         */

    /* Reset extent flag at start of CCW chain */
    if (chained == 0)
        dev->fbaxtdef = 0;

    /* Process depending on CCW opcode */
    switch (code) {

    case 0x02:
    /*---------------------------------------------------------------*/
    /* READ IPL                                                      */
    /*---------------------------------------------------------------*/
        /* Must be first CCW or chained from a previous READ IPL CCW */
        if (chained && prevcode != 0x02)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Zeroize the file mask and set extent for entire device */
        dev->fbamask = 0;
        dev->fbaxblkn = 0;
        dev->fbaxfirst = 0;
        dev->fbaxlast = dev->fbanumblk - 1;

        /* Seek to start of block zero */
        dev->fbarba = dev->fbaorigin * dev->fbablksiz;

        /* Overrun if data chaining */
        if ((flags & CCW_FLAGS_CD))
        {
            dev->sense[0] = SENSE_OR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Calculate number of bytes to read and set residual count */
        num = (count < dev->fbablksiz) ? count : dev->fbablksiz;
        *residual = count - num;
        if (count < dev->fbablksiz) *more = 1;

        /* Read physical block into channel buffer */
        rc = fba_read (dev, iobuf, num, unitstat);
        if (rc < num) break;

        /* Set extent defined flag */
        dev->fbaxtdef = 1;

        /* Set normal status */
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0x03:
    /*---------------------------------------------------------------*/
    /* CONTROL NO-OPERATION                                          */
    /*---------------------------------------------------------------*/
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0x41:
    /*---------------------------------------------------------------*/
    /* WRITE                                                         */
    /*---------------------------------------------------------------*/
        /* Reject if previous command was not LOCATE */
        if (prevcode != 0x43)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Reject if locate command did not specify write operation */
        if ((dev->fbaoper & FBAOPER_CODE) != FBAOPER_WRITE
            && (dev->fbaoper & FBAOPER_CODE) != FBAOPER_WRTVRFY)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Prepare a block of zeroes for write padding */
        memset (hexzeroes, 0, sizeof(hexzeroes));

        /* Write physical blocks of data to the device */
        while (dev->fbalcnum > 0)
        {
            /* Exit if data chaining and this CCW is exhausted */
            if ((flags & CCW_FLAGS_CD) && count == 0)
                break;

            /* Overrun if data chaining within a physical block */
            if ((flags & CCW_FLAGS_CD) && count < dev->fbablksiz)
            {
                dev->sense[0] = SENSE_OR;
                *unitstat = CSW_CE | CSW_DE | CSW_UC;
                break;
            }

            /* Calculate number of bytes to write */
            num = (count < dev->fbablksiz) ? count : dev->fbablksiz;
            if (num < dev->fbablksiz) *more = 1;

            /* Write physical block from channel buffer */
            if (num > 0)
            {
                rc = fba_write (dev, iobuf, num, unitstat);
                if (rc < num) break;
            }

            /* Fill remainder of block with zeroes */
            if (num < dev->fbablksiz)
            {
                rem = dev->fbablksiz - num;
                rc = fba_write (dev, hexzeroes, rem, unitstat);
                if (rc < rem) break;
            }

            /* Prepare to write next block */
            count -= num;
            iobuf += num;
            dev->fbalcnum--;

        } /* end while */

        /* Set residual byte count */
        *residual = count;
        if (dev->fbalcnum > 0) *more = 1;

        /* Set ending status */
        *unitstat |= CSW_CE | CSW_DE;
        break;

    case 0x42:
    /*---------------------------------------------------------------*/
    /* READ                                                          */
    /*---------------------------------------------------------------*/
        /* Reject if previous command was not LOCATE or READ IPL */
        if (prevcode != 0x43 && prevcode != 0x02)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Reject if locate command did not specify read operation */
        if (prevcode != 0x02
            && (dev->fbaoper & FBAOPER_CODE) != FBAOPER_READ
            && (dev->fbaoper & FBAOPER_CODE) != FBAOPER_READREP)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Read physical blocks of data from device */
        while (dev->fbalcnum > 0 && count > 0)
        {
            /* Overrun if data chaining within a physical block */
            if ((flags & CCW_FLAGS_CD) && count < dev->fbablksiz)
            {
                dev->sense[0] = SENSE_OR;
                *unitstat = CSW_CE | CSW_DE | CSW_UC;
                break;
            }

            /* Calculate number of bytes to read */
            num = (count < dev->fbablksiz) ? count : dev->fbablksiz;
            if (num < dev->fbablksiz) *more = 1;

            /* Read physical block into channel buffer */
            rc = fba_read (dev, iobuf, num, unitstat);
            if (rc < num) break;

            /* Prepare to read next block */
            count -= num;
            iobuf += num;
            dev->fbalcnum--;

        } /* end while */

        /* Set residual byte count */
        *residual = count;
        if (dev->fbalcnum > 0) *more = 1;

        /* Set ending status */
        *unitstat |= CSW_CE | CSW_DE;

        break;

    case 0x43:
    /*---------------------------------------------------------------*/
    /* LOCATE                                                        */
    /*---------------------------------------------------------------*/
        /* Calculate residual byte count */
        num = (count < 8) ? count : 8;
        *residual = count - num;

        /* Control information length must be at least 8 bytes */
        if (count < 8)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* LOCATE must be preceded by DEFINE EXTENT or READ IPL */
        if (dev->fbaxtdef == 0)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Save and validate the operation byte */
        dev->fbaoper = iobuf[0];
        if (dev->fbaoper & FBAOPER_RESV)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Validate and process operation code */
        if ((dev->fbaoper & FBAOPER_CODE) == FBAOPER_WRITE
            || (dev->fbaoper & FBAOPER_CODE) == FBAOPER_WRTVRFY)
        {
            /* Reject command if file mask inhibits all writes */
            if ((dev->fbamask & FBAMASK_CTL) == FBAMASK_CTL_INHWRT)
            {
                dev->sense[0] = SENSE_CR;
                *unitstat = CSW_CE | CSW_DE | CSW_UC;
                break;
            }
        }
        else if ((dev->fbaoper & FBAOPER_CODE) == FBAOPER_READ
                || (dev->fbaoper & FBAOPER_CODE) == FBAOPER_READREP)
        {
        }
        else if ((dev->fbaoper & FBAOPER_CODE) == FBAOPER_FMTDEFC)
        {
            /* Reject command if file mask inhibits format writes */
            if ((dev->fbamask & FBAMASK_CTL) == FBAMASK_CTL_INHFMT)
            {
                dev->sense[0] = SENSE_CR;
                *unitstat = CSW_CE | CSW_DE | CSW_UC;
                break;
            }
        }
        else /* Operation code is invalid */
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Byte 1 contains the replication count */
        repcnt = iobuf[1];

        /* Bytes 2-3 contain the block count */
        dev->fbalcnum = fetch_hw(iobuf + 2);

        /* Bytes 4-7 contain the displacement of the first block
           relative to the start of the dataset */
        dev->fbalcblk = fetch_fw(iobuf + 4);

        /* Verify that the block count is non-zero, and that
           the starting and ending blocks fall within the extent */
        if (   dev->fbalcnum == 0
            || dev->fbalcnum >  dev->fbaxlast + 1
            || dev->fbalcblk <  dev->fbaxfirst
            || dev->fbalcblk >  dev->fbaxlast + 1 - dev->fbalcnum)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* For replicated data, verify that the replication count
           is non-zero and is a multiple of the block count */
        if ((dev->fbaoper & FBAOPER_CODE) == FBAOPER_READREP)
        {
            if (repcnt == 0 || repcnt % dev->fbalcnum != 0)
            {
                dev->sense[0] = SENSE_CR;
                *unitstat = CSW_CE | CSW_DE | CSW_UC;
                break;
            }
        }

        /* Position device to start of block */
        dev->fbarba = (dev->fbaorigin
                     + dev->fbaxblkn
                     + dev->fbalcblk - dev->fbaxfirst
                      ) * dev->fbablksiz;

        logdevtr (dev, _("HHCDA077I Positioning to %8.8" I64_FMT "X (%" I64_FMT "u)\n"),
                 (long long unsigned int)dev->fbarba, (long long unsigned int)dev->fbarba);

        /* Return normal status */
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0x63:
    /*---------------------------------------------------------------*/
    /* DEFINE EXTENT                                                 */
    /*---------------------------------------------------------------*/
        /* Calculate residual byte count */
        num = (count < 16) ? count : 16;
        *residual = count - num;

        /* Control information length must be at least 16 bytes */
        if (count < 16)
        {
            logmsg(_("HHCDA078E define extent data too short: %d bytes\n"),
                    count);
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Reject if extent previously defined in this CCW chain */
        if (dev->fbaxtdef)
        {
            logmsg(_("HHCDA079E second define extent in chain\n"));
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Save and validate the file mask */
        dev->fbamask = iobuf[0];
        if ((dev->fbamask & (FBAMASK_RESV | FBAMASK_CE))
            || (dev->fbamask & FBAMASK_CTL) == FBAMASK_CTL_RESV)
        {
            logmsg(_("HHCDA080E invalid file mask %2.2X\n"),
                    dev->fbamask);
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

//      VM/ESA sends 0x00000200 0x00000000 0x00000000 0x0001404F
//      /* Verify that bytes 1-3 are zeroes */
//      if (iobuf[1] != 0 || iobuf[2] != 0 || iobuf[3] != 0)
//      {
//          logmsg(_("fbadasd: invalid reserved bytes %2.2X %2.2X %2.2X\n"),
//                  iobuf[1], iobuf[2], iobuf[3]);
//          dev->sense[0] = SENSE_CR;
//          *unitstat = CSW_CE | CSW_DE | CSW_UC;
//          break;
//      }

        /* Bytes 4-7 contain the block number of the first block
           of the extent relative to the start of the device */
        dev->fbaxblkn = fetch_fw(iobuf + 4);

        /* Bytes 8-11 contain the block number of the first block
           of the extent relative to the start of the dataset */
        dev->fbaxfirst = fetch_fw(iobuf + 8);

        /* Bytes 12-15 contain the block number of the last block
           of the extent relative to the start of the dataset */
        dev->fbaxlast = fetch_fw(iobuf + 12);

        /* Validate the extent description by checking that the
           ending block is not less than the starting block and
           that the ending block does not exceed the device size */
        if (dev->fbaxlast < dev->fbaxfirst
         || dev->fbaxblkn > (U32)dev->fbanumblk
         || dev->fbaxlast - dev->fbaxfirst >= dev->fbanumblk - dev->fbaxblkn)
        {
            logmsg(_("HHCDA081E invalid extent: first block %d, last block %d,\n"),
                    dev->fbaxfirst, dev->fbaxlast);
            logmsg(_("         numblks %d, device size %d\n"),
                    dev->fbaxblkn, dev->fbanumblk);
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Set extent defined flag and return normal status */
        dev->fbaxtdef = 1;
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0x64:
    /*---------------------------------------------------------------*/
    /* READ DEVICE CHARACTERISTICS                                   */
    /*---------------------------------------------------------------*/
        /* Calculate residual byte count */
        num = (count < dev->numdevchar) ? count : dev->numdevchar;
        *residual = count - num;
        if (count < dev->numdevchar) *more = 1;

        /* Copy device characteristics bytes to channel buffer */
        memcpy (iobuf, dev->devchar, num);

        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0x94:
    /*---------------------------------------------------------------*/
    /* DEVICE RELEASE                                                */
    /*---------------------------------------------------------------*/
        /* Reject if extent previously defined in this CCW chain */
        if (dev->fbaxtdef)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        if (dev->hnd->release) (dev->hnd->release) (dev);

        obtain_lock (&dev->lock);
        dev->reserved = 0;
        release_lock (&dev->lock);

        /* Return sense information */
        goto sense;

    case 0xB4:
    /*---------------------------------------------------------------*/
    /* DEVICE RESERVE                                                */
    /*---------------------------------------------------------------*/
        /* Reject if extent previously defined in this CCW chain */
        if (dev->fbaxtdef)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Reserve device to the ID of the active channel program */

        obtain_lock (&dev->lock);
        dev->reserved = 1;
        release_lock (&dev->lock);

        if (dev->hnd->reserve) (dev->hnd->reserve) (dev);

        /* Return sense information */
        goto sense;

    case 0x14:
    /*---------------------------------------------------------------*/
    /* UNCONDITIONAL RESERVE                                         */
    /*---------------------------------------------------------------*/
        /* Reject if this is not the first CCW in the chain */
        if (ccwseq > 0)
        {
            dev->sense[0] = SENSE_CR;
            *unitstat = CSW_CE | CSW_DE | CSW_UC;
            break;
        }

        /* Reserve device to the ID of the active channel program */

        obtain_lock (&dev->lock);
        dev->reserved = 1;
        release_lock (&dev->lock);

        if (dev->hnd->reserve) (dev->hnd->reserve) (dev);

        /* Return sense information */
        goto sense;

    case 0x04:
    /*---------------------------------------------------------------*/
    /* SENSE                                                         */
    /*---------------------------------------------------------------*/
    sense:
        /* Calculate residual byte count */
        num = (count < dev->numsense) ? count : dev->numsense;
        *residual = count - num;
        if (count < dev->numsense) *more = 1;

        /* Copy device sense bytes to channel I/O buffer */
        memcpy (iobuf, dev->sense, num);

        /* Clear the device sense bytes */
        memset (dev->sense, 0, sizeof(dev->sense));

        /* Return unit status */
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0xE4:
    /*---------------------------------------------------------------*/
    /* SENSE ID                                                      */
    /*---------------------------------------------------------------*/
        /* Calculate residual byte count */
        num = (count < dev->numdevid) ? count : dev->numdevid;
        *residual = count - num;
        if (count < dev->numdevid) *more = 1;

        /* Copy device identifier bytes to channel I/O buffer */
        memcpy (iobuf, dev->devid, num);

        /* Return unit status */
        *unitstat = CSW_CE | CSW_DE;
        break;

    case 0xA4:
    /*---------------------------------------------------------------*/
    /* READ AND RESET BUFFERED LOG                                   */
    /*---------------------------------------------------------------*/
        /* Calculate residual byte count */
        num = (count < 24) ? count : 24;
        *residual = count - num;
        if (count < 24) *more = 1;

        /* Copy device identifier bytes to channel I/O buffer */
        memset (iobuf, 0, num);

        /* Return unit status */
        *unitstat = CSW_CE | CSW_DE;
        break;

    default:
    /*---------------------------------------------------------------*/
    /* INVALID OPERATION                                             */
    /*---------------------------------------------------------------*/
        /* Set command reject sense byte, and unit check status */
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;

    } /* end switch(code) */

} /* end function fbadasd_execute_ccw */

/*-------------------------------------------------------------------*/
/* Read Standard Block (used by Diagnose instructions)               */
/*-------------------------------------------------------------------*/
DLL_EXPORT void fbadasd_read_block 
      ( DEVBLK *dev, int blknum, int blksize, int blkfactor, 
        BYTE *iobuf, BYTE *unitstat, U16 *residual )
{
int     rc;                             /* Return code               */
int     sector;       /* First sector being read                     */

    /* Unit check if block number is invalid */
    sector = blknum * blkfactor;
    if (sector >= dev->fbanumblk)
    {
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        return;
    }

    /* Seek to start of desired block */
    dev->fbarba = ( dev->fbaorigin + sector ) * dev->fbablksiz;

    /* Read block into I/O buffer */
    rc = fba_read (dev, iobuf, blksize, unitstat);
    if (rc < blksize)
    {
       dev->sense[0] = SENSE_CR;
       *unitstat = CSW_CE | CSW_DE | CSW_UC;
       return;
    }

    /* Return unit status and residual byte count */
    *unitstat = CSW_CE | CSW_DE;
    *residual = 0;

} /* end function fbadasd_read_block */

/*-------------------------------------------------------------------*/
/* Write Standard Block (used by Diagnose instructions)              */
/*-------------------------------------------------------------------*/
DLL_EXPORT void fbadasd_write_block ( 
        DEVBLK *dev, int blknum, int blksize, int blkfactor, 
        BYTE *iobuf, BYTE *unitstat, U16 *residual )
{
int     rc;           /* Return code from write function             */
int     sector;       /* First sector being read                     */
#if 0
U64     rba;          /* Large file size offset                      */
#endif
                                           
    /* Unit check if block number is invalid */
    sector = blknum * blkfactor;
    if (sector >= dev->fbanumblk || sector < 0 )
    {
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        return;
    }

    /* Seek to start of desired block */
    dev->fbarba = (off_t)(( dev->fbaorigin + sector ) * dev->fbablksiz);

    /* Read block into I/O buffer */
    rc = fba_write (dev, iobuf, blksize, unitstat);
    if (rc < blksize)
    {
       dev->sense[0] = SENSE_CR;
       *unitstat = CSW_CE | CSW_DE | CSW_UC;
       return;
    }

    /* Return unit status and residual byte count */
    *unitstat = CSW_CE | CSW_DE;
    *residual = 0;

} /* end function fbadasd_write_block */

/* Deprecated: Will be replaced by fbadasd_read/write_block functions */
/*-------------------------------------------------------------------*/
/* Synchronous Fixed Block I/O (used by Diagnose instruction)        */
/*-------------------------------------------------------------------*/
DLL_EXPORT void fbadasd_syncblk_io ( DEVBLK *dev, BYTE type, int blknum,
        int blksize, BYTE *iobuf, BYTE *unitstat, U16 *residual )
{
int     rc;                             /* Return code               */
int     blkfactor;                      /* Number of device blocks
                                           per logical block         */

    /* Calculate the blocking factor */
    blkfactor = blksize / dev->fbablksiz;

    /* Unit check if block number is invalid */
    if (blknum * blkfactor >= dev->fbanumblk)
    {
        dev->sense[0] = SENSE_CR;
        *unitstat = CSW_CE | CSW_DE | CSW_UC;
        return;
    }

    /* Seek to start of desired block */
    dev->fbarba = dev->fbaorigin * dev->fbablksiz;

    /* Process depending on operation type */
    switch (type) {

    case 0x01:
        /* Write block from I/O buffer */
        rc = fba_write (dev, iobuf, blksize, unitstat);
        if (rc < blksize) return;
        break;

    case 0x02:
        /* Read block into I/O buffer */
        rc = fba_read (dev, iobuf, blksize, unitstat);
        if (rc < blksize) return;
        break;

    } /* end switch(type) */

    /* Return unit status and residual byte count */
    *unitstat = CSW_CE | CSW_DE;
    *residual = 0;

} /* end function fbadasd_syncblk_io */

/*-------------------------------------------------------------------*/
/* Hercules suspend/resume text unit key values                      */
/*-------------------------------------------------------------------*/
#define SR_DEV_FBA_BUFCUR       ( SR_DEV_FBA | 0x001 )
#define SR_DEV_FBA_BUFOFF       ( SR_DEV_FBA | 0x002 )
#define SR_DEV_FBA_ORIGIN       ( SR_DEV_FBA | 0x003 )
#define SR_DEV_FBA_NUMBLK       ( SR_DEV_FBA | 0x004 )
#define SR_DEV_FBA_RBA          ( SR_DEV_FBA | 0x005 )
#define SR_DEV_FBA_END          ( SR_DEV_FBA | 0x006 )
#define SR_DEV_FBA_DXBLKN       ( SR_DEV_FBA | 0x007 )
#define SR_DEV_FBA_DXFIRST      ( SR_DEV_FBA | 0x008 )
#define SR_DEV_FBA_DXLAST       ( SR_DEV_FBA | 0x009 )
#define SR_DEV_FBA_LCBLK        ( SR_DEV_FBA | 0x00a )
#define SR_DEV_FBA_LCNUM        ( SR_DEV_FBA | 0x00b )
#define SR_DEV_FBA_BLKSIZ       ( SR_DEV_FBA | 0x00c )
#define SR_DEV_FBA_XTDEF        ( SR_DEV_FBA | 0x00d )
#define SR_DEV_FBA_OPER         ( SR_DEV_FBA | 0x00e )
#define SR_DEV_FBA_MASK         ( SR_DEV_FBA | 0x00f )

/*-------------------------------------------------------------------*/
/* Hercules suspend                                                  */
/*-------------------------------------------------------------------*/
int fbadasd_hsuspend(DEVBLK *dev, void *file) {
    if (dev->bufcur >= 0)
    {
        SR_WRITE_VALUE(file, SR_DEV_FBA_BUFCUR, dev->bufcur, sizeof(dev->bufcur));
        SR_WRITE_VALUE(file, SR_DEV_FBA_BUFOFF, dev->bufoff, sizeof(dev->bufoff));
    }
    SR_WRITE_VALUE(file, SR_DEV_FBA_ORIGIN, dev->fbaorigin, sizeof(dev->fbaorigin));
    SR_WRITE_VALUE(file, SR_DEV_FBA_NUMBLK, dev->fbanumblk, sizeof(dev->fbanumblk));
    SR_WRITE_VALUE(file, SR_DEV_FBA_RBA, dev->fbarba, sizeof(dev->fbarba));
    SR_WRITE_VALUE(file, SR_DEV_FBA_END, dev->fbaend, sizeof(dev->fbaend));
    SR_WRITE_VALUE(file, SR_DEV_FBA_DXBLKN, dev->fbaxblkn, sizeof(dev->fbaxblkn));
    SR_WRITE_VALUE(file, SR_DEV_FBA_DXFIRST, dev->fbaxfirst, sizeof(dev->fbaxfirst));
    SR_WRITE_VALUE(file, SR_DEV_FBA_DXLAST, dev->fbaxlast, sizeof(dev->fbaxlast));
    SR_WRITE_VALUE(file, SR_DEV_FBA_LCBLK, dev->fbalcblk, sizeof(dev->fbalcblk));
    SR_WRITE_VALUE(file, SR_DEV_FBA_LCNUM, dev->fbalcnum, sizeof(dev->fbalcnum));
    SR_WRITE_VALUE(file, SR_DEV_FBA_BLKSIZ, dev->fbablksiz, sizeof(dev->fbablksiz));
    SR_WRITE_VALUE(file, SR_DEV_FBA_XTDEF, dev->fbaxtdef, 1);
    SR_WRITE_VALUE(file, SR_DEV_FBA_OPER, dev->fbaoper, sizeof(dev->fbaoper));
    SR_WRITE_VALUE(file, SR_DEV_FBA_MASK, dev->fbamask, sizeof(dev->fbamask));
    return 0;
}

/*-------------------------------------------------------------------*/
/* Hercules resume                                                   */
/*-------------------------------------------------------------------*/
int fbadasd_hresume(DEVBLK *dev, void *file)
{
int     rc;
size_t  key, len;
BYTE byte;

    do {
        SR_READ_HDR(file, key, len);
        switch (key) {
        case SR_DEV_FBA_BUFCUR:
            SR_READ_VALUE(file, len, &rc, sizeof(rc));
            rc = (dev->hnd->read) ? (dev->hnd->read)(dev, rc, &byte) : -1;
            if ((int)rc < 0) return -1;
            break;
        case SR_DEV_FBA_BUFOFF:
            SR_READ_VALUE(file, len, &dev->bufoff, sizeof(dev->bufoff));
            break;
        case SR_DEV_FBA_ORIGIN:
            SR_READ_VALUE(file, len, &rc, sizeof(rc));
            if ((off_t)rc != dev->fbaorigin)
            {
                logmsg(_("HHCDA901E %4.4x FBA origin mismatch: %d, expected %d,\n"),
                       rc, dev->fbaorigin);
                return -1;
            }
            break;
        case SR_DEV_FBA_NUMBLK:
            SR_READ_VALUE(file, len, &rc, sizeof(rc));
            if ((int)rc != dev->fbanumblk)
            {
                logmsg(_("HHCDA902E %4.4x FBA numblk mismatch: %d, expected %d,\n"),
                       rc, dev->fbanumblk);
                return -1;
            }
            break;
        case SR_DEV_FBA_RBA:
            SR_READ_VALUE(file, len, &dev->fbarba, sizeof(dev->fbarba));
            break;
        case SR_DEV_FBA_END:
            SR_READ_VALUE(file, len, &dev->fbaend, sizeof(dev->fbaend));
            break;
        case SR_DEV_FBA_DXBLKN:
            SR_READ_VALUE(file, len, &dev->fbaxblkn, sizeof(dev->fbaxblkn));
            break;
        case SR_DEV_FBA_DXFIRST:
            SR_READ_VALUE(file, len, &dev->fbaxfirst, sizeof(dev->fbaxfirst));
            break;
        case SR_DEV_FBA_DXLAST:
            SR_READ_VALUE(file, len, &dev->fbaxlast, sizeof(dev->fbaxlast));
            break;
        case SR_DEV_FBA_LCBLK:
            SR_READ_VALUE(file, len, &dev->fbalcblk, sizeof(dev->fbalcblk));
            break;
        case SR_DEV_FBA_LCNUM:
            SR_READ_VALUE(file, len, &dev->fbalcnum, sizeof(dev->fbalcnum));
            break;
        case SR_DEV_FBA_BLKSIZ:
            SR_READ_VALUE(file, len, &rc, sizeof(rc));
            if ((int)rc != dev->fbablksiz)
            {
                logmsg(_("HHCDA903E %4.4x FBA blksiz mismatch: %d, expected %d,\n"),
                       rc, dev->fbablksiz);
                return -1;
            }
            break;
        case SR_DEV_FBA_XTDEF:
            SR_READ_VALUE(file, len, &rc, sizeof(rc));
            dev->fbaxtdef = rc;
            break;
        case SR_DEV_FBA_OPER:
            SR_READ_VALUE(file, len, &dev->fbaoper, sizeof(dev->fbaoper));
            break;
        case SR_DEV_FBA_MASK:
            SR_READ_VALUE(file, len, &dev->fbamask, sizeof(dev->fbamask));
            break;
        default:
            SR_READ_SKIP(file, len);
            break;
        } /* switch (key) */
    } while ((key & SR_DEV_MASK) == SR_DEV_FBA);
    return 0;
}

DLL_EXPORT DEVHND fbadasd_device_hndinfo = {
        &fbadasd_init_handler,          /* Device Initialisation      */
        &fbadasd_execute_ccw,           /* Device CCW execute         */
        &fbadasd_close_device,          /* Device Close               */
        &fbadasd_query_device,          /* Device Query               */
        NULL,                           /* Device Start channel pgm   */
        &fbadasd_end,                   /* Device End channel pgm     */
        NULL,                           /* Device Resume channel pgm  */
        &fbadasd_end,                   /* Device Suspend channel pgm */
        &fbadasd_read_blkgrp,           /* Device Read                */
        &fbadasd_update_blkgrp,         /* Device Write               */
        &fbadasd_used,                  /* Device Query used          */
        NULL,                           /* Device Reserve             */
        NULL,                           /* Device Release             */
        NULL,                           /* Device Attention           */
        NULL,                           /* Immediate CCW Codes        */
        NULL,                           /* Signal Adapter Input       */
        NULL,                           /* Signal Adapter Ouput       */
        &fbadasd_hsuspend,              /* Hercules suspend           */
        &fbadasd_hresume                /* Hercules resume            */
};
