]> git.kernelconcepts.de Git - karo-tx-redboot.git/blobdiff - packages/io/disk/v2_0/src/disk.c
unified MX27, MX25, MX37 trees
[karo-tx-redboot.git] / packages / io / disk / v2_0 / src / disk.c
index f5d9c02f10389c627791f128e48ba9252a27ca62..05a2708b1a22ebd9a64988ea2e8d8e758ea07cf2 100644 (file)
@@ -8,7 +8,7 @@
 //####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
@@ -58,7 +58,9 @@
 
 // ---------------------------------------------------------------------------
 
-//#define DEBUG 1
+#ifdef CYGDBG_IO_DISK_DEBUG
+#define DEBUG 1
+#endif
 
 #ifdef DEBUG
 # define D(_args_) diag_printf _args_
@@ -137,62 +139,150 @@ static cyg_bool disk_init(struct cyg_devtab_entry *tab);
 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;
@@ -223,6 +313,8 @@ read_mbr(disk_channel *chan)
     return ENOERR;
 }
 
+// ---------------------------------------------------------------------------
+
 static cyg_bool 
 disk_init(struct cyg_devtab_entry *tab)
 {
@@ -232,8 +324,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;
@@ -243,6 +346,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)
@@ -253,18 +358,31 @@ disk_connected(struct cyg_devtab_entry *tab,
  
     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
@@ -280,16 +398,17 @@ disk_connected(struct cyg_devtab_entry *tab,
     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;
      
@@ -306,6 +425,8 @@ disk_disconnected(struct cyg_devtab_entry *tab)
     return ENOERR;    
 }
     
+// ---------------------------------------------------------------------------
+
 static Cyg_ErrNo
 disk_lookup(struct cyg_devtab_entry **tab,
             struct cyg_devtab_entry  *sub_tab,
@@ -370,11 +491,12 @@ disk_lookup(struct cyg_devtab_entry **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;
@@ -382,43 +504,88 @@ disk_bread(cyg_io_handle_t  handle,
     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;
 }
@@ -428,11 +595,12 @@ disk_bread(cyg_io_handle_t  handle,
 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;
@@ -440,49 +608,106 @@ disk_bwrite(cyg_io_handle_t  handle,
     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)
 {
@@ -506,30 +731,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;
     
-    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;
 }
 
@@ -543,16 +787,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 );
 
-    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;
+    
 }
 
 // ---------------------------------------------------------------------------