--- /dev/null
+/*
+ * Zheng Cui
+ * cuizheng@cs.unm.edu
+ * July 2008
+ */
+
+#include <devices/ramdisk.h>
+#include <palacios/vmm.h>
+#include <string.h>
+
+#ifdef DEBUG_RAMDISK
+#define Ramdisk_Print(_f, _a...) PrintTrace("\nramdisk.c(%d) " _f, __LINE__, ## _a)
+#else 
+#define Ramdisk_Print(_f, _a...)
+#endif
+
+#define RD_PANIC(_f, _a...)     \
+  do {                       \
+    PrintDebug("ramdisk.c(%d) " _f, __LINE__, ## _a);  \
+      while(1);              \
+    }                        \
+   while(0)                                                            
+    
+#define RD_ERROR(_f, _a...) PrintTrace("\nramdisk.c(%d) " _f, __LINE__, ## _a)
+
+/*
+ * Debug facilities
+ */
+
+#define ATA_DETECT                     0xf0 //0X3E8
+#define ATA_RESET                      0xf1 //0X3E9
+#define ATA_CMD_DATA_IN                0xf2 //0X3EA
+#define ATA_CMD_DATA_OUT               0xf3 //0X3EB
+#define ATA_CMD_PACKET                 0xf4 //0X3EC
+#define ATAPI_GET_SENSE                0xf5 //0X3ED
+#define ATAPI_IS_READY                 0xf6 //0X3EE
+#define ATAPI_IS_CDROM                 0xf7 //0X3EF
+
+#define CDEMU_INIT                     0xf8 //0X2E8
+#define CDEMU_ISACTIVE                 0xf9 //0X2E9
+#define CDEMU_EMULATED_DRIVE           0xfa //0X2EA
+#define CDROM_BOOT                     0xfb //0X2EB
+
+
+#define HARD_DRIVE_POST                0xfc //0X2EC
+
+
+#define ATA_DEVICE_NO                  0xfd //0X2ED
+#define ATA_DEVICE_TYPE                0xfe //0X2ED
+
+#define INT13_HARDDISK                 0xff //0x2ef
+#define INT13_CDROM                    0xe0 //0x2f8
+#define INT13_CDEMU                    0xe1 //0x2f9
+#define INT13_ELTORITO                 0xe2 //0x2fa
+#define INT13_DISKETTE_FUNCTION        0xe3 //0x2fb
+
+// some packet handling macros
+#define EXTRACT_FIELD(arr,byte,start,num_bits) (((arr)[(byte)] >> (start)) & ((1 << (num_bits)) - 1))
+#define get_packet_field(c,b,s,n) (EXTRACT_FIELD((SELECTED_CONTROLLER((c)).buffer),(b),(s),(n)))
+#define get_packet_byte(c,b) (SELECTED_CONTROLLER((c)).buffer[(b)])
+#define get_packet_word(c,b) (((uint16)SELECTED_CONTROLLER((c)).buffer[(b)] << 8) | SELECTED_CONTROLLER((c)).buffer[(b)+1])
+
+
+#define CONTROLLER(c,a) (channels[(c)].drives[(a)]).controller
+#define DRIVE(c,a) (channels[(c)].drives[(a)])
+#define SELECTED_CONTROLLER(c) (CONTROLLER((c), channels[(c)].drive_select))
+#define SELECTED_DRIVE(c) (DRIVE((c), channels[(c)].drive_select))
+
+
+#define DRIVE_IS_PRESENT(c,a) (channels[(c)].drives[(a)].device_type != IDE_NONE)
+#define DRIVE_IS_HD(c,a) (channels[(c)].drives[(a)].device_type == IDE_DISK)
+#define DRIVE_IS_CD(c,a) (channels[(c)].drives[(a)].device_type == IDE_CDROM)
+#define SELECTED_MODEL(c) (channels[(c)].drives[channels[(c)].drive_select].model_no)
+
+#define MASTER_SELECTED(c) (!channels[(c)].drive_select)
+#define SLAVE_SELECTED(c)  (channels[(c)].drive_select)
+
+#define SELECTED_IS_PRESENT(c) (DRIVE_IS_PRESENT((c),SLAVE_SELECTED((c))))
+#define SELECTED_IS_HD(c) (DRIVE_IS_HD((c),SLAVE_SELECTED((c))))
+#define SELECTED_IS_CD(c) (DRIVE_IS_CD((c),SLAVE_SELECTED((c))))
+
+#define ANY_IS_PRESENT(c) (DRIVE_IS_PRESENT((c),0) || DRIVE_IS_PRESENT((c),1))
+#define SELECTED_TYPE_STRING(channel) ((SELECTED_IS_CD(channel)) ? "CD-ROM" : "NONE")
+
+#define WRITE_FEATURES(c,a) do { uint8 _a = a; CONTROLLER((c),0).features = _a; CONTROLLER((c),1).features = _a; } while(0)
+#define WRITE_SECTOR_COUNT(c,a) do { uint8 _a = a; CONTROLLER((c),0).sector_count = _a; CONTROLLER((c),1).sector_count = _a; } while(0)
+#define WRITE_SECTOR_NUMBER(c,a) do { uint8 _a = a; CONTROLLER((c),0).sector_no = _a; CONTROLLER((c),1).sector_no = _a; } while(0)
+#define WRITE_CYLINDER_LOW(c,a) do { uint8 _a = a; CONTROLLER((c),0).cylinder_no = (CONTROLLER((c),0).cylinder_no & 0xff00) | _a; CONTROLLER((c),1).cylinder_no = (CONTROLLER((c),1).cylinder_no & 0xff00) | _a; } while(0)
+#define WRITE_CYLINDER_HIGH(c,a) do { uint16 _a = a; CONTROLLER((c),0).cylinder_no = (_a << 8) | (CONTROLLER((c),0).cylinder_no & 0xff); CONTROLLER((c),1).cylinder_no = (_a << 8) | (CONTROLLER((c),1).cylinder_no & 0xff); } while(0)
+#define WRITE_HEAD_NO(c,a) do { uint8 _a = a; CONTROLLER((c),0).head_no = _a; CONTROLLER((c),1).head_no = _a; } while(0)
+#define WRITE_LBA_MODE(c,a) do { uint8 _a = a; CONTROLLER((c),0).lba_mode = _a; CONTROLLER((c),1).lba_mode = _a; } while(0)
+
+
+
+#define GOTO_RETURN_VALUE  if(io_len==4){\
+                             goto return_value32;\
+                             }\
+                           else if(io_len==2){\
+                             value16=(Bit16u)value32;\
+                             goto return_value16;\
+                             }\
+                           else{\
+                             value8=(Bit8u)value32;\
+                             goto return_value8;\
+                             }
+
+#define UNUSED(x) ((void)x)
+
+#define PACKET_SIZE 12
+
+static struct ramdisk_t *ramdisk_state;
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Static routines
+ */
+
+static
+uint_t ramdisk_read_port(ushort_t port,
+                        void *src,
+                        uint_t length,
+                        struct vm_device *dev);
+
+static
+uint_t ramdisk_write_port(ushort_t port,
+                         void *src,
+                         uint_t length,
+                         struct vm_device *dev);
+
+static
+uint_t ramdisk_read_port_ignore(ushort_t port,
+                               void *src,
+                               uint_t length,
+                               struct vm_device *dev);
+
+static
+uint_t ramdisk_write_port_ignore(ushort_t port,
+                                void *src,
+                                uint_t length,
+                                struct vm_device *dev);
+
+
+static
+Bit32u rd_read_handler(struct channel_t *channels, Bit32u address, unsigned io_len);
+
+static
+void rd_write_handler(struct channel_t *channels, Bit32u address, 
+                     Bit32u value, unsigned io_len);
+
+
+
+/*
+ * ATAPI routines
+ */
+
+static 
+void rd_identify_ATAPI_drive(struct channel_t *channels, Bit8u channel);
+
+
+static 
+void rd_init_send_atapi_command(struct channel_t *channels, Bit8u channel, Bit8u command, int req_length, int alloc_length, bool lazy /*= false*/);
+
+static 
+void rd_ready_to_send_atapi(struct channel_t *channels, Bit8u channel);
+
+
+static 
+void rd_atapi_cmd_error(struct channel_t *channels, Bit8u channel, sense_t sense_key, asc_t asc);
+
+static 
+void rd_init_mode_sense_single(struct channel_t *channels, Bit8u channel, const void* src, int size);
+
+static 
+void rd_atapi_cmd_nop(struct channel_t *channels, Bit8u channel);
+
+static
+void rd_command_aborted(struct channel_t *channels, Bit8u channel, unsigned value);
+
+
+/*
+ * Interrupt handling
+ */
+
+static 
+void rd_raise_interrupt(struct channel_t *channels, Bit8u channel);
+
+static 
+void rd_lower_irq(struct vm_device *dev, Bit32u irq);
+
+
+
+/*
+ * Helper routines
+ */
+
+uint16 rd_read_16bit(const uint8* buf) 
+{
+  return (buf[0] << 8) | buf[1];
+}
+
+
+
+uint32 rd_read_32bit(const uint8* buf) 
+{
+  return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
+////////////////////////////////////////////////////////////////////
+
+
+void rd_print_state(struct ramdisk_t *ramdisk, 
+                        struct vm_device *dev)
+//Bit32u   rd_init_harddrive(struct channel_t *channels)
+{
+
+  uchar_t channel; 
+  uchar_t device;
+  struct channel_t *channels = (struct channel_t *)(&(ramdisk->channels));
+
+  for (channel = 0; channel < MAX_ATA_CHANNEL; channel++)
+    memset((char *)(channels + channel), 0, sizeof(struct channel_t));
+
+  Ramdisk_Print("sizeof(*channels) = %d\n", sizeof((*channels)));
+  Ramdisk_Print("sizeof(channles->drives[0].controller) = %d\n", sizeof((channels->drives[0].controller)));
+  Ramdisk_Print("sizeof(channles->drives[0].cdrom) = %d\n", sizeof((channels->drives[0].cdrom)));
+  Ramdisk_Print("sizeof(channles->drives[0].sense) = %d\n", sizeof((channels->drives[0].sense)));
+  Ramdisk_Print("sizeof(channles->drives[0].atapi) = %d\n", sizeof((channels->drives[0].atapi)));
+
+
+  Ramdisk_Print("sizeof(channles->drives[0].controller.status) = %d\n", sizeof((channels->drives[0].controller.status)));
+  Ramdisk_Print("sizeof(channles->drives[0].controller.sector_count) = %d\n", sizeof((channels->drives[0].controller.sector_count)));
+  Ramdisk_Print("sizeof(channles->drives[0].controller.interrupt_reason) = %d\n", sizeof((channels->drives[0].controller.interrupt_reason)));
+
+  Ramdisk_Print("sizeof(channles->drives[0].controller.cylinder_no) = %d\n", sizeof((channels->drives[0].controller.cylinder_no)));
+  Ramdisk_Print("sizeof(channles->drives[0].controller.byte_count) = %d\n", sizeof((channels->drives[0].controller.byte_count)));
+
+
+  Ramdisk_Print("sizeof(channles->drives[0].controller.control) = %d\n", sizeof((channels->drives[0].controller.control)));
+
+
+  for (channel = 0; channel < MAX_ATA_CHANNEL; channel++){
+  
+    for (device = 0; device < 2; device++){
+                  
+      // Initialize controller state, even if device is not present
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.busy = %d\n",channel, device, channels[channel].drives[device].controller.status.busy);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.drive_ready = %d\n", channel, device, channels[channel].drives[device].controller.status.drive_ready);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.write_fault = %d\n", channel, device, channels[channel].drives[device].controller.status.write_fault);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.seek_complete = %d\n", channel, device, channels[channel].drives[device].controller.status.seek_complete);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.drq = %d\n", channel, device, channels[channel].drives[device].controller.status.drq);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.corrected_data = %d\n", channel, device, channels[channel].drives[device].controller.status.corrected_data);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.index_pulse = %d\n", channel, device, channels[channel].drives[device].controller.status.index_pulse);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.index_pulse_count = %d\n", channel, device, channels[channel].drives[device].controller.status.index_pulse_count);
+      Ramdisk_Print("channels[%d].drives[%d].controller.status.err = %d\n", channel, device, channels[channel].drives[device].controller.status.err);
+
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.error_register = %d\n", channel, device, channels[channel].drives[device].controller.error_register);
+      Ramdisk_Print("channels[%d].drives[%d].controller.head_no = %d\n", channel, device, channels[channel].drives[device].controller.head_no);
+      Ramdisk_Print("channels[%d].drives[%d].controller.sector_count = %d\n", channel, device, channels[channel].drives[device].controller.sector_count);
+      Ramdisk_Print("channels[%d].drives[%d].controller.sector_no = %d\n", channel, device, channels[channel].drives[device].controller.sector_no);
+      Ramdisk_Print("channels[%d].drives[%d].controller.cylinder_no = %d\n", channel, device, channels[channel].drives[device].controller.cylinder_no);
+      Ramdisk_Print("channels[%d].drives[%d].controller.current_command = %02x\n", channel, device, channels[channel].drives[device].controller.current_command);
+      Ramdisk_Print("channels[%d].drives[%d].controller.buffer_index = %d\n", channel, device, channels[channel].drives[device].controller.buffer_index);
+
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.control.reset = %d\n", channel, device, channels[channel].drives[device].controller.control.reset);
+      Ramdisk_Print("channels[%d].drives[%d].controller.control.disable_irq = %d\n", channel, device, channels[channel].drives[device].controller.control.disable_irq);
+
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.reset_in_progress = %d\n", channel, device, channels[channel].drives[device].controller.reset_in_progress);
+      Ramdisk_Print("channels[%d].drives[%d].controller.sectors_per_block = %02x\n", channel, device, channels[channel].drives[device].controller.sectors_per_block); 
+      Ramdisk_Print("channels[%d].drives[%d].controller.lba_mode = %d\n", channel, device, channels[channel].drives[device].controller.lba_mode); 
+      Ramdisk_Print("channels[%d].drives[%d].controller.features = %d\n", channel, device, channels[channel].drives[device].controller.features); 
+
+
+      Ramdisk_Print("channels[%d].drives[%d].model_no = %s\n", channel, device, channels[channel].drives[device].model_no); 
+      Ramdisk_Print("channels[%d].drives[%d].device_type = %d\n", channel, device, channels[channel].drives[device].device_type); 
+      Ramdisk_Print("channels[%d].drives[%d].cdrom.locked = %d\n", channel, device, channels[channel].drives[device].cdrom.locked); 
+      Ramdisk_Print("channels[%d].drives[%d].sense.sense_key = %d\n", channel, device, channels[channel].drives[device].sense.sense_key); 
+      Ramdisk_Print("channels[%d].drives[%d].sense.asc = %d\n", channel, device, channels[channel].drives[device].sense.asc); 
+      Ramdisk_Print("channels[%d].drives[%d].sense.ascq = %d\n", channel, device, channels[channel].drives[device].sense.ascq); 
+
+
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.c_d = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.c_d);
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.i_o = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.i_o);
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.rel = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.rel);
+
+      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.tag = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.tag);
+
+      Ramdisk_Print("channels[%d].drives[%d].cdrom.ready = %d\n", channel, device, channels[channel].drives[device].cdrom.ready);
+      
+    }//for device
+  }//for channel
+  
+  return;
+}
+
+
+Bit32u rd_init_harddrive(struct ramdisk_t *ramdisk, 
+                        struct vm_device *dev)
+{
+
+  
+  uchar_t channel; 
+  uchar_t device;
+  struct channel_t *channels = (struct channel_t *)(&(ramdisk->channels));
+
+  Ramdisk_Print("[rd_init_harddrive]\n");
+  for (channel = 0; channel < MAX_ATA_CHANNEL; channel++)
+    memset((char *)(channels + channel), 0, sizeof(struct channel_t));
+
+
+  for (channel = 0; channel < MAX_ATA_CHANNEL; channel++){
+  
+    channels[channel].ioaddr1 = 0x0;
+    channels[channel].ioaddr2 = 0x0;
+    channels[channel].irq = 0;
+
+    for (device = 0; device < 2; device++){
+      
+
+      CONTROLLER(channel,device).status.busy           = 0;
+      CONTROLLER(channel,device).status.drive_ready    = 1;
+      CONTROLLER(channel,device).status.write_fault    = 0;
+      CONTROLLER(channel,device).status.seek_complete  = 1;
+      CONTROLLER(channel,device).status.drq            = 0;
+      CONTROLLER(channel,device).status.corrected_data = 0;
+      CONTROLLER(channel,device).status.index_pulse    = 0;
+      CONTROLLER(channel,device).status.index_pulse_count = 0;
+      CONTROLLER(channel,device).status.err            = 0;
+
+      CONTROLLER(channel,device).error_register = 0x01; // diagnostic code: no error
+      CONTROLLER(channel,device).head_no        = 0;
+      CONTROLLER(channel,device).sector_count   = 1;
+      CONTROLLER(channel,device).sector_no      = 1;
+      CONTROLLER(channel,device).cylinder_no    = 0;
+      CONTROLLER(channel,device).current_command = 0x00;
+      CONTROLLER(channel,device).buffer_index = 0;
+
+      CONTROLLER(channel,device).control.reset       = 0;
+      CONTROLLER(channel,device).control.disable_irq = 0;
+      CONTROLLER(channel,device).reset_in_progress   = 0;
+
+      CONTROLLER(channel,device).sectors_per_block   = 0x80;
+      CONTROLLER(channel,device).lba_mode            = 0;
+      
+      CONTROLLER(channel,device).features            = 0;
+       
+         // If not present
+      channels[channel].drives[device].device_type           = IDE_NONE;
+
+      // Make model string
+      strncpy((char*)channels[channel].drives[device].model_no, 
+             "", 40);
+      while(strlen((char *)channels[channel].drives[device].model_no) < 40) {
+        strcat ((char*)channels[channel].drives[device].model_no, " ");
+      }
+
+//      Ramdisk_Print("channels[%d].drives[%d].controller.current_command = %02x\n", channel, device, channels[channel].drives[device].controller.current_command);
+//      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.c_d = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.c_d);
+
+//      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.i_o = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.i_o);
+
+//      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.rel = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.rel);
+
+//      Ramdisk_Print("channels[%d].drives[%d].controller.interrupt_reason.tag = %02x\n", channel, device, channels[channel].drives[device].controller.interrupt_reason.tag);
+      
+//      Ramdisk_Print("channels[%d].drives[%d].controller.control.disable_irq = %d\n", channel, device, channels[channel].drives[device].controller.control.disable_irq);
+      
+      
+      
+      if (channel == 1) {
+
+       channels[channel].ioaddr1 = 0x170;
+       channels[channel].ioaddr2 = 0x370;
+       channels[channel].irq =  15;
+       channels[channel].drive_select = 0;
+       
+       if (device == 0) {
+         // Make model string
+         strncpy((char*)channels[channel].drives[device].model_no, 
+                 "Zheng's Ramdisk", 40);
+         while (strlen((char *)channels[channel].drives[device].model_no) < 40) {
+           strcat ((char*)channels[channel].drives[device].model_no, " ");
+         }
+
+         Ramdisk_Print("CDROM on target %d/%d\n", channel, device);
+         
+         channels[channel].drives[device].device_type = IDE_CDROM;
+         channels[channel].drives[device].cdrom.locked = 0;
+         channels[channel].drives[device].sense.sense_key = SENSE_NONE;
+         channels[channel].drives[device].sense.asc = 0;
+         channels[channel].drives[device].sense.ascq = 0;
+         
+
+         //Check bit fields
+         channels[channel].drives[device].controller.sector_count = 0;
+         channels[channel].drives[device].controller.interrupt_reason.c_d = 1;
+         if (channels[channel].drives[device].controller.sector_count != 0x01) {
+           Ramdisk_Print("interrupt reason bit field error\n");
+           return INTR_REASON_BIT_ERR;
+         }
+
+         channels[channel].drives[device].controller.sector_count = 0;
+         channels[channel].drives[device].controller.interrupt_reason.i_o = 1;
+         if (channels[channel].drives[device].controller.sector_count != 0x02) {
+           Ramdisk_Print("interrupt reason bit field error\n");
+           return INTR_REASON_BIT_ERR;
+         }
+         
+         channels[channel].drives[device].controller.sector_count = 0;
+         channels[channel].drives[device].controller.interrupt_reason.rel = 1;
+         if (channels[channel].drives[device].controller.sector_count != 0x04) {
+           Ramdisk_Print("interrupt reason bit field error\n");
+           return INTR_REASON_BIT_ERR;
+         }
+       
+         channels[channel].drives[device].controller.sector_count = 0;
+         channels[channel].drives[device].controller.interrupt_reason.tag = 3;
+         if (channels[channel].drives[device].controller.sector_count != 0x18) {
+           Ramdisk_Print("interrupt reason bit field error\n");
+           return INTR_REASON_BIT_ERR;
+         }
+         
+         
+         channels[channel].drives[device].controller.sector_count = 0;
+
+         // allocate low level driver
+         channels[channel].drives[device].cdrom.cd = (struct cdrom_interface*)V3_Malloc(sizeof(struct cdrom_interface));
+         Ramdisk_Print("cd = %x\n", channels[channel].drives[device].cdrom.cd);
+         V3_ASSERT(channels[channel].drives[device].cdrom.cd != NULL);
+         
+         struct cdrom_interface *cdif = channels[channel].drives[device].cdrom.cd;
+         memset(cdif, 0, sizeof(struct cdrom_interface));
+         init_cdrom(cdif);
+         cdif->ops.init(cdif);
+
+         Ramdisk_Print("\t\tCD on ata%d-%d: '%s'\n",channel, device, "");
+         
+         if((channels[channel].drives[device].cdrom.cd->ops).insert_cdrom(cdif, NULL)) {
+           Ramdisk_Print("\t\tMedia present in CD-ROM drive\n");
+           channels[channel].drives[device].cdrom.ready = 1;
+           channels[channel].drives[device].cdrom.capacity = channels[channel].drives[device].cdrom.cd->ops.capacity(cdif);
+         } else {                  
+           Ramdisk_Print("\t\tCould not locate CD-ROM, continuing with media not present\n");
+           channels[channel].drives[device].cdrom.ready = 0;
+         }
+         
+       }//if device = 0
+      }//if channel = 0
+    }//for device
+  }//for channel
+
+  ramdisk->private_data = dev;
+  ramdisk_state = ramdisk;
+  Ramdisk_Print("ramdisk_state = %x\n", ramdisk_state);
+  Ramdisk_Print("ramdisk = %x\n", ramdisk);
+  //  rd_print_state(ramdisk, dev);
+  return 0;
+}
+
+
+void   rd_reset_harddrive(struct ramdisk_t *ramdisk, unsigned type)
+{
+  return;
+}
+
+
+void   rd_close_harddrive(struct ramdisk_t *ramdisk)
+{
+  return;
+}
+
+
+////////////////////////////////////////////////////////////////////
+
+
+static
+Bit32u rd_read_handler(struct channel_t *channels, Bit32u address, unsigned io_len)
+{
+  Bit8u value8;
+  Bit16u value16;
+  Bit32u value32;
+
+  unsigned drive_select;
+  unsigned index;      
+  unsigned increment = 0;
+
+  Bit8u  channel = MAX_ATA_CHANNEL;
+  Bit32u port = 0xff; // undefined
+
+  Ramdisk_Print("[rd_read_handler]\n");
+
+  for (channel=0; channel<MAX_ATA_CHANNEL; channel++) {
+    if ((address & 0xfff8) == channels[channel].ioaddr1) {
+      port = address - channels[channel].ioaddr1;
+      break;
+      }
+    else if ((address & 0xfff8) == channels[channel].ioaddr2) {
+      port = address - channels[channel].ioaddr2 + 0x10;
+      break;
+      }
+    }
+
+  if (channel == MAX_ATA_CHANNEL) {
+    if ((address < 0x03f6) || (address > 0x03f7)) {
+      RD_PANIC("Error: read: unable to find ATA channel, ioport=0x%04x\n", address);
+      return 0;
+    } else {
+      channel = 0;
+      port = address - 0x03e0;
+    }
+  }
+
+  drive_select = channels[channel].drive_select;
+  if (port != 0x00){
+    Ramdisk_Print("[R_handler] IO read addr at %x, on drive %d/%d, curcmd = %02x\n", address, channel, drive_select, SELECTED_CONTROLLER(channel).current_command);
+  }else{
+    
+  }
+
+
+  struct cdrom_interface *cdif = channels[channel].drives[drive_select].cdrom.cd;
+  switch (port) {
+
+  case 0x00: // hard disk data (16bit) 0x1f0
+
+    switch (SELECTED_CONTROLLER(channel).current_command) {
+    case 0xec:    // IDENTIFY DEVICE
+    case 0xa1:
+
+      index = 0;
+      SELECTED_CONTROLLER(channel).status.busy = 0;
+      SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+      SELECTED_CONTROLLER(channel).status.write_fault = 0;
+      SELECTED_CONTROLLER(channel).status.seek_complete = 1;
+      SELECTED_CONTROLLER(channel).status.corrected_data = 0;
+      SELECTED_CONTROLLER(channel).status.err = 0;
+       
+      index = SELECTED_CONTROLLER(channel).buffer_index;
+      value32 = SELECTED_CONTROLLER(channel).buffer[index];
+      index++;
+
+      if (io_len >= 2) {
+       value32 |= (SELECTED_CONTROLLER(channel).buffer[index] << 8);
+       index++;
+      }
+      if (io_len == 4) {
+       value32 |= (SELECTED_CONTROLLER(channel).buffer[index] << 16);
+       value32 |= (SELECTED_CONTROLLER(channel).buffer[index+1] << 24);
+       index += 2;
+      }
+      SELECTED_CONTROLLER(channel).buffer_index = index;
+      
+      if (SELECTED_CONTROLLER(channel).buffer_index >= 512) {
+       
+       SELECTED_CONTROLLER(channel).status.drq = 0;
+      }
+      GOTO_RETURN_VALUE;
+
+    case 0xa0: //send packet cmd 
+
+       index = SELECTED_CONTROLLER(channel).buffer_index;
+       increment = 0;
+       Ramdisk_Print("\t\tatapi.command(%02x), index(%d), cdrom.remaining_blocks(%d)\n", SELECTED_DRIVE(channel).atapi.command, index, SELECTED_DRIVE(channel).cdrom.remaining_blocks);
+       // Load block if necessary
+       if (index >= 2048) {
+         if (index > 2048)
+           RD_PANIC("\t\tindex > 2048 : 0x%x\n",index);
+         switch (SELECTED_DRIVE(channel).atapi.command) {
+         case 0x28: // read (10)
+         case 0xa8: // read (12)
+
+           if (!SELECTED_DRIVE(channel).cdrom.ready) {
+             RD_PANIC("\t\tRead with CDROM not ready\n");
+           } 
+           SELECTED_DRIVE(channel).cdrom.cd->ops.read_block(cdif, SELECTED_CONTROLLER(channel).buffer,
+                                                           SELECTED_DRIVE(channel).cdrom.next_lba);
+           SELECTED_DRIVE(channel).cdrom.next_lba++;
+           SELECTED_DRIVE(channel).cdrom.remaining_blocks--;
+           
+           
+           if (!SELECTED_DRIVE(channel).cdrom.remaining_blocks)
+             Ramdisk_Print("\t\tLast READ block loaded {CDROM}\n");
+           else
+             Ramdisk_Print("\t\tREAD block loaded (%d remaining) {CDROM}\n",
+                           SELECTED_DRIVE(channel).cdrom.remaining_blocks);
+           
+           // one block transfered, start at beginning
+           index = 0;
+           break;
+           
+         default: // no need to load a new block
+           break;
+         }
+       }
+
+       value32 = SELECTED_CONTROLLER(channel).buffer[index+increment];
+       increment++;
+       if (io_len >= 2) {
+         value32 |= (SELECTED_CONTROLLER(channel).buffer[index+increment] << 8);
+         increment++;
+       }
+       if (io_len == 4) {
+         value32 |= (SELECTED_CONTROLLER(channel).buffer[index+increment] << 16);
+         value32 |= (SELECTED_CONTROLLER(channel).buffer[index+increment+1] << 24);
+         increment += 2;
+       }
+       SELECTED_CONTROLLER(channel).buffer_index = index + increment;
+       SELECTED_CONTROLLER(channel).drq_index += increment;
+       
+       if (SELECTED_CONTROLLER(channel).drq_index >= (unsigned)SELECTED_DRIVE(channel).atapi.drq_bytes) {
+         SELECTED_CONTROLLER(channel).status.drq = 0;
+         SELECTED_CONTROLLER(channel).drq_index = 0;
+         
+         SELECTED_DRIVE(channel).atapi.total_bytes_remaining -= SELECTED_DRIVE(channel).atapi.drq_bytes;
+         
+         if (SELECTED_DRIVE(channel).atapi.total_bytes_remaining > 0) {
+           // one or more blocks remaining (works only for single block commands)
+
+           Ramdisk_Print("\t\tPACKET drq bytes read\n");
+           SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1;
+           SELECTED_CONTROLLER(channel).status.busy = 0;
+           SELECTED_CONTROLLER(channel).status.drq = 1;
+           SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 0;
+           
+           // set new byte count if last block
+           if (SELECTED_DRIVE(channel).atapi.total_bytes_remaining < SELECTED_CONTROLLER(channel).byte_count) {
+             SELECTED_CONTROLLER(channel).byte_count = SELECTED_DRIVE(channel).atapi.total_bytes_remaining;
+           }
+           SELECTED_DRIVE(channel).atapi.drq_bytes = SELECTED_CONTROLLER(channel).byte_count;
+           
+           rd_raise_interrupt(channels, channel);
+         } else {
+           // all bytes read
+           Ramdisk_Print("\t\tPACKET all bytes read\n");
+           SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1;
+           SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 1;
+           SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+           SELECTED_CONTROLLER(channel).interrupt_reason.rel = 0;
+           SELECTED_CONTROLLER(channel).status.busy = 0;
+           SELECTED_CONTROLLER(channel).status.drq = 0;
+           SELECTED_CONTROLLER(channel).status.err = 0;
+           
+           rd_raise_interrupt(channels, channel);
+         }
+       }
+       GOTO_RETURN_VALUE;
+       break;
+      
+    default:
+      Ramdisk_Print("\t\tread need support more command: %02x\n", SELECTED_CONTROLLER(channel).current_command);
+      break;
+    }
+    ///////////////////////////////////////////
+
+  case 0x01: // hard disk error register 0x1f1
+
+    SELECTED_CONTROLLER(channel).status.err = 0;
+    value8 = (!SELECTED_IS_PRESENT(channel)) ? 0 : SELECTED_CONTROLLER(channel).error_register;
+    goto return_value8;
+    break;
+  case 0x02: // hard disk sector count / interrupt reason 0x1f2
+    value8 = (!SELECTED_IS_PRESENT(channel)) ? 0 : SELECTED_CONTROLLER(channel).sector_count;
+    goto return_value8;
+    break;
+  case 0x03: // sector number 0x1f3
+    value8 = (!SELECTED_IS_PRESENT(channel)) ? 0 : SELECTED_CONTROLLER(channel).sector_no;
+    goto return_value8;
+  case 0x04: // cylinder low 0x1f4  
+    // -- WARNING : On real hardware the controller registers are shared between drives. 
+    // So we must respond even if the select device is not present. Some OS uses this fact 
+    // to detect the disks.... minix2 for example
+    value8 = (!ANY_IS_PRESENT(channel)) ? 0 : (SELECTED_CONTROLLER(channel).cylinder_no & 0x00ff);
+    goto return_value8;
+  case 0x05: // cylinder high 0x1f5
+    // -- WARNING : On real hardware the controller registers are shared between drives. 
+    // So we must respond even if the select device is not present. Some OS uses this fact 
+    // to detect the disks.... minix2 for example
+    value8 = (!ANY_IS_PRESENT(channel)) ? 0 : SELECTED_CONTROLLER(channel).cylinder_no >> 8;
+    goto return_value8;
+    
+  case 0x06: // hard disk drive and head register 0x1f6
+    // b7 Extended data field for ECC
+    // b6/b5: Used to be sector size.  00=256,01=512,10=1024,11=128
+    //   Since 512 was always used, bit 6 was taken to mean LBA mode:
+    //     b6 1=LBA mode, 0=CHS mode
+    //     b5 1
+    // b4: DRV
+    // b3..0 HD3..HD0
+    value8 = (1 << 7) |
+      ((SELECTED_CONTROLLER(channel).lba_mode>0) << 6) |
+      (1 << 5) | // 01b = 512 sector size
+      (channels[channel].drive_select << 4) |
+      (SELECTED_CONTROLLER(channel).head_no << 0);
+    goto return_value8;
+    break;
+    //CONTROLLER(channel,0).lba_mode
+    
+  case 0x07: // Hard Disk Status 0x1f7
+  case 0x16: // Hard Disk Alternate Status 0x3f6
+    if (!ANY_IS_PRESENT(channel)) {
+      // (mch) Just return zero for these registers
+      value8 = 0;
+    } else {
+      value8 = (
+               (SELECTED_CONTROLLER(channel).status.busy << 7) |
+               (SELECTED_CONTROLLER(channel).status.drive_ready << 6) |
+               (SELECTED_CONTROLLER(channel).status.write_fault << 5) |
+               (SELECTED_CONTROLLER(channel).status.seek_complete << 4) |
+               (SELECTED_CONTROLLER(channel).status.drq << 3) |
+               (SELECTED_CONTROLLER(channel).status.corrected_data << 2) |
+               (SELECTED_CONTROLLER(channel).status.index_pulse << 1) |
+               (SELECTED_CONTROLLER(channel).status.err) );
+      SELECTED_CONTROLLER(channel).status.index_pulse_count++;
+      SELECTED_CONTROLLER(channel).status.index_pulse = 0;
+      if (SELECTED_CONTROLLER(channel).status.index_pulse_count >= INDEX_PULSE_CYCLE) {
+        SELECTED_CONTROLLER(channel).status.index_pulse = 1;
+        SELECTED_CONTROLLER(channel).status.index_pulse_count = 0;
+      }
+    }
+    if (port == 0x07) {
+      rd_lower_irq((struct vm_device *)(ramdisk_state->private_data), channels[channel].irq);
+    }
+    goto return_value8;
+    break;
+    
+  case 0x17: // Hard Disk Address Register 0x3f7
+    // Obsolete and unsupported register.  Not driven by hard
+    // disk controller.  Report all 1's.  If floppy controller
+    // is handling this address, it will call this function
+    // set/clear D7 (the only bit it handles), then return
+    // the combined value
+    value8 = 0xff;
+    goto return_value8;
+    break;
+    
+  default:
+    RD_PANIC("hard drive: io read to address %x unsupported\n",
+             (unsigned) address);
+    
+    
+    ////////////////////////////////////////////
+  }
+  
+  Ramdisk_Print("\t\tError: hard drive: shouldnt get here!\n");
+  return 0;
+  
+return_value32:
+  Ramdisk_Print("\t\t32-bit read from %04x = %08x {%s}\n",
+           (unsigned) address, value32, SELECTED_TYPE_STRING(channel));
+  return value32;
+  
+ return_value16:
+  Ramdisk_Print("\t\t16-bit read from %04x = %04x {%s}\n",
+           (unsigned) address, value16, SELECTED_TYPE_STRING(channel));
+  return value16;
+  
+ return_value8:
+  Ramdisk_Print("\t\t8-bit read from %x = %02x {%s}\n",
+           (unsigned) address, value8, SELECTED_TYPE_STRING(channel));
+  return value8;
+}
+
+
+static
+void rd_write_handler(struct channel_t *channels, Bit32u address, 
+                     Bit32u value, unsigned io_len)
+{
+
+  //  off_t logical_sector;
+  //  off_t ret;
+  rd_bool prev_control_reset;
+
+  Bit32u id;
+  int toc_length;
+
+  Bit8u  channel = MAX_ATA_CHANNEL;
+  Bit32u port = 0xff; // undefined
+
+  Ramdisk_Print("[rd_write_handler]\n");
+  //  Bit8u atapi_command;
+  //int alloc_length;
+
+  for (channel=0; channel<MAX_ATA_CHANNEL; channel++) {
+    if ((address & 0xfff8) == channels[channel].ioaddr1) {
+      port = address - channels[channel].ioaddr1;
+      break;
+    }
+    else if ((address & 0xfff8) == channels[channel].ioaddr2) {
+      port = address - channels[channel].ioaddr2 + 0x10;
+      break;
+      }
+    }
+
+  if (channel == MAX_ATA_CHANNEL) {
+    if (address != 0x03f6) {
+      RD_PANIC("Panic: write: unable to find ATA channel, ioport=0x%04x\n", address);
+    } else {
+      channel = 0;
+      port = address - 0x03e0;
+    }
+  }
+
+  Ramdisk_Print("[W_handler] IO write to %x = %02x, channel = %d\n", (unsigned) address, (unsigned) value, channel);
+
+  struct cdrom_interface *cdif = SELECTED_DRIVE(channel).cdrom.cd;
+
+  switch (port) {
+
+  case 0x00: // 0x1f0
+    Ramdisk_Print("\t\twrite port 170\n");
+
+    //////////////////////////////////////////////////////////
+    switch (SELECTED_CONTROLLER(channel).current_command) {
+    case 0x30: // WRITE SECTORS
+      RD_PANIC("\t\tneed to implement 0x30(write sector) to port 0x170\n");
+      break;
+      
+    case 0xa0: // PACKET
+      if (SELECTED_CONTROLLER(channel).buffer_index >= PACKET_SIZE)
+       RD_PANIC("IO write(0x%04x): buffer_index >= PACKET_SIZE", address);
+      SELECTED_CONTROLLER(channel).buffer[SELECTED_CONTROLLER(channel).buffer_index] = value;
+      SELECTED_CONTROLLER(channel).buffer[SELECTED_CONTROLLER(channel).buffer_index+1] = (value >> 8);
+      SELECTED_CONTROLLER(channel).buffer_index += 2;
+      
+      /* if packet completely writtten */
+      if (SELECTED_CONTROLLER(channel).buffer_index >= PACKET_SIZE) {
+       // complete command received
+       Bit8u atapi_command = SELECTED_CONTROLLER(channel).buffer[0];
+       
+       Ramdisk_Print("\t\tcdrom: ATAPI command 0x%x started\n", atapi_command);
+
+       switch (atapi_command) {
+       case 0x00: // test unit ready
+         if (SELECTED_DRIVE(channel).cdrom.ready) {
+           rd_atapi_cmd_nop(channels, channel);
+         } else {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+         }
+         rd_raise_interrupt(channels, channel);
+         break;
+         
+       case 0x03: { // request sense
+         int alloc_length = SELECTED_CONTROLLER(channel).buffer[4];
+         rd_init_send_atapi_command(channels, channel, atapi_command, 18, alloc_length, false);
+                                   
+         // sense data
+         SELECTED_CONTROLLER(channel).buffer[0] = 0x70 | (1 << 7);
+         SELECTED_CONTROLLER(channel).buffer[1] = 0;
+         SELECTED_CONTROLLER(channel).buffer[2] = SELECTED_DRIVE(channel).sense.sense_key;
+         SELECTED_CONTROLLER(channel).buffer[3] = SELECTED_DRIVE(channel).sense.information.arr[0];
+         SELECTED_CONTROLLER(channel).buffer[4] = SELECTED_DRIVE(channel).sense.information.arr[1];
+         SELECTED_CONTROLLER(channel).buffer[5] = SELECTED_DRIVE(channel).sense.information.arr[2];
+         SELECTED_CONTROLLER(channel).buffer[6] = SELECTED_DRIVE(channel).sense.information.arr[3];
+         SELECTED_CONTROLLER(channel).buffer[7] = 17-7;
+         SELECTED_CONTROLLER(channel).buffer[8] = SELECTED_DRIVE(channel).sense.specific_inf.arr[0];
+         SELECTED_CONTROLLER(channel).buffer[9] = SELECTED_DRIVE(channel).sense.specific_inf.arr[1];
+         SELECTED_CONTROLLER(channel).buffer[10] = SELECTED_DRIVE(channel).sense.specific_inf.arr[2];
+         SELECTED_CONTROLLER(channel).buffer[11] = SELECTED_DRIVE(channel).sense.specific_inf.arr[3];
+         SELECTED_CONTROLLER(channel).buffer[12] = SELECTED_DRIVE(channel).sense.asc;
+         SELECTED_CONTROLLER(channel).buffer[13] = SELECTED_DRIVE(channel).sense.ascq;
+         SELECTED_CONTROLLER(channel).buffer[14] = SELECTED_DRIVE(channel).sense.fruc;
+         SELECTED_CONTROLLER(channel).buffer[15] = SELECTED_DRIVE(channel).sense.key_spec.arr[0];
+         SELECTED_CONTROLLER(channel).buffer[16] = SELECTED_DRIVE(channel).sense.key_spec.arr[1];
+         SELECTED_CONTROLLER(channel).buffer[17] = SELECTED_DRIVE(channel).sense.key_spec.arr[2];
+         
+         rd_ready_to_send_atapi(channels, channel);
+       }
+         break;
+         
+       case 0x1b: { // start stop unit
+         //bx_bool Immed = (SELECTED_CONTROLLER(channel).buffer[1] >> 0) & 1;
+         rd_bool LoEj = (SELECTED_CONTROLLER(channel).buffer[4] >> 1) & 1;
+         rd_bool Start = (SELECTED_CONTROLLER(channel).buffer[4] >> 0) & 1;
+         
+         if (!LoEj && !Start) { // stop the disc
+           RD_ERROR("FIXME: Stop disc not implemented\n");
+           rd_atapi_cmd_nop(channels, channel);
+           rd_raise_interrupt(channels, channel);
+         } else if (!LoEj && Start) { // start (spin up) the disc
+
+           SELECTED_DRIVE(channel).cdrom.cd->ops.start_cdrom(cdif);
+
+           RD_ERROR("FIXME: ATAPI start disc not reading TOC\n");
+           rd_atapi_cmd_nop(channels, channel);
+           rd_raise_interrupt(channels, channel);
+         } else if (LoEj && !Start) { // Eject the disc
+           rd_atapi_cmd_nop(channels, channel);
+           
+           if (SELECTED_DRIVE(channel).cdrom.ready) {
+
+             SELECTED_DRIVE(channel).cdrom.cd->ops.eject_cdrom(cdif);
+
+             SELECTED_DRIVE(channel).cdrom.ready = 0;
+             //bx_options.atadevice[channel][SLAVE_SELECTED(channel)].Ostatus->set(EJECTED);
+             //bx_gui->update_drive_status_buttons();
+           }
+           rd_raise_interrupt(channels, channel);
+         } else { // Load the disc
+           // My guess is that this command only closes the tray, that's a no-op for us
+           rd_atapi_cmd_nop(channels, channel);
+           rd_raise_interrupt(channels, channel);
+         }
+       }
+         break;
+         
+       case 0xbd: { // mechanism status
+         uint16 alloc_length = rd_read_16bit(SELECTED_CONTROLLER(channel).buffer + 8);
+         
+         if (alloc_length == 0)
+           RD_PANIC("Zero allocation length to MECHANISM STATUS not impl.\n");
+         
+         rd_init_send_atapi_command(channels, channel, atapi_command, 8, alloc_length, false);
+         
+         SELECTED_CONTROLLER(channel).buffer[0] = 0; // reserved for non changers
+         SELECTED_CONTROLLER(channel).buffer[1] = 0; // reserved for non changers
+         
+         SELECTED_CONTROLLER(channel).buffer[2] = 0; // Current LBA (TODO!)
+         SELECTED_CONTROLLER(channel).buffer[3] = 0; // Current LBA (TODO!)
+         SELECTED_CONTROLLER(channel).buffer[4] = 0; // Current LBA (TODO!)
+         
+         SELECTED_CONTROLLER(channel).buffer[5] = 1; // one slot
+         
+         SELECTED_CONTROLLER(channel).buffer[6] = 0; // slot table length
+         SELECTED_CONTROLLER(channel).buffer[7] = 0; // slot table length
+         
+         rd_ready_to_send_atapi(channels, channel);
+       }
+         break;
+         
+       case 0x5a: { // mode sense
+         uint16 alloc_length = rd_read_16bit(SELECTED_CONTROLLER(channel).buffer + 7);
+         
+         Bit8u PC = SELECTED_CONTROLLER(channel).buffer[2] >> 6;
+         Bit8u PageCode = SELECTED_CONTROLLER(channel).buffer[2] & 0x3f;
+         
+         switch (PC) {
+         case 0x0: // current values
+           switch (PageCode) {
+           case 0x01: // error recovery
+             rd_init_send_atapi_command(channels, channel, atapi_command, sizeof(struct error_recovery_t) + 8, alloc_length, false);
+             
+             rd_init_mode_sense_single(channels, channel, &SELECTED_DRIVE(channel).cdrom.current.error_recovery,
+                                    sizeof(struct error_recovery_t));
+             rd_ready_to_send_atapi(channels, channel);
+             break;
+             
+           case 0x2a: // CD-ROM capabilities & mech. status
+             rd_init_send_atapi_command(channels, channel, atapi_command, 28, alloc_length, false);
+             rd_init_mode_sense_single(channels, channel, &SELECTED_CONTROLLER(channel).buffer[8], 28);
+             SELECTED_CONTROLLER(channel).buffer[8] = 0x2a;
+             SELECTED_CONTROLLER(channel).buffer[9] = 0x12;
+             SELECTED_CONTROLLER(channel).buffer[10] = 0x00;
+             SELECTED_CONTROLLER(channel).buffer[11] = 0x00;
+             // Multisession, Mode 2 Form 2, Mode 2 Form 1
+             SELECTED_CONTROLLER(channel).buffer[12] = 0x70; 
+             SELECTED_CONTROLLER(channel).buffer[13] = (3 << 5);
+             SELECTED_CONTROLLER(channel).buffer[14] = (unsigned char)
+               (1 |
+                (SELECTED_DRIVE(channel).cdrom.locked ? (1 << 1) : 0) |
+                (1 << 3) |
+                (1 << 5));
+             SELECTED_CONTROLLER(channel).buffer[15] = 0x00;
+             SELECTED_CONTROLLER(channel).buffer[16] = (706 >> 8) & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[17] = 706 & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[18] = 0;
+             SELECTED_CONTROLLER(channel).buffer[19] = 2;
+             SELECTED_CONTROLLER(channel).buffer[20] = (512 >> 8) & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[21] = 512 & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[22] = (706 >> 8) & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[23] = 706 & 0xff;
+             SELECTED_CONTROLLER(channel).buffer[24] = 0;
+             SELECTED_CONTROLLER(channel).buffer[25] = 0;
+             SELECTED_CONTROLLER(channel).buffer[26] = 0;
+             SELECTED_CONTROLLER(channel).buffer[27] = 0;
+             rd_ready_to_send_atapi(channels, channel);
+             break;
+             
+           case 0x0d: // CD-ROM
+           case 0x0e: // CD-ROM audio control
+           case 0x3f: // all
+             RD_ERROR("cdrom: MODE SENSE (curr), code=%x not implemented yet\n",
+                       PageCode);
+           rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                           ASC_INV_FIELD_IN_CMD_PACKET);
+           rd_raise_interrupt(channels, channel);
+           break;
+             
+         default:
+           // not implemeted by this device
+           Ramdisk_Print("\t\tcdrom: MODE SENSE PC=%x, PageCode=%x, not implemented by device\n",
+                    PC, PageCode);
+           rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                           ASC_INV_FIELD_IN_CMD_PACKET);
+           rd_raise_interrupt(channels, channel);
+           break;
+         }
+         break;
+           
+         case 0x1: // changeable values
+           switch (PageCode) {
+           case 0x01: // error recovery
+           case 0x0d: // CD-ROM
+           case 0x0e: // CD-ROM audio control
+           case 0x2a: // CD-ROM capabilities & mech. status
+           case 0x3f: // all
+             RD_ERROR("cdrom: MODE SENSE (chg), code=%x not implemented yet\n",
+                      PageCode);
+           rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                             ASC_INV_FIELD_IN_CMD_PACKET);
+           rd_raise_interrupt(channels, channel);
+           break;
+           
+         default:
+             // not implemeted by this device
+           Ramdisk_Print("\t\tcdrom: MODE SENSE PC=%x, PageCode=%x, not implemented by device\n",
+                         PC, PageCode);
+         rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                            ASC_INV_FIELD_IN_CMD_PACKET);
+         rd_raise_interrupt(channels, channel);
+         break;
+       }
+         break;
+         
+       case 0x2: // default values
+         switch (PageCode) {
+         case 0x01: // error recovery
+         case 0x0d: // CD-ROM
+         case 0x0e: // CD-ROM audio control
+         case 0x2a: // CD-ROM capabilities & mech. status
+         case 0x3f: // all
+           RD_PANIC("cdrom: MODE SENSE (dflt), code=%x\n",
+                    PageCode);
+           break;
+           
+           default:
+             // not implemeted by this device
+             Ramdisk_Print("\t\tcdrom: MODE SENSE PC=%x, PageCode=%x, not implemented by device\n",
+                     PC, PageCode);
+             rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                                ASC_INV_FIELD_IN_CMD_PACKET);
+             rd_raise_interrupt(channels, channel);
+             break;
+         }
+         break;
+         
+       case 0x3: // saved values not implemented
+         rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+         rd_raise_interrupt(channels, channel);
+         break;
+         
+       default:
+         RD_PANIC("Should not get here!\n");
+         break;
+       }
+      }
+      break;
+      
+    case 0x12: { // inquiry
+      uint8 alloc_length = SELECTED_CONTROLLER(channel).buffer[4];
+      
+      rd_init_send_atapi_command(channels, channel, atapi_command, 36, alloc_length, false);
+      
+      SELECTED_CONTROLLER(channel).buffer[0] = 0x05; // CD-ROM
+      SELECTED_CONTROLLER(channel).buffer[1] = 0x80; // Removable
+      SELECTED_CONTROLLER(channel).buffer[2] = 0x00; // ISO, ECMA, ANSI version
+      SELECTED_CONTROLLER(channel).buffer[3] = 0x21; // ATAPI-2, as specified
+      SELECTED_CONTROLLER(channel).buffer[4] = 31; // additional length (total 36)
+      SELECTED_CONTROLLER(channel).buffer[5] = 0x00; // reserved
+      SELECTED_CONTROLLER(channel).buffer[6] = 0x00; // reserved
+      SELECTED_CONTROLLER(channel).buffer[7] = 0x00; // reserved
+      
+      // Vendor ID
+      const char* vendor_id = "VTAB    ";
+      int i;
+      for (i = 0; i < 8; i++)
+       SELECTED_CONTROLLER(channel).buffer[8+i] = vendor_id[i];
+      
+      // Product ID
+      const char* product_id = "Turbo CD-ROM    ";
+      for (i = 0; i < 16; i++)
+       SELECTED_CONTROLLER(channel).buffer[16+i] = product_id[i];
+      
+      // Product Revision level
+      const char* rev_level = "1.0 ";
+      for (i = 0; i < 4; i++)
+       SELECTED_CONTROLLER(channel).buffer[32+i] = rev_level[i];
+      
+      rd_ready_to_send_atapi(channels, channel);
+    }
+      break;
+      
+       case 0x25: { // read cd-rom capacity
+         // no allocation length???
+         rd_init_send_atapi_command(channels, channel, atapi_command, 8, 8, false);
+      
+         if (SELECTED_DRIVE(channel).cdrom.ready) {
+           uint32 capacity = SELECTED_DRIVE(channel).cdrom.capacity;
+           Ramdisk_Print("\t\tCapacity is %d sectors (%d bytes)\n", capacity, capacity * 2048);
+           SELECTED_CONTROLLER(channel).buffer[0] = (capacity >> 24) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[1] = (capacity >> 16) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[2] = (capacity >> 8) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[3] = (capacity >> 0) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[4] = (2048 >> 24) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[5] = (2048 >> 16) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[6] = (2048 >> 8) & 0xff;
+           SELECTED_CONTROLLER(channel).buffer[7] = (2048 >> 0) & 0xff;
+           rd_ready_to_send_atapi(channels, channel);
+         } else {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+           rd_raise_interrupt(channels, channel);
+         }
+       }
+         break;
+      
+       case 0xbe: { // read cd
+         if (SELECTED_DRIVE(channel).cdrom.ready) {
+           RD_ERROR("Read CD with CD present not implemented\n");
+           rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
+           rd_raise_interrupt(channels, channel);
+         } else {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+           rd_raise_interrupt(channels, channel);
+         }
+       }
+         break;
+      
+       case 0x43: { // read toc
+         if (SELECTED_DRIVE(channel).cdrom.ready) {
+           
+           bool msf = (SELECTED_CONTROLLER(channel).buffer[1] >> 1) & 1;
+           uint8 starting_track = SELECTED_CONTROLLER(channel).buffer[6];
+           
+           uint16 alloc_length = rd_read_16bit(SELECTED_CONTROLLER(channel).buffer + 7);
+           
+           uint8 format = (SELECTED_CONTROLLER(channel).buffer[9] >> 6);
+           int i;
+           switch (format) {
+           case 0:
+             
+             if (!(SELECTED_DRIVE(channel).cdrom.cd->ops.read_toc(cdif, SELECTED_CONTROLLER(channel).buffer,
+                                                              &toc_length, msf, starting_track))) {
+               rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                                  ASC_INV_FIELD_IN_CMD_PACKET);
+               rd_raise_interrupt(channels, channel);
+             } else {
+               rd_init_send_atapi_command(channels, channel, atapi_command, toc_length, alloc_length, false);
+               rd_ready_to_send_atapi(channels, channel);
+             }
+             break;
+             
+           case 1:
+             // multi session stuff. we ignore this and emulate a single session only
+             rd_init_send_atapi_command(channels, channel, atapi_command, 12, alloc_length, false);
+             
+             SELECTED_CONTROLLER(channel).buffer[0] = 0;
+             SELECTED_CONTROLLER(channel).buffer[1] = 0x0a;
+             SELECTED_CONTROLLER(channel).buffer[2] = 1;
+             SELECTED_CONTROLLER(channel).buffer[3] = 1;
+             for (i = 0; i < 8; i++)
+               SELECTED_CONTROLLER(channel).buffer[4+i] = 0;
+             
+             rd_ready_to_send_atapi(channels, channel);
+             break;
+             
+           case 2:
+           default:
+             RD_PANIC("(READ TOC) Format %d not supported\n", format);
+             break;
+           }
+         } else {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+           rd_raise_interrupt(channels, channel);
+         }
+       }
+         break;
+      
+       case 0x28: // read (10)
+       case 0xa8: // read (12)
+         { 
+       
+           uint32 transfer_length;
+           if (atapi_command == 0x28)
+             transfer_length = rd_read_16bit(SELECTED_CONTROLLER(channel).buffer + 7);
+           else
+             transfer_length = rd_read_32bit(SELECTED_CONTROLLER(channel).buffer + 6);
+           
+           uint32 lba = rd_read_32bit(SELECTED_CONTROLLER(channel).buffer + 2);
+           
+           if (!SELECTED_DRIVE(channel).cdrom.ready) {
+             rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+             rd_raise_interrupt(channels, channel);
+             break;
+           }
+           
+           if (transfer_length == 0) {
+             rd_atapi_cmd_nop(channels, channel);
+             rd_raise_interrupt(channels, channel);
+             Ramdisk_Print("\t\tREAD(%d) with transfer length 0, ok\n", atapi_command==0x28?10:12);
+             break;
+           }
+           
+           if (lba + transfer_length > SELECTED_DRIVE(channel).cdrom.capacity) {
+             rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
+             rd_raise_interrupt(channels, channel);
+             break;
+           }
+           
+           Ramdisk_Print("\t\tcdrom: READ (%d) LBA=%d LEN=%d\n", atapi_command==0x28?10:12, lba, transfer_length);
+           
+           // handle command
+           rd_init_send_atapi_command(channels, channel, atapi_command, transfer_length * 2048,
+                                      transfer_length * 2048, true);
+           SELECTED_DRIVE(channel).cdrom.remaining_blocks = transfer_length;
+           SELECTED_DRIVE(channel).cdrom.next_lba = lba;
+           rd_ready_to_send_atapi(channels, channel);
+         }
+         break;
+         
+       case 0x2b: { // seek
+         uint32 lba = rd_read_32bit(SELECTED_CONTROLLER(channel).buffer + 2);
+         if (!SELECTED_DRIVE(channel).cdrom.ready) {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+           rd_raise_interrupt(channels, channel);
+           break;
+         }
+         
+         if (lba > SELECTED_DRIVE(channel).cdrom.capacity) {
+           rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
+           rd_raise_interrupt(channels, channel);
+           break;
+         }
+         Ramdisk_Print("\t\tcdrom: SEEK (ignored)\n");
+         rd_atapi_cmd_nop(channels, channel);
+         rd_raise_interrupt(channels, channel);
+       }
+         break;
+      
+       case 0x1e: { // prevent/allow medium removal
+         if (SELECTED_DRIVE(channel).cdrom.ready) {
+           SELECTED_DRIVE(channel).cdrom.locked = SELECTED_CONTROLLER(channel).buffer[4] & 1;
+           rd_atapi_cmd_nop(channels, channel);
+         } else {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+         }
+         rd_raise_interrupt(channels, channel);
+       }
+         break;
+      
+       case 0x42: { // read sub-channel
+         bool msf = get_packet_field(channel,1, 1, 1);
+         bool sub_q = get_packet_field(channel,2, 6, 1);
+         uint8 data_format = get_packet_byte(channel,3);
+         uint8 track_number = get_packet_byte(channel,6);
+         uint16 alloc_length = get_packet_word(channel,7);
+         UNUSED(msf);
+         UNUSED(data_format);
+         UNUSED(track_number);
+         
+         if (!SELECTED_DRIVE(channel).cdrom.ready) {
+           rd_atapi_cmd_error(channels, channel, SENSE_NOT_READY, ASC_MEDIUM_NOT_PRESENT);
+           rd_raise_interrupt(channels, channel);
+         } else {
+           SELECTED_CONTROLLER(channel).buffer[0] = 0;
+           SELECTED_CONTROLLER(channel).buffer[1] = 0; // audio not supported
+           SELECTED_CONTROLLER(channel).buffer[2] = 0;
+           SELECTED_CONTROLLER(channel).buffer[3] = 0;
+       
+           int ret_len = 4; // header size
+       
+           if (sub_q) { // !sub_q == header only
+             RD_ERROR("Read sub-channel with SubQ not implemented\n");
+             rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST,
+                                ASC_INV_FIELD_IN_CMD_PACKET);
+             rd_raise_interrupt(channels, channel);
+           }
+       
+           rd_init_send_atapi_command(channels, channel, atapi_command, ret_len, alloc_length, false);
+           rd_ready_to_send_atapi(channels, channel);
+         }
+       }
+         break;
+      
+       case 0x51: { // read disc info
+         // no-op to keep the Linux CD-ROM driver happy
+         rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
+         rd_raise_interrupt(channels, channel);
+       }
+         break;
+      
+       case 0x55: // mode select
+       case 0xa6: // load/unload cd
+       case 0x4b: // pause/resume
+       case 0x45: // play audio
+       case 0x47: // play audio msf
+       case 0xbc: // play cd
+       case 0xb9: // read cd msf
+       case 0x44: // read header
+       case 0xba: // scan
+       case 0xbb: // set cd speed
+       case 0x4e: // stop play/scan
+       case 0x46: // ???
+       case 0x4a: // ???
+         RD_ERROR("ATAPI command 0x%x not implemented yet\n",
+                  atapi_command);
+         rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
+         rd_raise_interrupt(channels, channel);
+         break;
+       default:
+         RD_PANIC("Unknown ATAPI command 0x%x (%d)\n",
+                  atapi_command, atapi_command);
+         // We'd better signal the error if the user chose to continue
+         rd_atapi_cmd_error(channels, channel, SENSE_ILLEGAL_REQUEST, ASC_INV_FIELD_IN_CMD_PACKET);
+         rd_raise_interrupt(channels, channel);
+         break;
+       }
+      }
+      
+      break;
+      
+      
+    default:
+      RD_PANIC("\t\tIO write(0x%x): current command is %02xh\n", address,
+              (unsigned) SELECTED_CONTROLLER(channel).current_command);
+    }
+
+/////////////////////////////////////////////////////////
+    break;
+  case 0x01: // hard disk write precompensation 0x1f1
+    WRITE_FEATURES(channel,value);
+    break;
+
+  case 0x02: // hard disk sector count 0x1f2
+    WRITE_SECTOR_COUNT(channel,value);
+    break;
+    
+  case 0x03: // hard disk sector number 0x1f3
+    WRITE_SECTOR_NUMBER(channel,value);
+    break;
+    
+  case 0x04: // hard disk cylinder low 0x1f4
+    WRITE_CYLINDER_LOW(channel,value);
+    break;
+    
+  case 0x05: // hard disk cylinder high 0x1f5
+    WRITE_CYLINDER_HIGH(channel,value);
+    break;
+    
+  case 0x06: // hard disk drive and head register 0x1f6
+    // b7 Extended data field for ECC
+    // b6/b5: Used to be sector size.  00=256,01=512,10=1024,11=128
+    //   Since 512 was always used, bit 6 was taken to mean LBA mode:
+    //     b6 1=LBA mode, 0=CHS mode
+    //     b5 1
+    // b4: DRV
+    // b3..0 HD3..HD0
+    
+    if ( (value & 0xa0) != 0xa0 ) // 1x1xxxxx
+      Ramdisk_Print("\t\tIO write 0x%x (%02x): not 1x1xxxxxb\n", address, (unsigned) value);
+    
+    Bit32u drvsel = channels[channel].drive_select = (value >> 4) & 0x01;
+    WRITE_HEAD_NO(channel,value & 0xf);
+    if (SELECTED_CONTROLLER(channel).lba_mode == 0 && ((value >> 6) & 1) == 1){
+      Ramdisk_Print("\t\tenabling LBA mode\n");
+       }
+    WRITE_LBA_MODE(channel,(value >> 6) & 1);
+    SELECTED_DRIVE(channel).cdrom.cd->lba = (value >> 6) & 1;
+    
+    
+    if (!SELECTED_IS_PRESENT(channel)) {
+      Ramdisk_Print ("\t\tError: device set to %d which does not exist! channel = %d\n",drvsel, channel);
+      SELECTED_CONTROLLER(channel).error_register = 0x04; // aborted
+      SELECTED_CONTROLLER(channel).status.err = 1;
+    }
+    
+    break;
+    
+    
+  case 0x07: // hard disk command 0x1f7
+
+      switch (value) {
+        // ATAPI commands
+        case 0xa1: // IDENTIFY PACKET DEVICE
+             if (SELECTED_IS_CD(channel)) {
+                   SELECTED_CONTROLLER(channel).current_command = value;
+                   SELECTED_CONTROLLER(channel).error_register = 0;
+
+                   SELECTED_CONTROLLER(channel).status.busy = 0;
+                   SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+                   SELECTED_CONTROLLER(channel).status.write_fault = 0;
+                   SELECTED_CONTROLLER(channel).status.drq   = 1;
+                   SELECTED_CONTROLLER(channel).status.err   = 0;
+
+                   SELECTED_CONTROLLER(channel).status.seek_complete = 1;
+                   SELECTED_CONTROLLER(channel).status.corrected_data = 0;
+
+                   SELECTED_CONTROLLER(channel).buffer_index = 0;
+                   rd_raise_interrupt(channels, channel);
+                   rd_identify_ATAPI_drive(channels, channel);
+             } else {
+               rd_command_aborted(channels, channel, 0xa1);
+             }
+             break;
+
+        case 0xa0: // SEND PACKET (atapi)
+             if (SELECTED_IS_CD(channel)) {
+                   // PACKET
+                   if (SELECTED_CONTROLLER(channel).features & (1 << 0))
+                         RD_PANIC("\t\tPACKET-DMA not supported");
+                   if (SELECTED_CONTROLLER(channel).features & (1 << 1))
+                         RD_PANIC("\t\tPACKET-overlapped not supported");
+
+                   // We're already ready!
+                   SELECTED_CONTROLLER(channel).sector_count = 1;
+                   SELECTED_CONTROLLER(channel).status.busy = 0;
+                   SELECTED_CONTROLLER(channel).status.write_fault = 0;
+                   // serv bit??
+                   SELECTED_CONTROLLER(channel).status.drq = 1;
+                   SELECTED_CONTROLLER(channel).status.err = 0;
+
+                   // NOTE: no interrupt here
+                   SELECTED_CONTROLLER(channel).current_command = value;
+                   SELECTED_CONTROLLER(channel).buffer_index = 0;
+             } else {
+               rd_command_aborted (channels, channel, 0xa0);
+             }
+             break;
+
+      default:
+       Ramdisk_Print("\t\tneed translate command %2x\n", value);
+       break;
+      }//switch(value)
+
+    
+  case 0x16: // hard disk adapter control 0x3f6 
+    // (mch) Even if device 1 was selected, a write to this register
+    // goes to device 0 (if device 1 is absent)
+
+    prev_control_reset = SELECTED_CONTROLLER(channel).control.reset;
+    channels[channel].drives[0].controller.control.reset         = value & 0x04;
+    channels[channel].drives[1].controller.control.reset         = value & 0x04;
+    // CGS: was: SELECTED_CONTROLLER(channel).control.disable_irq    = value & 0x02;
+    channels[channel].drives[0].controller.control.disable_irq = value & 0x02;
+    channels[channel].drives[1].controller.control.disable_irq = value & 0x02;
+    
+    Ramdisk_Print("\t\tadpater control reg: reset controller = %d\n",
+                 (unsigned) (SELECTED_CONTROLLER(channel).control.reset) ? 1 : 0);
+    Ramdisk_Print("\t\tadpater control reg: disable_irq(X) = %d\n",
+                 (unsigned) (SELECTED_CONTROLLER(channel).control.disable_irq) ? 1 : 0);
+    
+    if (!prev_control_reset && SELECTED_CONTROLLER(channel).control.reset) {
+      // transition from 0 to 1 causes all drives to reset
+      Ramdisk_Print("\t\thard drive: RESET\n");
+      
+      // (mch) Set BSY, drive not ready
+      for (id = 0; id < 2; id++) {
+       CONTROLLER(channel,id).status.busy           = 1;
+       CONTROLLER(channel,id).status.drive_ready    = 0;
+       CONTROLLER(channel,id).reset_in_progress     = 1;
+       
+       CONTROLLER(channel,id).status.write_fault    = 0;
+       CONTROLLER(channel,id).status.seek_complete  = 1;
+       CONTROLLER(channel,id).status.drq            = 0;
+       CONTROLLER(channel,id).status.corrected_data = 0;
+       CONTROLLER(channel,id).status.err            = 0;
+       
+       CONTROLLER(channel,id).error_register = 0x01; // diagnostic code: no error
+       
+       CONTROLLER(channel,id).current_command = 0x00;
+       CONTROLLER(channel,id).buffer_index = 0;
+       
+       CONTROLLER(channel,id).sectors_per_block = 0x80;
+       CONTROLLER(channel,id).lba_mode          = 0;
+       
+       CONTROLLER(channel,id).control.disable_irq = 0;
+       rd_lower_irq((struct vm_device *)(ramdisk_state->private_data), channels[channel].irq);
+      }
+    } else if (SELECTED_CONTROLLER(channel).reset_in_progress &&
+              !SELECTED_CONTROLLER(channel).control.reset) {
+      // Clear BSY and DRDY
+      Ramdisk_Print("\t\tReset complete {%s}\n", SELECTED_TYPE_STRING(channel));
+      for (id = 0; id < 2; id++) {
+       CONTROLLER(channel,id).status.busy           = 0;
+       CONTROLLER(channel,id).status.drive_ready    = 1;
+       CONTROLLER(channel,id).reset_in_progress     = 0;
+       
+       // Device signature
+       if (DRIVE_IS_HD(channel,id)) {
+         Ramdisk_Print("\t\tdrive %d/%d is harddrive\n", channel, id);
+         CONTROLLER(channel,id).head_no        = 0;
+         CONTROLLER(channel,id).sector_count   = 1;
+         CONTROLLER(channel,id).sector_no      = 1;
+         CONTROLLER(channel,id).cylinder_no    = 0;
+       } else {
+         CONTROLLER(channel,id).head_no        = 0;
+         CONTROLLER(channel,id).sector_count   = 1;
+         CONTROLLER(channel,id).sector_no      = 1;
+         CONTROLLER(channel,id).cylinder_no    = 0xeb14;
+       }
+      }
+    }
+    Ramdisk_Print("\t\ts[0].controller.control.disable_irq = %02x\n", (channels[channel].drives[0]).controller.control.disable_irq);
+    Ramdisk_Print("\t\ts[1].controller.control.disable_irq = %02x\n", (channels[channel].drives[1]).controller.control.disable_irq);
+    break;
+    
+  default:
+    RD_PANIC("\t\thard drive: io write to address %x = %02x\n",
+                 (unsigned) address, (unsigned) value);
+  }  
+  
+  return;
+}
+
+
+static 
+void rd_identify_ATAPI_drive(struct channel_t *channels, Bit8u channel)
+{
+  unsigned i;
+  const char* serial_number = " VT00001\0\0\0\0\0\0\0\0\0\0\0\0";
+  const char* firmware = "ALPHA1  ";
+
+  SELECTED_DRIVE(channel).id_drive[0] = (2 << 14) | (5 << 8) | (1 << 7) | (2 << 5) | (0 << 0); // Removable CDROM, 50us response, 12 byte packets
+
+  for (i = 1; i <= 9; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+
+  for (i = 0; i < 10; i++) {
+       SELECTED_DRIVE(channel).id_drive[10+i] = (serial_number[i*2] << 8) |
+             serial_number[i*2 + 1];
+  }
+
+  for (i = 20; i <= 22; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+
+  for (i = 0; i < strlen(firmware)/2; i++) {
+       SELECTED_DRIVE(channel).id_drive[23+i] = (firmware[i*2] << 8) |
+             firmware[i*2 + 1];
+  }
+  V3_ASSERT((23+i) == 27);
+  
+  for (i = 0; i < strlen((char *) SELECTED_MODEL(channel))/2; i++) {
+       SELECTED_DRIVE(channel).id_drive[27+i] = (SELECTED_MODEL(channel)[i*2] << 8) |
+             SELECTED_MODEL(channel)[i*2 + 1];
+  }
+  V3_ASSERT((27+i) == 47);
+
+  SELECTED_DRIVE(channel).id_drive[47] = 0;
+  SELECTED_DRIVE(channel).id_drive[48] = 1; // 32 bits access
+
+  SELECTED_DRIVE(channel).id_drive[49] = (1 << 9); // LBA supported
+
+  SELECTED_DRIVE(channel).id_drive[50] = 0;
+  SELECTED_DRIVE(channel).id_drive[51] = 0;
+  SELECTED_DRIVE(channel).id_drive[52] = 0;
+
+  SELECTED_DRIVE(channel).id_drive[53] = 3; // words 64-70, 54-58 valid
+
+  for (i = 54; i <= 62; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+  // copied from CFA540A
+  SELECTED_DRIVE(channel).id_drive[63] = 0x0103; // variable (DMA stuff)
+  SELECTED_DRIVE(channel).id_drive[64] = 0x0001; // PIO
+  SELECTED_DRIVE(channel).id_drive[65] = 0x00b4;
+  SELECTED_DRIVE(channel).id_drive[66] = 0x00b4;
+  SELECTED_DRIVE(channel).id_drive[67] = 0x012c;
+  SELECTED_DRIVE(channel).id_drive[68] = 0x00b4;
+
+  SELECTED_DRIVE(channel).id_drive[69] = 0;
+  SELECTED_DRIVE(channel).id_drive[70] = 0;
+  SELECTED_DRIVE(channel).id_drive[71] = 30; // faked
+  SELECTED_DRIVE(channel).id_drive[72] = 30; // faked
+  SELECTED_DRIVE(channel).id_drive[73] = 0;
+  SELECTED_DRIVE(channel).id_drive[74] = 0;
+
+  SELECTED_DRIVE(channel).id_drive[75] = 0;
+
+  for (i = 76; i <= 79; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+  SELECTED_DRIVE(channel).id_drive[80] = 0x1e; // supports up to ATA/ATAPI-4
+  SELECTED_DRIVE(channel).id_drive[81] = 0;
+  SELECTED_DRIVE(channel).id_drive[82] = 0;
+  SELECTED_DRIVE(channel).id_drive[83] = 0;
+  SELECTED_DRIVE(channel).id_drive[84] = 0;
+  SELECTED_DRIVE(channel).id_drive[85] = 0;
+  SELECTED_DRIVE(channel).id_drive[86] = 0;
+  SELECTED_DRIVE(channel).id_drive[87] = 0;
+  SELECTED_DRIVE(channel).id_drive[88] = 0;
+
+  for (i = 89; i <= 126; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+  SELECTED_DRIVE(channel).id_drive[127] = 0;
+  SELECTED_DRIVE(channel).id_drive[128] = 0;
+
+  for (i = 129; i <= 159; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+  for (i = 160; i <= 255; i++)
+       SELECTED_DRIVE(channel).id_drive[i] = 0;
+
+  // now convert the id_drive array (native 256 word format) to
+  // the controller buffer (512 bytes)
+  Bit16u temp16;
+  for (i = 0; i <= 255; i++) {
+       temp16 = SELECTED_DRIVE(channel).id_drive[i];
+       SELECTED_CONTROLLER(channel).buffer[i*2] = temp16 & 0x00ff;
+       SELECTED_CONTROLLER(channel).buffer[i*2+1] = temp16 >> 8;
+  }
+
+  return;
+}
+
+
+
+static 
+void rd_raise_interrupt(struct channel_t *channels, Bit8u channel)
+{
+  Bit32u irq;
+  struct vm_device *dev;
+
+  Ramdisk_Print("[raise_interrupt] disable_irq = %02x\n", SELECTED_CONTROLLER(channel).control.disable_irq);
+
+  if (!SELECTED_CONTROLLER(channel).control.disable_irq) { 
+    Ramdisk_Print("\t\traising interrupt\n"); 
+  } else { 
+    Ramdisk_Print("\t\tNot raising interrupt\n"); 
+  }
+  if (!SELECTED_CONTROLLER(channel).control.disable_irq) {
+    irq = channels[channel].irq; 
+    Ramdisk_Print("\t\tRaising interrupt %d {%s}\n\n", irq, SELECTED_TYPE_STRING(channel));
+    //        DEV_pic_raise_irq(irq);
+    dev = (struct vm_device*) ramdisk_state->private_data;
+    Ramdisk_Print("\t\tdev = %x\n", dev);
+    dev->vm->vm_ops.raise_irq(dev->vm, irq);
+  } else {
+    Ramdisk_Print("\t\tirq is disabled\n");
+  }
+  
+  return;
+}
+
+static
+void rd_lower_irq(struct vm_device *dev, Bit32u irq)// __attribute__(regparm(1))
+{
+  Ramdisk_Print("[lower_irq] irq = %d\n", irq);
+  dev->vm->vm_ops.lower_irq(dev->vm, irq);
+}
+
+
+/*
+ * Public Routines
+ */
+static
+uint_t ramdisk_read_port(ushort_t port,
+                        void *src,
+                        uint_t length,
+                        struct vm_device *dev)
+{
+  uint_t i;
+  Ramdisk_Print("[ramdisk_read_port] port = %x, length = %d\n", port, length);
+    switch (length) {
+    case 1:
+      ((uchar_t*)src)[0] = rd_read_handler(ramdisk_state->channels, port, length);
+      break;
+    case 2:
+      ((ushort_t*)src)[0] = rd_read_handler(ramdisk_state->channels, port, length);
+      break;
+    case 4:
+      ((uint_t*)src)[0] = rd_read_handler(ramdisk_state->channels, port, length);
+      break;
+    default:
+      for (i = 0; i < length; i++) { 
+       ((uchar_t*)src)[i] = rd_read_handler(ramdisk_state->channels, port, 1);
+      }
+    }//switch length
+
+  return length;
+}
+
+
+static
+uint_t ramdisk_write_port(ushort_t port,
+                        void *src,
+                        uint_t length,
+                        struct vm_device *dev)
+{
+  Ramdisk_Print("[ramdisk_write_port] port = %x, length = %d\n", port, length);
+  /*
+  uint_t i;
+
+  for (i = 0; i < length; i++)
+    Ramdisk_Print("\t\tsrc[%d] = 0x%02x\n", i, ((uchar_t*)src)[i]);
+  */
+
+  switch(length) {
+  case 1:
+    rd_write_handler(ramdisk_state->channels, port, *((uchar_t *)src), length);
+    break;
+  case 2:
+    rd_write_handler(ramdisk_state->channels, port, *((ushort_t *)src), length);
+    break;
+  case 4:
+    rd_write_handler(ramdisk_state->channels, port, *((uint_t *)src), length);
+    break;
+  default:
+    rd_write_handler(ramdisk_state->channels, port, *((uchar_t *)src), length);
+    break;
+  }
+
+  return 1;
+}
+
+
+static void trace_info(ushort_t port, void *src, uint_t length)
+{
+  switch(port){
+
+  case 0x3e8:
+    if (length == 1 && *((uchar_t*) src) == ATA_DETECT)
+      Ramdisk_Print("ata_dectect()\n");
+    break;
+
+  case 0x3e9:
+    if (length == 1 && *((uchar_t*) src) == ATA_RESET)
+      Ramdisk_Print("ata_reset()\n");
+    break;
+
+  case 0x3ea:
+    if (length == 1 && *((uchar_t*) src) == ATA_CMD_DATA_IN)
+      Ramdisk_Print("ata_cmd_data_in()\n");
+    break;
+
+  case 0x3eb:
+    if (length == 1 && *((uchar_t*) src) == ATA_CMD_DATA_OUT)
+      Ramdisk_Print("ata_cmd_data_out()\n");
+    break;
+
+  case 0x3ec:
+    if (length == 1 && *((uchar_t*) src) == ATA_CMD_PACKET)
+      Ramdisk_Print("ata_cmd_packet()\n");
+    break;
+
+  case 0x3ed:
+    if (length == 1 && *((uchar_t*) src) == ATAPI_GET_SENSE)
+      Ramdisk_Print("atapi_get_sense()\n");
+    break;
+
+  case 0x3ee:
+    if (length == 1 && *((uchar_t*) src) == ATAPI_IS_READY)
+      Ramdisk_Print("atapi_is_ready()\n");
+    break;
+
+  case 0x3ef:
+    if (length == 1 && *((uchar_t*) src) == ATAPI_IS_CDROM)
+      Ramdisk_Print("atapi_is_cdrom()\n");
+    break;
+
+
+  case 0x2e8:
+    if (length == 1 && *((uchar_t*) src) == CDEMU_INIT)
+      Ramdisk_Print("cdemu_init()\n");
+    break;
+
+  case 0x2e9:
+    if (length == 1 && *((uchar_t*) src) == CDEMU_ISACTIVE)
+      Ramdisk_Print("cdemu_isactive()\n");
+    break;
+
+  case 0x2ea:
+    if (length == 1 && *((uchar_t*) src) == CDEMU_EMULATED_DRIVE)
+      Ramdisk_Print("cdemu_emulated_drive()\n");
+    break;
+
+  case 0x2eb:
+    if (length == 1 && *((uchar_t*) src) == CDROM_BOOT)
+      Ramdisk_Print("cdrom_boot()\n");
+    break;
+
+  case 0x2ec:
+    if (length == 1 && *((uchar_t*) src) == HARD_DRIVE_POST)
+      Ramdisk_Print("ata_hard_drive_post()\n");
+    break;
+
+  case 0x2ed:
+    if (length == 1)
+      Ramdisk_Print("ata_device_no(%d)\n", *((uchar_t*) src));
+    break;
+
+  case 0x2ee:
+    if (length == 1)
+      Ramdisk_Print("ata_device_type(%d)\n", *((uchar_t*) src));
+    break;
+
+  case 0x2ef:
+    if (length == 1 && *((uchar_t*) src) == INT13_HARDDISK)
+      Ramdisk_Print("int13_harddrive()\n");
+    break;
+
+  case 0x2f8:
+    if (length == 1 && *((uchar_t*) src) == INT13_CDROM)
+      Ramdisk_Print("int13_cdrom()\n");
+    break;
+
+  case 0x2f9:
+    if (length == 1 && *((uchar_t*) src) == INT13_CDEMU)
+      Ramdisk_Print("int13_cdemu()\n");
+    break;
+
+  case 0x2fa:
+    if (length == 1 && *((uchar_t*) src) == INT13_ELTORITO)
+      Ramdisk_Print("int13_eltorito()\n");
+    break;
+
+  case 0x2fb:
+    if (length == 1 && *((uchar_t*) src) == INT13_DISKETTE_FUNCTION)
+      Ramdisk_Print("int13_diskette_function()\n");
+    break;
+
+
+  default:
+    break;
+  }
+}
+
+
+uint_t ramdisk_read_port_ignore(ushort_t port,
+                        void *src,
+                        uint_t length,
+                        struct vm_device *dev)
+{
+  //  Ramdisk_Print("[ramdisk_read_port_ignore] port = %x, length = %d\n", port, length);
+  return 1;
+}
+
+uint_t ramdisk_write_port_ignore(ushort_t port,
+                        void *src,
+                        uint_t length,
+                        struct vm_device *dev)
+{
+
+  //  Ramdisk_Print("[ramdisk_write_port_ignore] port = %x, length = %d\n", port, length);
+
+  trace_info(port, src, length);
+  return 1;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+/*
+ * ATAPI subroutines
+ */
+
+static 
+void rd_init_send_atapi_command(struct channel_t *channels, Bit8u channel, Bit8u command, int req_length, int alloc_length, bool lazy)
+{
+  // SELECTED_CONTROLLER(channel).byte_count is a union of SELECTED_CONTROLLER(channel).cylinder_no;
+  // lazy is used to force a data read in the buffer at the next read.
+  
+  Ramdisk_Print("[rd_init_send_atapi_cmd]\n");
+  if (SELECTED_CONTROLLER(channel).byte_count == 0xffff)
+    SELECTED_CONTROLLER(channel).byte_count = 0xfffe;
+  
+  if ((SELECTED_CONTROLLER(channel).byte_count & 1)
+      && !(alloc_length <= SELECTED_CONTROLLER(channel).byte_count)) {
+    Ramdisk_Print("\t\tOdd byte count (0x%04x) to ATAPI command 0x%02x, using 0x%x\n", 
+                 SELECTED_CONTROLLER(channel).byte_count, command, SELECTED_CONTROLLER(channel).byte_count - 1);
+    SELECTED_CONTROLLER(channel).byte_count -= 1;
+  }
+  
+  if (SELECTED_CONTROLLER(channel).byte_count == 0)
+    RD_PANIC("\t\tATAPI command with zero byte count\n");
+  
+  if (alloc_length < 0)
+    RD_PANIC("\t\tAllocation length < 0\n");
+  if (alloc_length == 0)
+    alloc_length = SELECTED_CONTROLLER(channel).byte_count;
+  
+  SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1;
+  SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 0;
+  SELECTED_CONTROLLER(channel).status.busy = 0;
+  SELECTED_CONTROLLER(channel).status.drq = 1;
+  SELECTED_CONTROLLER(channel).status.err = 0;
+  
+  // no bytes transfered yet
+  if (lazy)
+    SELECTED_CONTROLLER(channel).buffer_index = 2048;
+  else
+    SELECTED_CONTROLLER(channel).buffer_index = 0;
+  SELECTED_CONTROLLER(channel).drq_index = 0;
+  
+  if (SELECTED_CONTROLLER(channel).byte_count > req_length)
+    SELECTED_CONTROLLER(channel).byte_count = req_length;
+  
+  if (SELECTED_CONTROLLER(channel).byte_count > alloc_length)
+    SELECTED_CONTROLLER(channel).byte_count = alloc_length;
+  
+  SELECTED_DRIVE(channel).atapi.command = command;
+  SELECTED_DRIVE(channel).atapi.drq_bytes = SELECTED_CONTROLLER(channel).byte_count;
+  SELECTED_DRIVE(channel).atapi.total_bytes_remaining = (req_length < alloc_length) ? req_length : alloc_length;
+  
+  // if (lazy) {
+  // // bias drq_bytes and total_bytes_remaining
+  // SELECTED_DRIVE(channel).atapi.drq_bytes += 2048;
+  // SELECTED_DRIVE(channel).atapi.total_bytes_remaining += 2048;
+  // }
+}
+
+
+static 
+void rd_atapi_cmd_error(struct channel_t *channels, Bit8u channel, sense_t sense_key, asc_t asc)
+{
+  Ramdisk_Print("[rd_atapi_cmd_error]\n");
+  Ramdisk_Print("Error: atapi_cmd_error channel=%02x key=%02x asc=%02x\n", channel, sense_key, asc);
+
+  SELECTED_CONTROLLER(channel).error_register = sense_key << 4;
+  SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1;
+  SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 1;
+  SELECTED_CONTROLLER(channel).interrupt_reason.rel = 0;
+  SELECTED_CONTROLLER(channel).status.busy = 0;
+  SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+  SELECTED_CONTROLLER(channel).status.write_fault = 0;
+  SELECTED_CONTROLLER(channel).status.drq = 0;
+  SELECTED_CONTROLLER(channel).status.err = 1;
+  
+  SELECTED_DRIVE(channel).sense.sense_key = sense_key;
+  SELECTED_DRIVE(channel).sense.asc = asc;
+  SELECTED_DRIVE(channel).sense.ascq = 0;
+}
+
+
+static 
+void rd_atapi_cmd_nop(struct channel_t *channels, Bit8u channel)
+{
+  Ramdisk_Print("[rd_atapi_cmd_nop]\n");
+  SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1;
+  SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 1;
+  SELECTED_CONTROLLER(channel).interrupt_reason.rel = 0;
+  SELECTED_CONTROLLER(channel).status.busy = 0;
+  SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+  SELECTED_CONTROLLER(channel).status.drq = 0;
+  SELECTED_CONTROLLER(channel).status.err = 0;
+}
+
+
+static 
+void rd_init_mode_sense_single(struct channel_t *channels, 
+                              Bit8u channel, const void* src, int size)
+{
+  Ramdisk_Print("[rd_init_mode_sense_single]\n");
+  // Header
+  SELECTED_CONTROLLER(channel).buffer[0] = (size+6) >> 8;
+  SELECTED_CONTROLLER(channel).buffer[1] = (size+6) & 0xff;
+  SELECTED_CONTROLLER(channel).buffer[2] = 0x70; // no media present
+  SELECTED_CONTROLLER(channel).buffer[3] = 0; // reserved
+  SELECTED_CONTROLLER(channel).buffer[4] = 0; // reserved
+  SELECTED_CONTROLLER(channel).buffer[5] = 0; // reserved
+  SELECTED_CONTROLLER(channel).buffer[6] = 0; // reserved
+  SELECTED_CONTROLLER(channel).buffer[7] = 0; // reserved
+  
+  // Data
+  memcpy(SELECTED_CONTROLLER(channel).buffer + 8, src, size);
+}
+
+
+static 
+void rd_ready_to_send_atapi(struct channel_t *channels, Bit8u channel)
+{
+  Ramdisk_Print("[rd_ready_to_send_atapi]\n");
+  rd_raise_interrupt(ramdisk_state->channels, channel);
+}
+
+
+static 
+void rd_command_aborted(struct channel_t *channels, 
+                       Bit8u channel, unsigned value)
+{
+  Ramdisk_Print("[rd_command_aborted]\n");
+  Ramdisk_Print("\t\taborting on command 0x%02x {%s}\n", value, SELECTED_TYPE_STRING(channel));
+  SELECTED_CONTROLLER(channel).current_command = 0;
+  SELECTED_CONTROLLER(channel).status.busy = 0;
+  SELECTED_CONTROLLER(channel).status.drive_ready = 1;
+  SELECTED_CONTROLLER(channel).status.err = 1;
+  SELECTED_CONTROLLER(channel).error_register = 0x04; // command ABORTED
+  SELECTED_CONTROLLER(channel).status.drq = 0;
+  SELECTED_CONTROLLER(channel).status.seek_complete = 0;
+  SELECTED_CONTROLLER(channel).status.corrected_data = 0;
+  SELECTED_CONTROLLER(channel).buffer_index = 0;
+  rd_raise_interrupt(ramdisk_state->channels, channel);
+}
+
+
+
+/*
+ * Success: return 0; 
+ * Failure: return integer greater than 0
+ */
+
+struct ramdisk_t * create_ramdisk()
+{
+  struct ramdisk_t *ramdisk;
+  ramdisk = (struct ramdisk_t *)V3_Malloc(sizeof(struct ramdisk_t));  
+  V3_ASSERT(ramdisk != NULL);
+  
+  ramdisk->cops.init = &rd_init_harddrive;
+  ramdisk->cops.close = &rd_close_harddrive;
+  ramdisk->cops.reset = &rd_reset_harddrive;
+
+  ramdisk->eops.read_port = &ramdisk_read_port;
+  ramdisk->eops.write_port = &ramdisk_write_port;
+  ramdisk->eops.read_port_ignore = &ramdisk_read_port_ignore;
+  ramdisk->eops.write_port_ignore = &ramdisk_write_port_ignore;
+ 
+  return ramdisk;
+}