root/arch/m68k/emu/nfblock.c
/*
 * ARAnyM block device driver
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/slab.h>

#include <asm/natfeat.h>

static long nfhd_id;

enum {
        /* emulation entry points */
        NFHD_READ_WRITE = 10,
        NFHD_GET_CAPACITY = 14,

        /* skip ACSI devices */
        NFHD_DEV_OFFSET = 8,
};

static inline s32 nfhd_read_write(u32 major, u32 minor, u32 rwflag, u32 recno,
                                  u32 count, u32 buf)
{
        return nf_call(nfhd_id + NFHD_READ_WRITE, major, minor, rwflag, recno,
                       count, buf);
}

static inline s32 nfhd_get_capacity(u32 major, u32 minor, u32 *blocks,
                                    u32 *blocksize)
{
        return nf_call(nfhd_id + NFHD_GET_CAPACITY, major, minor,
                       virt_to_phys(blocks), virt_to_phys(blocksize));
}

static LIST_HEAD(nfhd_list);

static int major_num;
module_param(major_num, int, 0);

struct nfhd_device {
        struct list_head list;
        int id;
        u32 blocks, bsize;
        int bshift;
        struct gendisk *disk;
};

static void nfhd_submit_bio(struct bio *bio)
{
        struct nfhd_device *dev = bio->bi_bdev->bd_disk->private_data;
        struct bio_vec bvec;
        struct bvec_iter iter;
        int dir, len, shift;
        sector_t sec = bio->bi_iter.bi_sector;

        dir = bio_data_dir(bio);
        shift = dev->bshift;
        bio_for_each_segment(bvec, bio, iter) {
                len = bvec.bv_len;
                len >>= 9;
                nfhd_read_write(dev->id, 0, dir, sec >> shift, len >> shift,
                                bvec_phys(&bvec));
                sec += len;
        }
        bio_endio(bio);
}

static int nfhd_getgeo(struct gendisk *disk, struct hd_geometry *geo)
{
        struct nfhd_device *dev = disk->private_data;

        geo->cylinders = dev->blocks >> (6 - dev->bshift);
        geo->heads = 4;
        geo->sectors = 16;

        return 0;
}

static const struct block_device_operations nfhd_ops = {
        .owner  = THIS_MODULE,
        .submit_bio = nfhd_submit_bio,
        .getgeo = nfhd_getgeo,
};

static int __init nfhd_init_one(int id, u32 blocks, u32 bsize)
{
        struct queue_limits lim = {
                .logical_block_size     = bsize,
                .features               = BLK_FEAT_ROTATIONAL,
        };
        struct nfhd_device *dev;
        int dev_id = id - NFHD_DEV_OFFSET;
        int err = -ENOMEM;

        pr_info("nfhd%u: found device with %u blocks (%u bytes)\n", dev_id,
                blocks, bsize);

        if (bsize < 512 || (bsize & (bsize - 1))) {
                pr_warn("nfhd%u: invalid block size\n", dev_id);
                return -EINVAL;
        }

        dev = kmalloc_obj(struct nfhd_device);
        if (!dev)
                goto out;

        dev->id = id;
        dev->blocks = blocks;
        dev->bsize = bsize;
        dev->bshift = ffs(bsize) - 10;

        dev->disk = blk_alloc_disk(&lim, NUMA_NO_NODE);
        if (IS_ERR(dev->disk)) {
                err = PTR_ERR(dev->disk);
                goto free_dev;
        }

        dev->disk->major = major_num;
        dev->disk->first_minor = dev_id * 16;
        dev->disk->minors = 16;
        dev->disk->fops = &nfhd_ops;
        dev->disk->private_data = dev;
        sprintf(dev->disk->disk_name, "nfhd%u", dev_id);
        set_capacity(dev->disk, (sector_t)blocks * (bsize / 512));
        err = add_disk(dev->disk);
        if (err)
                goto out_cleanup_disk;

        list_add_tail(&dev->list, &nfhd_list);

        return 0;

out_cleanup_disk:
        put_disk(dev->disk);
free_dev:
        kfree(dev);
out:
        return err;
}

static int __init nfhd_init(void)
{
        u32 blocks, bsize;
        int ret;
        int i;

        nfhd_id = nf_get_id("XHDI");
        if (!nfhd_id)
                return -ENODEV;

        ret = register_blkdev(major_num, "nfhd");
        if (ret < 0) {
                pr_warn("nfhd: unable to get major number\n");
                return ret;
        }

        if (!major_num)
                major_num = ret;

        for (i = NFHD_DEV_OFFSET; i < 24; i++) {
                if (nfhd_get_capacity(i, 0, &blocks, &bsize))
                        continue;
                nfhd_init_one(i, blocks, bsize);
        }

        return 0;
}

static void __exit nfhd_exit(void)
{
        struct nfhd_device *dev, *next;

        list_for_each_entry_safe(dev, next, &nfhd_list, list) {
                list_del(&dev->list);
                del_gendisk(dev->disk);
                put_disk(dev->disk);
                kfree(dev);
        }
        unregister_blkdev(major_num, "nfhd");
}

module_init(nfhd_init);
module_exit(nfhd_exit);

MODULE_DESCRIPTION("Atari NatFeat block device driver");
MODULE_LICENSE("GPL");