changeset 2300:654c837cc618

* include/disk.h (DISK_CHANNEL): No need for extra _part_dev slot intended for entire disk's own devtab. * src/disk.c (disk_lookup): Just access pdevs_dev directly with no adjusted offset. (disk_init): No longer set entry 0 from device devtab. It's unnecessary. * include/disk.h: Merge eCosCentric CVS with public eCos CVS. This merges (and therefore changes) the API. ChangeLog entries have been incorporated in the correct places below. * include/diskio.h: Also merged. * cdl/io_disk.cdl: Also merged. * src/disk.c: Also merged. * src/disk.c (read_partition): Switched to favour LBA partition information in partition tables. This also means we don't now rely on the driver reporting a correct CHS size for the disk. (disk_bread, disk_bwrite): API changed to take length in sectors rather than bytes. Also call hardware driver to transfer in (up to) max_transfer sized chunks, rather than a sector at a time. Call in to driver now made with DSR lock claimed, to avoid a race condition between the DSR and the calling thread. (disk_get_config): Added phys_block_size to channel info. * include/diskio.h (struct cyg_disk_identify_t): Added phys_block_size and max_transfer fields. These must be filled in by the driver. * include/disk.h (DISK_CHANNEL_INIT): New macro. Allows a DISK_CHANNEL to be defined dynamically. * src/disk.c (disk_bread, disk_bwrite): Made calls to cyg_thread_yield() dependent on presence ok kernel. * src/disk.c (disk_bwrite): Uncomment the cyg_thread_yield() at the end to prevent thread starvation between threads at the same priority. (disk_bread): Ditto. * src/disk.c (read_partition): Changed to account for very large disks which report bogus CHS geometry. We can only use the LBA partition parameters in such disks. (disk_set_config): Added some extra debug output. * src/disk.c: Various changes to support disconnect/reconnect of changeable media. * include/diskio.h: Added mount counter to disk_info structure. * include/disk.h: Changed disk_disconnected() function to take a pointer to a struct disk_channel rather than a struct cyg_devtab_entry. Added mount count to disk_channel structure. Moved include of diskio.h to end to fix declaration problems. * src/disk.c: Fixed bug in write routine where controller result field was not being initialized. Added signals on controller queue condition variable after setting controller busy flag to false. * include/disk.h: Made controller result and busy fields volatile. Just in case. * include/disk.h: * src/disk.c: Many changes. Added additional disk_controller data structure to better reflect hardware structure. Added support for serialization of multithreaded accesses to a single controller. Added support for asynchronous, interrupt driven, IO operations. Various tidies.
author jlarmour
date Thu, 21 Sep 2006 16:38:38 +0000
parents 04006a2b020d
children 67647617a206
files packages/io/disk/current/ChangeLog packages/io/disk/current/include/disk.h packages/io/disk/current/include/diskio.h packages/io/disk/current/src/disk.c
diffstat 4 files changed, 596 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/packages/io/disk/current/ChangeLog
+++ b/packages/io/disk/current/ChangeLog
@@ -1,14 +1,73 @@
+2006-09-21  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* include/disk.h (DISK_CHANNEL): No need for extra _part_dev
+	slot intended for entire disk's own devtab.
+
+	* src/disk.c (disk_lookup): Just access pdevs_dev directly
+	with no adjusted offset.
+	(disk_init): No longer set entry 0 from device devtab.
+	It's unnecessary.
+
+2006-09-20  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* include/disk.h: Merge eCosCentric CVS with public eCos CVS.
+	This merges (and therefore changes) the API. ChangeLog
+	entries have been incorporated in the correct places below.
+	* include/diskio.h: Also merged.
+	* cdl/io_disk.cdl: Also merged.
+	* src/disk.c: Also merged.
+
 2006-08-18  Andy Jackson  <andy@xylanta.com>
 
 	* cdl/io_disk.cdl: Made debugging CDL controlled. 
 	* src/disk.c: Added support to allow non-CHS disk devices to 
 	use LBA information in MBR.
 
+2006-02-03  Nick Garnett  <nickg@ecoscentric.com>
+
+	* src/disk.c (read_partition): Switched to favour LBA partition
+	information in partition tables. This also means we don't now rely
+	on the driver reporting a correct CHS size for the disk.
+	(disk_bread, disk_bwrite): API changed to take length in sectors
+	rather than bytes. Also call hardware driver to transfer in
+	(up to) max_transfer sized chunks, rather than a sector at a
+	time. Call in to driver now made with DSR lock claimed, to avoid a
+	race condition between the DSR and the calling thread.
+	(disk_get_config): Added phys_block_size to channel info.
+
+	* include/diskio.h (struct cyg_disk_identify_t): Added
+	phys_block_size and max_transfer fields. These must be filled in
+	by the driver.
+
+2005-12-12  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* include/disk.h (DISK_CHANNEL_INIT): New macro. Allows a
+	DISK_CHANNEL to be defined dynamically.
+
 2005-12-02  Andrew Lunn  <andrew.lunn@ascom.ch>
 
 	* src/disk.c: Add comments that bread/bwrite take the position and
 	len in terms of blocks, not bytes.
 
+2005-10-11  Nick Garnett  <nickg@ecoscentric.com>
+
+	* src/disk.c (disk_bread, disk_bwrite): Made calls to
+	cyg_thread_yield() dependent on presence ok kernel.
+
+2005-06-01  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* src/disk.c (disk_bwrite): Uncomment the cyg_thread_yield() at
+	the end to prevent thread starvation between threads at the same
+	priority.
+	(disk_bread): Ditto.
+
+2004-07-21  Nick Garnett  <nickg@ecoscentric.com>
+
+	* src/disk.c (read_partition): Changed to account for very large
+	disks which report bogus CHS geometry. We can only use the LBA
+	partition parameters in such disks.
+	(disk_set_config): Added some extra debug output.
+
 2004-07-01  Savin Zlobec  <savin@elatec.si> 
 
  	* src/disk.c:
@@ -18,7 +77,39 @@ 2004-07-01  Savin Zlobec  <savin@elatec.
  	radher than malloc. Extended DISK_CHANNEL macro to
   	support defining maximum number of partitions.
 
-2004-01-15  Nick Garnett  <nickg@calivar.com>
+2004-04-15  Nick Garnett  <nickg@ecoscentric.com>
+
+	* src/disk.c: 
+	Various changes to support disconnect/reconnect of changeable
+	media.
+
+	* include/diskio.h: Added mount counter to disk_info structure.
+
+	* include/disk.h: Changed disk_disconnected() function to take a
+	pointer to a struct disk_channel rather than a struct
+	cyg_devtab_entry. Added mount count to disk_channel structure.
+	Moved include of diskio.h to end to fix declaration problems.
+
+2004-02-04  Nick Garnett  <nickg@ecoscentric.com>
+
+	* src/disk.c: Fixed bug in write routine where controller result
+	field was not being initialized. Added signals on controller queue
+	condition variable after setting controller busy flag to false.
+
+	* include/disk.h: Made controller result and busy fields
+	volatile. Just in case.
+
+2004-01-23  Nick Garnett  <nickg@ecoscentric.com>
+
+	* include/disk.h: 
+	* src/disk.c:
+	Many changes. Added additional disk_controller data structure to
+	better reflect hardware structure. Added support for serialization
+	of multithreaded accesses to a single controller. Added support
+	for asynchronous, interrupt driven, IO operations. Various
+	tidies.
+
+2004-01-15  Nick Garnett  <nickg@ecoscentric.com>
 
 	* src/disk.c:
 	* include/disk.h: Removed block_pos arguments from
@@ -48,7 +139,8 @@ 2003-09-01 Savin Zlobec <savin@elatec.si
 //####ECOSGPLCOPYRIGHTBEGIN####
 // -------------------------------------------
 // This file is part of eCos, the Embedded Configurable Operating System.
-// Copyright (C) 2003 Savin Zlobec 
+// Copyright (C) 2003 Savin Zlobec
+// Copyright (C) 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
--- a/packages/io/disk/current/include/disk.h
+++ b/packages/io/disk/current/include/disk.h
@@ -11,7 +11,8 @@
 // -------------------------------------------
 // This file is part of eCos, the Embedded Configurable Operating System.
 // Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
-// Copyright (C) 2003 Savin Zlobec 
+// Copyright (C) 2003 Savin Zlobec
+// Copyright (C) 2004, 2005 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
@@ -59,12 +60,22 @@
 #include <pkgconf/io_disk.h>
 
 #include <cyg/infra/cyg_type.h>
+#include <cyg/io/devtab.h>
 #include <cyg/io/io.h>
-#include <cyg/io/diskio.h>
 #include <cyg/hal/drv_api.h>
+#include <string.h> /* memset() */
 
-typedef struct disk_channel disk_channel;
-typedef struct disk_funs    disk_funs;
+// ---------------------------------------------------------------------------
+
+typedef struct cyg_disk_partition_t cyg_disk_partition_t;
+typedef struct cyg_disk_info_t cyg_disk_info_t;
+typedef struct cyg_disk_identify_t cyg_disk_identify_t;
+
+typedef struct disk_channel    disk_channel;
+typedef struct disk_controller disk_controller;
+typedef struct disk_funs       disk_funs;
+
+// ---------------------------------------------------------------------------
 
 // Pointers into upper-level driver
 typedef struct {
@@ -77,33 +88,62 @@ typedef struct {
                                 cyg_disk_identify_t     *ident);
 
     // Disk device has been disconnected
-    Cyg_ErrNo (*disk_disconnected)(struct cyg_devtab_entry *tab);
+    Cyg_ErrNo (*disk_disconnected)(struct disk_channel *chan);
 
     // Lookup disk device
     Cyg_ErrNo (*disk_lookup)(struct cyg_devtab_entry **tab,
                              struct cyg_devtab_entry  *sub_tab,
                              const char               *name);
+
+    // Asynchronous block transfer done
+    void (*disk_transfer_done)(struct disk_channel *chan, Cyg_ErrNo res);
+    
 } disk_callbacks_t;
 
-#define DISK_CALLBACKS(_l,                              \
-                       _init,                           \
-                       _connected,                      \
-                       _disconnected,                   \
-                       _lookup)                         \
-disk_callbacks_t _l = {                                 \
-    _init,                                              \
-    _connected,                                         \
-    _disconnected,                                      \
-    _lookup                                             \
+#define DISK_CALLBACKS(_l,                      \
+                       _init,                   \
+                       _connected,              \
+                       _disconnected,           \
+                       _lookup,                 \
+                       _transfer_done)          \
+disk_callbacks_t _l = {                         \
+    _init,                                      \
+    _connected,                                 \
+    _disconnected,                              \
+    _lookup,                                    \
+    _transfer_done                              \
 };
 
 extern disk_callbacks_t cyg_io_disk_callbacks;
 
+// ---------------------------------------------------------------------------
+// Private data which describes a disk controller
+
+struct disk_controller {
+    cyg_drv_mutex_t     lock;           // Per-controller lock
+    cyg_drv_cond_t      queue;          // Access wait list
+    cyg_drv_cond_t      async;          // Async transfer waits here
+    void                *priv;          // Private data
+    volatile Cyg_ErrNo  result;         // Last operation result
+    cyg_bool            init;           // Initialized?
+    volatile cyg_bool   busy;           // Busy?
+};
+
+#define DISK_CONTROLLER(_l, _priv)              \
+static disk_controller _l = {                   \
+    priv:       &_priv,                         \
+    init:       false,                          \
+    busy:       false                           \
+};
+
+// ---------------------------------------------------------------------------
 // Private data which describes this channel
+
 struct disk_channel {
     disk_funs               *funs;
     disk_callbacks_t        *callbacks;
     void                    *dev_priv;    // device private data
+    disk_controller         *controller;  // pointer to controller
     cyg_disk_info_t         *info;        // disk info 
     cyg_disk_partition_t    *partition;   // partition data 
     struct cyg_devtab_entry *pdevs_dev;   // partition devs devtab ents 
@@ -111,12 +151,14 @@ struct disk_channel {
     cyg_bool                 mbr_support; // true if disk has MBR
     cyg_bool                 valid;       // true if device valid 
     cyg_bool                 init;        // true if initialized
+    cyg_ucount16             mounts;      // count of number of mounts
 };
 
 // Initialization macro for disk channel
 #define DISK_CHANNEL(_l,                                              \
                      _funs,                                           \
                      _dev_priv,                                       \
+                     _controller,                                     \
                      _mbr_supp,                                       \
                      _max_part_num)                                   \
 static struct cyg_devtab_entry _l##_part_dev[_max_part_num];          \
@@ -130,16 +172,49 @@ static disk_channel _l = {              
     &(_funs),                                                         \
     &cyg_io_disk_callbacks,                                           \
     &(_dev_priv),                                                     \
+    &(_controller),                                                   \
     &(_l##_disk_info),                                                \
     NULL,                                                             \
     _l##_part_dev,                                                    \
     _l##_part_chan,                                                   \
     _mbr_supp,                                                        \
     false,                                                            \
-    false                                                             \
+    false,                                                            \
+    0                                                                 \
 };
 
+// Initialization macro for disk channel allocated elsewhere.
+#define DISK_CHANNEL_INIT(_dc,                           \
+                          _funs,                         \
+                          _dev_priv,                     \
+                          _controller,                   \
+                          _disk_info,                    \
+                          _part_dev,                     \
+                          _part_chan,                    \
+                          _part_tab,                     \
+                          _mbr_supp,                     \
+                          _max_part_num)                 \
+    CYG_MACRO_START                                      \
+    memset((_disk_info), 0, sizeof(cyg_disk_info_t));    \
+    (_dc).funs = &(_funs);                               \
+    (_dc).callbacks = &cyg_io_disk_callbacks;            \
+    (_dc).dev_priv = (_dev_priv);                        \
+    (_dc).controller = &(_controller);                   \
+    (_dc).info = &(_disk_info);                          \
+    (_dc).info->partitions = (_part_tab);                \
+    (_dc).pdevs_dev = (_part_dev);                       \
+    (_dc).pdevs_chan = (_part_chan);                     \
+    (_dc).partition = NULL;                              \
+    (_dc).mbr_support = (_mbr_supp);                     \
+    (_dc).valid = false;                                 \
+    (_dc).init = false;                                  \
+    (_dc).mounts = 0;                                    \
+    (_dc).info->partitions_num = (_max_part_num);        \
+    CYG_MACRO_END
+
+// ---------------------------------------------------------------------------
 // Low level interface functions
+
 struct disk_funs {
 
     // Read block data into buf
@@ -168,7 +243,7 @@ struct disk_funs {
 };
 
 #define DISK_FUNS(_l,_read,_write,_get_config,_set_config)           \
-disk_funs _l = {                                                     \
+static disk_funs _l = {                                              \
   _read,                                                             \
   _write,                                                            \
   _get_config,                                                       \
@@ -177,4 +252,9 @@ disk_funs _l = {                        
 
 extern cyg_devio_table_t cyg_io_disk_devio;
 
+// ---------------------------------------------------------------------------
+
+#include <cyg/io/diskio.h>
+
+// ---------------------------------------------------------------------------
 #endif // CYGONCE_DISK_H
--- a/packages/io/disk/current/include/diskio.h
+++ b/packages/io/disk/current/include/diskio.h
@@ -10,7 +10,8 @@
 //####ECOSGPLCOPYRIGHTBEGIN####
 // -------------------------------------------
 // This file is part of eCos, the Embedded Configurable Operating System.
-// Copyright (C) 2003 Savin Zlobec 
+// Copyright (C) 2003 Savin Zlobec
+// Copyright (C) 2004, 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
@@ -55,11 +56,14 @@
 #include <cyg/infra/cyg_type.h>
 #include <cyg/io/config_keys.h>
 
+#include <cyg/io/devtab.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
-
-typedef struct {
+    
+struct cyg_disk_identify_t
+{
     char        serial[20+1];      // serial number
     char        firmware_rev[8+1]; // firmware revision
     char        model_num[40+1];   // model number 
@@ -67,24 +71,30 @@ typedef struct {
     cyg_uint32  heads_num;         // number of heads             (CHS)
     cyg_uint32  sectors_num;       // number of sectors per track (CHS)
     cyg_uint32  lba_sectors_num;   // total number of sectors in LBA mode
-} cyg_disk_identify_t;
+    cyg_uint32  phys_block_size;   // physical block size in sectors
+    cyg_uint32  max_transfer;      // Maximum transfer size in bytes
+};
 
-typedef struct {
+struct  cyg_disk_partition_t
+{
     cyg_uint8  type;    // partition type
     cyg_uint8  state;   // state 0x00 - inactive, 0x80 - active
     cyg_uint32 start;   // first sector number
     cyg_uint32 end;     // last sector number
     cyg_uint32 size;    // size in sectors
-} cyg_disk_partition_t;
+};
 
-typedef struct {
-    cyg_disk_partition_t *partitions;    // partition table
-    int                   partitions_num;// partition table size
-    cyg_disk_identify_t   ident;         // identify data
-    cyg_uint32            block_size;    // block size
-    cyg_uint32            blocks_num;    // number of blocks on disk
-    cyg_bool              connected;     // true if device connected
-} cyg_disk_info_t;
+struct  cyg_disk_info_t
+{
+    cyg_disk_partition_t       *partitions;    // partition table
+    int                         partitions_num;// partition table size
+    cyg_disk_identify_t         ident;         // identify data
+    cyg_uint32                  block_size;    // logical block size
+    cyg_uint32                  blocks_num;    // number of blocks on disk
+    cyg_uint32                  phys_block_size; // physical block size
+    cyg_bool                    connected;     // true if device connected
+    cyg_ucount16                mounts;        // total number of mounts for all partitions
+};
 
 #ifdef __cplusplus
 }
--- a/packages/io/disk/current/src/disk.c
+++ b/packages/io/disk/current/src/disk.c
@@ -8,7 +8,8 @@
 //####ECOSGPLCOPYRIGHTBEGIN####
 // -------------------------------------------
 // This file is part of eCos, the Embedded Configurable Operating System.
-// Copyright (C) 2003 Savin Zlobec 
+// Copyright (C) 2003 Savin Zlobec
+// Copyright (C) 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
@@ -139,24 +140,27 @@ static cyg_bool disk_init(struct cyg_dev
 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,
@@ -164,56 +168,122 @@ read_partition(cyg_uint8            *dat
 {
     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);
 
-    // If disk doesn't have cylinders/heads, use LBA data rather than CHS
-    if (ident->cylinders_num == 0 && ident->heads_num == 0 &&
-        ident->sectors_num== 0)
+    READ_DWORD(&data[8], start);        
+    READ_DWORD(&data[12], size);
+
+    // Use the LBA start and size fields if they are valid. Otherwise
+    // fall back to CHS.
+    
+    if( start > 0 && size > 0 )
     {
-        // Use LBA data to determine disk size
-        READ_DWORD(&data[8], part->start);
-        part->end = (part->start + part->size) - 1;
-        D(("LBA partition data (%d,%d,%d)\n", 
-           part->start, part->end, part->size));
+        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
     {
-        // Use CHS data to determine disk size
         READ_CHS(&data[1], c, h, s);
-        D(("partition start CHS %d,%d,%d\n", c, h, s));
-        CHS_TO_LBA(ident, c, h, s, part->start);
-
+        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);
-        D(("partition end CHS %d,%d,%d\n", c, h, s));
-        CHS_TO_LBA(ident, c, h, s, part->end);
-        D(("CHS partition data (%d,%d,%d)\n", 
-           part->start, part->end, part->size));
+        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;
@@ -244,6 +314,8 @@ read_mbr(disk_channel *chan)
     return ENOERR;
 }
 
+// ---------------------------------------------------------------------------
+
 static cyg_bool 
 disk_init(struct cyg_devtab_entry *tab)
 {
@@ -253,8 +325,19 @@ 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;
@@ -264,6 +347,8 @@ disk_init(struct cyg_devtab_entry *tab)
     return true;
 }
 
+// ---------------------------------------------------------------------------
+
 static Cyg_ErrNo
 disk_connected(struct cyg_devtab_entry *tab,
                cyg_disk_identify_t     *ident)
@@ -274,18 +359,31 @@ disk_connected(struct cyg_devtab_entry *
  
     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
@@ -301,16 +399,17 @@ disk_connected(struct cyg_devtab_entry *
     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;
      
@@ -327,6 +426,8 @@ disk_disconnected(struct cyg_devtab_entr
     return ENOERR;    
 }
     
+// ---------------------------------------------------------------------------
+
 static Cyg_ErrNo
 disk_lookup(struct cyg_devtab_entry **tab,
             struct cyg_devtab_entry  *sub_tab,
@@ -396,6 +497,7 @@ disk_bread(cyg_io_handle_t  handle,
 {
     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;
@@ -403,43 +505,88 @@ disk_bread(cyg_io_handle_t  handle,
     Cyg_ErrNo   res  = ENOERR;
     cyg_uint32  last;
 
-    if (!info->connected || !chan->valid)
-        return -EINVAL;
+    cyg_drv_mutex_lock( &ctlr->lock );
+
+    while( ctlr->busy )
+        cyg_drv_cond_wait( &ctlr->queue );
+
+    if (info->connected && chan->valid)
+    {
+        ctlr->busy = true;
     
-    if (NULL != chan->partition)
-    {
-        pos += chan->partition->start;
-        last = chan->partition->end;
+        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_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();
+            
+            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
-    {
-        last = info->blocks_num-1;
-    }
- 
-    D(("disk read block=%d len=%d buf=%p\n", pos, *len, buf)); 
- 
-    while (size > 0)
-    {
-        if (pos > last)
-        {
-            res = -EIO;
-            break;
-        }
-        
-        res = (funs->read)(chan, (void*)bbuf, info->block_size, pos);
-        if (ENOERR != res)
-            break;
+        res = -EINVAL;
 
-        if (!info->connected)
-        {
-            res = -EINVAL;
-            break;
-        }
-            
-        bbuf += info->block_size;
-        pos++;
-        size--;
-    }
+done:
+    
+    cyg_drv_mutex_unlock( &ctlr->lock );
+#ifdef CYGPKG_KERNEL
+    cyg_thread_yield();
+#endif
+    
     *len -= size;
     return res;
 }
@@ -454,6 +601,7 @@ disk_bwrite(cyg_io_handle_t  handle,
 {
     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;
@@ -461,49 +609,106 @@ disk_bwrite(cyg_io_handle_t  handle,
     Cyg_ErrNo   res  = ENOERR;
     cyg_uint32  last;
 
-    if (!info->connected || !chan->valid)
-        return -EINVAL;
+    cyg_drv_mutex_lock( &ctlr->lock );
+
+    while( ctlr->busy )
+        cyg_drv_cond_wait( &ctlr->queue );
+
+    if (info->connected && chan->valid)
+    {
+        ctlr->busy = true;
+        
+        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_uint32 tfr = size;
+        
+            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 (NULL != chan->partition)
-    {
-        pos += chan->partition->start;
-        last = chan->partition->end;
+            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
-    {
-        last = info->blocks_num-1;
-    }
+        res = -EINVAL;
+
+done:
     
-    D(("disk write block=%d len=%d buf=%p\n", pos, *len, buf)); 
-   
-    while (size > 0)
-    {
-        if (pos > last)
-        {
-            res = -EIO;
-            break;
-        }
-        
-        res = (funs->write)(chan, (void*)bbuf, info->block_size, pos);
-        if (ENOERR != res)
-            break;
- 
-        if (!info->connected)
-        {
-            res = -EINVAL;
-            break;
-        }
- 
-        bbuf += info->block_size;
-        pos++;
-        size--;
-    }
+    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)
 {
@@ -527,30 +732,49 @@ disk_get_config(cyg_io_handle_t  handle,
 {
     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;
+    
+        D(("disk get config key=%d\n", key)); 
     
-    switch (key) {
-    case CYG_IO_GET_CONFIG_DISK_INFO:
-        if (*len < sizeof(cyg_disk_info_t)) {
-            return -EINVAL;
+        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;       
+        
+        ctlr->busy = false;
+        cyg_drv_cond_signal( &ctlr->queue );
+    }
+    else
+        res = -EINVAL;
 
-    default:
-        // pass down to lower layers
-        res = (funs->get_config)(chan, key, xbuf, len);
-    }
-   
+    cyg_drv_mutex_unlock( &ctlr->lock );    
+    
     return res;
 }
 
@@ -564,16 +788,53 @@ disk_set_config(cyg_io_handle_t  handle,
 {
     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 );
+
+    if (info->connected && chan->valid)
+    {
+        ctlr->busy = true;
+        
+        D(("disk set config key=%d\n", key)); 
 
-    D(("disk set config key=%d\n", key)); 
- 
-    // pass down to lower layers
-    return (funs->set_config)(chan, key, xbuf, len);
+        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;
+    
 }
 
 // ---------------------------------------------------------------------------