diff options
Diffstat (limited to 'drivers/block/swim_iop.c')
-rw-r--r-- | drivers/block/swim_iop.c | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/drivers/block/swim_iop.c b/drivers/block/swim_iop.c new file mode 100644 index 00000000000..a1283f6dc01 --- /dev/null +++ b/drivers/block/swim_iop.c @@ -0,0 +1,579 @@ +/* + * Driver for the SWIM (Super Woz Integrated Machine) IOP + * floppy controller on the Macintosh IIfx and Quadra 900/950 + * + * Written by Joshua M. Thompson (funaho@jurai.org) + * based on the SWIM3 driver (c) 1996 by Paul Mackerras. + * + * 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 of the License, or (at your option) any later version. + * + * 1999-06-12 (jmt) - Initial implementation. + */ + +/* + * ------------------- + * Theory of Operation + * ------------------- + * + * Since the SWIM IOP is message-driven we implement a simple request queue + * system. One outstanding request may be queued at any given time (this is + * an IOP limitation); only when that request has completed can a new request + * be sent. + */ + +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/fd.h> +#include <linux/ioctl.h> +#include <linux/blkdev.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/mac_iop.h> +#include <asm/swim_iop.h> + +#define DRIVER_VERSION "Version 0.1 (1999-06-12)" + +#define MAX_FLOPPIES 4 + +enum swim_state { + idle, + available, + revalidating, + transferring, + ejecting +}; + +struct floppy_state { + enum swim_state state; + int drive_num; /* device number */ + int secpercyl; /* disk geometry information */ + int secpertrack; + int total_secs; + int write_prot; /* 1 if write-protected, 0 if not, -1 dunno */ + int ref_count; + struct timer_list timeout; + int ejected; + struct wait_queue *wait; + int wanted; + int timeout_pending; +}; + +struct swim_iop_req { + int sent; + int complete; + __u8 command[32]; + struct floppy_state *fs; + void (*done)(struct swim_iop_req *); +}; + +static struct swim_iop_req *current_req; +static int floppy_count; + +static struct floppy_state floppy_states[MAX_FLOPPIES]; +static DEFINE_SPINLOCK(swim_iop_lock); + +#define CURRENT elv_next_request(swim_queue) + +static char *drive_names[7] = { + "not installed", /* DRV_NONE */ + "unknown (1)", /* DRV_UNKNOWN */ + "a 400K drive", /* DRV_400K */ + "an 800K drive" /* DRV_800K */ + "unknown (4)", /* ???? */ + "an FDHD", /* DRV_FDHD */ + "unknown (6)", /* ???? */ + "an Apple HD20" /* DRV_HD20 */ +}; + +int swimiop_init(void); +static void swimiop_init_request(struct swim_iop_req *); +static int swimiop_send_request(struct swim_iop_req *); +static void swimiop_receive(struct iop_msg *, struct pt_regs *); +static void swimiop_status_update(int, struct swim_drvstatus *); +static int swimiop_eject(struct floppy_state *fs); + +static int floppy_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long param); +static int floppy_open(struct inode *inode, struct file *filp); +static int floppy_release(struct inode *inode, struct file *filp); +static int floppy_check_change(struct gendisk *disk); +static int floppy_revalidate(struct gendisk *disk); +static int grab_drive(struct floppy_state *fs, enum swim_state state, + int interruptible); +static void release_drive(struct floppy_state *fs); +static void set_timeout(struct floppy_state *fs, int nticks, + void (*proc)(unsigned long)); +static void fd_request_timeout(unsigned long); +static void do_fd_request(request_queue_t * q); +static void start_request(struct floppy_state *fs); + +static struct block_device_operations floppy_fops = { + .open = floppy_open, + .release = floppy_release, + .ioctl = floppy_ioctl, + .media_changed = floppy_check_change, + .revalidate_disk= floppy_revalidate, +}; + +static struct request_queue *swim_queue; +/* + * SWIM IOP initialization + */ + +int swimiop_init(void) +{ + volatile struct swim_iop_req req; + struct swimcmd_status *cmd = (struct swimcmd_status *) &req.command[0]; + struct swim_drvstatus *ds = &cmd->status; + struct floppy_state *fs; + int i; + + current_req = NULL; + floppy_count = 0; + + if (!iop_ism_present) + return -ENODEV; + + if (register_blkdev(FLOPPY_MAJOR, "fd")) + return -EBUSY; + + swim_queue = blk_init_queue(do_fd_request, &swim_iop_lock); + if (!swim_queue) { + unregister_blkdev(FLOPPY_MAJOR, "fd"); + return -ENOMEM; + } + + printk("SWIM-IOP: %s by Joshua M. Thompson (funaho@jurai.org)\n", + DRIVER_VERSION); + + if (iop_listen(SWIM_IOP, SWIM_CHAN, swimiop_receive, "SWIM") != 0) { + printk(KERN_ERR "SWIM-IOP: IOP channel already in use; can't initialize.\n"); + unregister_blkdev(FLOPPY_MAJOR, "fd"); + blk_cleanup_queue(swim_queue); + return -EBUSY; + } + + printk(KERN_ERR "SWIM_IOP: probing for installed drives.\n"); + + for (i = 0 ; i < MAX_FLOPPIES ; i++) { + memset(&floppy_states[i], 0, sizeof(struct floppy_state)); + fs = &floppy_states[floppy_count]; + + swimiop_init_request(&req); + cmd->code = CMD_STATUS; + cmd->drive_num = i + 1; + if (swimiop_send_request(&req) != 0) continue; + while (!req.complete); + if (cmd->error != 0) { + printk(KERN_ERR "SWIM-IOP: probe on drive %d returned error %d\n", i, (uint) cmd->error); + continue; + } + if (ds->installed != 0x01) continue; + printk("SWIM-IOP: drive %d is %s (%s, %s, %s, %s)\n", i, + drive_names[ds->info.type], + ds->info.external? "ext" : "int", + ds->info.scsi? "scsi" : "floppy", + ds->info.fixed? "fixed" : "removable", + ds->info.secondary? "secondary" : "primary"); + swimiop_status_update(floppy_count, ds); + fs->state = idle; + + init_timer(&fs->timeout); + floppy_count++; + } + printk("SWIM-IOP: detected %d installed drives.\n", floppy_count); + + for (i = 0; i < floppy_count; i++) { + struct gendisk *disk = alloc_disk(1); + if (!disk) + continue; + disk->major = FLOPPY_MAJOR; + disk->first_minor = i; + disk->fops = &floppy_fops; + sprintf(disk->disk_name, "fd%d", i); + disk->private_data = &floppy_states[i]; + disk->queue = swim_queue; + set_capacity(disk, 2880 * 2); + add_disk(disk); + } + + return 0; +} + +static void swimiop_init_request(struct swim_iop_req *req) +{ + req->sent = 0; + req->complete = 0; + req->done = NULL; +} + +static int swimiop_send_request(struct swim_iop_req *req) +{ + unsigned long flags; + int err; + + /* It's doubtful an interrupt routine would try to send */ + /* a SWIM request, but I'd rather play it safe here. */ + + local_irq_save(flags); + + if (current_req != NULL) { + local_irq_restore(flags); + return -ENOMEM; + } + + current_req = req; + + /* Interrupts should be back on for iop_send_message() */ + + local_irq_restore(flags); + + err = iop_send_message(SWIM_IOP, SWIM_CHAN, (void *) req, + sizeof(req->command), (__u8 *) &req->command[0], + swimiop_receive); + + /* No race condition here; we own current_req at this point */ + + if (err) { + current_req = NULL; + } else { + req->sent = 1; + } + return err; +} + +/* + * Receive a SWIM message from the IOP. + * + * This will be called in two cases: + * + * 1. A message has been successfully sent to the IOP. + * 2. An unsolicited message was received from the IOP. + */ + +void swimiop_receive(struct iop_msg *msg, struct pt_regs *regs) +{ + struct swim_iop_req *req; + struct swimmsg_status *sm; + struct swim_drvstatus *ds; + + req = current_req; + + switch(msg->status) { + case IOP_MSGSTATUS_COMPLETE: + memcpy(&req->command[0], &msg->reply[0], sizeof(req->command)); + req->complete = 1; + if (req->done) (*req->done)(req); + current_req = NULL; + break; + case IOP_MSGSTATUS_UNSOL: + sm = (struct swimmsg_status *) &msg->message[0]; + ds = &sm->status; + swimiop_status_update(sm->drive_num, ds); + iop_complete_message(msg); + break; + } +} + +static void swimiop_status_update(int drive_num, struct swim_drvstatus *ds) +{ + struct floppy_state *fs = &floppy_states[drive_num]; + + fs->write_prot = (ds->write_prot == 0x80); + if ((ds->disk_in_drive != 0x01) && (ds->disk_in_drive != 0x02)) { + fs->ejected = 1; + } else { + fs->ejected = 0; + } + switch(ds->info.type) { + case DRV_400K: + fs->secpercyl = 10; + fs->secpertrack = 10; + fs->total_secs = 800; + break; + case DRV_800K: + fs->secpercyl = 20; + fs->secpertrack = 10; + fs->total_secs = 1600; + break; + case DRV_FDHD: + fs->secpercyl = 36; + fs->secpertrack = 18; + fs->total_secs = 2880; + break; + default: + fs->secpercyl = 0; + fs->secpertrack = 0; + fs->total_secs = 0; + break; + } +} + +static int swimiop_eject(struct floppy_state *fs) +{ + int err, n; + struct swim_iop_req req; + struct swimcmd_eject *cmd = (struct swimcmd_eject *) &req.command[0]; + + err = grab_drive(fs, ejecting, 1); + if (err) return err; + + swimiop_init_request(&req); + cmd->code = CMD_EJECT; + cmd->drive_num = fs->drive_num; + err = swimiop_send_request(&req); + if (err) { + release_drive(fs); + return err; + } + for (n = 2*HZ; n > 0; --n) { + if (req.complete) break; + if (signal_pending(current)) { + err = -EINTR; + break; + } + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + } + release_drive(fs); + return cmd->error; +} + +static struct floppy_struct floppy_type = + { 2880,18,2,80,0,0x1B,0x00,0xCF,0x6C,NULL }; /* 7 1.44MB 3.5" */ + +static int floppy_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long param) +{ + struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; + int err; + + if ((cmd & 0x80) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + switch (cmd) { + case FDEJECT: + if (fs->ref_count != 1) + return -EBUSY; + err = swimiop_eject(fs); + return err; + case FDGETPRM: + if (copy_to_user((void *) param, (void *) &floppy_type, + sizeof(struct floppy_struct))) + return -EFAULT; + return 0; + } + return -ENOTTY; +} + +static int floppy_open(struct inode *inode, struct file *filp) +{ + struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; + + if (fs->ref_count == -1 || filp->f_flags & O_EXCL) + return -EBUSY; + + if ((filp->f_flags & O_NDELAY) == 0 && (filp->f_mode & 3)) { + check_disk_change(inode->i_bdev); + if (fs->ejected) + return -ENXIO; + } + + if ((filp->f_mode & 2) && fs->write_prot) + return -EROFS; + + if (filp->f_flags & O_EXCL) + fs->ref_count = -1; + else + ++fs->ref_count; + + return 0; +} + +static int floppy_release(struct inode *inode, struct file *filp) +{ + struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; + if (fs->ref_count > 0) + fs->ref_count--; + return 0; +} + +static int floppy_check_change(struct gendisk *disk) +{ + struct floppy_state *fs = disk->private_data; + return fs->ejected; +} + +static int floppy_revalidate(struct gendisk *disk) +{ + struct floppy_state *fs = disk->private_data; + grab_drive(fs, revalidating, 0); + /* yadda, yadda */ + release_drive(fs); + return 0; +} + +static void floppy_off(unsigned int nr) +{ +} + +static int grab_drive(struct floppy_state *fs, enum swim_state state, + int interruptible) +{ + unsigned long flags; + + local_irq_save(flags); + if (fs->state != idle) { + ++fs->wanted; + while (fs->state != available) { + if (interruptible && signal_pending(current)) { + --fs->wanted; + local_irq_restore(flags); + return -EINTR; + } + interruptible_sleep_on(&fs->wait); + } + --fs->wanted; + } + fs->state = state; + local_irq_restore(flags); + return 0; +} + +static void release_drive(struct floppy_state *fs) +{ + unsigned long flags; + + local_irq_save(flags); + fs->state = idle; + start_request(fs); + local_irq_restore(flags); +} + +static void set_timeout(struct floppy_state *fs, int nticks, + void (*proc)(unsigned long)) +{ + unsigned long flags; + + local_irq_save(flags); + if (fs->timeout_pending) + del_timer(&fs->timeout); + init_timer(&fs->timeout); + fs->timeout.expires = jiffies + nticks; + fs->timeout.function = proc; + fs->timeout.data = (unsigned long) fs; + add_timer(&fs->timeout); + fs->timeout_pending = 1; + local_irq_restore(flags); +} + +static void do_fd_request(request_queue_t * q) +{ + int i; + + for (i = 0 ; i < floppy_count ; i++) { + start_request(&floppy_states[i]); + } +} + +static void fd_request_complete(struct swim_iop_req *req) +{ + struct floppy_state *fs = req->fs; + struct swimcmd_rw *cmd = (struct swimcmd_rw *) &req->command[0]; + + del_timer(&fs->timeout); + fs->timeout_pending = 0; + fs->state = idle; + if (cmd->error) { + printk(KERN_ERR "SWIM-IOP: error %d on read/write request.\n", cmd->error); + end_request(CURRENT, 0); + } else { + CURRENT->sector += cmd->num_blocks; + CURRENT->current_nr_sectors -= cmd->num_blocks; + if (CURRENT->current_nr_sectors <= 0) { + end_request(CURRENT, 1); + return; + } + } + start_request(fs); +} + +static void fd_request_timeout(unsigned long data) +{ + struct floppy_state *fs = (struct floppy_state *) data; + + fs->timeout_pending = 0; + end_request(CURRENT, 0); + fs->state = idle; +} + +static void start_request(struct floppy_state *fs) +{ + volatile struct swim_iop_req req; + struct swimcmd_rw *cmd = (struct swimcmd_rw *) &req.command[0]; + + if (fs->state == idle && fs->wanted) { + fs->state = available; + wake_up(&fs->wait); + return; + } + while (CURRENT && fs->state == idle) { + if (CURRENT->bh && !buffer_locked(CURRENT->bh)) + panic("floppy: block not locked"); +#if 0 + printk("do_fd_req: dev=%s cmd=%d sec=%ld nr_sec=%ld buf=%p\n", + CURRENT->rq_disk->disk_name, CURRENT->cmd, + CURRENT->sector, CURRENT->nr_sectors, CURRENT->buffer); + printk(" rq_status=%d errors=%d current_nr_sectors=%ld\n", + CURRENT->rq_status, CURRENT->errors, CURRENT->current_nr_sectors); +#endif + + if (CURRENT->sector < 0 || CURRENT->sector >= fs->total_secs) { + end_request(CURRENT, 0); + continue; + } + if (CURRENT->current_nr_sectors == 0) { + end_request(CURRENT, 1); + continue; + } + if (fs->ejected) { + end_request(CURRENT, 0); + continue; + } + + swimiop_init_request(&req); + req.fs = fs; + req.done = fd_request_complete; + + if (CURRENT->cmd == WRITE) { + if (fs->write_prot) { + end_request(CURRENT, 0); + continue; + } + cmd->code = CMD_WRITE; + } else { + cmd->code = CMD_READ; + + } + cmd->drive_num = fs->drive_num; + cmd->buffer = CURRENT->buffer; + cmd->first_block = CURRENT->sector; + cmd->num_blocks = CURRENT->current_nr_sectors; + + if (swimiop_send_request(&req)) { + end_request(CURRENT, 0); + continue; + } + + set_timeout(fs, HZ*CURRENT->current_nr_sectors, + fd_request_timeout); + + fs->state = transferring; + } +} |