#ifndef FLOPPY_H
#define FLOPPY_H
#include <types.h>
#include <arch/interrupt.h>
#define FDC_SECTORS 18 #define FDC_HEADS 2 #define FDC_TRACKS 80 #define FDC_SECTOR_SIZE 512
#define FDC_TIME_MOTOR_SPINUP 500
#define FDC_TIME_MOTOR_OFF 3000
#define FDC_DOR 0x3F2 #define FDC_MSR 0x3F4 #define FDC_DSR 0x3F4 #define FDC_DATA 0x3F5 #define FDC_DIR 0x3F7 #define FDC_CCR 0x3F7
#define CMD_SPECIFY 0x03 #define CMD_WRITE 0xC5 #define CMD_READ 0xE6 #define CMD_RECAL 0x07 #define CMD_SENSEI 0x08 #define CMD_FORMAT 0x4D #define CMD_SEEK 0x0F #define CMD_VERSION 0x10
#define MSR_BUSY 0x10
#define MSR_DMA 0x20
#define MSR_DIR 0x40
#define MSR_READY 0x80
#define FDC_BUFFER_SIZE \
( PAGE_ALIGN_UP(floppy_type[ fdc_geometry ].spt * FDC_SECTOR_SIZE) )
#define FLOPPY_CACHE_MAX_BLOCKS 512
typedef struct floppy_struct
{
size_t size, spt, heads, tracks; uint8_t fmtgap, rwgap, rate; char *name; } floppy_struct_t;
typedef struct floppy_cache_block
{
int num;
int dirty;
clock_t timestamp;
uint8_t data[ FDC_SECTOR_SIZE ];
} floppy_cache_block_t;
void floppy_thread();
bool init_floppy();
bool fdc_is_changed();
int fdc_seek(int block);
int fdc_read(int block, uint8_t *buffer, size_t count);
int fdc_write(int block, uint8_t *buffer, size_t count);
int fdc_sync( void );
#endif
#include <const.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <arch/clock.h>
#include <arch/i386.h>
#include <arch/interrupt.h>
#include <arch/mem.h>
#include <arch/paging.h>
#include <kernel/console.h>
#include <kernel/debug.h>
#include <kernel/dma.h>
#include <kernel/queue.h>
#include <kernel/semaphore.h>
#include <kernel/sched.h>
#include <kernel/floppy.h>
volatile bool fdc_motor = FALSE;
volatile int fdc_timeout = 0;
volatile int fdc_motor_countdown = 0;
volatile bool fdc_done = FALSE;
volatile byte fdc_status[ 7 ] = { 0 };
volatile byte fdc_track = 0xff;
volatile byte ST0 = 0;
static floppy_struct_t floppy_type[] = {
{ 2880, 18, 2, 80, 0x54, 0x1b, 0x00, "H1440" },
{ 3360, 21, 2, 80, 0x0c, 0x1c, 0x00, "H1680" },
};
byte fdc_geometry = 0;
uint8_t *fdc_buffer = NULL;
DECLARE_MUTEX( fdc_mutex );
DECLARE_WAITQUEUE( fdc_wait_queue );
DECLARE_QUEUE( floppy_cache );
int floppy_block_count = 0;
static floppy_cache_block_t *floppy_cache_get_block( int num )
{
floppy_cache_block_t *buf;
queue_t *entry;
int n;
queue_for_each( entry, n, floppy_cache )
{
buf = queue_get_entry( entry );
if( buf->num == num )
{
buf->timestamp = sys_get_ticks();
return( buf );
}
}
return( NULL );
}
static void floppy_cache_delete_lru()
{
floppy_cache_block_t *buf, *old_buf = NULL;
queue_t *entry, *old_entry = NULL;
int n;
clock_t min_stamp = sys_get_ticks() + 1;
queue_for_each( entry, n, floppy_cache )
{
buf = queue_get_entry( entry );
if( buf->timestamp < min_stamp )
{
old_entry = entry;
old_buf = buf;
min_stamp = buf->timestamp;
}
}
if( old_entry != NULL )
{
#ifdef FLOPPY_DEBUG
printk( KERN_DEBUG "free lru block %i.\n", old_buf->num );
#endif
queue_del_entry( &floppy_cache, old_entry );
kfree( old_buf );
}
}
static void floppy_cache_push_block( int num, uint8_t *block )
{
floppy_cache_block_t *fb;
fb = kmalloc( sizeof(floppy_cache_block_t), GFP_KERNEL );
if( fb == NULL )
{
printk( KERN_ERR
"%s(): out of kernel memory!\n",
__FUNCTION__ );
return;
}
fb->num = num;
fb->dirty = 0;
fb->timestamp = sys_get_ticks();
memcpy( fb->data, block, sizeof(fb->data) );
if( floppy_block_count < FLOPPY_CACHE_MAX_BLOCKS )
{
floppy_block_count++;
}
else
{
floppy_cache_delete_lru();
}
add_queue( &floppy_cache, fb );
}
static void floppy_cache_clear( void )
{
queue_t *entry, *dummy;
floppy_cache_block_t *fb;
int n;
queue_for_each_safe( entry, dummy, n, floppy_cache )
{
fb = queue_get_entry( entry );
queue_del_entry( &floppy_cache, entry );
kfree( fb );
}
#ifdef FLOPPY_DEBUG
printk( KERN_DEBUG "%u blocks freed.\n", floppy_block_count );
#endif
floppy_block_count = 0;
}
static int fdc_wait_until_ready( void )
{
int counter, status;
for( counter = 0; counter < 10000; counter++ )
{
status = inb_p( FDC_MSR );
if( status & MSR_READY )
{
return( status );
}
}
return( -1 );
}
static int fdc_sendbyte( uint8_t b )
{
int msr;
msr = fdc_wait_until_ready();
if( msr < 0 )
{
return( -1 );
}
if( (msr & (MSR_READY | MSR_DIR | MSR_DMA)) == MSR_READY )
{
outb_p( FDC_DATA, b );
return( 0 );
}
return( -1 );
}
static int fdc_getbyte()
{
int msr;
msr = fdc_wait_until_ready();
if( msr < 0 )
{
return( -1 );
}
msr &= MSR_DIR | MSR_READY | MSR_BUSY | MSR_DMA;
if( msr == (MSR_DIR | MSR_READY | MSR_BUSY) )
{
return( inb_p(FDC_DATA) );
}
return( -1 );
}
static void fdc_motor_on( void )
{
if( !fdc_motor )
{
outb( FDC_DOR, 0x1C );
delay( FDC_TIME_MOTOR_SPINUP );
fdc_motor = TRUE;
}
fdc_motor_countdown = -1;
}
static void fdc_motor_off( void )
{
if( fdc_motor && (fdc_motor_countdown == -1) )
{
fdc_motor_countdown = FDC_TIME_MOTOR_OFF / 1000 * HZ;
}
}
static bool __fdc_is_changed( void )
{
if( inb(FDC_DIR) & 0x80 )
{
floppy_cache_clear();
return( TRUE );
}
return( FALSE );
}
static bool fdc_wait( bool sensei )
{
byte i;
uint32_t flags;
int ret;
local_irq_save( flags );
fdc_timeout = 3 * HZ;
for(;;)
{
if( (fdc_done) || (fdc_timeout == 0) )
{
break;
}
wait_event( fdc_wait_queue, fdc_done || (fdc_timeout == 0) );
}
i = 0;
while( (i < 7) && (inb(FDC_MSR) & MSR_BUSY) )
{
fdc_status[ i++ ] = fdc_getbyte();
}
if( sensei )
{
fdc_sendbyte( CMD_SENSEI );
ST0 = fdc_getbyte();
fdc_track = fdc_getbyte();
}
if( fdc_done == FALSE )
{
ret = FALSE;
}
else
{
fdc_done = FALSE;
ret = TRUE;
}
local_irq_restore( flags );
return( ret );
}
static void fdc_recalibrate()
{
fdc_motor_on();
fdc_sendbyte( CMD_RECAL );
fdc_sendbyte( 0 );
fdc_wait( TRUE );
fdc_motor_off();
}
static int __fdc_seek( int track )
{
if( fdc_buffer == NULL )
{
return( -ENODEV );
}
if( fdc_track == track )
{
return( 0 );
}
fdc_motor_on();
fdc_sendbyte( CMD_SEEK );
fdc_sendbyte( 0 );
fdc_sendbyte( track );
if( fdc_wait(TRUE) == FALSE )
{
fdc_motor_off();
return( -ETIME );
}
delay( 15 );
fdc_motor_off();
if( (ST0 == 0x20) && (fdc_track == track) )
{
return( 0 );
}
return( -ESPIPE );
}
int fdc_seek( int block )
{
int ret;
block = ( block /
floppy_type[ fdc_geometry ].spt *
floppy_type[ fdc_geometry ].heads ) + 1;
DOWN( &fdc_mutex );
ret = __fdc_seek( block );
UP( &fdc_mutex );
return( ret );
}
static void fdc_reset()
{
outb( FDC_DOR, 0x00 );
outb( FDC_DSR, 0x00 );
outb( FDC_DOR, 0x0c );
fdc_wait( TRUE );
fdc_sendbyte( CMD_SPECIFY );
fdc_sendbyte( 0xdf );
fdc_sendbyte( 0x02 );
floppy_cache_clear();
__fdc_seek( 1 );
fdc_recalibrate();
}
static void lba2chs(int lba, int *track, int *head, int *sector)
{
*track = lba / (floppy_type[fdc_geometry].spt * floppy_type[fdc_geometry].heads);
*head = (lba / floppy_type[fdc_geometry].spt) % floppy_type[fdc_geometry].heads;
*sector = (lba % floppy_type[fdc_geometry].spt) + 1;
}
static int fdc_rw( int block, uint8_t *buffer, int nsec, bool do_read )
{
int track, head, sector, tries;
lba2chs( block, &track, &head, §or );
if( !do_read )
{
memcpy( fdc_buffer, buffer, nsec * FDC_SECTOR_SIZE );
}
for( tries = 0; tries < 3; tries++ )
{
if( __fdc_is_changed() )
{
floppy_cache_clear();
__fdc_seek( 1 );
fdc_recalibrate();
return( -ENOMEDIUM );
}
fdc_motor_on();
if( __fdc_seek(track) < 0 )
{
fdc_motor_off();
return( -ESPIPE );
}
outb( FDC_CCR, floppy_type[fdc_geometry].rate );
lock_dma_controller();
if( do_read )
{
dma_xfer( 2, VIRTUAL((size_t)fdc_buffer), nsec * FDC_SECTOR_SIZE, FALSE );
fdc_sendbyte( CMD_READ );
}
else
{
dma_xfer( 2, VIRTUAL((size_t)fdc_buffer), nsec * FDC_SECTOR_SIZE, TRUE );
fdc_sendbyte( CMD_WRITE );
}
fdc_sendbyte( head << 2 );
fdc_sendbyte( track );
fdc_sendbyte( head );
fdc_sendbyte( sector );
fdc_sendbyte( 2 );
fdc_sendbyte( floppy_type[fdc_geometry].spt );
fdc_sendbyte( floppy_type[fdc_geometry].rwgap );
fdc_sendbyte( 0xff );
if( fdc_wait(FALSE) == FALSE )
{
unlock_dma_controller();
fdc_motor_off();
return( -ETIME );
}
unlock_dma_controller();
if( (fdc_status[0] & 0xc0) == 0 )
{
break;
}
fdc_recalibrate();
}
fdc_motor_off();
if( do_read )
{
memcpy( buffer, fdc_buffer, nsec * FDC_SECTOR_SIZE );
}
return( (tries == 3) ? -EIO : 0 );
}
int __fdc_phys_read( int block, uint8_t *buf, size_t count )
{
int track, track2, head, head2, sector, sector2;
int upper, i;
floppy_cache_block_t *tmp = NULL;
uint8_t *ptr = buf;
int first = block;
int last = block + count - 1;
int ret = 0;
lba2chs( first, &track, &head, §or );
lba2chs( last, &track2, &head2, §or2 );
while( first <= last )
{
if( (track == track2) && (head == head2) )
{
upper = sector2;
}
else
{
upper = floppy_type[ fdc_geometry ].spt;
}
ret = fdc_rw( first, ptr, (upper - sector + 1), TRUE );
if( ret < 0 )
{
goto out;
}
for( i = 0; i < (upper - sector + 1); i++, ptr += FDC_SECTOR_SIZE )
{
tmp = floppy_cache_get_block( first + i );
if( tmp == NULL )
{
floppy_cache_push_block( first + i, ptr );
}
else
{
tmp->timestamp = sys_get_ticks();
}
}
first = first + i;
lba2chs( first, &track, &head, §or );
}
out:
return( ret );
}
int __fdc_read( int block, uint8_t *buffer, size_t count )
{
floppy_cache_block_t *tmp = NULL;
uint8_t *ptr;
int i;
int ret = 0;
if( fdc_buffer == NULL )
{
return( -ENODEV );
}
for( i = 0, ptr = buffer; i < count; i++, ptr += FDC_SECTOR_SIZE )
{
tmp = floppy_cache_get_block( block + i );
if( tmp == NULL )
{
ret = __fdc_phys_read( block + i, ptr, count - i );
goto out;
}
else
{
memcpy( ptr, tmp->data, FDC_SECTOR_SIZE );
tmp->timestamp = sys_get_ticks();
}
}
out:
return( ret );
}
int fdc_read( int block, uint8_t *buffer, size_t count )
{
int ret;
DOWN( &fdc_mutex );
ret = __fdc_read( block, buffer, count );
UP( &fdc_mutex );
return( ret );
}
static int __fdc_write( int block, uint8_t *buffer, size_t count )
{
floppy_cache_block_t *tmp = NULL;
uint8_t *ptr;
int i;
int ret = 0;
if( fdc_buffer == NULL )
{
return( -ENODEV );
}
for( i = 0; i < count; i++ )
{
ptr = buffer + ( FDC_SECTOR_SIZE * i );
tmp = floppy_cache_get_block( block + i );
if( tmp == NULL )
{
ret = fdc_rw( block + i, ptr, 1, FALSE );
if( ret < 0 )
{
goto out;
}
}
else
{
memcpy( tmp->data, ptr, FDC_SECTOR_SIZE );
tmp->dirty = 1;
tmp->timestamp = sys_get_ticks();
}
}
out:
return( ret );
}
int fdc_write( int block, uint8_t *buffer, size_t count )
{
int ret;
DOWN( &fdc_mutex );
ret = __fdc_write( block, buffer, count );
UP( &fdc_mutex );
return( ret );
}
static int __fdc_sync( void )
{
queue_t *entry;
floppy_cache_block_t *fb;
int n;
int ret;
queue_for_each( entry, n, floppy_cache )
{
fb = queue_get_entry( entry );
if( fb->dirty == 1 )
{
ret = fdc_rw( fb->num, fb->data, 1, FALSE );
if( ret < 0 )
{
goto out;
}
}
}
ret = 0;
out:
floppy_cache_clear();
__fdc_seek( 1 );
fdc_recalibrate();
return( ret );
}
int fdc_sync( void )
{
DOWN( &fdc_mutex );
(void)__fdc_sync();
UP( &fdc_mutex );
return( 0 );
}
bool fdc_is_changed()
{
register bool ret;
DOWN( &fdc_mutex );
ret = __fdc_is_changed();
UP( &fdc_mutex );
return( ret );
}
void floppy_thread()
{
if( fdc_timeout > 0 )
{
if( (--fdc_timeout) == 0 )
{
wakeup_queue( fdc_wait_queue );
}
}
if( fdc_motor_countdown > 0 )
{
fdc_motor_countdown--;
}
else if( fdc_motor && !fdc_motor_countdown )
{
outb( FDC_DOR, 0x0C );
fdc_motor = FALSE;
}
}
void floppy_handler( irq_context_t *c )
{
fdc_done = TRUE;
wakeup_queue( fdc_wait_queue );
}
void fdc_remove()
{
DOWN( &fdc_mutex );
floppy_cache_clear();
uninstall_handler( FLOPPY_IRQ );
dma_phys_free( VIRTUAL((size_t)fdc_buffer), FDC_BUFFER_SIZE );
fdc_buffer = NULL;
UP( &fdc_mutex );
}
bool init_floppy()
{
int v, ret;
DOWN( &fdc_mutex );
install_trap_handler( FLOPPY_IRQ, (void *)floppy_handler );
fdc_reset();
fdc_sendbyte( CMD_VERSION );
v = fdc_getbyte();
UP( &fdc_mutex );
switch ( v )
{
case 0xFF:
printk( KERN_ERR
"%s(): floppy controller not found!\n",
__FUNCTION__ );
ret = FALSE;
break;
case 0x80:
printk( KERN_INFO
"%s(): NEC controller.\n",
__FUNCTION__ );
ret = TRUE;
break;
case 0x81:
printk( KERN_INFO
"%s(): VMware controller.\n",
__FUNCTION__ );
ret = TRUE;
break;
case 0x90:
printk( KERN_INFO
"%s(): enhanced controller.\n",
__FUNCTION__ );
ret = TRUE;
break;
default:
printk( KERN_INFO
"%s(): unknown controller [%#04x].\n",
__FUNCTION__, v );
ret = TRUE;
break;
}
if( ret == TRUE )
{
DOWN( &fdc_mutex );
fdc_buffer = PHYSICAL( dma_phys_alloc(FDC_BUFFER_SIZE) );
if( fdc_buffer == NULL )
{
UP( &fdc_mutex );
printk( KERN_ERR
"%s(): out of dma memory!\n",
__FUNCTION__ );
return( FALSE );
}
UP( &fdc_mutex );
printk( KERN_INFO
"%s(): floppy buffer at [%p]->[%p]\n",
__FUNCTION__,
fdc_buffer, (size_t)fdc_buffer + PAGE_SIZE );
}
return( ret );
}