18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * ARAnyM block device driver
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
58c2ecf20Sopenharmony_ci * License.  See the file COPYING in the main directory of this archive
68c2ecf20Sopenharmony_ci * for more details.
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/kernel.h>
148c2ecf20Sopenharmony_ci#include <linux/errno.h>
158c2ecf20Sopenharmony_ci#include <linux/types.h>
168c2ecf20Sopenharmony_ci#include <linux/genhd.h>
178c2ecf20Sopenharmony_ci#include <linux/blkdev.h>
188c2ecf20Sopenharmony_ci#include <linux/hdreg.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <asm/natfeat.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic long nfhd_id;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cienum {
268c2ecf20Sopenharmony_ci	/* emulation entry points */
278c2ecf20Sopenharmony_ci	NFHD_READ_WRITE = 10,
288c2ecf20Sopenharmony_ci	NFHD_GET_CAPACITY = 14,
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	/* skip ACSI devices */
318c2ecf20Sopenharmony_ci	NFHD_DEV_OFFSET = 8,
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistatic inline s32 nfhd_read_write(u32 major, u32 minor, u32 rwflag, u32 recno,
358c2ecf20Sopenharmony_ci				  u32 count, u32 buf)
368c2ecf20Sopenharmony_ci{
378c2ecf20Sopenharmony_ci	return nf_call(nfhd_id + NFHD_READ_WRITE, major, minor, rwflag, recno,
388c2ecf20Sopenharmony_ci		       count, buf);
398c2ecf20Sopenharmony_ci}
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic inline s32 nfhd_get_capacity(u32 major, u32 minor, u32 *blocks,
428c2ecf20Sopenharmony_ci				    u32 *blocksize)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	return nf_call(nfhd_id + NFHD_GET_CAPACITY, major, minor,
458c2ecf20Sopenharmony_ci		       virt_to_phys(blocks), virt_to_phys(blocksize));
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic LIST_HEAD(nfhd_list);
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic int major_num;
518c2ecf20Sopenharmony_cimodule_param(major_num, int, 0);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistruct nfhd_device {
548c2ecf20Sopenharmony_ci	struct list_head list;
558c2ecf20Sopenharmony_ci	int id;
568c2ecf20Sopenharmony_ci	u32 blocks, bsize;
578c2ecf20Sopenharmony_ci	int bshift;
588c2ecf20Sopenharmony_ci	struct request_queue *queue;
598c2ecf20Sopenharmony_ci	struct gendisk *disk;
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic blk_qc_t nfhd_submit_bio(struct bio *bio)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	struct nfhd_device *dev = bio->bi_disk->private_data;
658c2ecf20Sopenharmony_ci	struct bio_vec bvec;
668c2ecf20Sopenharmony_ci	struct bvec_iter iter;
678c2ecf20Sopenharmony_ci	int dir, len, shift;
688c2ecf20Sopenharmony_ci	sector_t sec = bio->bi_iter.bi_sector;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	dir = bio_data_dir(bio);
718c2ecf20Sopenharmony_ci	shift = dev->bshift;
728c2ecf20Sopenharmony_ci	bio_for_each_segment(bvec, bio, iter) {
738c2ecf20Sopenharmony_ci		len = bvec.bv_len;
748c2ecf20Sopenharmony_ci		len >>= 9;
758c2ecf20Sopenharmony_ci		nfhd_read_write(dev->id, 0, dir, sec >> shift, len >> shift,
768c2ecf20Sopenharmony_ci				page_to_phys(bvec.bv_page) + bvec.bv_offset);
778c2ecf20Sopenharmony_ci		sec += len;
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci	bio_endio(bio);
808c2ecf20Sopenharmony_ci	return BLK_QC_T_NONE;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic int nfhd_getgeo(struct block_device *bdev, struct hd_geometry *geo)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	struct nfhd_device *dev = bdev->bd_disk->private_data;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	geo->cylinders = dev->blocks >> (6 - dev->bshift);
888c2ecf20Sopenharmony_ci	geo->heads = 4;
898c2ecf20Sopenharmony_ci	geo->sectors = 16;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	return 0;
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic const struct block_device_operations nfhd_ops = {
958c2ecf20Sopenharmony_ci	.owner	= THIS_MODULE,
968c2ecf20Sopenharmony_ci	.submit_bio = nfhd_submit_bio,
978c2ecf20Sopenharmony_ci	.getgeo	= nfhd_getgeo,
988c2ecf20Sopenharmony_ci};
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic int __init nfhd_init_one(int id, u32 blocks, u32 bsize)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	struct nfhd_device *dev;
1038c2ecf20Sopenharmony_ci	int dev_id = id - NFHD_DEV_OFFSET;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	pr_info("nfhd%u: found device with %u blocks (%u bytes)\n", dev_id,
1068c2ecf20Sopenharmony_ci		blocks, bsize);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	if (bsize < 512 || (bsize & (bsize - 1))) {
1098c2ecf20Sopenharmony_ci		pr_warn("nfhd%u: invalid block size\n", dev_id);
1108c2ecf20Sopenharmony_ci		return -EINVAL;
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	dev = kmalloc(sizeof(struct nfhd_device), GFP_KERNEL);
1148c2ecf20Sopenharmony_ci	if (!dev)
1158c2ecf20Sopenharmony_ci		goto out;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	dev->id = id;
1188c2ecf20Sopenharmony_ci	dev->blocks = blocks;
1198c2ecf20Sopenharmony_ci	dev->bsize = bsize;
1208c2ecf20Sopenharmony_ci	dev->bshift = ffs(bsize) - 10;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	dev->queue = blk_alloc_queue(NUMA_NO_NODE);
1238c2ecf20Sopenharmony_ci	if (dev->queue == NULL)
1248c2ecf20Sopenharmony_ci		goto free_dev;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	blk_queue_logical_block_size(dev->queue, bsize);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	dev->disk = alloc_disk(16);
1298c2ecf20Sopenharmony_ci	if (!dev->disk)
1308c2ecf20Sopenharmony_ci		goto free_queue;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	dev->disk->major = major_num;
1338c2ecf20Sopenharmony_ci	dev->disk->first_minor = dev_id * 16;
1348c2ecf20Sopenharmony_ci	dev->disk->fops = &nfhd_ops;
1358c2ecf20Sopenharmony_ci	dev->disk->private_data = dev;
1368c2ecf20Sopenharmony_ci	sprintf(dev->disk->disk_name, "nfhd%u", dev_id);
1378c2ecf20Sopenharmony_ci	set_capacity(dev->disk, (sector_t)blocks * (bsize / 512));
1388c2ecf20Sopenharmony_ci	dev->disk->queue = dev->queue;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	add_disk(dev->disk);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	list_add_tail(&dev->list, &nfhd_list);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	return 0;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cifree_queue:
1478c2ecf20Sopenharmony_ci	blk_cleanup_queue(dev->queue);
1488c2ecf20Sopenharmony_cifree_dev:
1498c2ecf20Sopenharmony_ci	kfree(dev);
1508c2ecf20Sopenharmony_ciout:
1518c2ecf20Sopenharmony_ci	return -ENOMEM;
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic int __init nfhd_init(void)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	u32 blocks, bsize;
1578c2ecf20Sopenharmony_ci	int ret;
1588c2ecf20Sopenharmony_ci	int i;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	nfhd_id = nf_get_id("XHDI");
1618c2ecf20Sopenharmony_ci	if (!nfhd_id)
1628c2ecf20Sopenharmony_ci		return -ENODEV;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	ret = register_blkdev(major_num, "nfhd");
1658c2ecf20Sopenharmony_ci	if (ret < 0) {
1668c2ecf20Sopenharmony_ci		pr_warn("nfhd: unable to get major number\n");
1678c2ecf20Sopenharmony_ci		return ret;
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (!major_num)
1718c2ecf20Sopenharmony_ci		major_num = ret;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	for (i = NFHD_DEV_OFFSET; i < 24; i++) {
1748c2ecf20Sopenharmony_ci		if (nfhd_get_capacity(i, 0, &blocks, &bsize))
1758c2ecf20Sopenharmony_ci			continue;
1768c2ecf20Sopenharmony_ci		nfhd_init_one(i, blocks, bsize);
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return 0;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic void __exit nfhd_exit(void)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct nfhd_device *dev, *next;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	list_for_each_entry_safe(dev, next, &nfhd_list, list) {
1878c2ecf20Sopenharmony_ci		list_del(&dev->list);
1888c2ecf20Sopenharmony_ci		del_gendisk(dev->disk);
1898c2ecf20Sopenharmony_ci		put_disk(dev->disk);
1908c2ecf20Sopenharmony_ci		blk_cleanup_queue(dev->queue);
1918c2ecf20Sopenharmony_ci		kfree(dev);
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci	unregister_blkdev(major_num, "nfhd");
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cimodule_init(nfhd_init);
1978c2ecf20Sopenharmony_cimodule_exit(nfhd_exit);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
200