sos-code-article10/drivers/ide.c

786 lines
23 KiB
C

/* 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 <sos/types.h>
#include <sos/errno.h>
#include <sos/assert.h>
#include <sos/ksynch.h>
#include <drivers/devices.h>
#include <drivers/bochs.h>
#include <drivers/part.h>
#include <hwcore/irq.h>
#include <hwcore/ioports.h>
/**
* @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 ();
}