//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
-// Copyright (C) 2003 Savin Zlobec
+// Copyright (C) 2003. 2004, 2005, 2006 eCosCentric Limited
//
// eCos 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
// ---------------------------------------------------------------------------
-//#define DEBUG 1
+#ifdef CYGDBG_IO_DISK_DEBUG
+#define DEBUG 1
+#endif
#ifdef DEBUG
# define D(_args_) diag_printf _args_
static Cyg_ErrNo disk_connected(struct cyg_devtab_entry *tab,
cyg_disk_identify_t *ident);
-static Cyg_ErrNo disk_disconnected(struct cyg_devtab_entry *tab);
+static Cyg_ErrNo disk_disconnected(struct disk_channel *chan);
static Cyg_ErrNo disk_lookup(struct cyg_devtab_entry **tab,
struct cyg_devtab_entry *sub_tab,
const char *name);
+static void disk_transfer_done(struct disk_channel *chan, Cyg_ErrNo res);
+
DISK_CALLBACKS(cyg_io_disk_callbacks,
disk_init,
disk_connected,
disk_disconnected,
- disk_lookup
+ disk_lookup,
+ disk_transfer_done
);
// ---------------------------------------------------------------------------
-
//
// Read partition from data
//
+
static void
read_partition(cyg_uint8 *data,
cyg_disk_info_t *info,
cyg_disk_partition_t *part)
{
+ cyg_disk_identify_t *ident = &info->ident;
cyg_uint16 c, h, s;
+ cyg_uint32 start, end, size;
+#ifdef DEBUG
+ diag_printf("Partition data:\n");
+ diag_dump_buf( data, 16 );
+ diag_printf("Disk geometry: %d/%d/%d\n",info->ident.cylinders_num,
+ info->ident.heads_num, info->ident.sectors_num );
+#endif
+
+ // Retrieve basic information
part->type = data[4];
part->state = data[0];
+ READ_DWORD(&data[12], part->size);
- READ_CHS(&data[1], c, h, s);
- CHS_TO_LBA(&info->ident, c, h, s, part->start);
+ READ_DWORD(&data[8], start);
+ READ_DWORD(&data[12], size);
- READ_CHS(&data[5], c, h, s);
- CHS_TO_LBA(&info->ident, c, h, s, part->end);
+ // Use the LBA start and size fields if they are valid. Otherwise
+ // fall back to CHS.
- READ_DWORD(&data[12], part->size);
+ if( start > 0 && size > 0 )
+ {
+ READ_DWORD(&data[8], start);
+ end = start + size - 1;
+
+#ifdef DEBUG
+ diag_printf("Using LBA partition parameters\n");
+ diag_printf(" LBA start %d\n",start);
+ diag_printf(" LBA size %d\n",size);
+ diag_printf(" LBA end %d\n",end);
+#endif
+
+ }
+ else
+ {
+ READ_CHS(&data[1], c, h, s);
+ CHS_TO_LBA(ident, c, h, s, start);
+#ifdef DEBUG
+ diag_printf("Using CHS partition parameters\n");
+ diag_printf(" CHS start %d/%d/%d => %d\n",c,h,s,start);
+#endif
+
+ READ_CHS(&data[5], c, h, s);
+ CHS_TO_LBA(ident, c, h, s, end);
+#ifdef DEBUG
+ diag_printf(" CHS end %d/%d/%d => %d\n",c,h,s,end);
+ diag_printf(" CHS size %d\n",size);
+#endif
+
+ }
+
+ part->size = size;
+ part->start = start;
+ part->end = end;
}
+// ---------------------------------------------------------------------------
//
// Read Master Boot Record (partitions)
//
+
static Cyg_ErrNo
read_mbr(disk_channel *chan)
{
cyg_disk_info_t *info = chan->info;
disk_funs *funs = chan->funs;
+ disk_controller *ctlr = chan->controller;
cyg_uint8 buf[512];
Cyg_ErrNo res = ENOERR;
int i;
-
+
+ D(("read MBR\n"));
+
for (i = 0; i < info->partitions_num; i++)
info->partitions[i].type = 0x00;
-
- res = (funs->read)(chan, (void *)buf, 512, 0);
+
+
+
+ cyg_drv_mutex_lock( &ctlr->lock );
+
+ while( ctlr->busy )
+ cyg_drv_cond_wait( &ctlr->queue );
+
+ ctlr->busy = true;
+
+ ctlr->result = -EWOULDBLOCK;
+
+ for( i = 0; i < sizeof(buf); i++ )
+ buf[i] = 0;
+
+ res = (funs->read)(chan, (void *)buf, 1, 0);
+
+ if( res == -EWOULDBLOCK )
+ {
+ // If the driver replys EWOULDBLOCK, then the transfer is
+ // being handled asynchronously and when it is finished it
+ // will call disk_transfer_done(). This will wake us up here
+ // to continue.
+
+ while( ctlr->result == -EWOULDBLOCK )
+ cyg_drv_cond_wait( &ctlr->async );
+
+ res = ctlr->result;
+ }
+
+ ctlr->busy = false;
+
+ cyg_drv_mutex_unlock( &ctlr->lock );
+
if (ENOERR != res)
return res;
+#ifdef DEBUG
+ diag_dump_buf_with_offset( buf, 512, buf );
+#endif
+
if (MBR_SIG_BYTE0 == buf[MBR_SIG_ADDR+0] && MBR_SIG_BYTE1 == buf[MBR_SIG_ADDR+1])
{
int npart;
return ENOERR;
}
+// ---------------------------------------------------------------------------
+
static cyg_bool
disk_init(struct cyg_devtab_entry *tab)
{
if (!chan->init)
{
+ disk_controller *controller = chan->controller;
+
+ if( !controller->init )
+ {
+ cyg_drv_mutex_init( &controller->lock );
+ cyg_drv_cond_init( &controller->queue, &controller->lock );
+ cyg_drv_cond_init( &controller->async, &controller->lock );
+
+ controller->init = true;
+ }
+
info->connected = false;
-
+
// clear partition data
for (i = 0; i < info->partitions_num; i++)
info->partitions[i].type = 0x00;
return true;
}
+// ---------------------------------------------------------------------------
+
static Cyg_ErrNo
disk_connected(struct cyg_devtab_entry *tab,
cyg_disk_identify_t *ident)
if (!chan->init)
return -EINVAL;
+
+ // If the device is already connected, nothing more to do
+ if( info->connected )
+ return ENOERR;
+
+ // If any of these assertions fire, it is probable that the
+ // hardware driver has not been updated to match the current disk
+ // API.
+ CYG_ASSERT( ident->lba_sectors_num > 0, "Bad LBA sector count" );
+ CYG_ASSERT( ident->phys_block_size > 0, "Bad physical block size");
+ CYG_ASSERT( ident->max_transfer > 0, "Bad max transfer size");
info->ident = *ident;
info->block_size = 512;
info->blocks_num = ident->lba_sectors_num;
+ info->phys_block_size = ident->phys_block_size;
D(("disk connected\n"));
- D((" serial = '%s'\n", ident->serial));
- D((" firmware rev = '%s'\n", ident->firmware_rev));
- D((" model num = '%s'\n", ident->model_num));
- D((" block_size = %d\n", info->block_size));
- D((" blocks_num = %d\n", info->blocks_num));
-
+ D((" serial = '%s'\n", ident->serial));
+ D((" firmware rev = '%s'\n", ident->firmware_rev));
+ D((" model num = '%s'\n", ident->model_num));
+ D((" block_size = %d\n", info->block_size));
+ D((" blocks_num = %u\n", info->blocks_num));
+ D((" phys_block_size = %d\n", info->phys_block_size));
+
if (chan->mbr_support)
{
// read disk master boot record
return res;
}
+// ---------------------------------------------------------------------------
+
static Cyg_ErrNo
-disk_disconnected(struct cyg_devtab_entry *tab)
+disk_disconnected(disk_channel *chan)
{
- disk_channel *chan = (disk_channel *) tab->priv;
cyg_disk_info_t *info = chan->info;
int i;
if (!chan->init)
return -EINVAL;
-
+
info->connected = false;
chan->valid = false;
return ENOERR;
}
+// ---------------------------------------------------------------------------
+
static Cyg_ErrNo
disk_lookup(struct cyg_devtab_entry **tab,
struct cyg_devtab_entry *sub_tab,
static Cyg_ErrNo
disk_bread(cyg_io_handle_t handle,
void *buf,
- cyg_uint32 *len,
- cyg_uint32 pos)
+ cyg_uint32 *len, // In blocks
+ cyg_uint32 pos) // In blocks
{
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *) handle;
disk_channel *chan = (disk_channel *) t->priv;
+ disk_controller *ctlr = chan->controller;
disk_funs *funs = chan->funs;
cyg_disk_info_t *info = chan->info;
cyg_uint32 size = *len;
Cyg_ErrNo res = ENOERR;
cyg_uint32 last;
- if (!info->connected || !chan->valid)
- return -EINVAL;
-
- if (NULL != chan->partition)
- {
- pos += chan->partition->start;
- last = chan->partition->end;
- }
- else
- {
- last = info->blocks_num-1;
- }
-
- D(("disk read block=%d len=%d buf=%p\n", pos, *len, buf));
-
- while (size > 0)
+ cyg_drv_mutex_lock( &ctlr->lock );
+
+ while( ctlr->busy )
+ cyg_drv_cond_wait( &ctlr->queue );
+
+ if (info->connected && chan->valid)
{
- if (pos > last)
+ ctlr->busy = true;
+
+ if (NULL != chan->partition)
{
- res = -EIO;
- break;
+ pos += chan->partition->start;
+ last = chan->partition->end;
}
-
- res = (funs->read)(chan, (void*)bbuf, info->block_size, pos);
- if (ENOERR != res)
- break;
-
- if (!info->connected)
+ else
{
- res = -EINVAL;
- break;
+ last = info->blocks_num-1;
}
+
+ D(("disk read block=%d len=%d buf=%p\n", pos, *len, buf));
+
+ while( size > 0 )
+ {
+ cyg_uint32 tfr = size;
+
+ if (pos > last)
+ {
+ res = -EIO;
+ break;
+ }
+
+ if( tfr > info->ident.max_transfer )
+ tfr = info->ident.max_transfer;
+
+ ctlr->result = -EWOULDBLOCK;
+
+ cyg_drv_dsr_lock();
+
+ res = (funs->read)(chan, (void*)bbuf, tfr, pos);
+
+ if( res == -EWOULDBLOCK )
+ {
+ // If the driver replys EWOULDBLOCK, then the transfer is
+ // being handled asynchronously and when it is finished it
+ // will call disk_transfer_done(). This will wake us up here
+ // to continue.
+
+ while( ctlr->result == -EWOULDBLOCK )
+ cyg_drv_cond_wait( &ctlr->async );
+
+ res = ctlr->result;
+ }
+
+ cyg_drv_dsr_unlock();
- bbuf += info->block_size;
- pos++;
- size--;
+ if (ENOERR != res)
+ goto done;
+
+ if (!info->connected)
+ {
+ res = -EINVAL;
+ goto done;
+ }
+
+ bbuf += tfr * info->block_size;
+ pos += tfr;
+ size -= tfr;
+ }
+
+ ctlr->busy = false;
+ cyg_drv_cond_signal( &ctlr->queue );
}
+ else
+ res = -EINVAL;
+
+done:
+
+ cyg_drv_mutex_unlock( &ctlr->lock );
+#ifdef CYGPKG_KERNEL
+ cyg_thread_yield();
+#endif
+
*len -= size;
return res;
}
static Cyg_ErrNo
disk_bwrite(cyg_io_handle_t handle,
const void *buf,
- cyg_uint32 *len,
- cyg_uint32 pos)
+ cyg_uint32 *len, // In blocks
+ cyg_uint32 pos) // In blocks
{
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *) handle;
disk_channel *chan = (disk_channel *) t->priv;
+ disk_controller *ctlr = chan->controller;
disk_funs *funs = chan->funs;
cyg_disk_info_t *info = chan->info;
cyg_uint32 size = *len;
Cyg_ErrNo res = ENOERR;
cyg_uint32 last;
- if (!info->connected || !chan->valid)
- return -EINVAL;
-
- if (NULL != chan->partition)
- {
- pos += chan->partition->start;
- last = chan->partition->end;
- }
- else
- {
- last = info->blocks_num-1;
- }
-
- D(("disk write block=%d len=%d buf=%p\n", pos, *len, buf));
-
- while (size > 0)
+ cyg_drv_mutex_lock( &ctlr->lock );
+
+ while( ctlr->busy )
+ cyg_drv_cond_wait( &ctlr->queue );
+
+ if (info->connected && chan->valid)
{
- if (pos > last)
+ ctlr->busy = true;
+
+ if (NULL != chan->partition)
{
- res = -EIO;
- break;
+ pos += chan->partition->start;
+ last = chan->partition->end;
+ }
+ else
+ {
+ last = info->blocks_num-1;
}
+
+ D(("disk write block=%d len=%d buf=%p\n", pos, *len, buf));
+
+ while( size > 0 )
+ {
+ cyg_uint32 tfr = size;
- res = (funs->write)(chan, (void*)bbuf, info->block_size, pos);
- if (ENOERR != res)
- break;
+ if (pos > last)
+ {
+ res = -EIO;
+ goto done;
+ }
+
+ if( tfr > info->ident.max_transfer )
+ tfr = info->ident.max_transfer;
+
+ ctlr->result = -EWOULDBLOCK;
+
+ cyg_drv_dsr_lock();
+
+ res = (funs->write)(chan, (void*)bbuf, tfr, pos);
+
+ if( res == -EWOULDBLOCK )
+ {
+ // If the driver replys EWOULDBLOCK, then the transfer is
+ // being handled asynchronously and when it is finished it
+ // will call disk_transfer_done(). This will wake us up here
+ // to continue.
+
+ while( ctlr->result == -EWOULDBLOCK )
+ cyg_drv_cond_wait( &ctlr->async );
+
+ res = ctlr->result;
+ }
+
+ cyg_drv_dsr_unlock();
+
+ if (ENOERR != res)
+ goto done;
- if (!info->connected)
- {
- res = -EINVAL;
- break;
+ if (!info->connected)
+ {
+ res = -EINVAL;
+ goto done;
+ }
+
+ bbuf += tfr * info->block_size;
+ pos += tfr;
+ size -= tfr;
+
}
- bbuf += info->block_size;
- pos++;
- size--;
+ ctlr->busy = false;
+ cyg_drv_cond_signal( &ctlr->queue );
}
+ else
+ res = -EINVAL;
+
+done:
+
+ cyg_drv_mutex_unlock( &ctlr->lock );
+#ifdef CYGPKG_KERNEL
+ cyg_thread_yield();
+#endif
+
*len -= size;
return res;
}
// ---------------------------------------------------------------------------
+static void disk_transfer_done(struct disk_channel *chan, Cyg_ErrNo res)
+{
+ disk_controller *ctlr = chan->controller;
+
+ ctlr->result = res;
+
+ cyg_drv_cond_signal( &ctlr->async );
+}
+
+// ---------------------------------------------------------------------------
+
static cyg_bool
disk_select(cyg_io_handle_t handle, cyg_uint32 which, CYG_ADDRWORD info)
{
{
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *) handle;
disk_channel *chan = (disk_channel *) t->priv;
+ disk_controller *ctlr = chan->controller;
cyg_disk_info_t *info = chan->info;
cyg_disk_info_t *buf = (cyg_disk_info_t *) xbuf;
disk_funs *funs = chan->funs;
Cyg_ErrNo res = ENOERR;
- if (!info->connected || !chan->valid)
- return -EINVAL;
+ cyg_drv_mutex_lock( &ctlr->lock );
+
+ while( ctlr->busy )
+ cyg_drv_cond_wait( &ctlr->queue );
- D(("disk get config key=%d\n", key));
+ if (info->connected && chan->valid)
+ {
+ ctlr->busy = true;
- switch (key) {
- case CYG_IO_GET_CONFIG_DISK_INFO:
- if (*len < sizeof(cyg_disk_info_t)) {
- return -EINVAL;
+ D(("disk get config key=%d\n", key));
+
+ switch (key) {
+ case CYG_IO_GET_CONFIG_DISK_INFO:
+ if (*len < sizeof(cyg_disk_info_t)) {
+ res = -EINVAL;
+ break;
+ }
+ D(("chan->info->block_size %u\n", chan->info->block_size ));
+ D(("chan->info->blocks_num %u\n", chan->info->blocks_num ));
+ D(("chan->info->phys_block_size %u\n", chan->info->phys_block_size ));
+ *buf = *chan->info;
+ *len = sizeof(cyg_disk_info_t);
+ break;
+
+ default:
+ // pass down to lower layers
+ res = (funs->get_config)(chan, key, xbuf, len);
}
- *buf = *chan->info;
- *len = sizeof(cyg_disk_info_t);
- break;
-
- default:
- // pass down to lower layers
- res = (funs->get_config)(chan, key, xbuf, len);
+
+ ctlr->busy = false;
+ cyg_drv_cond_signal( &ctlr->queue );
}
-
+ else
+ res = -EINVAL;
+
+ cyg_drv_mutex_unlock( &ctlr->lock );
+
return res;
}
{
cyg_devtab_entry_t *t = (cyg_devtab_entry_t *) handle;
disk_channel *chan = (disk_channel *) t->priv;
+ disk_controller *ctlr = chan->controller;
cyg_disk_info_t *info = chan->info;
disk_funs *funs = chan->funs;
+ Cyg_ErrNo res = ENOERR;
+
+ cyg_drv_mutex_lock( &ctlr->lock );
- if (!info->connected || !chan->valid)
- return -EINVAL;
+ while( ctlr->busy )
+ cyg_drv_cond_wait( &ctlr->queue );
- D(("disk set config key=%d\n", key));
-
- // pass down to lower layers
- return (funs->set_config)(chan, key, xbuf, len);
+ if (info->connected && chan->valid)
+ {
+ ctlr->busy = true;
+
+ D(("disk set config key=%d\n", key));
+
+ switch ( key )
+ {
+ case CYG_IO_SET_CONFIG_DISK_MOUNT:
+ chan->mounts++;
+ info->mounts++;
+ D(("disk mount: chan %d disk %d\n",chan->mounts, info->mounts));
+ break;
+
+ case CYG_IO_SET_CONFIG_DISK_UMOUNT:
+ chan->mounts--;
+ info->mounts--;
+ D(("disk umount: chan %d disk %d\n",chan->mounts, info->mounts));
+ break;
+
+ default:
+ break;
+ }
+
+ // pass down to lower layers
+ res = (funs->set_config)(chan, key, xbuf, len);
+
+ ctlr->busy = false;
+ cyg_drv_cond_signal( &ctlr->queue );
+ }
+ else
+ res = -EINVAL;
+
+ cyg_drv_mutex_unlock( &ctlr->lock );
+
+ return res;
+
}
// ---------------------------------------------------------------------------