diff options
author | Edward O'Callaghan <eocallaghan@alterapraxis.com> | 2014-01-23 08:30:42 +1100 |
---|---|---|
committer | Patrick Georgi <patrick@georgi-clan.de> | 2014-02-15 14:14:36 +0100 |
commit | efc5841ab404aa615306a233dcbcda225b9380b5 (patch) | |
tree | 03a980e7af1539ab4d9181b0cdb180eb18d4c39d /payloads/libpayload/drivers/storage/ahci_common.c | |
parent | 09af15e09eed91c98d4b97446b6b5c445b90517d (diff) | |
download | coreboot-efc5841ab404aa615306a233dcbcda225b9380b5.tar.xz |
libpayload/ahci: Fix a warning by decompartmentalise the AHCI driver.
Decompartmentalise AHCI driver into two parts, ATA and ATAPI. Add a few
superficial comments while here. This also fixes a compiler warning.
Change-Id: Ia1fd545b39868a81cbc311f6ffc786f9f1f61415
Signed-off-by: Edward O'Callaghan <eocallaghan@alterapraxis.com>
Reviewed-on: http://review.coreboot.org/4783
Tested-by: build bot (Jenkins)
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Diffstat (limited to 'payloads/libpayload/drivers/storage/ahci_common.c')
-rw-r--r-- | payloads/libpayload/drivers/storage/ahci_common.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/storage/ahci_common.c b/payloads/libpayload/drivers/storage/ahci_common.c new file mode 100644 index 0000000000..bcb0ce70bc --- /dev/null +++ b/payloads/libpayload/drivers/storage/ahci_common.c @@ -0,0 +1,250 @@ +/* + * This file is part of the libpayload project. + * + * 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((void *)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((void *)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; +} |