]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
AHCI skeleton
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Mon, 19 Jul 2010 19:22:54 +0000 (21:22 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Mon, 19 Jul 2010 19:22:54 +0000 (21:22 +0200)
conf/i386.rmk
disk/ahci.c [new file with mode: 0644]
include/grub/scsi.h

index b1df584a60ef2b357365ff468e4123c46af390aa..54edb468b7e573382da36f69e9a3c47f2bdad15b 100644 (file)
@@ -36,6 +36,11 @@ ata_mod_SOURCES = disk/ata.c
 ata_mod_CFLAGS = $(COMMON_CFLAGS)
 ata_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+pkglib_MODULES += ahci.mod
+ahci_mod_SOURCES = disk/ahci.c
+ahci_mod_CFLAGS = $(COMMON_CFLAGS)
+ahci_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For setpci.mod
 pkglib_MODULES += setpci.mod
 setpci_mod_SOURCES = commands/setpci.c
diff --git a/disk/ahci.c b/disk/ahci.c
new file mode 100644 (file)
index 0000000..1372fd6
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2007, 2008, 2009, 2010  Free Software Foundation, Inc.
+ *
+ *  GRUB 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/pci.h>
+#include <grub/scsi.h>
+
+struct grub_ahci_cmd_head
+{
+  grub_uint32_t unused[8];
+};
+
+struct grub_ahci_hba_port
+{
+  grub_uint64_t command_list_base;
+  grub_uint32_t unused[12];
+  grub_uint32_t sata_active;
+  grub_uint32_t command_issue;
+  grub_uint32_t unused[16];
+};
+
+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 ports_implemented;
+  grub_uint32_t unused1[7];
+  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];
+};
+
+struct grub_ahci_device
+{
+  struct grub_ahci_device *next;
+  volatile struct grub_ahci_hba *hba;
+  int port;
+  int num;
+  struct grub_pci_dma_chunk *command_list_chunk;
+  volatile struct grub_ahci_cmd_head *command_list;
+};
+
+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_address_t addr;
+  grub_uint32_t class;
+  grub_uint32_t bar;
+  int nports;
+  volatile struct grub_ahci_hba *hba;
+
+  /* Read class.  */
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+  class = grub_pci_read (addr);
+
+  /* Check if this class ID matches that of a PCI IDE Controller.  */
+  if (class >> 8 != 0x010601)
+    return 0;
+
+  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)
+      != (GRUB_PCI_ADDR_SPACE_MEMORY | GRUB_PCI_ADDR_MEM_TYPE_32))
+    return 0;
+
+  hba = grub_pci_device_map_range (dev, bar & GRUB_PCI_ADDR_MEM_MASK,
+                                  sizeof (hba));
+
+  hba->global_control |= GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN;
+
+  nports = hba->cap & GRUB_AHCI_HBA_CAP_MASK;
+
+  if (! (hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_OS_OWNED))
+    {
+      grub_uint64_t endtime;
+
+      grub_dprintf ("ahci", "Requesting AHCI ownership\n");
+      hba->bios_handoff = (hba->bios_handoff & ~GRUB_AHCI_BIOS_HANDOFF_RWC)
+       | GRUB_AHCI_BIOS_HANDOFF_OS_OWNED;
+      grub_dprintf ("ahci", "Waiting for BIOS to give up ownership\n");
+      endtime = grub_get_time_ms () + 1000;
+      while ((hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED)
+            && grub_get_time_ms () < endtime);
+      if (hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED)
+       {
+         grub_dprintf ("ahci", "Forcibly taking ownership\n");
+         hba->bios_handoff = GRUB_AHCI_BIOS_HANDOFF_OS_OWNED;
+         hba->bios_handoff |= GRUB_AHCI_BIOS_HANDOFF_OS_OWNERSHIP_CHANGED;
+       }
+      else
+       grub_dprintf ("ahci", "AHCI ownership obtained\n");
+    }
+  else
+    grub_dprintf ("ahci", "AHCI is already in OS mode\n");
+
+  for (i = 0; i < nports; i++)
+    {
+      struct grub_ahci_device *adev;
+      struct grub_pci_dma_chunk *command_list;
+
+      if (!(hba->ports_implemented & (1 << i)))
+       continue;
+
+      command_list = grub_memalign_dma32 (1024,
+                                         sizeof (struct grub_ahci_cmd_head));
+      if (!command_list)
+       return 1;
+
+      adev = grub_malloc (sizeof (*adev));
+      if (!adev)
+       {
+         grub_dma_free (command_list);
+         return 1;
+       }
+
+      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->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));
+    }
+
+  return 0;
+}
+
+static grub_err_t
+grub_ahci_initialize (void)
+{
+  grub_pci_iterate (grub_ahci_pciinit);
+  return grub_errno;
+}
+
+
+\f
+
+static int
+grub_ahci_iterate (int (*hook) (int bus, int luns))
+{
+  struct grub_ahci_device *dev;
+
+  FOR_LIST_ELEMENTS(dev, grub_ahci_devices)
+    if (hook (dev->num, 1))
+      return 1;
+
+  return 0;
+}
+
+#if 0
+static int
+find_free_cmd_slot (struct grub_ahci_device *dev)
+{
+  int i;
+  for (i = 0; i < 32; i++)
+    {
+      if (dev->hda->ports[dev->port].command_issue & (1 << i))
+       continue;
+      if (dev->hda->ports[dev->port].sata_active & (1 << i))
+       continue;
+      return i;
+    }
+  return -1;
+}
+#endif
+
+static grub_err_t
+grub_ahci_packet (struct grub_ahci_device *dev, char *packet,
+                 grub_size_t size)
+{
+
+  return GRUB_ERR_NONE;
+}
+
+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)
+{
+  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;
+    }
+
+  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)
+{
+  struct grub_ahci_device *dev;
+
+  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;
+
+  return GRUB_ERR_NONE;
+}
+
+
+static struct grub_scsi_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
+  };
+
+\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_fini ();
+      grub_disk_firmware_fini = NULL;
+    }
+
+  /* AHCI initialization.  */
+  grub_ahci_initialize ();
+
+  /* AHCI devices are handled by scsi.mod.  */
+  grub_scsi_dev_register (&grub_ahci_dev);
+}
+
+GRUB_MOD_FINI(ahci)
+{
+  grub_scsi_dev_unregister (&grub_ahci_dev);
+}
index b3c60f3e818a139e7ba7d15363a370111108d960..289cd8e4a2266ff2fa9b067afee0480f9334dc4a 100644 (file)
@@ -29,7 +29,8 @@ struct grub_scsi;
 enum
   {
     GRUB_SCSI_SUBSYSTEM_USBMS,
-    GRUB_SCSI_SUBSYSTEM_ATAPI
+    GRUB_SCSI_SUBSYSTEM_ATAPI,
+    GRUB_SCSI_SUBSYSTEM_AHCI
   };
 
 #define GRUB_SCSI_ID_SUBSYSTEM_SHIFT 24