| /* |
| * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * This program 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 2 of the |
| * License, or any later version. |
| * |
| * This program 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 this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <byteswap.h> |
| #include <ipxe/list.h> |
| #include <ipxe/interface.h> |
| #include <ipxe/blockdev.h> |
| #include <ipxe/edd.h> |
| #include <ipxe/ata.h> |
| |
| /** @file |
| * |
| * ATA block device |
| * |
| */ |
| |
| /****************************************************************************** |
| * |
| * Interface methods |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Issue ATA command |
| * |
| * @v control ATA control interface |
| * @v data ATA data interface |
| * @v command ATA command |
| * @ret tag Command tag, or negative error |
| */ |
| int ata_command ( struct interface *control, struct interface *data, |
| struct ata_cmd *command ) { |
| struct interface *dest; |
| ata_command_TYPE ( void * ) *op = |
| intf_get_dest_op ( control, ata_command, &dest ); |
| void *object = intf_object ( dest ); |
| int tag; |
| |
| if ( op ) { |
| tag = op ( object, data, command ); |
| } else { |
| /* Default is to fail to issue the command */ |
| tag = -EOPNOTSUPP; |
| } |
| |
| intf_put ( dest ); |
| return tag; |
| } |
| |
| /****************************************************************************** |
| * |
| * ATA devices and commands |
| * |
| ****************************************************************************** |
| */ |
| |
| /** List of all ATA commands */ |
| static LIST_HEAD ( ata_commands ); |
| |
| /** An ATA device */ |
| struct ata_device { |
| /** Reference count */ |
| struct refcnt refcnt; |
| /** Block control interface */ |
| struct interface block; |
| /** ATA control interface */ |
| struct interface ata; |
| |
| /** Device number |
| * |
| * Must be ATA_DEV_MASTER or ATA_DEV_SLAVE. |
| */ |
| unsigned int device; |
| /** Maximum number of blocks per single transfer */ |
| unsigned int max_count; |
| /** Device uses LBA48 extended addressing */ |
| int lba48; |
| }; |
| |
| /** An ATA command */ |
| struct ata_command { |
| /** Reference count */ |
| struct refcnt refcnt; |
| /** ATA device */ |
| struct ata_device *atadev; |
| /** List of ATA commands */ |
| struct list_head list; |
| |
| /** Block data interface */ |
| struct interface block; |
| /** ATA data interface */ |
| struct interface ata; |
| |
| /** Command type */ |
| struct ata_command_type *type; |
| /** Command tag */ |
| uint32_t tag; |
| |
| /** Private data */ |
| uint8_t priv[0]; |
| }; |
| |
| /** An ATA command type */ |
| struct ata_command_type { |
| /** Name */ |
| const char *name; |
| /** Additional working space */ |
| size_t priv_len; |
| /** Command for non-LBA48-capable devices */ |
| uint8_t cmd_lba; |
| /** Command for LBA48-capable devices */ |
| uint8_t cmd_lba48; |
| /** |
| * Calculate data-in buffer |
| * |
| * @v atacmd ATA command |
| * @v buffer Available buffer |
| * @v len Available buffer length |
| * @ret data_in Data-in buffer |
| * @ret data_in_len Data-in buffer length |
| */ |
| void ( * data_in ) ( struct ata_command *atacmd, userptr_t buffer, |
| size_t len, userptr_t *data_in, |
| size_t *data_in_len ); |
| /** |
| * Calculate data-out buffer |
| * |
| * |
| * @v atacmd ATA command |
| * @v buffer Available buffer |
| * @v len Available buffer length |
| * @ret data_out Data-out buffer |
| * @ret data_out_len Data-out buffer length |
| */ |
| void ( * data_out ) ( struct ata_command *atacmd, userptr_t buffer, |
| size_t len, userptr_t *data_out, |
| size_t *data_out_len ); |
| /** |
| * Handle ATA command completion |
| * |
| * @v atacmd ATA command |
| * @v rc Reason for completion |
| */ |
| void ( * done ) ( struct ata_command *atacmd, int rc ); |
| }; |
| |
| /** |
| * Get reference to ATA device |
| * |
| * @v atadev ATA device |
| * @ret atadev ATA device |
| */ |
| static inline __attribute__ (( always_inline )) struct ata_device * |
| atadev_get ( struct ata_device *atadev ) { |
| ref_get ( &atadev->refcnt ); |
| return atadev; |
| } |
| |
| /** |
| * Drop reference to ATA device |
| * |
| * @v atadev ATA device |
| */ |
| static inline __attribute__ (( always_inline )) void |
| atadev_put ( struct ata_device *atadev ) { |
| ref_put ( &atadev->refcnt ); |
| } |
| |
| /** |
| * Get reference to ATA command |
| * |
| * @v atacmd ATA command |
| * @ret atacmd ATA command |
| */ |
| static inline __attribute__ (( always_inline )) struct ata_command * |
| atacmd_get ( struct ata_command *atacmd ) { |
| ref_get ( &atacmd->refcnt ); |
| return atacmd; |
| } |
| |
| /** |
| * Drop reference to ATA command |
| * |
| * @v atacmd ATA command |
| */ |
| static inline __attribute__ (( always_inline )) void |
| atacmd_put ( struct ata_command *atacmd ) { |
| ref_put ( &atacmd->refcnt ); |
| } |
| |
| /** |
| * Get ATA command private data |
| * |
| * @v atacmd ATA command |
| * @ret priv Private data |
| */ |
| static inline __attribute__ (( always_inline )) void * |
| atacmd_priv ( struct ata_command *atacmd ) { |
| return atacmd->priv; |
| } |
| |
| /** |
| * Free ATA command |
| * |
| * @v refcnt Reference count |
| */ |
| static void atacmd_free ( struct refcnt *refcnt ) { |
| struct ata_command *atacmd = |
| container_of ( refcnt, struct ata_command, refcnt ); |
| |
| /* Remove from list of commands */ |
| list_del ( &atacmd->list ); |
| atadev_put ( atacmd->atadev ); |
| |
| /* Free command */ |
| free ( atacmd ); |
| } |
| |
| /** |
| * Close ATA command |
| * |
| * @v atacmd ATA command |
| * @v rc Reason for close |
| */ |
| static void atacmd_close ( struct ata_command *atacmd, int rc ) { |
| struct ata_device *atadev = atacmd->atadev; |
| |
| if ( rc != 0 ) { |
| DBGC ( atadev, "ATA %p tag %08x closed: %s\n", |
| atadev, atacmd->tag, strerror ( rc ) ); |
| } |
| |
| /* Shut down interfaces */ |
| intf_shutdown ( &atacmd->ata, rc ); |
| intf_shutdown ( &atacmd->block, rc ); |
| } |
| |
| /** |
| * Handle ATA command completion |
| * |
| * @v atacmd ATA command |
| * @v rc Reason for close |
| */ |
| static void atacmd_done ( struct ata_command *atacmd, int rc ) { |
| |
| /* Hand over to the command completion handler */ |
| atacmd->type->done ( atacmd, rc ); |
| } |
| |
| /** |
| * Use provided data buffer for ATA command |
| * |
| * @v atacmd ATA command |
| * @v buffer Available buffer |
| * @v len Available buffer length |
| * @ret data Data buffer |
| * @ret data_len Data buffer length |
| */ |
| static void atacmd_data_buffer ( struct ata_command *atacmd __unused, |
| userptr_t buffer, size_t len, |
| userptr_t *data, size_t *data_len ) { |
| *data = buffer; |
| *data_len = len; |
| } |
| |
| /** |
| * Use no data buffer for ATA command |
| * |
| * @v atacmd ATA command |
| * @v buffer Available buffer |
| * @v len Available buffer length |
| * @ret data Data buffer |
| * @ret data_len Data buffer length |
| */ |
| static void atacmd_data_none ( struct ata_command *atacmd __unused, |
| userptr_t buffer __unused, size_t len __unused, |
| userptr_t *data __unused, |
| size_t *data_len __unused ) { |
| /* Nothing to do */ |
| } |
| |
| /** |
| * Use private data buffer for ATA command |
| * |
| * @v atacmd ATA command |
| * @v buffer Available buffer |
| * @v len Available buffer length |
| * @ret data Data buffer |
| * @ret data_len Data buffer length |
| */ |
| static void atacmd_data_priv ( struct ata_command *atacmd, |
| userptr_t buffer __unused, size_t len __unused, |
| userptr_t *data, size_t *data_len ) { |
| *data = virt_to_user ( atacmd_priv ( atacmd ) ); |
| *data_len = atacmd->type->priv_len; |
| } |
| |
| /** ATA READ command type */ |
| static struct ata_command_type atacmd_read = { |
| .name = "READ", |
| .cmd_lba = ATA_CMD_READ, |
| .cmd_lba48 = ATA_CMD_READ_EXT, |
| .data_in = atacmd_data_buffer, |
| .data_out = atacmd_data_none, |
| .done = atacmd_close, |
| }; |
| |
| /** ATA WRITE command type */ |
| static struct ata_command_type atacmd_write = { |
| .name = "WRITE", |
| .cmd_lba = ATA_CMD_WRITE, |
| .cmd_lba48 = ATA_CMD_WRITE_EXT, |
| .data_in = atacmd_data_none, |
| .data_out = atacmd_data_buffer, |
| .done = atacmd_close, |
| }; |
| |
| /** ATA IDENTIFY private data */ |
| struct ata_identify_private { |
| /** Identity data */ |
| struct ata_identity identity; |
| }; |
| |
| /** |
| * Return ATA model string (for debugging) |
| * |
| * @v identify ATA identity data |
| * @ret model Model string |
| */ |
| static const char * ata_model ( struct ata_identity *identity ) { |
| static union { |
| uint16_t words[ sizeof ( identity->model ) / 2 ]; |
| char text[ sizeof ( identity->model ) + 1 /* NUL */ ]; |
| } buf; |
| unsigned int i; |
| |
| for ( i = 0 ; i < ( sizeof ( identity->model ) / 2 ) ; i++ ) |
| buf.words[i] = bswap_16 ( identity->model[i] ); |
| |
| return buf.text; |
| } |
| |
| /** |
| * Handle ATA IDENTIFY command completion |
| * |
| * @v atacmd ATA command |
| * @v rc Reason for completion |
| */ |
| static void atacmd_identify_done ( struct ata_command *atacmd, int rc ) { |
| struct ata_device *atadev = atacmd->atadev; |
| struct ata_identify_private *priv = atacmd_priv ( atacmd ); |
| struct ata_identity *identity = &priv->identity; |
| struct block_device_capacity capacity; |
| |
| /* Close if command failed */ |
| if ( rc != 0 ) { |
| atacmd_close ( atacmd, rc ); |
| return; |
| } |
| |
| /* Extract capacity */ |
| if ( identity->supports_lba48 & cpu_to_le16 ( ATA_SUPPORTS_LBA48 ) ) { |
| atadev->lba48 = 1; |
| capacity.blocks = le64_to_cpu ( identity->lba48_sectors ); |
| } else { |
| capacity.blocks = le32_to_cpu ( identity->lba_sectors ); |
| } |
| capacity.blksize = ATA_SECTOR_SIZE; |
| capacity.max_count = atadev->max_count; |
| DBGC ( atadev, "ATA %p is a %s\n", atadev, ata_model ( identity ) ); |
| DBGC ( atadev, "ATA %p has %#llx blocks (%ld MB) and uses %s\n", |
| atadev, capacity.blocks, |
| ( ( signed long ) ( capacity.blocks >> 11 ) ), |
| ( atadev->lba48 ? "LBA48" : "LBA" ) ); |
| |
| /* Return capacity to caller */ |
| block_capacity ( &atacmd->block, &capacity ); |
| |
| /* Close command */ |
| atacmd_close ( atacmd, 0 ); |
| } |
| |
| /** ATA IDENTITY command type */ |
| static struct ata_command_type atacmd_identify = { |
| .name = "IDENTIFY", |
| .priv_len = sizeof ( struct ata_identify_private ), |
| .cmd_lba = ATA_CMD_IDENTIFY, |
| .cmd_lba48 = ATA_CMD_IDENTIFY, |
| .data_in = atacmd_data_priv, |
| .data_out = atacmd_data_none, |
| .done = atacmd_identify_done, |
| }; |
| |
| /** ATA command block interface operations */ |
| static struct interface_operation atacmd_block_op[] = { |
| INTF_OP ( intf_close, struct ata_command *, atacmd_close ), |
| }; |
| |
| /** ATA command block interface descriptor */ |
| static struct interface_descriptor atacmd_block_desc = |
| INTF_DESC_PASSTHRU ( struct ata_command, block, |
| atacmd_block_op, ata ); |
| |
| /** ATA command ATA interface operations */ |
| static struct interface_operation atacmd_ata_op[] = { |
| INTF_OP ( intf_close, struct ata_command *, atacmd_done ), |
| }; |
| |
| /** ATA command ATA interface descriptor */ |
| static struct interface_descriptor atacmd_ata_desc = |
| INTF_DESC_PASSTHRU ( struct ata_command, ata, |
| atacmd_ata_op, block ); |
| |
| /** |
| * Create ATA command |
| * |
| * @v atadev ATA device |
| * @v block Block data interface |
| * @v type ATA command type |
| * @v lba Starting logical block address |
| * @v count Number of blocks to transfer |
| * @v buffer Data buffer |
| * @v len Length of data buffer |
| * @ret rc Return status code |
| */ |
| static int atadev_command ( struct ata_device *atadev, |
| struct interface *block, |
| struct ata_command_type *type, |
| uint64_t lba, unsigned int count, |
| userptr_t buffer, size_t len ) { |
| struct ata_command *atacmd; |
| struct ata_cmd command; |
| int tag; |
| int rc; |
| |
| /* Allocate and initialise structure */ |
| atacmd = zalloc ( sizeof ( *atacmd ) + type->priv_len ); |
| if ( ! atacmd ) { |
| rc = -ENOMEM; |
| goto err_zalloc; |
| } |
| ref_init ( &atacmd->refcnt, atacmd_free ); |
| intf_init ( &atacmd->block, &atacmd_block_desc, &atacmd->refcnt ); |
| intf_init ( &atacmd->ata, &atacmd_ata_desc, |
| &atacmd->refcnt ); |
| atacmd->atadev = atadev_get ( atadev ); |
| list_add ( &atacmd->list, &ata_commands ); |
| atacmd->type = type; |
| |
| /* Sanity check */ |
| if ( len != ( count * ATA_SECTOR_SIZE ) ) { |
| DBGC ( atadev, "ATA %p tag %08x buffer length mismatch (count " |
| "%d len %zd)\n", atadev, atacmd->tag, count, len ); |
| rc = -EINVAL; |
| goto err_len; |
| } |
| |
| /* Construct command */ |
| memset ( &command, 0, sizeof ( command ) ); |
| command.cb.lba.native = lba; |
| command.cb.count.native = count; |
| command.cb.device = ( atadev->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA ); |
| command.cb.lba48 = atadev->lba48; |
| if ( ! atadev->lba48 ) |
| command.cb.device |= command.cb.lba.bytes.low_prev; |
| command.cb.cmd_stat = |
| ( atadev->lba48 ? type->cmd_lba48 : type->cmd_lba ); |
| type->data_in ( atacmd, buffer, len, |
| &command.data_in, &command.data_in_len ); |
| type->data_out ( atacmd, buffer, len, |
| &command.data_out, &command.data_out_len ); |
| |
| /* Issue command */ |
| if ( ( tag = ata_command ( &atadev->ata, &atacmd->ata, |
| &command ) ) < 0 ) { |
| rc = tag; |
| DBGC ( atadev, "ATA %p tag %08x could not issue command: %s\n", |
| atadev, atacmd->tag, strerror ( rc ) ); |
| goto err_command; |
| } |
| atacmd->tag = tag; |
| |
| DBGC2 ( atadev, "ATA %p tag %08x %s cmd %02x dev %02x LBA%s %08llx " |
| "count %04x\n", atadev, atacmd->tag, atacmd->type->name, |
| command.cb.cmd_stat, command.cb.device, |
| ( command.cb.lba48 ? "48" : "" ), |
| ( unsigned long long ) command.cb.lba.native, |
| command.cb.count.native ); |
| |
| /* Attach to parent interface, mortalise self, and return */ |
| intf_plug_plug ( &atacmd->block, block ); |
| ref_put ( &atacmd->refcnt ); |
| return 0; |
| |
| err_command: |
| err_len: |
| atacmd_close ( atacmd, rc ); |
| ref_put ( &atacmd->refcnt ); |
| err_zalloc: |
| return rc; |
| } |
| |
| /** |
| * Issue ATA block read |
| * |
| * @v atadev ATA device |
| * @v block Block data interface |
| * @v lba Starting logical block address |
| * @v count Number of blocks to transfer |
| * @v buffer Data buffer |
| * @v len Length of data buffer |
| * @ret rc Return status code |
| |
| */ |
| static int atadev_read ( struct ata_device *atadev, |
| struct interface *block, |
| uint64_t lba, unsigned int count, |
| userptr_t buffer, size_t len ) { |
| return atadev_command ( atadev, block, &atacmd_read, |
| lba, count, buffer, len ); |
| } |
| |
| /** |
| * Issue ATA block write |
| * |
| * @v atadev ATA device |
| * @v block Block data interface |
| * @v lba Starting logical block address |
| * @v count Number of blocks to transfer |
| * @v buffer Data buffer |
| * @v len Length of data buffer |
| * @ret rc Return status code |
| */ |
| static int atadev_write ( struct ata_device *atadev, |
| struct interface *block, |
| uint64_t lba, unsigned int count, |
| userptr_t buffer, size_t len ) { |
| return atadev_command ( atadev, block, &atacmd_write, |
| lba, count, buffer, len ); |
| } |
| |
| /** |
| * Read ATA device capacity |
| * |
| * @v atadev ATA device |
| * @v block Block data interface |
| * @ret rc Return status code |
| */ |
| static int atadev_read_capacity ( struct ata_device *atadev, |
| struct interface *block ) { |
| struct ata_identity *identity; |
| |
| assert ( atacmd_identify.priv_len == sizeof ( *identity ) ); |
| assert ( atacmd_identify.priv_len == ATA_SECTOR_SIZE ); |
| return atadev_command ( atadev, block, &atacmd_identify, |
| 0, 1, UNULL, ATA_SECTOR_SIZE ); |
| } |
| |
| /** |
| * Close ATA device |
| * |
| * @v atadev ATA device |
| * @v rc Reason for close |
| */ |
| static void atadev_close ( struct ata_device *atadev, int rc ) { |
| struct ata_command *atacmd; |
| struct ata_command *tmp; |
| |
| /* Shut down interfaces */ |
| intf_shutdown ( &atadev->block, rc ); |
| intf_shutdown ( &atadev->ata, rc ); |
| |
| /* Shut down any remaining commands */ |
| list_for_each_entry_safe ( atacmd, tmp, &ata_commands, list ) { |
| if ( atacmd->atadev != atadev ) |
| continue; |
| atacmd_get ( atacmd ); |
| atacmd_close ( atacmd, rc ); |
| atacmd_put ( atacmd ); |
| } |
| } |
| |
| /** |
| * Describe ATA device using EDD |
| * |
| * @v atadev ATA device |
| * @v type EDD interface type |
| * @v path EDD device path |
| * @ret rc Return status code |
| */ |
| static int atadev_edd_describe ( struct ata_device *atadev, |
| struct edd_interface_type *type, |
| union edd_device_path *path ) { |
| |
| type->type = cpu_to_le64 ( EDD_INTF_TYPE_ATA ); |
| path->ata.slave = ( ( atadev->device == ATA_DEV_SLAVE ) ? 0x01 : 0x00 ); |
| return 0; |
| } |
| |
| /** ATA device block interface operations */ |
| static struct interface_operation atadev_block_op[] = { |
| INTF_OP ( block_read, struct ata_device *, atadev_read ), |
| INTF_OP ( block_write, struct ata_device *, atadev_write ), |
| INTF_OP ( block_read_capacity, struct ata_device *, |
| atadev_read_capacity ), |
| INTF_OP ( intf_close, struct ata_device *, atadev_close ), |
| INTF_OP ( edd_describe, struct ata_device *, atadev_edd_describe ), |
| }; |
| |
| /** ATA device block interface descriptor */ |
| static struct interface_descriptor atadev_block_desc = |
| INTF_DESC_PASSTHRU ( struct ata_device, block, |
| atadev_block_op, ata ); |
| |
| /** ATA device ATA interface operations */ |
| static struct interface_operation atadev_ata_op[] = { |
| INTF_OP ( intf_close, struct ata_device *, atadev_close ), |
| }; |
| |
| /** ATA device ATA interface descriptor */ |
| static struct interface_descriptor atadev_ata_desc = |
| INTF_DESC_PASSTHRU ( struct ata_device, ata, |
| atadev_ata_op, block ); |
| |
| /** |
| * Open ATA device |
| * |
| * @v block Block control interface |
| * @v ata ATA control interface |
| * @v device ATA device number |
| * @v max_count Maximum number of blocks per single transfer |
| * @ret rc Return status code |
| */ |
| int ata_open ( struct interface *block, struct interface *ata, |
| unsigned int device, unsigned int max_count ) { |
| struct ata_device *atadev; |
| |
| /* Allocate and initialise structure */ |
| atadev = zalloc ( sizeof ( *atadev ) ); |
| if ( ! atadev ) |
| return -ENOMEM; |
| ref_init ( &atadev->refcnt, NULL ); |
| intf_init ( &atadev->block, &atadev_block_desc, &atadev->refcnt ); |
| intf_init ( &atadev->ata, &atadev_ata_desc, &atadev->refcnt ); |
| atadev->device = device; |
| atadev->max_count = max_count; |
| |
| /* Attach to ATA and parent and interfaces, mortalise self, |
| * and return |
| */ |
| intf_plug_plug ( &atadev->ata, ata ); |
| intf_plug_plug ( &atadev->block, block ); |
| ref_put ( &atadev->refcnt ); |
| return 0; |
| } |