summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2025-05-09 22:47:43 -0400
committerIan Moffett <ian@osmora.org>2025-05-09 22:48:25 -0400
commit69216b3d2b8e471982cc2faa545dd95cd4ad7dfb (patch)
treed753d6125d5053cbcc6bc6aadc795674db718a3e
parent64b2cb973b109d10c2a15edcbf2a102712564271 (diff)
kernel: ahci: Implement ATA IDENTIFY command
Signed-off-by: Ian Moffett <ian@osmora.org>
-rw-r--r--sys/dev/ic/ahci.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/sys/dev/ic/ahci.c b/sys/dev/ic/ahci.c
index 60ad759..b95f5bf 100644
--- a/sys/dev/ic/ahci.c
+++ b/sys/dev/ic/ahci.c
@@ -157,6 +157,42 @@ ahci_hba_reset(struct ahci_hba *hba)
}
/*
+ * Dump identify structure for debugging
+ * purposes.
+ */
+static void
+ahci_dump_identity(struct ata_identity *identity)
+{
+ char serial_number[20];
+ char model_number[40];
+ char tmp;
+
+ memcpy(serial_number, identity->serial_number, sizeof(serial_number));
+ memcpy(model_number, identity->model_number, sizeof(model_number));
+
+ serial_number[sizeof(serial_number) - 1] = '\0';
+ model_number[sizeof(model_number) - 1] = '\0';
+
+ /* Fixup endianess for serial number */
+ for (size_t i = 0; i < sizeof(serial_number); i += 2) {
+ tmp = serial_number[i];
+ serial_number[i] = serial_number[i + 1];
+ serial_number[i + 1] = tmp;
+ }
+
+ /* Fixup endianess for model number */
+ for (size_t i = 0; i < sizeof(model_number); i += 2) {
+ tmp = model_number[i];
+ model_number[i] = model_number[i + 1];
+ model_number[i + 1] = tmp;
+ }
+
+ pr_trace("drive attached\n");
+ pr_trace("model number: %s\n", model_number);
+}
+
+
+/*
* Stop an HBA port's command list and FIS
* engine.
*/
@@ -282,6 +318,99 @@ hba_port_reset(struct hba_port *port)
return 0;
}
+static int
+ahci_submit_cmd(struct ahci_hba *hba, struct hba_port *port, uint8_t slot)
+{
+ const uint32_t BUSY_BITS = (AHCI_PXTFD_BSY | AHCI_PXTFD_DRQ);
+ const uint8_t MAX_ATTEMPTS = 3;
+ uint32_t ci;
+ uint8_t attempts = 0;
+ int status = 0;
+
+ /*
+ * Spin on `TFD.BSY` and `TFD.DRQ` to ensure
+ * that the port is not busy before we send
+ * any commands.
+ */
+ if (ahci_poll_reg(&port->tfd, BUSY_BITS, false) < 0) {
+ pr_trace("cmd failed, port busy (slot=%d)\n", slot);
+ return -EBUSY;
+ }
+
+ /*
+ * Submit and wait for completion, this may take
+ * a bit so give it several attempts.
+ */
+ ci = mmio_read32(&port->ci);
+ mmio_write32(&port->ci, ci | BIT(slot));
+ while ((attempts++) < MAX_ATTEMPTS) {
+ status = ahci_poll_reg(&port->ci, BIT(slot), false);
+ if (status == 0) {
+ break;
+ }
+ }
+ if (status != 0) {
+ return status;
+ }
+
+ return 0;
+}
+
+/*
+ * Send an ATA IDENTIFY command to a
+ * SATA device.
+ */
+static int
+ahci_identify(struct ahci_hba *hba, struct hba_port *port)
+{
+ paddr_t base, buf;
+ struct ahci_cmd_hdr *cmdhdr;
+ struct ahci_cmdtab *cmdtbl;
+ struct ahci_fis_h2d *fis;
+ int cmdslot, status;
+
+ buf = vm_alloc_frame(1);
+ if (buf == 0) {
+ pr_trace("failed to alloc frame\n");
+ return -ENOMEM;
+ }
+
+ cmdslot = ahci_alloc_cmdslot(hba, port);
+ if (cmdslot < 0) {
+ pr_trace("failed to alloc cmdslot\n");
+ vm_free_frame(buf, 1);
+ return cmdslot;
+ }
+
+ base = ahci_cmdbase(port);
+ base += cmdslot * sizeof(*cmdhdr);
+
+ /* Setup the command header */
+ cmdhdr = PHYS_TO_VIRT(base);
+ cmdhdr->w = 0;
+ cmdhdr->cfl = sizeof(struct ahci_fis_h2d) / 4;
+ cmdhdr->prdtl = 1;
+
+ cmdtbl = PHYS_TO_VIRT(cmdhdr->ctba);
+ cmdtbl->prdt[0].dba = buf;
+ cmdtbl->prdt[0].dbc = 511;
+ cmdtbl->prdt[0].i = 0;
+
+ fis = (void *)&cmdtbl->cfis;
+ fis->command = ATA_CMD_IDENTIFY;
+ fis->c = 1;
+ fis->type = FIS_TYPE_H2D;
+
+ if ((status = ahci_submit_cmd(hba, port, cmdslot)) != 0) {
+ goto done;
+ }
+
+ ahci_dump_identity(PHYS_TO_VIRT(buf));
+done:
+ vm_free_frame(buf, 1);
+ return status;
+}
+
/*
* Initialize a drive on an HBA port
*