2005-04-16 15:20:36 -07:00
/*
* 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 ;
}
2005-09-10 00:27:28 -07:00
schedule_timeout_interruptible ( 1 ) ;
2005-04-16 15:20:36 -07:00
}
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 ;
}
}