| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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; | |
| /** | |
| * Initialise data-in buffer | |
| * | |
| * @v atacmd ATA command | |
| * @v xferbuf Data transfer buffer | |
| * @v buffer Available buffer | |
| * @v len Available buffer length | |
| */ | |
| void ( * data_in ) ( struct ata_command *atacmd, | |
| struct xfer_buffer *xferbuf, | |
| void *buffer, size_t len ); | |
| /** | |
| * Initialise data-out buffer | |
| * | |
| * @v atacmd ATA command | |
| * @v xferbuf Data transfer buffer | |
| * @v buffer Available buffer | |
| * @v len Available buffer length | |
| */ | |
| void ( * data_out ) ( struct ata_command *atacmd, | |
| struct xfer_buffer *xferbuf, | |
| void *buffer, size_t 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 xferbuf Data transfer buffer | |
| * @v buffer Available buffer | |
| * @v len Available buffer length | |
| */ | |
| static void atacmd_data_buffer ( struct ata_command *atacmd __unused, | |
| struct xfer_buffer *xferbuf, | |
| void *buffer, size_t len ) { | |
| xferbuf_fixed_init ( xferbuf, buffer, len ); | |
| } | |
| /** | |
| * Use no data buffer for ATA command | |
| * | |
| * @v atacmd ATA command | |
| * @v xferbuf Data transfer buffer | |
| * @v buffer Available buffer | |
| * @v len Available buffer length | |
| */ | |
| static void atacmd_data_none ( struct ata_command *atacmd __unused, | |
| struct xfer_buffer *xferbuf, | |
| void *buffer __unused, size_t len __unused ) { | |
| xferbuf_void_init ( xferbuf ); | |
| } | |
| /** | |
| * Use private data buffer for ATA command | |
| * | |
| * @v atacmd ATA command | |
| * @v xferbuf Data transfer buffer | |
| * @v buffer Available buffer | |
| * @v len Available buffer length | |
| */ | |
| static void atacmd_data_priv ( struct ata_command *atacmd, | |
| struct xfer_buffer *xferbuf, | |
| void *buffer __unused, size_t len __unused ) { | |
| xferbuf_fixed_init ( xferbuf, atacmd_priv ( atacmd ), | |
| 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, | |
| void *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, &command.data_in, buffer, len ); | |
| type->data_out ( atacmd, &command.data_out, buffer, 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, | |
| void *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, | |
| void *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, NULL, 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; | |
| } |