| /* |
| * |
| * Copyright (C) 2012 secunet Security Networks AG |
| * Copyright (C) 2013 Edward O'Callaghan <eocallaghan@alterapraxis.com> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <libpayload.h> |
| #include <pci.h> |
| #include <storage/ata.h> |
| #include <storage/ahci.h> |
| |
| #include "ahci_private.h" |
| |
| #ifdef DEBUG_STATUS |
| static inline u32 _ahci_clear_status(volatile u32 *const reg, |
| const char *const r, |
| const char *const f) |
| { |
| const u32 bits = *reg; |
| if (bits) |
| *reg = bits; |
| printf("ahci: %s: %s == 0x%08x\n", f, r, bits); |
| |
| return bits; |
| } |
| #define ahci_clear_status(p, r) _ahci_clear_status(&(p)->r, #r, __func__) |
| #else |
| static inline u32 _ahci_clear_status(volatile u32 *const reg) |
| { |
| const u32 bits = *reg; |
| if (bits) |
| *reg = bits; |
| return bits; |
| } |
| #define ahci_clear_status(p, r) _ahci_clear_status(&(p)->r) |
| #endif |
| |
| /** Give a buffer with even address. */ |
| static u8 *ahci_prdbuf_init(ahci_dev_t *const dev, |
| u8 *const user_buf, const size_t len, |
| const int out) |
| { |
| if ((u32)user_buf & 1) { |
| printf("ahci: Odd buffer pointer (%p).\n", user_buf); |
| if (dev->buf) /* orphaned buffer */ |
| free(dev->buf - *(dev->buf - 1)); |
| dev->buf = malloc(len + 2); |
| if (!dev->buf) |
| return NULL; |
| dev->user_buf = user_buf; |
| dev->write_back = !out; |
| dev->buflen = len; |
| if ((u32)dev->buf & 1) { |
| dev->buf[0] = 1; |
| dev->buf += 1; |
| } else { |
| dev->buf[0] = 1; |
| dev->buf[1] = 2; |
| dev->buf += 2; |
| } |
| if (out) |
| memcpy(dev->buf, user_buf, len); |
| return dev->buf; |
| } else { |
| return user_buf; |
| } |
| } |
| |
| static void ahci_prdbuf_finalize(ahci_dev_t *const dev) |
| { |
| if (dev->buf) { |
| if (dev->write_back) |
| memcpy(dev->user_buf, dev->buf, dev->buflen); |
| free(dev->buf - *(dev->buf - 1)); |
| } |
| dev->buf = NULL; |
| dev->user_buf = NULL; |
| dev->write_back = 0; |
| dev->buflen = 0; |
| } |
| |
| int ahci_cmdengine_start(hba_port_t *const port) |
| { |
| /* CR has to be clear before starting the command engine. |
| This shouldn't take too long, but we should time out nevertheless. */ |
| int timeout = 1000; /* Time out after 1000 * 1us == 1ms. */ |
| while ((port->cmd_stat & HBA_PxCMD_CR) && timeout--) |
| udelay(1); |
| if (timeout < 0) { |
| printf("ahci: Timeout during start of command engine.\n"); |
| return 1; |
| } |
| |
| port->cmd_stat |= HBA_PxCMD_FRE; |
| port->cmd_stat |= HBA_PxCMD_ST; |
| return 0; |
| } |
| |
| int ahci_cmdengine_stop(hba_port_t *const port) |
| { |
| port->cmd_stat &= ~HBA_PxCMD_ST; |
| |
| /* Wait for the controller to clear CR. |
| This shouldn't take too long, but we should time out nevertheless. */ |
| int timeout = 1000; /* Time out after 1000 * 1us == 1ms. */ |
| while ((port->cmd_stat & HBA_PxCMD_CR) && timeout--) |
| udelay(1); |
| if (timeout < 0) { |
| printf("ahci: Timeout during stopping of command engine.\n"); |
| return 1; |
| } |
| |
| port->cmd_stat &= ~HBA_PxCMD_FRE; |
| |
| /* Wait for the controller to clear FR. |
| This shouldn't take too long, but we should time out nevertheless. */ |
| timeout = 1000; /* Time out after 1000 * 1us == 1ms. */ |
| while ((port->cmd_stat & HBA_PxCMD_FR) && timeout--) |
| udelay(1); |
| if (timeout < 0) { |
| printf("ahci: Timeout during stopping of command engine.\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| ssize_t ahci_cmdslot_exec(ahci_dev_t *const dev) |
| { |
| const int slotnum = 0; /* We always use the first slot. */ |
| |
| if (!(dev->port->cmd_stat & HBA_PxCMD_CR)) |
| return -1; |
| |
| /* Trigger command execution. */ |
| dev->port->cmd_issue |= (1 << slotnum); |
| |
| /* Wait for the controller to finish command execution. */ |
| int timeout = 50000; /* Time out after 50000 * 100us == 5s. */ |
| while ((dev->port->cmd_issue & (1 << slotnum)) && |
| !(dev->port->intr_status & HBA_PxIS_TFES) && |
| timeout--) |
| udelay(100); |
| if (timeout < 0) { |
| printf("ahci: Timeout during command execution.\n"); |
| return -1; |
| } |
| |
| ahci_prdbuf_finalize(dev); |
| |
| const u32 intr_status = ahci_clear_status(dev->port, intr_status); |
| if (intr_status & (HBA_PxIS_FATAL | HBA_PxIS_PCS)) { |
| ahci_error_recovery(dev, intr_status); |
| return -1; |
| } else { |
| return dev->cmdlist[slotnum].prd_bytes; |
| } |
| } |
| |
| size_t ahci_cmdslot_prepare(ahci_dev_t *const dev, |
| u8 *const user_buf, size_t buf_len, |
| const int out) |
| { |
| const int slotnum = 0; /* We always use the first slot. */ |
| |
| size_t read_count = 0; |
| |
| memset((void *)&dev->cmdlist[slotnum], |
| '\0', sizeof(dev->cmdlist[slotnum])); |
| memset((void *)dev->cmdtable, |
| '\0', sizeof(*dev->cmdtable)); |
| dev->cmdlist[slotnum].cmd = CMD_CFL(FIS_H2D_FIS_LEN); |
| dev->cmdlist[slotnum].cmdtable_base = virt_to_phys(dev->cmdtable); |
| |
| if (buf_len > 0) { |
| size_t prdt_len; |
| u8 *buf; |
| int i; |
| |
| prdt_len = ((buf_len - 1) >> BYTES_PER_PRD_SHIFT) + 1; |
| const size_t max_prdt_len = ARRAY_SIZE(dev->cmdtable->prdt); |
| if (prdt_len > max_prdt_len) { |
| prdt_len = max_prdt_len; |
| buf_len = prdt_len << BYTES_PER_PRD_SHIFT; |
| } |
| |
| dev->cmdlist[slotnum].prdt_length = prdt_len; |
| read_count = buf_len; |
| |
| buf = ahci_prdbuf_init(dev, user_buf, buf_len, out); |
| if (!buf) |
| return 0; |
| for (i = 0; i < prdt_len; ++i) { |
| const size_t bytes = |
| (buf_len < BYTES_PER_PRD) |
| ? buf_len : BYTES_PER_PRD; |
| dev->cmdtable->prdt[i].data_base = virt_to_phys(buf); |
| dev->cmdtable->prdt[i].flags = PRD_TABLE_BYTES(bytes); |
| buf_len -= bytes; |
| buf += bytes; |
| } |
| } |
| |
| return read_count; |
| } |
| |
| int ahci_identify_device(ata_dev_t *const ata_dev, u8 *const buf) |
| { |
| ahci_dev_t *const dev = (ahci_dev_t *)ata_dev; |
| |
| ahci_cmdslot_prepare(dev, buf, 512, 0); |
| |
| dev->cmdtable->fis[0] = FIS_HOST_TO_DEVICE; |
| dev->cmdtable->fis[1] = FIS_H2D_CMD; |
| dev->cmdtable->fis[2] = ata_dev->identify_cmd; |
| |
| if ((ahci_cmdslot_exec(dev) < 0) || (dev->cmdlist->prd_bytes != 512)) |
| return -1; |
| else |
| return 0; |
| } |