*/
#include <grub/ata.h>
+#include <grub/scsi.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/misc.h>
static int quiet = 0;
static grub_err_t
-grub_hdparm_do_ata_cmd (grub_disk_t disk, grub_uint8_t cmd,
+grub_hdparm_do_ata_cmd (grub_ata_t ata, grub_uint8_t cmd,
grub_uint8_t features, grub_uint8_t sectors,
void * buffer, int size)
{
struct grub_disk_ata_pass_through_parms apt;
grub_memset (&apt, 0, sizeof (apt));
- apt.taskfile[GRUB_ATA_REG_CMD] = cmd;
- apt.taskfile[GRUB_ATA_REG_FEATURES] = features;
- apt.taskfile[GRUB_ATA_REG_SECTORS] = sectors;
+ apt.taskfile.cmd = cmd;
+ apt.taskfile.features = features;
+ apt.taskfile.sectors = sectors;
apt.buffer = buffer;
apt.size = size;
- if (grub_disk_ata_pass_through (disk, &apt))
+ if (ata->dev->readwrite (ata, &apt))
return grub_errno;
return GRUB_ERR_NONE;
}
static int
-grub_hdparm_do_check_powermode_cmd (grub_disk_t disk)
+grub_hdparm_do_check_powermode_cmd (grub_ata_t ata)
{
struct grub_disk_ata_pass_through_parms apt;
grub_memset (&apt, 0, sizeof (apt));
- apt.taskfile[GRUB_ATA_REG_CMD] = GRUB_ATA_CMD_CHECK_POWER_MODE;
+ apt.taskfile.cmd = GRUB_ATA_CMD_CHECK_POWER_MODE;
- if (grub_disk_ata_pass_through (disk, &apt))
+ if (ata->dev->readwrite (ata, &apt))
return -1;
- return apt.taskfile[GRUB_ATA_REG_SECTORS];
+ return apt.taskfile.sectors;
}
static int
-grub_hdparm_do_smart_cmd (grub_disk_t disk, grub_uint8_t features)
+grub_hdparm_do_smart_cmd (grub_ata_t ata, grub_uint8_t features)
{
struct grub_disk_ata_pass_through_parms apt;
grub_memset (&apt, 0, sizeof (apt));
- apt.taskfile[GRUB_ATA_REG_CMD] = GRUB_ATA_CMD_SMART;
- apt.taskfile[GRUB_ATA_REG_FEATURES] = features;
- apt.taskfile[GRUB_ATA_REG_LBAMID] = 0x4f;
- apt.taskfile[GRUB_ATA_REG_LBAHIGH] = 0xc2;
+ apt.taskfile.cmd = GRUB_ATA_CMD_SMART;
+ apt.taskfile.features = features;
+ apt.taskfile.lba_mid = 0x4f;
+ apt.taskfile.lba_high = 0xc2;
- if (grub_disk_ata_pass_through (disk, &apt))
+ if (ata->dev->readwrite (ata, &apt))
return -1;
if (features == GRUB_ATA_FEAT_SMART_STATUS)
{
- if ( apt.taskfile[GRUB_ATA_REG_LBAMID] == 0x4f
- && apt.taskfile[GRUB_ATA_REG_LBAHIGH] == 0xc2)
+ if ( apt.taskfile.lba_mid == 0x4f
+ && apt.taskfile.lba_high == 0xc2)
return 0; /* Good SMART status. */
- else if ( apt.taskfile[GRUB_ATA_REG_LBAMID] == 0xf4
- && apt.taskfile[GRUB_ATA_REG_LBAHIGH] == 0x2c)
+ else if ( apt.taskfile.lba_mid == 0xf4
+ && apt.taskfile.lba_high == 0x2c)
return 1; /* Bad SMART status. */
else
return -1;
static grub_err_t
grub_hdparm_simple_cmd (const char * msg,
- grub_disk_t disk, grub_uint8_t cmd)
+ grub_ata_t ata, grub_uint8_t cmd)
{
if (! quiet && msg)
grub_printf ("%s", msg);
- grub_err_t err = grub_hdparm_do_ata_cmd (disk, cmd, 0, 0, NULL, 0);
+ grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, 0, 0, NULL, 0);
if (! quiet && msg)
grub_printf ("%s\n", ! err ? "" : ": not supported");
static grub_err_t
grub_hdparm_set_val_cmd (const char * msg, int val,
- grub_disk_t disk, grub_uint8_t cmd,
+ grub_ata_t ata, grub_uint8_t cmd,
grub_uint8_t features, grub_uint8_t sectors)
{
if (! quiet && msg && *msg)
grub_printf ("Disable %s", msg);
}
- grub_err_t err = grub_hdparm_do_ata_cmd (disk, cmd, features, sectors,
+ grub_err_t err = grub_hdparm_do_ata_cmd (ata, cmd, features, sectors,
NULL, 0);
if (! quiet && msg)
grub_cmd_hdparm (grub_extcmd_t cmd, int argc, char **args) // state????
{
struct grub_arg_list *state = cmd->state;
+ struct grub_ata *ata;
/* Check command line. */
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "argument is not a device name");
args[0][len - 1] = 0;
- if (! grub_disk_ata_pass_through)
- return grub_error (GRUB_ERR_BAD_ARGUMENT, "ATA pass through not available");
-
int i = 0;
int apm = get_int_arg (&state[i++]);
int power = state[i++].set;
return grub_error (GRUB_ERR_BAD_ARGUMENT, "partition not allowed");
}
+ switch (disk->dev->id)
+ {
+ case GRUB_DISK_DEVICE_ATA_ID:
+ ata = disk->data;
+ break;
+ case GRUB_DISK_DEVICE_SCSI_ID:
+ if (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF)
+ == GRUB_SCSI_SUBSYSTEM_PATA
+ || (((disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xFF)
+ == GRUB_SCSI_SUBSYSTEM_AHCI))
+ {
+ ata = ((struct grub_scsi *) disk->data)->data;
+ break;
+ }
+ default:
+ return grub_error (GRUB_ERR_IO, "not an ATA device");
+ }
+
+
/* Change settings. */
if (aam >= 0)
grub_hdparm_set_val_cmd ("Automatic Acoustic Management", (aam ? aam : -1),
- disk, GRUB_ATA_CMD_SET_FEATURES, (aam ? 0x42 : 0xc2), aam);
+ ata, GRUB_ATA_CMD_SET_FEATURES,
+ (aam ? 0x42 : 0xc2), aam);
if (apm >= 0)
grub_hdparm_set_val_cmd ("Advanced Power Management",
- (apm != 255 ? apm : -1), disk, GRUB_ATA_CMD_SET_FEATURES,
- (apm != 255 ? 0x05 : 0x85), (apm != 255 ? apm : 0));
+ (apm != 255 ? apm : -1), ata,
+ GRUB_ATA_CMD_SET_FEATURES,
+ (apm != 255 ? 0x05 : 0x85),
+ (apm != 255 ? apm : 0));
if (standby_tout >= 0)
{
grub_printf (")");
}
/* The IDLE cmd sets disk to idle mode and configures standby timer. */
- grub_hdparm_set_val_cmd ("", -1, disk, GRUB_ATA_CMD_IDLE, 0, standby_tout);
+ grub_hdparm_set_val_cmd ("", -1, ata, GRUB_ATA_CMD_IDLE, 0, standby_tout);
}
if (enable_smart >= 0)
{
if (! quiet)
grub_printf ("%sable SMART operations", (enable_smart ? "En" : "Dis"));
- int err = grub_hdparm_do_smart_cmd (disk, (enable_smart ?
+ int err = grub_hdparm_do_smart_cmd (ata, (enable_smart ?
GRUB_ATA_FEAT_SMART_ENABLE : GRUB_ATA_FEAT_SMART_DISABLE));
if (! quiet)
grub_printf ("%s\n", err ? ": not supported" : "");
}
if (sec_freeze)
- grub_hdparm_simple_cmd ("Freeze security settings", disk,
+ grub_hdparm_simple_cmd ("Freeze security settings", ata,
GRUB_ATA_CMD_SECURITY_FREEZE_LOCK);
/* Print/dump IDENTIFY. */
if (ident || dumpid)
{
char buf[GRUB_DISK_SECTOR_SIZE];
- if (grub_hdparm_do_ata_cmd (disk, GRUB_ATA_CMD_IDENTIFY_DEVICE,
+ if (grub_hdparm_do_ata_cmd (ata, GRUB_ATA_CMD_IDENTIFY_DEVICE,
0, 0, buf, sizeof (buf)))
grub_printf ("Cannot read ATA IDENTIFY data\n");
else
if (power)
{
grub_printf ("Disk power mode is: ");
- int mode = grub_hdparm_do_check_powermode_cmd (disk);
+ int mode = grub_hdparm_do_check_powermode_cmd (ata);
if (mode < 0)
grub_printf ("unknown\n");
else
{
if (! quiet)
grub_printf ("SMART status is: ");
- int err = grub_hdparm_do_smart_cmd (disk, GRUB_ATA_FEAT_SMART_STATUS);
+ int err = grub_hdparm_do_smart_cmd (ata, GRUB_ATA_FEAT_SMART_STATUS);
if (! quiet)
grub_printf ("%s\n", (err < 0 ? "unknown" :
err == 0 ? "OK" : "*BAD*"));
/* Change power mode. */
if (standby_now)
- grub_hdparm_simple_cmd ("Set disk to standby mode", disk,
+ grub_hdparm_simple_cmd ("Set disk to standby mode", ata,
GRUB_ATA_CMD_STANDBY_IMMEDIATE);
if (sleep_now)
- grub_hdparm_simple_cmd ("Set disk to sleep mode", disk,
+ grub_hdparm_simple_cmd ("Set disk to sleep mode", ata,
GRUB_ATA_CMD_SLEEP);
grub_disk_close (disk);
#include <grub/mm.h>
#include <grub/time.h>
#include <grub/pci.h>
+#include <grub/ata.h>
#include <grub/scsi.h>
+#include <grub/misc.h>
+#include <grub/list.h>
struct grub_ahci_cmd_head
{
- grub_uint32_t unused[8];
+ grub_uint32_t config;
+ grub_uint32_t transfered;
+ grub_uint64_t command_table_base;
+ grub_uint32_t unused[4];
+};
+
+struct grub_ahci_prdt_entry
+{
+ grub_uint64_t data_base;
+ grub_uint32_t unused;
+ grub_uint32_t size;
+};
+
+struct grub_ahci_cmd_table
+{
+ grub_uint8_t cfis[0x40];
+ grub_uint8_t command[16];
+ grub_uint32_t reserved[0xc];
+ struct grub_ahci_prdt_entry prdt[1];
};
struct grub_ahci_hba_port
{
grub_uint64_t command_list_base;
- grub_uint32_t unused[12];
+ grub_uint64_t fis_base;
+ grub_uint32_t intstatus;
+ grub_uint32_t inten;
+ grub_uint32_t command;
+ grub_uint32_t unused1[6];
grub_uint32_t sata_active;
grub_uint32_t command_issue;
- grub_uint32_t unused[16];
+ grub_uint32_t unused2[17];
};
struct grub_ahci_hba
{
grub_uint32_t cap;
-#define GRUB_AHCI_HBA_CAP_MASK 0x1f
grub_uint32_t global_control;
-#define GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN 0x80000000
+ grub_uint32_t intr_status;
grub_uint32_t ports_implemented;
- grub_uint32_t unused1[7];
+ grub_uint32_t unused1[6];
grub_uint32_t bios_handoff;
-#define GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED 1
-#define GRUB_AHCI_BIOS_HANDOFF_OS_OWNED 2
-#define GRUB_AHCI_BIOS_HANDOFF_OS_OWNERSHIP_CHANGED 8
-#define GRUB_AHCI_BIOS_HANDOFF_RWC 8
grub_uint32_t unused2[53];
struct grub_ahci_hba_port ports[32];
};
+enum
+ {
+ GRUB_AHCI_HBA_CAP_NPORTS_MASK = 0x1f
+ };
+
+enum
+ {
+ GRUB_AHCI_HBA_GLOBAL_CONTROL_INTR_EN = 0x00000002,
+ GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN = 0x80000000,
+ };
+
+enum
+ {
+ GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED = 1,
+ GRUB_AHCI_BIOS_HANDOFF_OS_OWNED = 2,
+ GRUB_AHCI_BIOS_HANDOFF_OS_OWNERSHIP_CHANGED = 8,
+ GRUB_AHCI_BIOS_HANDOFF_RWC = 8
+ };
+
+
struct grub_ahci_device
{
struct grub_ahci_device *next;
int num;
struct grub_pci_dma_chunk *command_list_chunk;
volatile struct grub_ahci_cmd_head *command_list;
+ struct grub_pci_dma_chunk *command_table_chunk;
+ volatile struct grub_ahci_cmd_table *command_table;
};
+enum
+ {
+ GRUB_AHCI_CONFIG_READ = 0,
+ GRUB_AHCI_CONFIG_CFIS_LENGTH_MASK = 0x1f,
+ GRUB_AHCI_CONFIG_ATAPI = 0x20,
+ GRUB_AHCI_CONFIG_WRITE = 0x40,
+ GRUB_AHCI_CONFIG_PREFETCH = 0x80,
+ GRUB_AHCI_CONFIG_RESET = 0x100,
+ GRUB_AHCI_CONFIG_BIST = 0x200,
+ GRUB_AHCI_CONFIG_CLEAR_R_OK = 0x400,
+ GRUB_AHCI_CONFIG_PMP_MASK = 0xf000,
+ GRUB_AHCI_CONFIG_PRDT_LENGTH_MASK = 0xffff0000,
+ };
+#define GRUB_AHCI_CONFIG_CFIS_LENGTH_SHIFT 0
+#define GRUB_AHCI_CONFIG_PMP_SHIFT 12
+#define GRUB_AHCI_CONFIG_PRDT_LENGTH_SHIFT 16
+#define GRUB_AHCI_INTERRUPT_ON_COMPLETE 0x80000000
+
+#define GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH 0x200000
+
static struct grub_ahci_device *grub_ahci_devices;
static int numdevs;
static int NESTED_FUNC_ATTR
grub_ahci_pciinit (grub_pci_device_t dev,
- grub_pci_id_t pciid __attribute__ ((unused)))
+ grub_pci_id_t pciid __attribute__ ((unused)))
{
grub_pci_address_t addr;
grub_uint32_t class;
grub_uint32_t bar;
- int nports;
+ unsigned i, nports;
volatile struct grub_ahci_hba *hba;
/* Read class. */
addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG5);
bar = grub_pci_read (addr);
- if (bar & (GRUB_PCI_ADDR_SPACE_MASK | GRUB_PCI_ADDR_MEM_TYPE_MASK
- | GRUB_PCI_ADDR_MEM_PREFETCH)
+ if ((bar & (GRUB_PCI_ADDR_SPACE_MASK | GRUB_PCI_ADDR_MEM_TYPE_MASK
+ | GRUB_PCI_ADDR_MEM_PREFETCH))
!= (GRUB_PCI_ADDR_SPACE_MEMORY | GRUB_PCI_ADDR_MEM_TYPE_32))
return 0;
hba->global_control |= GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN;
- nports = hba->cap & GRUB_AHCI_HBA_CAP_MASK;
+ nports = (hba->cap & GRUB_AHCI_HBA_CAP_NPORTS_MASK) + 1;
if (! (hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_OS_OWNED))
{
}
else
grub_dprintf ("ahci", "AHCI is already in OS mode\n");
+ grub_dprintf ("ahci", "%d AHCI ports\n", nports);
for (i = 0; i < nports; i++)
{
struct grub_ahci_device *adev;
struct grub_pci_dma_chunk *command_list;
+ struct grub_pci_dma_chunk *command_table;
if (!(hba->ports_implemented & (1 << i)))
continue;
if (!command_list)
return 1;
+ command_table = grub_memalign_dma32 (1024,
+ sizeof (struct grub_ahci_cmd_table));
+ if (!command_table)
+ {
+ grub_dma_free (command_list);
+ return 1;
+ }
+
adev = grub_malloc (sizeof (*adev));
if (!adev)
{
grub_dma_free (command_list);
+ grub_dma_free (command_table);
return 1;
}
+ grub_dprintf ("ahci", "found device ahci%d (port %d)\n", numdevs, i);
+
adev->hba = hba;
adev->port = i;
adev->num = numdevs++;
adev->command_list_chunk = command_list;
adev->command_list = grub_dma_get_virt (command_list);
+ adev->command_table_chunk = command_table;
+ adev->command_table = grub_dma_get_virt (command_table);
+ adev->command_list->command_table_base
+ = grub_dma_get_phys (command_table);
+
adev->hba->ports[i].command_list_base = grub_dma_get_phys (command_list);
grub_list_push (GRUB_AS_LIST_P (&grub_ahci_devices),
GRUB_AS_LIST (adev));
\f
static int
-grub_ahci_iterate (int (*hook) (int bus, int luns))
+grub_ahci_iterate (int (*hook) (int id, int bus))
{
struct grub_ahci_device *dev;
FOR_LIST_ELEMENTS(dev, grub_ahci_devices)
- if (hook (dev->num, 1))
+ if (hook (GRUB_SCSI_SUBSYSTEM_AHCI, dev->num))
return 1;
return 0;
}
#endif
-static grub_err_t
-grub_ahci_packet (struct grub_ahci_device *dev, char *packet,
- grub_size_t size)
-{
-
- return GRUB_ERR_NONE;
-}
+enum
+ {
+ GRUB_AHCI_FIS_REG_H2D = 0x27
+ };
-static grub_err_t
-grub_ahci_read (struct grub_scsi *scsi,
- grub_size_t cmdsize __attribute__((unused)),
- char *cmd, grub_size_t size, char *buf)
+static const int register_map[11] = { 3 /* Features */,
+ 12 /* Sectors */,
+ 4 /* LBA low */,
+ 5 /* LBA mid */,
+ 6 /* LBA high */,
+ 7 /* Device */,
+ 2 /* CMD register */,
+ 13 /* Sectors 48 */,
+ 8 /* LBA48 low */,
+ 9 /* LBA48 mid */,
+ 10 /* LBA48 high */ };
+
+static grub_err_t
+grub_ahci_readwrite (grub_ata_t disk,
+ struct grub_disk_ata_pass_through_parms *parms)
{
- struct grub_ahci_device *dev = (struct grub_ahci_device *) scsi->data;
-
- grub_dprintf("ahci", "grub_ahci_read (size=%llu)\n", (unsigned long long) size);
-
- if (grub_atapi_packet (dev, cmd, size))
- return grub_errno;
-
- grub_size_t nread = 0;
- while (nread < size)
- {
- /* Wait for !BSY, DRQ, I/O, !C/D. */
- if (grub_atapi_wait_drq (dev, GRUB_ATAPI_IREASON_DATA_IN, GRUB_ATA_TOUT_DATA))
- return grub_errno;
-
- /* Get byte count for this DRQ assertion. */
- unsigned cnt = grub_ata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
- | grub_ata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
- grub_dprintf("ata", "DRQ count=%u\n", cnt);
-
- /* Count of last transfer may be uneven. */
- if (! (0 < cnt && cnt <= size - nread && (! (cnt & 1) || cnt == size - nread)))
- return grub_error (GRUB_ERR_READ_ERROR, "invalid ATAPI transfer count");
-
- /* Read the data. */
- grub_ata_pio_read (dev, buf + nread, cnt);
-
- if (cnt & 1)
- buf[nread + cnt - 1] = (char) grub_le_to_cpu16 (grub_inw (dev->ioaddress + GRUB_ATA_REG_DATA));
-
- nread += cnt;
- }
+ struct grub_ahci_device *dev = (struct grub_ahci_device *) disk->data;
+ struct grub_pci_dma_chunk *bufc;
+ grub_uint64_t endtime;
+ unsigned i;
+
+ grub_dprintf("ahci", "grub_ahci_read (size=%llu, cmdsize = %llu)\n",
+ (unsigned long long) parms->size,
+ (unsigned long long) parms->cmdsize);
+
+ if (parms->cmdsize != 0 && parms->cmdsize != 12 && parms->cmdsize != 16)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "incorrect ATAPI command size");
+
+ if (parms->size > GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "too big data buffer");
+
+ bufc = grub_memalign_dma32 (1024, parms->size + (parms->size & 1));
+
+ /* FIXME: support port multipliers. */
+ dev->command_list[0].config
+ = (4 << GRUB_AHCI_CONFIG_CFIS_LENGTH_SHIFT)
+ | GRUB_AHCI_CONFIG_CLEAR_R_OK
+ | (0 << GRUB_AHCI_CONFIG_PMP_SHIFT)
+ | (1 << GRUB_AHCI_CONFIG_PRDT_LENGTH_SHIFT)
+ | (parms->cmdsize ? GRUB_AHCI_CONFIG_ATAPI : 0)
+ | (parms->write ? GRUB_AHCI_CONFIG_WRITE : GRUB_AHCI_CONFIG_READ);
+ dev->command_list[0].transfered = 0;
+ dev->command_list[0].command_table_base
+ = grub_dma_get_phys (dev->command_table_chunk);
+ grub_memset ((char *) dev->command_list[0].unused, 0,
+ sizeof (dev->command_list[0].unused));
+ grub_memset ((char *) &dev->command_table[0], 0,
+ sizeof (dev->command_table[0]));
+ if (parms->cmdsize)
+ grub_memcpy ((char *) dev->command_table[0].command, parms->cmd,
+ parms->cmdsize);
+
+ dev->command_table[0].cfis[0] = GRUB_AHCI_FIS_REG_H2D;
+ for (i = 0; i < sizeof (parms->taskfile.raw); i++)
+ dev->command_table[0].cfis[register_map[i]] = parms->taskfile.raw[i];
+
+ dev->command_table[0].prdt[0].data_base = grub_dma_get_phys (bufc);
+ dev->command_table[0].prdt[0].unused = 0;
+ dev->command_table[0].prdt[0].size = (parms->size + (parms->size & 1) - 1)
+ | GRUB_AHCI_INTERRUPT_ON_COMPLETE;
+
+ if (parms->write)
+ grub_memcpy ((char *) grub_dma_get_virt (bufc), parms->buffer, parms->size);
+
+ grub_dprintf ("ahci", "AHCI command schedulded\n");
+ dev->hba->ports[dev->port].inten = (1 << 5);
+ dev->hba->ports[dev->port].intstatus = (1 << 5);
+ dev->hba->ports[dev->port].command_issue |= 1;
+ dev->hba->ports[dev->port].command |= 1;
+
+ endtime = grub_get_time_ms () + 1000;
+ while (!(dev->hba->ports[dev->port].intstatus & (1 << 5)))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "AHCI timeout\n");
+ dev->hba->ports[dev->port].command &= ~1;
+ /* FIXME: free resources. */
+ return grub_error (GRUB_ERR_IO, "AHCI transfer timeouted");
+ }
+
+ grub_dprintf ("ahci", "AHCI command completed succesfully\n");
+ dev->hba->ports[dev->port].command &= ~1;
+
+ if (!parms->write)
+ grub_memcpy (parms->buffer, (char *) grub_dma_get_virt (bufc), parms->size);
+ grub_dma_free (bufc);
return GRUB_ERR_NONE;
}
static grub_err_t
-grub_ahci_write (struct grub_scsi *scsi __attribute__((unused)),
- grub_size_t cmdsize __attribute__((unused)),
- char *cmd __attribute__((unused)),
- grub_size_t size __attribute__((unused)),
- char *buf __attribute__((unused)))
-{
- // XXX: scsi.mod does not use write yet.
- return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "AHCI write not implemented");
-}
-
-static grub_err_t
-grub_ahci_open (int devnum, struct grub_scsi *scsi)
+grub_ahci_open (int id, int devnum, struct grub_ata *ata)
{
struct grub_ahci_device *dev;
+ if (id != GRUB_SCSI_SUBSYSTEM_AHCI)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an AHCI device");
+
FOR_LIST_ELEMENTS(dev, grub_ahci_devices)
{
if (dev->num == devnum)
break;
}
- grub_dprintf ("ata", "opening AHCI dev `ahci%d'\n", devnum);
-
if (! dev)
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such AHCI device");
- scsi->data = dev;
+ grub_dprintf ("ahci", "opening AHCI dev `ahci%d'\n", dev->num);
+
+ ata->data = dev;
return GRUB_ERR_NONE;
}
-
-static struct grub_scsi_dev grub_ahci_dev =
+static struct grub_ata_dev grub_ahci_dev =
{
- .name = "ahci",
- .id = GRUB_SCSI_SUBSYSTEM_AHCI,
.iterate = grub_ahci_iterate,
.open = grub_ahci_open,
- .read = grub_ahci_read,
- .write = grub_ahci_write
+ .readwrite = grub_ahci_readwrite,
};
\f
GRUB_MOD_INIT(ahci)
{
/* To prevent two drivers operating on the same disks. */
- grub_disk_firmware_is_tainted = 1;
- if (grub_disk_firmware_fini)
+ // grub_disk_firmware_is_tainted = 1;
+ if (0 && grub_disk_firmware_fini)
{
grub_disk_firmware_fini ();
grub_disk_firmware_fini = NULL;
grub_ahci_initialize ();
/* AHCI devices are handled by scsi.mod. */
- grub_scsi_dev_register (&grub_ahci_dev);
+ grub_ata_dev_register (&grub_ahci_dev);
}
GRUB_MOD_FINI(ahci)
{
- grub_scsi_dev_unregister (&grub_ahci_dev);
+ grub_ata_dev_unregister (&grub_ahci_dev);
}
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
-#include <grub/time.h>
-#include <grub/pci.h>
#include <grub/scsi.h>
-#include <grub/cs5536.h>
-/* At the moment, only two IDE ports are supported. */
-static const grub_port_t grub_ata_ioaddress[] = { GRUB_ATA_CH0_PORT1,
- GRUB_ATA_CH1_PORT1 };
-static const grub_port_t grub_ata_ioaddress2[] = { GRUB_ATA_CH0_PORT2,
- GRUB_ATA_CH1_PORT2 };
-
-static struct grub_ata_device *grub_ata_devices;
-
-/* Wait for !BSY. */
-grub_err_t
-grub_ata_wait_not_busy (struct grub_ata_device *dev, int milliseconds)
-{
- /* ATA requires 400ns (after a write to CMD register) or
- 1 PIO cycle (after a DRQ block transfer) before
- first check of BSY. */
- grub_millisleep (1);
-
- int i = 1;
- grub_uint8_t sts;
- while ((sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS))
- & GRUB_ATA_STATUS_BUSY)
- {
- if (i >= milliseconds)
- {
- grub_dprintf ("ata", "timeout: %dms, status=0x%x\n",
- milliseconds, sts);
- return grub_error (GRUB_ERR_TIMEOUT, "ATA timeout");
- }
-
- grub_millisleep (1);
- i++;
- }
-
- return GRUB_ERR_NONE;
-}
-
-static inline void
-grub_ata_wait (void)
-{
- grub_millisleep (50);
-}
-
-/* Wait for !BSY, DRQ. */
-grub_err_t
-grub_ata_wait_drq (struct grub_ata_device *dev, int rw,
- int milliseconds)
-{
- if (grub_ata_wait_not_busy (dev, milliseconds))
- return grub_errno;
-
- /* !DRQ implies error condition. */
- grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
- if ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
- != GRUB_ATA_STATUS_DRQ)
- {
- grub_dprintf ("ata", "ata error: status=0x%x, error=0x%x\n",
- sts, grub_ata_regget (dev, GRUB_ATA_REG_ERROR));
- if (! rw)
- return grub_error (GRUB_ERR_READ_ERROR, "ATA read error");
- else
- return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
- }
-
- return GRUB_ERR_NONE;
-}
+static grub_ata_dev_t grub_ata_dev_list;
/* Byteorder has to be changed before strings can be read. */
static void
dst[len] = '\0';
}
-void
-grub_ata_pio_read (struct grub_ata_device *dev, char *buf, grub_size_t size)
-{
- grub_uint16_t *buf16 = (grub_uint16_t *) buf;
- unsigned int i;
-
- /* Read in the data, word by word. */
- for (i = 0; i < size / 2; i++)
- buf16[i] = grub_le_to_cpu16 (grub_inw(dev->ioaddress + GRUB_ATA_REG_DATA));
-}
-
-static void
-grub_ata_pio_write (struct grub_ata_device *dev, char *buf, grub_size_t size)
-{
- grub_uint16_t *buf16 = (grub_uint16_t *) buf;
- unsigned int i;
-
- /* Write the data, word by word. */
- for (i = 0; i < size / 2; i++)
- grub_outw(grub_cpu_to_le16 (buf16[i]), dev->ioaddress + GRUB_ATA_REG_DATA);
-}
-
static void
-grub_ata_dumpinfo (struct grub_ata_device *dev, char *info)
+grub_ata_dumpinfo (struct grub_ata *dev, char *info)
{
char text[41];
}
static grub_err_t
-grub_atapi_identify (struct grub_ata_device *dev)
+grub_atapi_identify (struct grub_ata *dev)
{
+ struct grub_disk_ata_pass_through_parms parms;
char *info;
+ grub_err_t err;
info = grub_malloc (GRUB_DISK_SECTOR_SIZE);
if (! info)
return grub_errno;
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4);
- grub_ata_wait ();
- if (grub_ata_check_ready (dev))
- {
- grub_free (info);
- return grub_errno;
- }
+ grub_memset (&parms, 0, sizeof (parms));
+ parms.taskfile.disk = 0;
+ parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_PACKET_DEVICE;
- grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_IDENTIFY_PACKET_DEVICE);
- grub_ata_wait ();
+ err = dev->dev->readwrite (dev, &parms);
+ if (err)
+ return err;
- if (grub_ata_wait_drq (dev, 0, GRUB_ATA_TOUT_STD))
- {
- grub_free (info);
- return grub_errno;
- }
- grub_ata_pio_read (dev, info, GRUB_DISK_SECTOR_SIZE);
+ if (parms.size != GRUB_DISK_SECTOR_SIZE)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "device cannot be identified");
dev->atapi = 1;
}
static grub_err_t
-grub_atapi_wait_drq (struct grub_ata_device *dev,
- grub_uint8_t ireason,
- int milliseconds)
-{
- /* Wait for !BSY, DRQ, ireason */
- if (grub_ata_wait_not_busy (dev, milliseconds))
- return grub_errno;
-
- grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
- grub_uint8_t irs = grub_ata_regget (dev, GRUB_ATAPI_REG_IREASON);
-
- /* OK if DRQ is asserted and interrupt reason is as expected. */
- if ((sts & GRUB_ATA_STATUS_DRQ)
- && (irs & GRUB_ATAPI_IREASON_MASK) == ireason)
- return GRUB_ERR_NONE;
-
- /* !DRQ implies error condition. */
- grub_dprintf ("ata", "atapi error: status=0x%x, ireason=0x%x, error=0x%x\n",
- sts, irs, grub_ata_regget (dev, GRUB_ATA_REG_ERROR));
-
- if (! (sts & GRUB_ATA_STATUS_DRQ)
- && (irs & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_ERROR)
- {
- if (ireason == GRUB_ATAPI_IREASON_CMD_OUT)
- return grub_error (GRUB_ERR_READ_ERROR, "ATA PACKET command error");
- else
- return grub_error (GRUB_ERR_READ_ERROR, "ATAPI read error");
- }
-
- return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
-}
-
-static grub_err_t
-grub_atapi_packet (struct grub_ata_device *dev, char *packet,
- grub_size_t size)
-{
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
- if (grub_ata_check_ready (dev))
- return grub_errno;
-
- /* Send ATA PACKET command. */
- grub_ata_regset (dev, GRUB_ATA_REG_FEATURES, 0);
- grub_ata_regset (dev, GRUB_ATAPI_REG_IREASON, 0);
- grub_ata_regset (dev, GRUB_ATAPI_REG_CNTHIGH, size >> 8);
- grub_ata_regset (dev, GRUB_ATAPI_REG_CNTLOW, size & 0xFF);
-
- grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_PACKET);
-
- /* Wait for !BSY, DRQ, !I/O, C/D. */
- if (grub_atapi_wait_drq (dev, GRUB_ATAPI_IREASON_CMD_OUT, GRUB_ATA_TOUT_STD))
- return grub_errno;
-
- /* Write the packet. */
- grub_ata_pio_write (dev, packet, 12);
-
- return GRUB_ERR_NONE;
-}
-
-static grub_err_t
-grub_ata_identify (struct grub_ata_device *dev)
+grub_ata_identify (struct grub_ata *dev)
{
+ struct grub_disk_ata_pass_through_parms parms;
char *info;
grub_uint16_t *info16;
+ grub_err_t err;
info = grub_malloc (GRUB_DISK_SECTOR_SIZE);
if (! info)
return grub_errno;
info16 = (grub_uint16_t *) info;
+ grub_memset (&parms, 0, sizeof (parms));
+ parms.buffer = info;
+ parms.taskfile.disk = 0;
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4);
- grub_ata_wait ();
- if (grub_ata_check_ready (dev))
- {
- grub_free (info);
- return grub_errno;
- }
+ parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_DEVICE;
- grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_IDENTIFY_DEVICE);
- grub_ata_wait ();
+ err = dev->dev->readwrite (dev, &parms);
+ if (err)
+ return err;
- if (grub_ata_wait_drq (dev, 0, GRUB_ATA_TOUT_STD))
+ if (parms.size != GRUB_DISK_SECTOR_SIZE)
{
+ grub_uint8_t sts = parms.taskfile.status;
grub_free (info);
- grub_errno = GRUB_ERR_NONE;
- grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
-
if ((sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ
| GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_ERR
- && (grub_ata_regget (dev, GRUB_ATA_REG_ERROR) & 0x04 /* ABRT */))
+ && (parms.taskfile.error & 0x04 /* ABRT */))
/* Device without ATA IDENTIFY, try ATAPI. */
return grub_atapi_identify (dev);
"device cannot be identified");
}
- grub_ata_pio_read (dev, info, GRUB_DISK_SECTOR_SIZE);
-
- /* Re-check status to avoid bogus identify data due to stuck DRQ. */
- grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
- if (sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
- {
- grub_dprintf ("ata", "bad status=0x%x\n", sts);
- grub_free (info);
- /* No device, return error but don't print message. */
- grub_errno = GRUB_ERR_NONE;
- return GRUB_ERR_UNKNOWN_DEVICE;
- }
-
/* Now it is certain that this is not an ATAPI device. */
dev->atapi = 0;
}
static grub_err_t
-check_device (struct grub_ata_device *dev)
-{
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
- grub_ata_wait ();
-
- /* Try to detect if the port is in use by writing to it,
- waiting for a while and reading it again. If the value
- was preserved, there is a device connected. */
- grub_ata_regset (dev, GRUB_ATA_REG_SECTORS, 0x5A);
- grub_ata_wait ();
- grub_uint8_t sec = grub_ata_regget (dev, GRUB_ATA_REG_SECTORS);
- grub_dprintf ("ata", "sectors=0x%x\n", sec);
- if (sec != 0x5A)
- return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no device connected");
-
- /* The above test may detect a second (slave) device
- connected to a SATA controller which supports only one
- (master) device. It is not safe to use the status register
- READY bit to check for controller channel existence. Some
- ATAPI commands (RESET, DIAGNOSTIC) may clear this bit. */
-
- /* Use the IDENTIFY DEVICE command to query the device. */
- return grub_ata_identify (dev);
-}
-
-static grub_err_t
-grub_ata_device_initialize (int port, int device, int addr, int addr2)
-{
- struct grub_ata_device *dev;
- struct grub_ata_device **devp;
- grub_err_t err;
-
- grub_dprintf ("ata", "detecting device %d,%d (0x%x, 0x%x)\n",
- port, device, addr, addr2);
-
- dev = grub_malloc (sizeof(*dev));
- if (! dev)
- return grub_errno;
-
- /* Setup the device information. */
- dev->port = port;
- dev->device = device;
- dev->ioaddress = addr + GRUB_MACHINE_PCI_IO_BASE;
- dev->ioaddress2 = addr2 + GRUB_MACHINE_PCI_IO_BASE;
- dev->next = NULL;
-
- /* Register the device. */
- for (devp = &grub_ata_devices; *devp; devp = &(*devp)->next);
- *devp = dev;
-
- err = check_device (dev);
- if (err)
- grub_print_error ();
-
- return 0;
-}
-
-static int NESTED_FUNC_ATTR
-grub_ata_pciinit (grub_pci_device_t dev,
- grub_pci_id_t pciid)
-{
- static int compat_use[2] = { 0 };
- grub_pci_address_t addr;
- grub_uint32_t class;
- grub_uint32_t bar1;
- grub_uint32_t bar2;
- int rega;
- int regb;
- int i;
- static int controller = 0;
- int cs5536 = 0;
- int nports = 2;
-
- /* Read class. */
- addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
- class = grub_pci_read (addr);
-
- /* AMD CS5536 Southbridge. */
- if (pciid == GRUB_CS5536_PCIID)
- {
- cs5536 = 1;
- nports = 1;
- }
-
- /* Check if this class ID matches that of a PCI IDE Controller. */
- if (!cs5536 && (class >> 16 != 0x0101))
- return 0;
-
- for (i = 0; i < nports; i++)
- {
- /* Set to 0 when the channel operated in compatibility mode. */
- int compat;
-
- /* We don't support non-compatibility mode for CS5536. */
- if (cs5536)
- compat = 0;
- else
- compat = (class >> (8 + 2 * i)) & 1;
-
- rega = 0;
- regb = 0;
-
- /* If the channel is in compatibility mode, just assign the
- default registers. */
- if (compat == 0 && !compat_use[i])
- {
- rega = grub_ata_ioaddress[i];
- regb = grub_ata_ioaddress2[i];
- compat_use[i] = 1;
- }
- else if (compat)
- {
- /* Read the BARs, which either contain a mmapped IO address
- or the IO port address. */
- addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
- + sizeof (grub_uint64_t) * i);
- bar1 = grub_pci_read (addr);
- addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
- + sizeof (grub_uint64_t) * i
- + sizeof (grub_uint32_t));
- bar2 = grub_pci_read (addr);
-
- /* Check if the BARs describe an IO region. */
- if ((bar1 & 1) && (bar2 & 1))
- {
- rega = bar1 & ~3;
- regb = bar2 & ~3;
- }
- }
-
- grub_dprintf ("ata",
- "PCI dev (%d,%d,%d) compat=%d rega=0x%x regb=0x%x\n",
- grub_pci_get_bus (dev), grub_pci_get_device (dev),
- grub_pci_get_function (dev), compat, rega, regb);
-
- if (rega && regb)
- {
- grub_errno = GRUB_ERR_NONE;
- grub_ata_device_initialize (controller * 2 + i, 0, rega, regb);
-
- /* Most errors raised by grub_ata_device_initialize() are harmless.
- They just indicate this particular drive is not responding, most
- likely because it doesn't exist. We might want to ignore specific
- error types here, instead of printing them. */
- if (grub_errno)
- {
- grub_print_error ();
- grub_errno = GRUB_ERR_NONE;
- }
-
- grub_ata_device_initialize (controller * 2 + i, 1, rega, regb);
-
- /* Likewise. */
- if (grub_errno)
- {
- grub_print_error ();
- grub_errno = GRUB_ERR_NONE;
- }
- }
- }
-
- controller++;
-
- return 0;
-}
-
-static grub_err_t
-grub_ata_initialize (void)
-{
- grub_pci_iterate (grub_ata_pciinit);
- return 0;
-}
-
-static void
-grub_ata_setlba (struct grub_ata_device *dev, grub_disk_addr_t sector,
- grub_size_t size)
-{
- grub_ata_regset (dev, GRUB_ATA_REG_SECTORS, size);
- grub_ata_regset (dev, GRUB_ATA_REG_LBALOW, sector & 0xFF);
- grub_ata_regset (dev, GRUB_ATA_REG_LBAMID, (sector >> 8) & 0xFF);
- grub_ata_regset (dev, GRUB_ATA_REG_LBAHIGH, (sector >> 16) & 0xFF);
-}
-
-static grub_err_t
-grub_ata_setaddress (struct grub_ata_device *dev,
- grub_ata_addressing_t addressing,
+grub_ata_setaddress (struct grub_ata *dev,
+ struct grub_disk_ata_pass_through_parms *parms,
grub_disk_addr_t sector,
grub_size_t size)
{
- switch (addressing)
+ switch (dev->addr)
{
case GRUB_ATA_CHS:
{
return grub_error (GRUB_ERR_OUT_OF_RANGE,
"sector %d cannot be addressed "
"using CHS addressing", sector);
-
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, (dev->device << 4) | head);
- if (grub_ata_check_ready (dev))
- return grub_errno;
-
- grub_ata_regset (dev, GRUB_ATA_REG_SECTNUM, sect);
- grub_ata_regset (dev, GRUB_ATA_REG_CYLLSB, cylinder & 0xFF);
- grub_ata_regset (dev, GRUB_ATA_REG_CYLMSB, cylinder >> 8);
+
+ parms->taskfile.disk = head;
+ parms->taskfile.sectnum = sect;
+ parms->taskfile.cyllsb = cylinder & 0xFF;
+ parms->taskfile.cylmsb = cylinder >> 8;
break;
}
case GRUB_ATA_LBA:
if (size == 256)
size = 0;
- grub_ata_regset (dev, GRUB_ATA_REG_DISK,
- 0xE0 | (dev->device << 4) | ((sector >> 24) & 0x0F));
- if (grub_ata_check_ready (dev))
- return grub_errno;
+ parms->taskfile.disk = ((sector >> 24) & 0x0F);
- grub_ata_setlba (dev, sector, size);
+ parms->taskfile.sectors = size;
+ parms->taskfile.lba_low = sector & 0xFF;
+ parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
+ parms->taskfile.lba_high = (sector >> 16) & 0xFF;
break;
case GRUB_ATA_LBA48:
if (size == 65536)
size = 0;
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | (dev->device << 4));
- if (grub_ata_check_ready (dev))
- return grub_errno;
+ parms->taskfile.disk = 0;
/* Set "Previous". */
- grub_ata_setlba (dev, sector >> 24, size >> 8);
+ parms->taskfile.sectors = size & 0xFF;
+ parms->taskfile.lba_low = sector & 0xFF;
+ parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
+ parms->taskfile.lba_high = (sector >> 16) & 0xFF;
+
/* Set "Current". */
- grub_ata_setlba (dev, sector, size);
+ parms->taskfile.sectors48 = (size >> 8) & 0xFF;
+ parms->taskfile.lba48_low = (sector >> 24) & 0xFF;
+ parms->taskfile.lba48_mid = (sector >> 32) & 0xFF;
+ parms->taskfile.lba48_high = (sector >> 40) & 0xFF;
break;
}
grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector,
grub_size_t size, char *buf, int rw)
{
- struct grub_ata_device *dev = (struct grub_ata_device *) disk->data;
-
- grub_dprintf("ata", "grub_ata_readwrite (size=%llu, rw=%d)\n", (unsigned long long) size, rw);
+ struct grub_ata *ata = disk->data;
- grub_ata_addressing_t addressing = dev->addr;
+ grub_ata_addressing_t addressing = ata->addr;
grub_size_t batch;
int cmd, cmd_write;
+ grub_dprintf("ata", "grub_ata_readwrite (size=%llu, rw=%d)\n",
+ (unsigned long long) size, rw);
+
if (addressing == GRUB_ATA_LBA48 && ((sector + size) >> 28) != 0)
{
batch = 65536;
{
if (addressing == GRUB_ATA_LBA48)
addressing = GRUB_ATA_LBA;
- batch = 256;
+ if (addressing != GRUB_ATA_CHS)
+ batch = 256;
+ else
+ batch = 1;
cmd = GRUB_ATA_CMD_READ_SECTORS;
cmd_write = GRUB_ATA_CMD_WRITE_SECTORS;
}
grub_size_t nsectors = 0;
while (nsectors < size)
{
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_err_t err;
+
if (size - nsectors < batch)
batch = size - nsectors;
grub_dprintf("ata", "rw=%d, sector=%llu, batch=%llu\n", rw, (unsigned long long) sector, (unsigned long long) batch);
-
- /* Send read/write command. */
- if (grub_ata_setaddress (dev, addressing, sector, batch))
- return grub_errno;
-
- grub_ata_regset (dev, GRUB_ATA_REG_CMD, (! rw ? cmd : cmd_write));
-
- unsigned sect;
- for (sect = 0; sect < batch; sect++)
- {
- /* Wait for !BSY, DRQ. */
- if (grub_ata_wait_drq (dev, rw, GRUB_ATA_TOUT_DATA))
- return grub_errno;
-
- /* Transfer data. */
- if (! rw)
- grub_ata_pio_read (dev, buf, GRUB_DISK_SECTOR_SIZE);
- else
- grub_ata_pio_write (dev, buf, GRUB_DISK_SECTOR_SIZE);
-
- buf += GRUB_DISK_SECTOR_SIZE;
- }
-
- if (rw)
- {
- /* Check for write error. */
- if (grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
- return grub_errno;
-
- if (grub_ata_regget (dev, GRUB_ATA_REG_STATUS)
- & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
- return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
- }
-
+ grub_memset (&parms, 0, sizeof (parms));
+ grub_ata_setaddress (ata, &parms, sector, batch);
+ parms.taskfile.cmd = (! rw ? cmd : cmd_write);
+ parms.buffer = buf;
+ parms.size = batch * GRUB_DISK_SECTOR_SIZE;
+
+ err = ata->dev->readwrite (ata, &parms);
+ if (err)
+ return err;
+ if (parms.size != batch * GRUB_DISK_SECTOR_SIZE)
+ return grub_error (GRUB_ERR_READ_ERROR, "incomplete read");
+ buf += GRUB_DISK_SECTOR_SIZE * batch;
sector += batch;
nsectors += batch;
}
\f
-static int
-grub_ata_iterate (int (*hook) (const char *name))
+static inline void
+grub_ata_real_close (struct grub_ata *ata)
{
- struct grub_ata_device *dev;
+ if (ata->dev->close)
+ ata->dev->close (ata);
+}
+
+static struct grub_ata *
+grub_ata_real_open (int id, int bus)
+{
+ struct grub_ata *ata;
+ grub_ata_dev_t p;
- for (dev = grub_ata_devices; dev; dev = dev->next)
+ ata = grub_malloc (sizeof (*ata));
+ if (!ata)
+ return NULL;
+ for (p = grub_ata_dev_list; p; p = p->next)
{
- char devname[10];
grub_err_t err;
-
- err = check_device (dev);
- if (err)
+ if (p->open (id, bus, ata))
{
grub_errno = GRUB_ERR_NONE;
continue;
}
+ ata->dev = p;
+ /* Use the IDENTIFY DEVICE command to query the device. */
+ err = grub_ata_identify (ata);
+ if (err)
+ {
+ grub_free (ata);
+ return NULL;
+ }
+ return ata;
+ }
+ grub_free (ata);
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device");
+ return NULL;
+}
- if (dev->atapi)
- continue;
-
- grub_snprintf (devname, sizeof (devname),
- "ata%d", dev->port * 2 + dev->device);
+static int
+grub_ata_iterate (int (*hook_in) (const char *name))
+{
+ auto int hook (int id, int bus);
+ int hook (int id, int bus)
+ {
+ struct grub_ata *ata;
+ int ret;
+ char devname[40];
- if (hook (devname))
- return 1;
- }
+ ata = grub_ata_real_open (id, bus);
+ if (!ata)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ if (ata->atapi)
+ {
+ grub_ata_real_close (ata);
+ return 0;
+ }
+ grub_snprintf (devname, sizeof (devname),
+ "%s%d", grub_scsi_names[id], bus);
+ ret = hook_in (devname);
+ grub_ata_real_close (ata);
+ return ret;
+ }
+
+ grub_ata_dev_t p;
+
+ for (p = grub_ata_dev_list; p; p = p->next)
+ if (p->iterate && p->iterate (hook))
+ return 1;
return 0;
}
static grub_err_t
grub_ata_open (const char *name, grub_disk_t disk)
{
- struct grub_ata_device *dev;
- grub_err_t err;
-
- for (dev = grub_ata_devices; dev; dev = dev->next)
- {
- char devname[10];
- grub_snprintf (devname, sizeof (devname),
- "ata%d", dev->port * 2 + dev->device);
- if (grub_strcmp (name, devname) == 0)
- break;
- }
-
- if (! dev)
- return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+ unsigned id, bus;
+ struct grub_ata *ata;
- if (dev->atapi)
+ for (id = 0; id < GRUB_SCSI_NUM_SUBSYSTEMS; id++)
+ if (grub_strncmp (grub_scsi_names[id], name,
+ grub_strlen (grub_scsi_names[id])) == 0
+ && grub_isdigit (name[grub_strlen (grub_scsi_names[id])]))
+ break;
+ if (id == GRUB_SCSI_NUM_SUBSYSTEMS)
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
+ bus = grub_strtoul (name + grub_strlen (grub_scsi_names[id]), 0, 0);
+ ata = grub_ata_real_open (id, bus);
+ if (!ata)
+ return grub_errno;
- err = check_device (dev);
-
- if (err)
- return err;
+ if (ata->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
- disk->total_sectors = dev->size;
+ disk->total_sectors = ata->size;
- disk->id = (unsigned long) dev;
+ disk->id = grub_make_scsi_id (id, bus, 0);
disk->has_partitions = 1;
- disk->data = dev;
+ disk->data = ata;
return 0;
}
static void
-grub_ata_close (grub_disk_t disk __attribute__((unused)))
+grub_ata_close (grub_disk_t disk)
{
-
+ struct grub_ata *ata = disk->data;
+ grub_ata_real_close (ata);
}
static grub_err_t
\f
/* ATAPI code. */
-static int
-grub_atapi_iterate (int (*hook) (int bus, int luns))
-{
- struct grub_ata_device *dev;
-
- for (dev = grub_ata_devices; dev; dev = dev->next)
- {
- grub_err_t err;
-
- err = check_device (dev);
- if (err)
- {
- grub_errno = GRUB_ERR_NONE;
- continue;
- }
-
- if (! dev->atapi)
- continue;
-
- if (hook (dev->port * 2 + dev->device, 1))
- return 1;
- }
-
- return 0;
-
-}
-
static grub_err_t
-grub_atapi_read (struct grub_scsi *scsi,
- grub_size_t cmdsize __attribute__((unused)),
- char *cmd, grub_size_t size, char *buf)
+grub_atapi_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf)
{
- struct grub_ata_device *dev = (struct grub_ata_device *) scsi->data;
+ struct grub_ata *dev = scsi->data;
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_err_t err;
grub_dprintf("ata", "grub_atapi_read (size=%llu)\n", (unsigned long long) size);
+ grub_memset (&parms, 0, sizeof (parms));
+
+ parms.taskfile.disk = 0;
+ parms.taskfile.features = 0;
+ parms.taskfile.atapi_ireason = 0;
+ parms.taskfile.atapi_cnthigh = size >> 8;
+ parms.taskfile.atapi_cntlow = size & 0xff;
+ parms.taskfile.cmd = GRUB_ATA_CMD_PACKET;
+ parms.cmd = cmd;
+ parms.cmdsize = cmdsize;
+
+ parms.size = size;
+ parms.buffer = buf;
+
+ err = dev->dev->readwrite (dev, &parms);
+ if (err)
+ return err;
- if (grub_atapi_packet (dev, cmd, size))
- return grub_errno;
-
- grub_size_t nread = 0;
- while (nread < size)
- {
- /* Wait for !BSY, DRQ, I/O, !C/D. */
- if (grub_atapi_wait_drq (dev, GRUB_ATAPI_IREASON_DATA_IN, GRUB_ATA_TOUT_DATA))
- return grub_errno;
-
- /* Get byte count for this DRQ assertion. */
- unsigned cnt = grub_ata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
- | grub_ata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
- grub_dprintf("ata", "DRQ count=%u\n", cnt);
-
- /* Count of last transfer may be uneven. */
- if (! (0 < cnt && cnt <= size - nread && (! (cnt & 1) || cnt == size - nread)))
- return grub_error (GRUB_ERR_READ_ERROR, "invalid ATAPI transfer count");
-
- /* Read the data. */
- grub_ata_pio_read (dev, buf + nread, cnt);
-
- if (cnt & 1)
- buf[nread + cnt - 1] = (char) grub_le_to_cpu16 (grub_inw (dev->ioaddress + GRUB_ATA_REG_DATA));
-
- nread += cnt;
- }
-
+ if (parms.size != size)
+ return grub_error (GRUB_ERR_READ_ERROR, "incomplete ATAPI read");
return GRUB_ERR_NONE;
}
}
static grub_err_t
-grub_atapi_open (int devnum, struct grub_scsi *scsi)
+grub_atapi_open (int id, int bus, struct grub_scsi *scsi)
{
- struct grub_ata_device *dev;
- struct grub_ata_device *devfnd = 0;
- grub_err_t err;
+ struct grub_ata *ata;
- for (dev = grub_ata_devices; dev; dev = dev->next)
- {
- if (dev->port * 2 + dev->device == devnum)
- {
- devfnd = dev;
- break;
- }
- }
+ ata = grub_ata_real_open (id, bus);
+ if (!ata)
+ return grub_errno;
+
+ if (! ata->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
- grub_dprintf ("ata", "opening ATAPI dev `ata%d'\n", devnum);
+ scsi->data = ata;
- if (! devfnd)
- return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
+ return GRUB_ERR_NONE;
+}
- err = check_device (devfnd);
- if (err)
- return err;
+static int
+grub_atapi_iterate (int (*hook_in) (int id, int bus, int luns))
+{
+ auto int hook (int id, int bus);
+ int hook (int id, int bus)
+ {
+ struct grub_ata *ata;
+ int ret;
- if (! devfnd->atapi)
- return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
+ ata = grub_ata_real_open (id, bus);
- scsi->data = devfnd;
+ if (!ata)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ if (!ata->atapi)
+ {
+ grub_ata_real_close (ata);
+ return 0;
+ }
+ ret = hook_in (id, bus, 1);
+ grub_ata_real_close (ata);
+ return ret;
+ }
+
+ grub_ata_dev_t p;
+
+ for (p = grub_ata_dev_list; p; p = p->next)
+ if (p->iterate && p->iterate (hook))
+ return 1;
+ return 0;
+}
- return GRUB_ERR_NONE;
+static void
+grub_atapi_close (grub_scsi_t disk)
+{
+ struct grub_ata *ata = disk->data;
+ grub_ata_real_close (ata);
+}
+
+
+void
+grub_ata_dev_register (grub_ata_dev_t dev)
+{
+ dev->next = grub_ata_dev_list;
+ grub_ata_dev_list = dev;
}
+void
+grub_ata_dev_unregister (grub_ata_dev_t dev)
+{
+ grub_ata_dev_t *p, q;
+
+ for (p = &grub_ata_dev_list, q = *p; q; p = &(q->next), q = q->next)
+ if (q == dev)
+ {
+ *p = q->next;
+ break;
+ }
+}
static struct grub_scsi_dev grub_atapi_dev =
{
- .name = "ata",
- .id = GRUB_SCSI_SUBSYSTEM_ATAPI,
.iterate = grub_atapi_iterate,
.open = grub_atapi_open,
+ .close = grub_atapi_close,
.read = grub_atapi_read,
- .write = grub_atapi_write
+ .write = grub_atapi_write,
+ .next = 0
};
\f
GRUB_MOD_INIT(ata)
{
- /* To prevent two drivers operating on the same disks. */
- grub_disk_firmware_is_tainted = 1;
- if (grub_disk_firmware_fini)
- {
- grub_disk_firmware_fini ();
- grub_disk_firmware_fini = NULL;
- }
-
- /* ATA initialization. */
- grub_ata_initialize ();
-
grub_disk_dev_register (&grub_atadisk_dev);
/* ATAPI devices are handled by scsi.mod. */
*/
#include <grub/ata.h>
+#include <grub/scsi.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/mm.h>
+#include <grub/pci.h>
+#include <grub/cs5536.h>
+#include <grub/time.h>
+/* At the moment, only two IDE ports are supported. */
+static const grub_port_t grub_pata_ioaddress[] = { GRUB_ATA_CH0_PORT1,
+ GRUB_ATA_CH1_PORT1 };
+static const grub_port_t grub_pata_ioaddress2[] = { GRUB_ATA_CH0_PORT2,
+ GRUB_ATA_CH1_PORT2 };
-/* ATA pass through support, used by hdparm.mod. */
+struct grub_pata_device
+{
+ /* IDE port to use. */
+ int port;
+
+ /* IO addresses on which the registers for this device can be
+ found. */
+ grub_port_t ioaddress;
+ grub_port_t ioaddress2;
+
+ /* Two devices can be connected to a single cable. Use this field
+ to select device 0 (commonly known as "master") or device 1
+ (commonly known as "slave"). */
+ int device;
+
+ struct grub_pata_device *next;
+};
+
+static struct grub_pata_device *grub_pata_devices;
+
+static inline void
+grub_pata_regset (struct grub_pata_device *dev, int reg, int val)
+{
+ grub_outb (val, dev->ioaddress + reg);
+}
+
+static inline grub_uint8_t
+grub_pata_regget (struct grub_pata_device *dev, int reg)
+{
+ return grub_inb (dev->ioaddress + reg);
+}
+
+static inline void
+grub_pata_regset2 (struct grub_pata_device *dev, int reg, int val)
+{
+ grub_outb (val, dev->ioaddress2 + reg);
+}
+
+static inline grub_uint8_t
+grub_pata_regget2 (struct grub_pata_device *dev, int reg)
+{
+ return grub_inb (dev->ioaddress2 + reg);
+}
+
+/* Wait for !BSY. */
static grub_err_t
-grub_ata_pass_through (grub_disk_t disk,
- struct grub_disk_ata_pass_through_parms *parms)
+grub_pata_wait_not_busy (struct grub_pata_device *dev, int milliseconds)
+{
+ /* ATA requires 400ns (after a write to CMD register) or
+ 1 PIO cycle (after a DRQ block transfer) before
+ first check of BSY. */
+ grub_millisleep (1);
+
+ int i = 1;
+ grub_uint8_t sts;
+ while ((sts = grub_pata_regget (dev, GRUB_ATA_REG_STATUS))
+ & GRUB_ATA_STATUS_BUSY)
+ {
+ if (i >= milliseconds)
+ {
+ grub_dprintf ("pata", "timeout: %dms, status=0x%x\n",
+ milliseconds, sts);
+ return grub_error (GRUB_ERR_TIMEOUT, "PATA timeout");
+ }
+
+ grub_millisleep (1);
+ i++;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static inline grub_err_t
+grub_pata_check_ready (struct grub_pata_device *dev)
{
- if (disk->dev->id != GRUB_DISK_DEVICE_ATA_ID)
- return grub_error (GRUB_ERR_BAD_DEVICE,
- "device not accessed via ata.mod");
+ if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
+ return grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_STD);
- struct grub_ata_device *dev = (struct grub_ata_device *) disk->data;
+ return GRUB_ERR_NONE;
+}
- if (! (parms->size == 0 || parms->size == GRUB_DISK_SECTOR_SIZE))
+static inline void
+grub_pata_wait (void)
+{
+ grub_millisleep (50);
+}
+
+static void
+grub_pata_pio_read (struct grub_pata_device *dev, char *buf, grub_size_t size)
+{
+ grub_uint16_t *buf16 = (grub_uint16_t *) buf;
+ unsigned int i;
+
+ /* Read in the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ buf16[i] = grub_le_to_cpu16 (grub_inw(dev->ioaddress + GRUB_ATA_REG_DATA));
+ if (size & 1)
+ buf[size - 1] = (char) grub_le_to_cpu16 (grub_inw (dev->ioaddress
+ + GRUB_ATA_REG_DATA));
+}
+
+static void
+grub_pata_pio_write (struct grub_pata_device *dev, char *buf, grub_size_t size)
+{
+ grub_uint16_t *buf16 = (grub_uint16_t *) buf;
+ unsigned int i;
+
+ /* Write the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ grub_outw(grub_cpu_to_le16 (buf16[i]), dev->ioaddress + GRUB_ATA_REG_DATA);
+}
+
+/* ATA pass through support, used by hdparm.mod. */
+static grub_err_t
+grub_pata_readwrite (struct grub_ata *disk,
+ struct grub_disk_ata_pass_through_parms *parms)
+{
+ struct grub_pata_device *dev = (struct grub_pata_device *) disk->data;
+ grub_size_t nread = 0;
+
+ if (! (parms->cmdsize == 0 || parms->cmdsize == 12))
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
- "ATA multi-sector read and DATA OUT not implemented");
+ "ATAPI non-12 byte commands not supported");
- grub_dprintf ("ata", "ata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
- parms->taskfile[GRUB_ATA_REG_CMD],
- parms->taskfile[GRUB_ATA_REG_FEATURES],
- parms->taskfile[GRUB_ATA_REG_SECTORS]);
- grub_dprintf ("ata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%d\n",
- parms->taskfile[GRUB_ATA_REG_LBAHIGH],
- parms->taskfile[GRUB_ATA_REG_LBAMID],
- parms->taskfile[GRUB_ATA_REG_LBALOW], parms->size);
+ grub_dprintf ("pata", "pata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
+ parms->taskfile.cmd,
+ parms->taskfile.features,
+ parms->taskfile.sectors);
+ grub_dprintf ("pata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%d\n",
+ parms->taskfile.lba_high,
+ parms->taskfile.lba_mid,
+ parms->taskfile.lba_low, parms->size);
/* Set registers. */
- grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4
- | (parms->taskfile[GRUB_ATA_REG_DISK] & 0xf));
- if (grub_ata_check_ready (dev))
+ grub_pata_regset (dev, GRUB_ATA_REG_DISK, (parms->cmdsize ? 0 : 0xE0)
+ | dev->device << 4
+ | (parms->taskfile.disk & 0xf));
+ if (grub_pata_check_ready (dev))
return grub_errno;
int i;
+ for (i = GRUB_ATA_REG_SECTORS; i <= GRUB_ATA_REG_LBAHIGH; i++)
+ grub_pata_regset (dev, i,
+ parms->taskfile.raw[7 + (i - GRUB_ATA_REG_SECTORS)]);
for (i = GRUB_ATA_REG_FEATURES; i <= GRUB_ATA_REG_LBAHIGH; i++)
- grub_ata_regset (dev, i, parms->taskfile[i]);
+ grub_pata_regset (dev, i, parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES]);
/* Start command. */
- grub_ata_regset (dev, GRUB_ATA_REG_CMD, parms->taskfile[GRUB_ATA_REG_CMD]);
+ grub_pata_regset (dev, GRUB_ATA_REG_CMD, parms->taskfile.cmd);
/* Wait for !BSY. */
- if (grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
return grub_errno;
/* Check status. */
- grub_int8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
- grub_dprintf ("ata", "status=0x%x\n", sts);
+ grub_int8_t sts = grub_pata_regget (dev, GRUB_ATA_REG_STATUS);
+ grub_dprintf ("pata", "status=0x%x\n", sts);
+
+ if (parms->cmdsize)
+ {
+ grub_uint8_t irs = grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON);
+ /* OK if DRQ is asserted and interrupt reason is as expected. */
+ if (!((sts & GRUB_ATA_STATUS_DRQ)
+ && (irs & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_CMD_OUT))
+ return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
+ /* Write the packet. */
+ grub_pata_pio_write (dev, parms->cmd, parms->cmdsize);
+ }
/* Transfer data. */
- if ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_DRQ)
+ while (nread < parms->size
+ && ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ == GRUB_ATA_STATUS_DRQ)
+ && (!parms->cmdsize
+ || ((grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON)
+ & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_DATA_IN)))
{
- if (parms->size != GRUB_DISK_SECTOR_SIZE)
- return grub_error (GRUB_ERR_READ_ERROR, "DRQ unexpected");
- grub_ata_pio_read (dev, parms->buffer, GRUB_DISK_SECTOR_SIZE);
+ unsigned cnt;
+ if (parms->cmdsize)
+ {
+ cnt = grub_pata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
+ | grub_pata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
+ grub_dprintf("pata", "DRQ count=%u\n", cnt);
+
+ /* Count of last transfer may be uneven. */
+ if (! (0 < cnt && cnt <= parms->size - nread
+ && (! (cnt & 1) || cnt == parms->size - nread)))
+ return grub_error (GRUB_ERR_READ_ERROR,
+ "invalid ATAPI transfer count");
+ }
+ else
+ cnt = GRUB_DISK_SECTOR_SIZE;
+ if (cnt > parms->size - nread)
+ cnt = parms->size - nread;
+
+ if (parms->write)
+ grub_pata_pio_write (dev, (char *) parms->buffer + nread, cnt);
+ else
+ grub_pata_pio_read (dev, (char *) parms->buffer + nread, cnt);
+
+ nread += cnt;
+ }
+ if (parms->write)
+ {
+ /* Check for write error. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS)
+ & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
}
+ parms->size = nread;
/* Return registers. */
for (i = GRUB_ATA_REG_ERROR; i <= GRUB_ATA_REG_STATUS; i++)
- parms->taskfile[i] = grub_ata_regget (dev, i);
+ parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES] = grub_pata_regget (dev, i);
- grub_dprintf ("ata", "status=0x%x, error=0x%x, sectors=0x%x\n",
- parms->taskfile[GRUB_ATA_REG_STATUS],
- parms->taskfile[GRUB_ATA_REG_ERROR],
- parms->taskfile[GRUB_ATA_REG_SECTORS]);
+ grub_dprintf ("pata", "status=0x%x, error=0x%x, sectors=0x%x\n",
+ parms->taskfile.status,
+ parms->taskfile.error,
+ parms->taskfile.sectors);
- if (parms->taskfile[GRUB_ATA_REG_STATUS]
+ if (parms->taskfile.status
& (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
- return grub_error (GRUB_ERR_READ_ERROR, "ATA passthrough failed");
+ return grub_error (GRUB_ERR_READ_ERROR, "PATA passthrough failed");
return GRUB_ERR_NONE;
}
+static grub_err_t
+check_device (struct grub_pata_device *dev)
+{
+ grub_pata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
+ grub_pata_wait ();
+
+ /* Try to detect if the port is in use by writing to it,
+ waiting for a while and reading it again. If the value
+ was preserved, there is a device connected. */
+ grub_pata_regset (dev, GRUB_ATA_REG_SECTORS, 0x5A);
+ grub_pata_wait ();
+ grub_uint8_t sec = grub_pata_regget (dev, GRUB_ATA_REG_SECTORS);
+ grub_dprintf ("ata", "sectors=0x%x\n", sec);
+ if (sec != 0x5A)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no device connected");
+
+ /* The above test may detect a second (slave) device
+ connected to a SATA controller which supports only one
+ (master) device. It is not safe to use the status register
+ READY bit to check for controller channel existence. Some
+ ATAPI commands (RESET, DIAGNOSTIC) may clear this bit. */
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_pata_device_initialize (int port, int device, int addr, int addr2)
+{
+ struct grub_pata_device *dev;
+ struct grub_pata_device **devp;
+ grub_err_t err;
+
+ grub_dprintf ("pata", "detecting device %d,%d (0x%x, 0x%x)\n",
+ port, device, addr, addr2);
+
+ dev = grub_malloc (sizeof(*dev));
+ if (! dev)
+ return grub_errno;
+
+ /* Setup the device information. */
+ dev->port = port;
+ dev->device = device;
+ dev->ioaddress = addr + GRUB_MACHINE_PCI_IO_BASE;
+ dev->ioaddress2 = addr2 + GRUB_MACHINE_PCI_IO_BASE;
+ dev->next = NULL;
+
+ /* Register the device. */
+ for (devp = &grub_pata_devices; *devp; devp = &(*devp)->next);
+ *devp = dev;
+
+ err = check_device (dev);
+ if (err)
+ grub_print_error ();
+
+ return 0;
+}
+
+static int NESTED_FUNC_ATTR
+grub_pata_pciinit (grub_pci_device_t dev,
+ grub_pci_id_t pciid)
+{
+ static int compat_use[2] = { 0 };
+ grub_pci_address_t addr;
+ grub_uint32_t class;
+ grub_uint32_t bar1;
+ grub_uint32_t bar2;
+ int rega;
+ int regb;
+ int i;
+ static int controller = 0;
+ int cs5536 = 0;
+ int nports = 2;
+
+ /* Read class. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class = grub_pci_read (addr);
+
+ /* AMD CS5536 Southbridge. */
+ if (pciid == GRUB_CS5536_PCIID)
+ {
+ cs5536 = 1;
+ nports = 1;
+ }
+
+ /* Check if this class ID matches that of a PCI IDE Controller. */
+ if (!cs5536 && (class >> 16 != 0x0101))
+ return 0;
+
+ for (i = 0; i < nports; i++)
+ {
+ /* Set to 0 when the channel operated in compatibility mode. */
+ int compat;
+
+ /* We don't support non-compatibility mode for CS5536. */
+ if (cs5536)
+ compat = 0;
+ else
+ compat = (class >> (8 + 2 * i)) & 1;
+
+ rega = 0;
+ regb = 0;
+
+ /* If the channel is in compatibility mode, just assign the
+ default registers. */
+ if (compat == 0 && !compat_use[i])
+ {
+ rega = grub_pata_ioaddress[i];
+ regb = grub_pata_ioaddress2[i];
+ compat_use[i] = 1;
+ }
+ else if (compat)
+ {
+ /* Read the BARs, which either contain a mmapped IO address
+ or the IO port address. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
+ + sizeof (grub_uint64_t) * i);
+ bar1 = grub_pci_read (addr);
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
+ + sizeof (grub_uint64_t) * i
+ + sizeof (grub_uint32_t));
+ bar2 = grub_pci_read (addr);
+
+ /* Check if the BARs describe an IO region. */
+ if ((bar1 & 1) && (bar2 & 1))
+ {
+ rega = bar1 & ~3;
+ regb = bar2 & ~3;
+ }
+ }
+
+ grub_dprintf ("pata",
+ "PCI dev (%d,%d,%d) compat=%d rega=0x%x regb=0x%x\n",
+ grub_pci_get_bus (dev), grub_pci_get_device (dev),
+ grub_pci_get_function (dev), compat, rega, regb);
+
+ if (rega && regb)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ grub_pata_device_initialize (controller * 2 + i, 0, rega, regb);
+
+ /* Most errors raised by grub_ata_device_initialize() are harmless.
+ They just indicate this particular drive is not responding, most
+ likely because it doesn't exist. We might want to ignore specific
+ error types here, instead of printing them. */
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ grub_pata_device_initialize (controller * 2 + i, 1, rega, regb);
+
+ /* Likewise. */
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+ }
+
+ controller++;
+
+ return 0;
+}
+
+static grub_err_t
+grub_pata_initialize (void)
+{
+ grub_pci_iterate (grub_pata_pciinit);
+ return 0;
+}
+
+static grub_err_t
+grub_pata_open (int id, int devnum, struct grub_ata *ata)
+{
+ struct grub_pata_device *dev;
+ struct grub_pata_device *devfnd = 0;
+ grub_err_t err;
+
+ if (id != GRUB_SCSI_SUBSYSTEM_PATA)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a PATA device");
+
+ for (dev = grub_pata_devices; dev; dev = dev->next)
+ {
+ if (dev->port * 2 + dev->device == devnum)
+ {
+ devfnd = dev;
+ break;
+ }
+ }
+
+ grub_dprintf ("pata", "opening PATA dev `ata%d'\n", devnum);
+
+ if (! devfnd)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such PATA device");
+
+ err = check_device (devfnd);
+ if (err)
+ return err;
+
+ ata->data = devfnd;
+
+ return GRUB_ERR_NONE;
+}
+
+static int
+grub_pata_iterate (int (*hook) (int id, int bus))
+{
+ struct grub_pata_device *dev;
+
+ for (dev = grub_pata_devices; dev; dev = dev->next)
+ if (hook (GRUB_SCSI_SUBSYSTEM_PATA, dev->port * 2 + dev->device))
+ return 1;
+
+ return 0;
+}
+
+
+static struct grub_ata_dev grub_pata_dev =
+ {
+ .iterate = grub_pata_iterate,
+ .open = grub_pata_open,
+ .readwrite = grub_pata_readwrite,
+ };
+
+
\f
GRUB_MOD_INIT(ata_pthru)
{
- /* Register ATA pass through function. */
- grub_disk_ata_pass_through = grub_ata_pass_through;
+ /* To prevent two drivers operating on the same disks. */
+ grub_disk_firmware_is_tainted = 1;
+ if (grub_disk_firmware_fini)
+ {
+ grub_disk_firmware_fini ();
+ grub_disk_firmware_fini = NULL;
+ }
+
+ /* ATA initialization. */
+ grub_pata_initialize ();
+
+ grub_ata_dev_register (&grub_pata_dev);
}
GRUB_MOD_FINI(ata_pthru)
{
- if (grub_disk_ata_pass_through == grub_ata_pass_through)
- grub_disk_ata_pass_through = NULL;
+ grub_ata_dev_unregister (&grub_pata_dev);
}
\f
static grub_scsi_dev_t grub_scsi_dev_list;
+char grub_scsi_names[GRUB_SCSI_NUM_SUBSYSTEMS][5] = {
+ [GRUB_SCSI_SUBSYSTEM_USBMS] = "usb",
+ [GRUB_SCSI_SUBSYSTEM_PATA] = "ata",
+ [GRUB_SCSI_SUBSYSTEM_AHCI] = "ahci"
+};
+
void
grub_scsi_dev_register (grub_scsi_dev_t dev)
{
{
grub_scsi_dev_t p;
- auto int scsi_iterate (int bus, int luns);
+ auto int scsi_iterate (int id, int bus, int luns);
- int scsi_iterate (int bus, int luns)
+ int scsi_iterate (int id, int bus, int luns)
{
int i;
{
char *sname;
int ret;
- sname = grub_xasprintf ("%s%d", p->name, bus);
+ sname = grub_xasprintf ("%s%d", grub_scsi_names[id], bus);
if (!sname)
return 1;
ret = hook (sname);
{
char *sname;
int ret;
- sname = grub_xasprintf ("%s%d%c", p->name, bus, 'a' + i);
+ sname = grub_xasprintf ("%s%d%c", grub_scsi_names[id], bus, 'a' + i);
if (!sname)
return 1;
ret = hook (sname);
int lun, bus;
grub_uint64_t maxtime;
const char *nameend;
+ unsigned id;
nameend = name + grub_strlen (name) - 1;
/* Try to detect a LUN ('a'-'z'), otherwise just use the first
if (! scsi)
return grub_errno;
- for (p = grub_scsi_dev_list; p; p = p->next)
+ for (id = 0; id < ARRAY_SIZE (grub_scsi_names); id++)
+ if (grub_strncmp (grub_scsi_names[id], name, nameend - name) == 0)
+ break;
+
+ if (id == ARRAY_SIZE (grub_scsi_names))
{
- if (grub_strncmp (p->name, name, nameend - name) != 0)
- continue;
+ grub_free (scsi);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a SCSI disk");
+ }
- if (p->open (bus, scsi))
- continue;
+ for (p = grub_scsi_dev_list; p; p = p->next)
+ {
+ if (p->open (id, bus, scsi))
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
- disk->id = grub_make_scsi_id (p->id, bus, lun);
+ disk->id = grub_make_scsi_id (id, bus, lun);
disk->data = scsi;
scsi->dev = p;
scsi->lun = lun;
}
grub_free (scsi);
-
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a SCSI disk");
}
\f
static int
-grub_usbms_iterate (int (*hook) (int bus, int luns))
+grub_usbms_iterate (int (*hook) (int id, int bus, int luns))
{
unsigned i;
for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
if (grub_usbms_devices[i])
{
- if (hook (i, grub_usbms_devices[i]->luns))
+ if (hook (GRUB_SCSI_SUBSYSTEM_USBMS, i, grub_usbms_devices[i]->luns))
return 1;
}
}
static grub_err_t
-grub_usbms_open (int devnum, struct grub_scsi *scsi)
+grub_usbms_open (int id, int devnum, struct grub_scsi *scsi)
{
+ if (id != GRUB_SCSI_SUBSYSTEM_USBMS)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "not USB Mass Storage device");
+
grub_usb_poll_devices ();
if (!grub_usbms_devices[devnum])
static struct grub_scsi_dev grub_usbms_dev =
{
- .name = "usb",
- .id = GRUB_SCSI_SUBSYSTEM_USBMS,
.iterate = grub_usbms_iterate,
.open = grub_usbms_open,
.read = grub_usbms_read,
GRUB_ATA_TOUT_DATA = 10000 /* 10s DATA I/O timeout. */
};
-struct grub_ata_device
+typedef union
{
- /* IDE port to use. */
- int port;
-
- /* IO addresses on which the registers for this device can be
- found. */
- grub_port_t ioaddress;
- grub_port_t ioaddress2;
+ grub_uint8_t raw[11];
+ struct
+ {
+ union
+ {
+ grub_uint8_t features;
+ grub_uint8_t error;
+ };
+ union
+ {
+ grub_uint8_t sectors;
+ grub_uint8_t atapi_ireason;
+ };
+ union
+ {
+ grub_uint8_t lba_low;
+ grub_uint8_t sectnum;
+ };
+ union
+ {
+ grub_uint8_t lba_mid;
+ grub_uint8_t cyllsb;
+ grub_uint8_t atapi_cntlow;
+ };
+ union
+ {
+ grub_uint8_t lba_high;
+ grub_uint8_t cylmsb;
+ grub_uint8_t atapi_cnthigh;
+ };
+ grub_uint8_t disk;
+ union
+ {
+ grub_uint8_t cmd;
+ grub_uint8_t status;
+ };
+ grub_uint8_t sectors48;
+ grub_uint8_t lba48_low;
+ grub_uint8_t lba48_mid;
+ grub_uint8_t lba48_high;
+ };
+} grub_ata_regs_t;
- /* Two devices can be connected to a single cable. Use this field
- to select device 0 (commonly known as "master") or device 1
- (commonly known as "slave"). */
- int device;
+/* ATA pass through parameters and function. */
+struct grub_disk_ata_pass_through_parms
+{
+ grub_ata_regs_t taskfile;
+ void * buffer;
+ grub_size_t size;
+ int write;
+ void *cmd;
+ int cmdsize;
+};
+struct grub_ata
+{
/* Addressing methods available for accessing this device. If CHS
is only available, use that. Otherwise use LBA, except for the
high sectors. In that case use LBA48. */
/* Set to 0 for ATA, set to 1 for ATAPI. */
int atapi;
- struct grub_ata_device *next;
+ void *data;
+
+ struct grub_ata_dev *dev;
};
-grub_err_t EXPORT_FUNC(grub_ata_wait_not_busy) (struct grub_ata_device *dev,
- int milliseconds);
-grub_err_t EXPORT_FUNC(grub_ata_wait_drq) (struct grub_ata_device *dev,
- int rw, int milliseconds);
-void EXPORT_FUNC(grub_ata_pio_read) (struct grub_ata_device *dev,
- char *buf, grub_size_t size);
+typedef struct grub_ata *grub_ata_t;
-static inline void
-grub_ata_regset (struct grub_ata_device *dev, int reg, int val)
+struct grub_ata_dev
{
- grub_outb (val, dev->ioaddress + reg);
-}
+ /* Call HOOK with each device name, until HOOK returns non-zero. */
+ int (*iterate) (int (*hook) (int id, int bus));
-static inline grub_uint8_t
-grub_ata_regget (struct grub_ata_device *dev, int reg)
-{
- return grub_inb (dev->ioaddress + reg);
-}
+ /* Open the device named NAME, and set up SCSI. */
+ grub_err_t (*open) (int id, int bus, struct grub_ata *scsi);
-static inline void
-grub_ata_regset2 (struct grub_ata_device *dev, int reg, int val)
-{
- grub_outb (val, dev->ioaddress2 + reg);
-}
+ /* Close the scsi device SCSI. */
+ void (*close) (struct grub_ata *ata);
-static inline grub_uint8_t
-grub_ata_regget2 (struct grub_ata_device *dev, int reg)
-{
- return grub_inb (dev->ioaddress2 + reg);
-}
+ /* Read SIZE bytes from the device SCSI into BUF after sending the
+ command CMD of size CMDSIZE. */
+ grub_err_t (*readwrite) (struct grub_ata *ata,
+ struct grub_disk_ata_pass_through_parms *parms);
-static inline grub_err_t
-grub_ata_check_ready (struct grub_ata_device *dev)
-{
- if (grub_ata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
- return grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_STD);
+ /* The next scsi device. */
+ struct grub_ata_dev *next;
+};
+
+typedef struct grub_ata_dev *grub_ata_dev_t;
- return GRUB_ERR_NONE;
-}
+void grub_ata_dev_register (grub_ata_dev_t dev);
+void grub_ata_dev_unregister (grub_ata_dev_t dev);
#endif /* ! GRUB_ATA_HEADER */
extern void (* EXPORT_VAR(grub_disk_firmware_fini)) (void);
extern int EXPORT_VAR(grub_disk_firmware_is_tainted);
-
-/* ATA pass through parameters and function. */
-struct grub_disk_ata_pass_through_parms
-{
- grub_uint8_t taskfile[8];
- void * buffer;
- int size;
-};
-
-extern grub_err_t (* EXPORT_VAR(grub_disk_ata_pass_through)) (grub_disk_t,
- struct grub_disk_ata_pass_through_parms *);
-
+
#endif /* ! GRUB_DISK_HEADER */
enum
{
GRUB_SCSI_SUBSYSTEM_USBMS,
- GRUB_SCSI_SUBSYSTEM_ATAPI,
- GRUB_SCSI_SUBSYSTEM_AHCI
+ GRUB_SCSI_SUBSYSTEM_PATA,
+ GRUB_SCSI_SUBSYSTEM_AHCI,
+ GRUB_SCSI_NUM_SUBSYSTEMS
};
+extern char grub_scsi_names[GRUB_SCSI_NUM_SUBSYSTEMS][5];
+
#define GRUB_SCSI_ID_SUBSYSTEM_SHIFT 24
#define GRUB_SCSI_ID_BUS_SHIFT 8
#define GRUB_SCSI_ID_LUN_SHIFT 0
struct grub_scsi_dev
{
- /* The device name. */
- const char *name;
-
- grub_uint8_t id;
-
/* Call HOOK with each device name, until HOOK returns non-zero. */
- int (*iterate) (int (*hook) (int bus, int luns));
+ int (*iterate) (int (*hook) (int id, int bus, int luns));
/* Open the device named NAME, and set up SCSI. */
- grub_err_t (*open) (int bus, struct grub_scsi *scsi);
+ grub_err_t (*open) (int id, int bus, struct grub_scsi *scsi);
/* Close the scsi device SCSI. */
void (*close) (struct grub_scsi *scsi);
void (*grub_disk_firmware_fini) (void);
int grub_disk_firmware_is_tainted;
-grub_err_t (* grub_disk_ata_pass_through) (grub_disk_t,
- struct grub_disk_ata_pass_through_parms *);
-
-
#if 0
static unsigned long grub_disk_cache_hits;
static unsigned long grub_disk_cache_misses;