/*
 * ch375.c - block driver for CH375 based ISA to USB mass-storage boards
 * (Linux 2.2).
 *
 * A port of the protocol used by FreddyV's DOS driver CH375 V0.22; see
 * ch375_hw.h.  The board has no IRQ, so transfers poll the chip.
 *
 *   insmod ch375.o io=0x260 speed=0
 *   mknod /dev/ch375a b 60 0; mknod /dev/ch375a1 b 60 1; ...
 *
 * One USB disk per board, up to 15 partitions (minor 1..15).
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/version.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define CH375_MAJOR     60      /* local/experimental range */

#define MAJOR_NR CH375_MAJOR
#define DEVICE_NAME "ch375"
#define DEVICE_REQUEST do_ch375_request
#define DEVICE_NR(device) (MINOR(device) >> CH375_SHIFT)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#include <linux/blk.h>

#define CH375_SHIFT     4
#define CH375_MINORS    (1 << CH375_SHIFT)

#define CH_INB(port)            inb(port)
#define CH_OUTB(val, port)      outb(val, port)
#define CH_MDELAY(ms)           mdelay(ms)
#define CH_LOG(args)            printk args
#include "ch375_hw.h"

static int io = 0x260;
static int speed = 0;
static int sectors = 0;         /* override the size reported by DISK_SIZE */
MODULE_PARM(io, "i");
MODULE_PARM(speed, "i");
MODULE_PARM(sectors, "i");
MODULE_PARM_DESC(io, "I/O base (data port; command port is io+1), default 0x260");
MODULE_PARM_DESC(speed, "delay units per port access, as the DOS % parameter");
MODULE_PARM_DESC(sectors, "disk size in 512-byte sectors, if DISK_SIZE is wrong");

static struct ch375 ch;
static int ch375_busy;
static int ch375_access;
static int ch375_valid;
static struct wait_queue *ch375_wait_open;

static struct hd_struct ch375_part[CH375_MINORS];
static int ch375_sizes[CH375_MINORS];
static int ch375_blocksizes[CH375_MINORS];

/*
 * 2.2.6 exports invalidate_buffers(dev); later 2.2 kernels (2.2.26) turned
 * it into a macro around __invalidate_buffers(dev, destroy).  Look up
 * whichever the running kernel has, so one ch375.o loads on both.
 */
static void (*ch375_inv_old)(kdev_t);
static void (*ch375_inv_new)(kdev_t, int);

static void ch375_invalidate(kdev_t dev)
{
        if (ch375_inv_new)
                ch375_inv_new(dev, 0);
        else
                ch375_inv_old(dev);
}

static struct gendisk ch375_gendisk = {
        MAJOR_NR,       /* major number */
        "ch375",        /* major name */
        CH375_SHIFT,    /* bits to shift to get real from partition */
        CH375_MINORS,   /* number of partitions per real */
        1,              /* maximum number of real */
        NULL,           /* init, called from init_module */
        ch375_part,     /* hd struct */
        ch375_sizes,    /* block sizes */
        0,              /* number */
        NULL,           /* internal */
        NULL            /* next */
};

/*
 * Count the sectors from the current buffer head on that are contiguous
 * in memory, so that one DISK_READ/DISK_WRITE covers as many as possible.
 */
static unsigned long ch375_run(struct request *req)
{
        struct buffer_head *bh = req->bh;
        unsigned long n = req->current_nr_sectors;

        if (!bh)
                return n;
        while (bh->b_reqnext &&
               bh->b_reqnext->b_data == bh->b_data + bh->b_size &&
               n + (bh->b_reqnext->b_size >> 9) <= CH_MAX_SECTORS) {
                bh = bh->b_reqnext;
                n += bh->b_size >> 9;
        }
        return n;
}

/* called with io_request_lock held and interrupts off */
static void do_ch375_request(void)
{
        unsigned long block, count, done;
        int minor, ok;

        if (ch375_busy)
                return;
        ch375_busy = 1;
        while (CURRENT) {
                INIT_REQUEST;
                minor = MINOR(CURRENT->rq_dev);
                count = ch375_run(CURRENT);
                if (DEVICE_NR(CURRENT->rq_dev) != 0 ||
                    CURRENT->sector + count > ch375_part[minor].nr_sects ||
                    (CURRENT->cmd != READ && CURRENT->cmd != WRITE)) {
                        end_request(0);
                        continue;
                }
                block = CURRENT->sector + ch375_part[minor].start_sect;

                spin_unlock_irq(&io_request_lock);
                ok = ch_xfer(&ch, CURRENT->cmd == WRITE, block, count,
                             (unsigned char *) CURRENT->buffer);
                spin_lock_irq(&io_request_lock);

                /* complete every buffer head the transfer covered */
                for (done = 0; done < count && CURRENT; ) {
                        done += CURRENT->current_nr_sectors;
                        end_request(ok);
                }
        }
        ch375_busy = 0;
}

static int ch375_open(struct inode *inode, struct file *file)
{
        if (DEVICE_NR(inode->i_rdev) != 0)
                return -ENXIO;
        while (!ch375_valid)
                sleep_on(&ch375_wait_open);
        MOD_INC_USE_COUNT;
        ch375_access++;
        return 0;
}

static int ch375_release(struct inode *inode, struct file *file)
{
        if (DEVICE_NR(inode->i_rdev) == 0) {
                sync_dev(inode->i_rdev);
                ch375_access--;
                MOD_DEC_USE_COUNT;
        }
        return 0;
}

static int ch375_reread_partitions(kdev_t dev)
{
        int minor;

        cli();
        ch375_valid = (ch375_access != 1);
        sti();
        if (ch375_valid)
                return -EBUSY;

        for (minor = CH375_MINORS - 1; minor >= 0; minor--) {
                kdev_t devp = MKDEV(MAJOR_NR, minor);
                struct super_block *sb = get_super(devp);

                sync_dev(devp);
                if (sb)
                        invalidate_inodes(sb);
                ch375_invalidate(devp);
                ch375_part[minor].start_sect = 0;
                ch375_part[minor].nr_sects = 0;
        }
        ch375_part[0].nr_sects = ch.sectors;
        resetup_one_dev(&ch375_gendisk, 0);

        ch375_valid = 1;
        wake_up(&ch375_wait_open);
        return 0;
}

static int ch375_ioctl(struct inode *inode, struct file *file,
                       unsigned int cmd, unsigned long arg)
{
        if (!inode || !inode->i_rdev || DEVICE_NR(inode->i_rdev) != 0)
                return -EINVAL;
        switch (cmd) {
        case HDIO_GETGEO:
        {
                /* there is no real geometry; use the usual LBA translation */
                struct hd_geometry g;

                if (!arg)
                        return -EINVAL;
                g.heads = 255;
                g.sectors = 63;
                g.cylinders = ch.sectors / (255 * 63);
                g.start = ch375_part[MINOR(inode->i_rdev)].start_sect;
                return copy_to_user((void *) arg, &g, sizeof g) ? -EFAULT : 0;
        }
        case BLKRASET:
                if (!capable(CAP_SYS_ADMIN))
                        return -EACCES;
                if (arg > 0xff)
                        return -EINVAL;
                read_ahead[MAJOR(inode->i_rdev)] = arg;
                return 0;
        case BLKRAGET:
                return put_user(read_ahead[MAJOR(inode->i_rdev)], (long *) arg);
        case BLKGETSIZE:
                if (!arg)
                        return -EINVAL;
                return put_user(ch375_part[MINOR(inode->i_rdev)].nr_sects, (long *) arg);
        case BLKFLSBUF:
                if (!capable(CAP_SYS_ADMIN))
                        return -EACCES;
                fsync_dev(inode->i_rdev);
                ch375_invalidate(inode->i_rdev);
                return 0;
        case BLKRRPART:
                if (!capable(CAP_SYS_ADMIN))
                        return -EACCES;
                return ch375_reread_partitions(inode->i_rdev);
        RO_IOCTLS(inode->i_rdev, arg);
        default:
                return -EINVAL;
        }
}

static struct file_operations ch375_fops = {
        NULL,                   /* lseek - default */
        block_read,             /* read - general block-dev read */
        block_write,            /* write - general block-dev write */
        NULL,                   /* readdir - bad */
        NULL,                   /* poll */
        ch375_ioctl,            /* ioctl */
        NULL,                   /* mmap */
        ch375_open,             /* open */
        NULL,                   /* flush */
        ch375_release,          /* release */
        block_fsync             /* fsync */
};

int init_module(void)
{
        int i, ret;

        ch375_inv_new = (void (*)(kdev_t, int))
                get_module_symbol(NULL, "__invalidate_buffers");
        ch375_inv_old = (void (*)(kdev_t))
                get_module_symbol(NULL, "invalidate_buffers");
        if (!ch375_inv_new && !ch375_inv_old) {
                printk(KERN_ERR "ch375: kernel exports no invalidate_buffers\n");
                return -ENOSYS;
        }
        /*
         * blk_dev[] and struct request changed size between 2.2.6 and
         * 2.2.26, so a module built for the wrong kernel would set another
         * major's request_fn.  The kernel points every entry's plug_tq.data
         * at the entry itself at boot; if ours doesn't, the layout is wrong.
         */
        if (blk_dev[MAJOR_NR].plug_tq.data != &blk_dev[MAJOR_NR]) {
                printk(KERN_ERR "ch375: built for kernel " UTS_RELEASE
                       ", block layer layout does not match the running kernel\n");
                return -EINVAL;
        }
        if (check_region(io, 2)) {
                printk(KERN_ERR "ch375: ports 0x%x-0x%x busy\n", io, io + 1);
                return -EBUSY;
        }
        ch.base = io;
        ch.speed = speed;
        ch.mode = 0;
        if (!ch_check_exist(&ch)) {
                printk(KERN_ERR "ch375: no CH375 at 0x%x\n", io);
                return -ENODEV;
        }
        ret = ch_reset_init(&ch);
        printk(KERN_INFO "ch375: chip version 0x%02x at 0x%x, bus mode %d, host mode reply 0x%02x (built for " UTS_RELEASE ")\n",
               ch.version, io, ch.mode, ret);
        if (!ch_mount(&ch)) {
                printk(KERN_ERR "ch375: no USB disk attached or it did not initialise\n");
                return -ENODEV;
        }
        printk(KERN_INFO "ch375: DISK_SIZE reply %d bytes:", ch.datalen);
        for (i = 0; i < ch.datalen && i < 8; i++)
                printk(" %02x", ch.data[i]);
        printk("\n");
        if (sectors > 0)
                ch.sectors = sectors;
        else if (ch.blksize != 512) {
                printk(KERN_ERR "ch375: block size %lu not supported, try sectors=\n", ch.blksize);
                return -ENODEV;
        }
        printk(KERN_INFO "ch375a: %lu sectors (%lu MB)\n", ch.sectors, ch.sectors >> 11);

        if (register_blkdev(MAJOR_NR, "ch375", &ch375_fops)) {
                printk(KERN_ERR "ch375: unable to get major %d\n", MAJOR_NR);
                return -EBUSY;
        }
        request_region(io, 2, "ch375");
        blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
        read_ahead[MAJOR_NR] = 8;
        for (i = 0; i < CH375_MINORS; i++)
                ch375_blocksizes[i] = 1024;
        blksize_size[MAJOR_NR] = ch375_blocksizes;

        ch375_gendisk.nr_real = 1;
        ch375_gendisk.next = gendisk_head;
        gendisk_head = &ch375_gendisk;
        ch375_part[0].nr_sects = ch.sectors;
        ch375_valid = 1;
        resetup_one_dev(&ch375_gendisk, 0);
        return 0;
}

void cleanup_module(void)
{
        struct gendisk **gdp;
        int minor;

        for (minor = 0; minor < CH375_MINORS; minor++) {
                kdev_t devp = MKDEV(MAJOR_NR, minor);

                sync_dev(devp);
                ch375_invalidate(devp);
        }
        for (gdp = &gendisk_head; *gdp; gdp = &(*gdp)->next)
                if (*gdp == &ch375_gendisk) {
                        *gdp = ch375_gendisk.next;
                        break;
                }
        unregister_blkdev(MAJOR_NR, "ch375");
        blk_dev[MAJOR_NR].request_fn = NULL;
        blksize_size[MAJOR_NR] = NULL;
        blk_size[MAJOR_NR] = NULL;
        release_region(io, 2);
        printk(KERN_INFO "ch375: unloaded (%lu timeouts, %lu retries)\n",
               ch.timeouts, ch.retries);
}

