/* Copyright (C) 2005 Thomas Petazzoni, David Decotigny This program 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 Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include /** * @file ide.c * * Basic PIO IDE implementation based on the ATA standards * http://www.t13.org/ */ /** * Busy-wait for a given time * * @param delay Delay to wait * * @todo Implement a calibration routine */ static void udelay(int delay) { int i; for (i = 0; i < (delay * 1000) ; i++) { i++; i--; } } /* * Each IDE controller is controlled through a set of 9 I/O ports, * starting from a base address, which is different for each IDE * controller. On a standard PC with two onboard IDE controllers, the * I/O registers of the first controller start at 0x1F0 * (IDE_CONTROLLER_0_BASE), the I/O registers of the second controller * start at 0x170 (IDE_CONTROLLER_1_BASE). These I/O registers are * 8-bits wide, and can be read using the inb() macro, and written * using the outb() macro. * * The first controller is generally known as "primary" controller, * and the second one is know as "secondary" controller. Each of them * can handle at most two devices : the "master" device and the * "slave" device. * * The registers can be used to issue commands and send data to the * IDE controller, for example to identify a device, to read or write * a device. Then are different ways of transmitting data to the IDE * controller : * * - program the IDE controller, send the data, and wait for the * completion of the request. This method is called "polling". * * - program the IDE controller, send the data, block the current * process and do something else. The completion of the request will * be signaled asynchronously by an interrupt (IRQ), which will * allow to restart the sleeping process. * * - program the IDE controller and the DMA controller. There's no * need to transfer the data to the IDE controller : the DMA * controller will do it. This allows to use the CPU to do something * useful during the transfer of data between the main memory and * the IDE controller. * * In this driver, we use the two first methods. The polling method is * used to fetch the identification of the devices, while the IRQ * method is used to read and write to devices. */ #define IDE_CONTROLLER_0_BASE 0x1F0 #define IDE_CONTROLLER_1_BASE 0x170 #define IDE_CONTROLLER_0_IRQ 14 #define IDE_CONTROLLER_1_IRQ 15 /* * All defines below are relative to the base address of the I/O * registers (IDE_CONTROLLER_0_BASE and IDE_CONTROLLER_1_BASE) */ /** * Read/write register that allows to transfer the data to be written * or to fetch the read data from the IDE controller. */ #define ATA_DATA 0x00 /** * Read only register that gives information about errors that occured * during operation */ #define ATA_ERROR 0x01 /** * Write only register to set precompensation. It is in fact the same * register as the ATA_ERROR register. It simply has a different * behaviour when reading and writing it. */ #define ATA_PRECOMP 0x01 /** * Write only register used to set the count of sectors on which the * request applies. */ #define ATA_SECTOR_COUNT 0x02 /** * Write only register used to set the number of the starting sector * of the request. */ #define ATA_SECTOR_NUMBER 0x03 /** * Write only register used to set the 8 lower bits of the starting * cylinder number of the request */ #define ATA_CYL_LSB 0x04 /** * Write only register used to set the 8 higher bits of the starting * cylinder number of the request */ #define ATA_CYL_MSB 0x05 /** * Write only register that allows to select whether the LBA mode * should be used, and to select whether the request concerns the * slave or master device. */ #define ATA_DRIVE 0x06 #define ATA_D_IBM 0xa0 /* bits that must be set */ #define ATA_D_LBA 0x40 /* use LBA ? */ #define ATA_D_MASTER 0x00 /* select master */ #define ATA_D_SLAVE 0x10 /* select slave */ /** * Read only register that contains the status of the controller. Each * bit of this register as a different signification. */ #define ATA_STATUS 0x07 #define ATA_S_ERROR 0x01 /* error */ #define ATA_S_INDEX 0x02 /* index */ #define ATA_S_CORR 0x04 /* data corrected */ #define ATA_S_DRQ 0x08 /* data request */ #define ATA_S_DSC 0x10 /* drive Seek Completed */ #define ATA_S_DWF 0x20 /* drive write fault */ #define ATA_S_DRDY 0x40 /* drive ready */ #define ATA_S_BSY 0x80 /* busy */ /** * Write only register used to set the command that the IDE controller * should process. In our driver, only ATA_C_ATA_IDENTIFY, ATA_C_READ * and ATA_C_WRITE are used. */ #define ATA_CMD 0x07 #define ATA_C_ATA_IDENTIFY 0xec /* get ATA params */ #define ATA_C_ATAPI_IDENTIFY 0xa1 /* get ATAPI params*/ #define ATA_C_READ 0x20 /* read command */ #define ATA_C_WRITE 0x30 /* write command */ #define ATA_C_READ_MULTI 0xc4 /* read multi command */ #define ATA_C_WRITE_MULTI 0xc5 /* write multi command */ #define ATA_C_SET_MULTI 0xc6 /* set multi size command */ #define ATA_C_PACKET_CMD 0xa0 /* set multi size command */ /** * Read register that contains more information about the status of * the controller */ #define ATA_ALTPORT 0x206 /* (R) alternate Status register */ /** * Write only register that allows to control the controller */ #define ATA_DEVICE_CONTROL 0x206 /* (W) device control register */ #define ATA_A_nIEN 0x02 /* disable interrupts */ #define ATA_A_RESET 0x04 /* RESET controller */ #define ATA_A_4BIT 0x08 /* 4 head bits */ /** Magic numbers used to detect ATAPI devices */ #define ATAPI_MAGIC_LSB 0x14 #define ATAPI_MAGIC_MSB 0xeb /* This structure describe the informations returned by the Identify Device command (imposed by the ATA standard) */ struct ide_device_info { sos_ui16_t general_config_info; /* 0 */ sos_ui16_t nb_logical_cylinders; /* 1 */ sos_ui16_t reserved1; /* 2 */ sos_ui16_t nb_logical_heads; /* 3 */ sos_ui16_t unformatted_bytes_track; /* 4 */ sos_ui16_t unformatted_bytes_sector; /* 5 */ sos_ui16_t nb_logical_sectors; /* 6 */ sos_ui16_t vendor1[3]; /* 7-9 */ sos_ui8_t serial_number[20]; /* 10-19 */ sos_ui16_t buffer_type; /* 20 */ sos_ui16_t buffer_size; /* 21 */ sos_ui16_t ecc_bytes; /* 22 */ sos_ui8_t firmware_revision[8]; /* 23-26 */ sos_ui8_t model_number[40]; /* 27-46 */ sos_ui8_t max_multisect; /* 47 */ sos_ui8_t vendor2; sos_ui16_t dword_io; /* 48 */ sos_ui8_t vendor3; /* 49 */ sos_ui8_t capabilities; sos_ui16_t reserved2; /* 50 */ sos_ui8_t vendor4; /* 51 */ sos_ui8_t pio_trans_mode; sos_ui8_t vendor5; /* 52 */ sos_ui8_t dma_trans_mode; sos_ui16_t fields_valid; /* 53 */ sos_ui16_t cur_logical_cylinders; /* 54 */ sos_ui16_t cur_logical_heads; /* 55 */ sos_ui16_t cur_logical_sectors; /* 56 */ sos_ui16_t capacity1; /* 57 */ sos_ui16_t capacity0; /* 58 */ sos_ui8_t multsect; /* 59 */ sos_ui8_t multsect_valid; sos_ui32_t lba_capacity; /* 60-61 */ sos_ui16_t dma_1word; /* 62 */ sos_ui16_t dma_multiword; /* 63 */ sos_ui16_t pio_modes; /* 64 */ sos_ui16_t min_mword_dma; /* 65 */ sos_ui16_t recommended_mword_dma; /* 66 */ sos_ui16_t min_pio_cycle_time; /* 67 */ sos_ui16_t min_pio_cycle_time_iordy; /* 68 */ sos_ui16_t reserved3[11]; /* 69-79 */ sos_ui16_t major_version; /* 80 */ sos_ui16_t minor_version; /* 81 */ sos_ui16_t command_sets1; /* 82 */ sos_ui16_t command_sets2; /* 83 dixit lk : b14 (smart enabled) */ sos_ui16_t reserved4[4]; /* 84-87 */ sos_ui16_t dma_ultra; /* 88 dixit lk */ sos_ui16_t reserved5[37]; /* 89-125 */ sos_ui16_t last_lun; /* 126 */ sos_ui16_t reserved6; /* 127 */ sos_ui16_t security; /* 128 */ sos_ui16_t reserved7[127]; } __attribute__((packed)); #define MAX_IDE_CONTROLLERS 2 #define MAX_IDE_DEVICES 2 #define IDE_BLK_SIZE 512 #define IDE_DEVICE(ctrl,device) (((ctrl) * 2) + (device)) #define IDE_MINOR(ctrl,device) (IDE_DEVICE(ctrl,device)*16) typedef enum { IDE_DEVICE_NONE, IDE_DEVICE_HARDDISK, IDE_DEVICE_CDROM } ide_device_type_t; struct ide_controller; struct ide_device { int id; ide_device_type_t type; enum { IDE_DEVICE_MASTER, IDE_DEVICE_SLAVE } position; int cyls; int heads; int sectors; int blocks; sos_bool_t support_lba; struct ide_controller *ctrl; }; struct ide_controller { int id; int ioaddr; int irq; enum { IDE_CTRL_NOT_PRESENT, IDE_CTRL_PRESENT } state; struct ide_device devices[MAX_IDE_DEVICES]; struct sos_kmutex mutex; struct sos_ksema ack_io_sem; }; struct ide_controller ide_controllers[MAX_IDE_CONTROLLERS] = { { .id = 0, .ioaddr = IDE_CONTROLLER_0_BASE, .irq = IDE_CONTROLLER_0_IRQ, .state = IDE_CTRL_NOT_PRESENT, }, { .id = 1, .ioaddr = IDE_CONTROLLER_1_BASE, .irq = IDE_CONTROLLER_1_IRQ, .state = IDE_CTRL_NOT_PRESENT } }; void ide_irq_handler (int irq_level) { int i; struct ide_controller *ctrl = NULL; for (i = 0; i < MAX_IDE_CONTROLLERS ; i++) { if (ide_controllers[i].irq == irq_level) { ctrl = & ide_controllers[i]; break; } } SOS_ASSERT_FATAL (ctrl != NULL); sos_ksema_up (& ctrl->ack_io_sem); } static int ide_get_device_info (struct ide_device *dev) { int devselect; sos_ui16_t buffer[256]; struct ide_device_info *info; int timeout, i; int status; SOS_ASSERT_FATAL (dev->type == IDE_DEVICE_HARDDISK); if (dev->position == IDE_DEVICE_MASTER) devselect = ATA_D_MASTER; else devselect = ATA_D_SLAVE; /* Ask the controller to NOT send interrupts to acknowledge commands */ outb(ATA_A_nIEN | ATA_A_4BIT, dev->ctrl->ioaddr + ATA_DEVICE_CONTROL); udelay(1); /* Select the device (master or slave) */ outb(ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); /* Send the IDENTIFY command */ outb(ATA_C_ATA_IDENTIFY, dev->ctrl->ioaddr + ATA_CMD); /* Wait for command completion (wait while busy bit is set) */ for(timeout = 0; timeout < 30000; timeout++) { status = inb(dev->ctrl->ioaddr + ATA_STATUS); if(!(status & ATA_S_BSY)) break; udelay(1); } /* DRQ bit indicates that data is ready to be read. If it is not set after an IDENTIFY command, there is a problem */ if(! (status & ATA_S_DRQ)) { return SOS_EFATAL; } /* Read data from the controller buffer to a temporary buffer */ for(i = 0; i < 256; i++) buffer[i] = inw(dev->ctrl->ioaddr + ATA_DATA); /* The buffer contains an information structure, defined by ATA specification. To ease its access, we use the previously defined ide_device_info structure. */ info = (struct ide_device_info *) buffer; /* Fetch intersting informations from the structure */ dev->heads = info->nb_logical_heads; dev->cyls = info->nb_logical_cylinders; dev->sectors = info->nb_logical_sectors; dev->support_lba = FALSE; /* Determines if device supports LBA. The method is a bit ugly, but there's no other way */ if (info->capabilities & (1 << 1) && info->major_version && (info->fields_valid & 1) && (info->lba_capacity & 1)) dev->support_lba = TRUE; /* Determines the capacity of the device */ if (dev->heads == 16 && dev->sectors == 63 && dev->cyls == 16383) { if (dev->support_lba) dev->blocks = info->lba_capacity; else return SOS_EFATAL; } else dev->blocks = dev->cyls * dev->sectors * dev->heads; return SOS_OK; } static ide_device_type_t ide_probe_device (struct ide_device *dev) { int status; int devselect; if (dev->position == IDE_DEVICE_MASTER) devselect = ATA_D_MASTER; else devselect = ATA_D_SLAVE; /* Select the given device */ outb (ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); /* Read the status of the device */ status = inb(dev->ctrl->ioaddr + ATA_STATUS); /* If status indicates a busy device, it means that there's no device */ if (status & ATA_S_BSY) return IDE_DEVICE_NONE; /* If status indicates that drive is ready and drive has complete seeking, then we've got an hard drive */ if (status & (ATA_S_DRDY | ATA_S_DSC)) return IDE_DEVICE_HARDDISK; /* Detect CD-ROM drives by reading the cylinder low byte and cylinder high byte, and check if they match magic values */ if(inb(dev->ctrl->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB && inb(dev->ctrl->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB) return IDE_DEVICE_CDROM; return IDE_DEVICE_NONE; } static sos_ret_t ide_probe_controller(struct ide_controller *ctrl) { sos_bool_t hasdevice = FALSE; sos_kmutex_init (& ctrl->mutex, "ide-mutex", SOS_KWQ_ORDER_FIFO); sos_ksema_init (& ctrl->ack_io_sem, "ide-sem", 0, SOS_KWQ_ORDER_FIFO); /* Master */ ctrl->devices[0].id = 0; ctrl->devices[0].position = IDE_DEVICE_MASTER; ctrl->devices[0].ctrl = ctrl; ctrl->devices[0].type = ide_probe_device (& ctrl->devices[0]); if (ctrl->devices[0].type == IDE_DEVICE_HARDDISK) { ide_get_device_info (& ctrl->devices[0]); hasdevice = TRUE; } /* Slave */ ctrl->devices[1].id = 1; ctrl->devices[1].position = IDE_DEVICE_SLAVE; ctrl->devices[1].ctrl = ctrl; ctrl->devices[1].type = ide_probe_device (& ctrl->devices[1]); if (ctrl->devices[1].type == IDE_DEVICE_HARDDISK) { ide_get_device_info (& ctrl->devices[1]); hasdevice = TRUE; } if (hasdevice) sos_irq_set_routine (ctrl->irq, ide_irq_handler); return SOS_OK; } static sos_ret_t ide_io_operation (struct ide_device *dev, sos_vaddr_t buf, int block, sos_bool_t iswrite) { sos_ui8_t cyl_lo, cyl_hi, sect, head, status; int devselect, i; SOS_ASSERT_FATAL (dev->type == IDE_DEVICE_HARDDISK); if (block > dev->blocks) return -SOS_EFATAL; if (dev->position == IDE_DEVICE_MASTER) devselect = ATA_D_MASTER; else devselect = ATA_D_SLAVE; /* Compute the position of the block in the device in terms of cylinders, sectors and heads. As cylinders must be sent in two separate parts, we compute it that way. */ if (dev->support_lba) { sect = (block & 0xff); cyl_lo = (block >> 8) & 0xff; cyl_hi = (block >> 16) & 0xff; head = ((block >> 24) & 0x7) | 0x40; } else { int cylinder = block / (dev->heads * dev->sectors); int temp = block % (dev->heads * dev->sectors); cyl_lo = cylinder & 0xff; cyl_hi = (cylinder >> 8) & 0xff; head = temp / dev->sectors; sect = (temp % dev->sectors) + 1; } /* Make sure nobody is using the same controller at the same time */ sos_kmutex_lock (& dev->ctrl->mutex, NULL); /* Select device */ outb(ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); udelay(100); /* Write to registers */ outb(ATA_A_4BIT, dev->ctrl->ioaddr + ATA_DEVICE_CONTROL); outb(1, dev->ctrl->ioaddr + ATA_ERROR); outb(0, dev->ctrl->ioaddr + ATA_PRECOMP); outb(1, dev->ctrl->ioaddr + ATA_SECTOR_COUNT); outb(sect, dev->ctrl->ioaddr + ATA_SECTOR_NUMBER); outb(cyl_lo, dev->ctrl->ioaddr + ATA_CYL_LSB); outb(cyl_hi, dev->ctrl->ioaddr + ATA_CYL_MSB); outb((ATA_D_IBM | devselect | head), dev->ctrl->ioaddr + ATA_DRIVE); /* Send the command, either read or write */ if (iswrite) outb(ATA_C_WRITE, dev->ctrl->ioaddr + ATA_CMD); else outb(ATA_C_READ, dev->ctrl->ioaddr + ATA_CMD); /* Wait for the device ready to transfer */ do { udelay(1); } while (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_BSY); /* If an error was detected, stop here */ if (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR) { sos_kmutex_unlock (& dev->ctrl->mutex); return -SOS_EFATAL; } /* If it's a write I/O, transfer the contents of the buffer provided by the user to the controller internal buffer, so that the controller can write the data to the disk */ if (iswrite) { /* Wait for the DRQ bit to be set */ while (1) { status = inb(dev->ctrl->ioaddr + ATA_STATUS); if (status & ATA_S_ERROR) { sos_kmutex_unlock (& dev->ctrl->mutex); return -SOS_EFATAL; } if (!(status & ATA_S_BSY) && (status & ATA_S_DRQ)) break; } /* Do the transfer to the device's buffer */ sos_ui16_t *buffer = (sos_ui16_t *) buf; for (i = 0 ; i < 256 ; i++) outw (buffer[i], dev->ctrl->ioaddr + ATA_DATA); /* Wait for the device to have received all the data */ while (1) { status = inb(dev->ctrl->ioaddr + ATA_STATUS); if (status & ATA_S_ERROR) { sos_kmutex_unlock (& dev->ctrl->mutex); return -SOS_EFATAL; } if (!(status & ATA_S_BSY) && !(status & ATA_S_DRQ)) break; } } /* Sleep until the IRQ wakes us up */ sos_ksema_down (& dev->ctrl->ack_io_sem, NULL); /* ATA specs tell to read the alternate status reg and ignore its result */ inb(dev->ctrl->ioaddr + ATA_ALTPORT); if (! iswrite) { /* Wait for the DRQ bit to be set */ while (1) { status = inb(dev->ctrl->ioaddr + ATA_STATUS); if (status & ATA_S_ERROR) { sos_kmutex_unlock (& dev->ctrl->mutex); return -SOS_EFATAL; } if (!(status & ATA_S_BSY) && (status & ATA_S_DRQ)) break; } /* copy data from the controller internal buffer to the buffer provided by the user */ sos_ui16_t *buffer = (sos_ui16_t *) buf; for (i = 0 ; i < 256 ; i++) buffer [i] = inw (dev->ctrl->ioaddr + ATA_DATA); /* ATA specs tell to read the alternate status reg and ignore its result */ inb(dev->ctrl->ioaddr + ATA_ALTPORT); } /* If an error was detected, stop here */ if (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR) { sos_kmutex_unlock (& dev->ctrl->mutex); return -SOS_EFATAL; } /* Someone else can safely use devices on this controller for other requests */ sos_kmutex_unlock (& dev->ctrl->mutex); return SOS_OK; } static sos_ret_t ide_read_device (void *blkdev_instance, sos_vaddr_t dest_buf, sos_luoffset_t block_offset) { struct ide_device *dev; dev = (struct ide_device *) blkdev_instance; return ide_io_operation (dev, dest_buf, block_offset, FALSE); } static sos_ret_t ide_write_device (void *blkdev_instance, sos_vaddr_t src_buf, sos_luoffset_t block_offset) { struct ide_device *dev; dev = (struct ide_device *) blkdev_instance; return ide_io_operation (dev, src_buf, block_offset, TRUE); } static struct sos_blockdev_operations ide_ops = { .read_block = ide_read_device, .write_block = ide_write_device, .ioctl = NULL }; static sos_ret_t ide_register_devices (void) { int ctrl, dev; sos_ret_t ret; for (ctrl = 0; ctrl < MAX_IDE_CONTROLLERS ; ctrl++) { for (dev = 0; dev < MAX_IDE_DEVICES ; dev++) { char name[16]; struct ide_device *device; device = & ide_controllers[ctrl].devices[dev]; snprintf (name, sizeof(name), "hd%c", ('a' + IDE_DEVICE(ctrl, dev))); if (device->type == IDE_DEVICE_HARDDISK) { sos_bochs_printf("%s: harddisk %d Mb <", name, (device->blocks * IDE_BLK_SIZE >> 20)); ret = sos_blockdev_register_disk (SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl, dev), IDE_BLK_SIZE, device->blocks, 128, & ide_ops, device); if (ret != SOS_OK) { sos_bochs_printf ("Warning: could not register disk>\n"); continue; } ret = sos_part_detect (SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl, dev), IDE_BLK_SIZE, name); if (ret != SOS_OK) { sos_bochs_printf ("Warning could not detect partitions (%d)>\n", ret); continue; } /* Finalize output */ sos_bochs_printf (">\n"); } else if (device->type == IDE_DEVICE_CDROM) sos_bochs_printf("%s: CDROM\n", name); } } return SOS_OK; } static sos_ret_t ide_unregister_devices (void) { int ctrl, dev; sos_ret_t ret; for (ctrl = 0; ctrl < MAX_IDE_CONTROLLERS ; ctrl++) { for (dev = 0; dev < MAX_IDE_DEVICES ; dev++) { struct ide_device *device; device = & ide_controllers[ctrl].devices[dev]; if (device->type != IDE_DEVICE_HARDDISK) continue; sos_part_undetect (SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl,dev)); ret = sos_blockdev_unregister_device (SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl,dev)); if (ret != SOS_OK) sos_bochs_printf ("Error while unregistering device %d:%d\n", SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl,dev)); } } return SOS_OK; } sos_ret_t sos_ide_subsystem_setup (void) { sos_ret_t ret; ret = ide_probe_controller(& ide_controllers[0]); if (ret != SOS_OK) sos_bochs_printf ("Error while probing IDE controller 0\n"); ret =ide_probe_controller(& ide_controllers[1]); if (ret != SOS_OK) sos_bochs_printf ("Error while probing IDE controller 1\n"); ide_register_devices (); return SOS_OK; } sos_ret_t ide_driver_cleanup (void) { return ide_unregister_devices (); }