| /* |
| * Bogus Block Driver for PowerPC Full System Simulator |
| * |
| * (C) Copyright IBM Corporation 2003-2005 |
| * |
| * Bogus Disk Driver |
| * |
| * Author: Eric Van Hensbegren <ericvh@gmail.com> |
| * |
| * inspired by drivers/block/nbd.c |
| * written by Pavel Machek and Steven Whitehouse |
| * |
| * Some code is from the IBM Full System Simulator Group in ARL |
| * Author: Patrick Bohrer <IBM Austin Research Lab> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to: |
| * Free Software Foundation |
| * 51 Franklin Street, Fifth Floor |
| * Boston, MA 02111-1301 USA |
| * |
| */ |
| |
| #include <linux/major.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/sched.h> |
| #include <linux/fs.h> |
| #include <linux/stat.h> |
| #include <linux/errno.h> |
| #include <linux/file.h> |
| #include <linux/ioctl.h> |
| #include <linux/blkdev.h> |
| #include <net/sock.h> |
| #include <asm/prom.h> |
| #include <asm/systemsim.h> |
| #include <asm/prom.h> |
| |
| #include <asm/uaccess.h> |
| #include <asm/types.h> |
| |
| #define MAJOR_NR 42 |
| #define MAX_SYSTEMSIM_BD 128 |
| |
| #define SYSTEMSIM_BD_SET_BLKSIZE _IO( 0xab, 1 ) |
| #define SYSTEMSIM_BD_SET_SIZE _IO( 0xab, 2 ) |
| #define SYSTEMSIM_BD_SET_SIZE_BLOCKS _IO( 0xab, 7 ) |
| #define SYSTEMSIM_BD_DISCONNECT _IO( 0xab, 8 ) |
| |
| struct systemsim_bd_device { |
| int initialized; |
| int refcnt; |
| int flags; |
| struct gendisk *disk; |
| }; |
| |
| static struct systemsim_bd_device systemsim_bd_dev[MAX_SYSTEMSIM_BD]; |
| |
| #define BD_INFO_SYNC 0 |
| #define BD_INFO_STATUS 1 |
| #define BD_INFO_BLKSZ 2 |
| #define BD_INFO_DEVSZ 3 |
| #define BD_INFO_CHANGE 4 |
| |
| #define BOGUS_DISK_READ 116 |
| #define BOGUS_DISK_WRITE 117 |
| #define BOGUS_DISK_INFO 118 |
| |
| static inline int |
| systemsim_disk_read(int devno, void *buf, ulong sect, ulong nrsect) |
| { |
| return callthru3(BOGUS_DISK_READ, (unsigned long)buf, |
| (unsigned long)sect, |
| (unsigned long)((nrsect << 16) | devno)); |
| } |
| |
| static inline int |
| systemsim_disk_write(int devno, void *buf, ulong sect, ulong nrsect) |
| { |
| return callthru3(BOGUS_DISK_WRITE, (unsigned long)buf, |
| (unsigned long)sect, |
| (unsigned long)((nrsect << 16) | devno)); |
| } |
| |
| static inline int systemsim_disk_info(int op, int devno) |
| { |
| return callthru2(BOGUS_DISK_INFO, (unsigned long)op, |
| (unsigned long)devno); |
| } |
| |
| static int systemsim_bd_init_disk(int devno) |
| { |
| struct gendisk *disk = systemsim_bd_dev[devno].disk; |
| unsigned int sz; |
| |
| /* check disk configured */ |
| if (!systemsim_disk_info(BD_INFO_STATUS, devno)) { |
| printk(KERN_ERR |
| "Attempting to open bogus disk before initializaiton\n"); |
| return 0; |
| } |
| |
| systemsim_bd_dev[devno].initialized++; |
| |
| sz = systemsim_disk_info(BD_INFO_DEVSZ, devno); |
| |
| printk("Initializing disk %d with devsz %u\n", devno, sz); |
| |
| set_capacity(disk, sz << 1); |
| |
| return 1; |
| } |
| |
| static void do_systemsim_bd_request(request_queue_t * q) |
| { |
| int result = 0; |
| struct request *req; |
| |
| while ((req = elv_next_request(q)) != NULL) { |
| int minor = req->rq_disk->first_minor; |
| |
| switch (rq_data_dir(req)) { |
| case READ: |
| result = systemsim_disk_read(minor, |
| req->buffer, req->sector, |
| req->current_nr_sectors); |
| break; |
| case WRITE: |
| result = systemsim_disk_write(minor, |
| req->buffer, req->sector, |
| req->current_nr_sectors); |
| }; |
| |
| if (result) |
| end_request(req, 0); /* failure */ |
| else |
| end_request(req, 1); /* success */ |
| } |
| } |
| |
| static int systemsim_bd_release(struct inode *inode, struct file *file) |
| { |
| struct systemsim_bd_device *lo; |
| int dev; |
| |
| if (!inode) |
| return -ENODEV; |
| dev = inode->i_bdev->bd_disk->first_minor; |
| if (dev >= MAX_SYSTEMSIM_BD) |
| return -ENODEV; |
| if (systemsim_disk_info(BD_INFO_SYNC, dev) < 0) { |
| printk(KERN_ALERT "systemsim_bd_release: unable to sync\n"); |
| } |
| lo = &systemsim_bd_dev[dev]; |
| if (lo->refcnt <= 0) |
| printk(KERN_ALERT "systemsim_bd_release: refcount(%d) <= 0\n", |
| lo->refcnt); |
| lo->refcnt--; |
| return 0; |
| } |
| |
| static int systemsim_bd_revalidate(struct gendisk *disk) |
| { |
| int devno = disk->first_minor; |
| |
| systemsim_bd_init_disk(devno); |
| |
| return 0; |
| } |
| |
| static int systemsim_bd_open(struct inode *inode, struct file *file) |
| { |
| int dev; |
| |
| if (!inode) |
| return -EINVAL; |
| dev = inode->i_bdev->bd_disk->first_minor; |
| if (dev >= MAX_SYSTEMSIM_BD) |
| return -ENODEV; |
| |
| check_disk_change(inode->i_bdev); |
| |
| if (!systemsim_bd_dev[dev].initialized) |
| if (!systemsim_bd_init_disk(dev)) |
| return -ENODEV; |
| |
| systemsim_bd_dev[dev].refcnt++; |
| return 0; |
| } |
| |
| static struct block_device_operations systemsim_bd_fops = { |
| owner:THIS_MODULE, |
| open:systemsim_bd_open, |
| release:systemsim_bd_release, |
| /* media_changed: systemsim_bd_check_change, */ |
| revalidate_disk:systemsim_bd_revalidate, |
| }; |
| |
| static spinlock_t systemsim_bd_lock = SPIN_LOCK_UNLOCKED; |
| |
| static int mambo_detect(void) |
| { |
| struct device_node *n; |
| |
| n = of_find_node_by_path("/mambo"); |
| if (n) { |
| of_node_put(n); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int __init systemsim_bd_init(void) |
| { |
| struct device_node *systemsim; |
| int err = -ENOMEM; |
| int i; |
| |
| systemsim = find_path_device("/systemsim"); |
| |
| if (systemsim == NULL) { |
| printk("NO SYSTEMSIM BOGUS DISK DETECTED\n"); |
| return -1; |
| } |
| |
| if (!mambo_detect()) |
| return -ENODEV; |
| |
| /* |
| * We could detect which disks are configured in openfirmware |
| * but I think this unnecessarily limits us from being able to |
| * hot-plug bogus disks durning run-time. |
| * |
| */ |
| |
| for (i = 0; i < MAX_SYSTEMSIM_BD; i++) { |
| struct gendisk *disk = alloc_disk(1); |
| if (!disk) |
| goto out; |
| systemsim_bd_dev[i].disk = disk; |
| /* |
| * The new linux 2.5 block layer implementation requires |
| * every gendisk to have its very own request_queue struct. |
| * These structs are big so we dynamically allocate them. |
| */ |
| disk->queue = |
| blk_init_queue(do_systemsim_bd_request, &systemsim_bd_lock); |
| if (!disk->queue) { |
| put_disk(disk); |
| goto out; |
| } |
| } |
| |
| if (register_blkdev(MAJOR_NR, "systemsim_bd")) { |
| err = -EIO; |
| goto out; |
| } |
| #ifdef MODULE |
| printk("systemsim bogus disk: registered device at major %d\n", |
| MAJOR_NR); |
| #else |
| printk("systemsim bogus disk: compiled in with kernel\n"); |
| #endif |
| |
| /* |
| * left device name alone for now as too much depends on it |
| * external to the kernel |
| * |
| */ |
| |
| for (i = 0; i < MAX_SYSTEMSIM_BD; i++) { /* load defaults */ |
| struct gendisk *disk = systemsim_bd_dev[i].disk; |
| systemsim_bd_dev[i].initialized = 0; |
| systemsim_bd_dev[i].refcnt = 0; |
| systemsim_bd_dev[i].flags = 0; |
| disk->major = MAJOR_NR; |
| disk->first_minor = i; |
| disk->fops = &systemsim_bd_fops; |
| disk->private_data = &systemsim_bd_dev[i]; |
| sprintf(disk->disk_name, "mambobd%d", i); |
| set_capacity(disk, 0x7ffffc00ULL << 1); /* 2 TB */ |
| add_disk(disk); |
| } |
| |
| return 0; |
| out: |
| while (i--) { |
| if (systemsim_bd_dev[i].disk->queue) |
| blk_cleanup_queue(systemsim_bd_dev[i].disk->queue); |
| put_disk(systemsim_bd_dev[i].disk); |
| } |
| return -EIO; |
| } |
| |
| static void __exit systemsim_bd_cleanup(void) |
| { |
| |
| if (unregister_blkdev(MAJOR_NR, "systemsim_bd") != 0) |
| printk("systemsim_bd: cleanup_module failed\n"); |
| else |
| printk("systemsim_bd: module cleaned up.\n"); |
| } |
| |
| module_init(systemsim_bd_init); |
| module_exit(systemsim_bd_cleanup); |
| |
| MODULE_DESCRIPTION("Systemsim Block Device"); |
| MODULE_LICENSE("GPL"); |