mirror of
https://github.com/ipxe/ipxe
synced 2026-01-01 09:09:32 +03:00
[block] Replace gPXE block-device API with an iPXE asynchronous interface
The block device interface used in gPXE predates the invention of even
the old gPXE data-transfer interface, let alone the current iPXE
generic asynchronous interface mechanism. Bring this old code up to
date, with the following benefits:
o Block device commands can be cancelled by the requestor. The INT 13
layer uses this to provide a global timeout on all INT 13 calls,
with the result that an unexpected passive failure mode (such as
an iSCSI target ACKing the request but never sending a response)
will lead to a timeout that gets reported back to the INT 13 user,
rather than simply freezing the system.
o INT 13,00 (reset drive) is now able to reset the underlying block
device. INT 13 users, such as DOS, that use INT 13,00 as a method
for error recovery now have a chance of recovering.
o All block device commands are tagged, with a numerical tag that
will show up in debugging output and in packet captures; this will
allow easier interpretation of bug reports that include both
sources of information.
o The extremely ugly hacks used to generate the boot firmware tables
have been eradicated and replaced with a generic acpi_describe()
method (exploiting the ability of iPXE interfaces to pass through
methods to an underlying interface). The ACPI tables are now
built in a shared data block within .bss16, rather than each
requiring dedicated space in .data16.
o The architecture-independent concept of a SAN device has been
exposed to the iPXE core through the sanboot API, which provides
calls to hook, unhook, boot, and describe SAN devices. This
allows for much more flexible usage patterns (such as hooking an
empty SAN device and then running an OS installer via TFTP).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
@@ -19,12 +19,14 @@
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#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/process.h>
|
||||
#include <ipxe/ata.h>
|
||||
|
||||
/** @file
|
||||
@@ -33,156 +35,625 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
||||
*
|
||||
*/
|
||||
|
||||
static inline __attribute__ (( always_inline )) struct ata_device *
|
||||
block_to_ata ( struct block_device *blockdev ) {
|
||||
return container_of ( blockdev, struct ata_device, blockdev );
|
||||
}
|
||||
/******************************************************************************
|
||||
*
|
||||
* Interface methods
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Issue ATA command
|
||||
*
|
||||
* @v ata ATA device
|
||||
* @v control ATA control interface
|
||||
* @v data ATA data interface
|
||||
* @v command ATA command
|
||||
* @ret rc Return status code
|
||||
* @ret tag Command tag, or negative error
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) int
|
||||
ata_command ( struct ata_device *ata, struct ata_command *command ) {
|
||||
int rc;
|
||||
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;
|
||||
|
||||
DBG ( "ATA cmd %02x dev %02x LBA%s %llx count %04x\n",
|
||||
command->cb.cmd_stat, command->cb.device,
|
||||
( command->cb.lba48 ? "48" : "" ),
|
||||
( unsigned long long ) command->cb.lba.native,
|
||||
command->cb.count.native );
|
||||
|
||||
/* Flag command as in-progress */
|
||||
command->rc = -EINPROGRESS;
|
||||
|
||||
/* Issue ATA command */
|
||||
if ( ( rc = ata->command ( ata, command ) ) != 0 ) {
|
||||
/* Something went wrong with the issuing mechanism */
|
||||
DBG ( "ATA could not issue command: %s\n", strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Wait for command to complete */
|
||||
while ( command->rc == -EINPROGRESS )
|
||||
step();
|
||||
if ( ( rc = command->rc ) != 0 ) {
|
||||
/* Something went wrong with the command execution */
|
||||
DBG ( "ATA command failed: %s\n", strerror ( rc ) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read block from ATA device
|
||||
*
|
||||
* @v blockdev Block device
|
||||
* @v block LBA block number
|
||||
* @v count Block count
|
||||
* @v buffer Data buffer
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int ata_read ( struct block_device *blockdev, uint64_t block,
|
||||
unsigned long count, userptr_t buffer ) {
|
||||
struct ata_device *ata = block_to_ata ( blockdev );
|
||||
struct ata_command command;
|
||||
|
||||
memset ( &command, 0, sizeof ( command ) );
|
||||
command.cb.lba.native = block;
|
||||
command.cb.count.native = count;
|
||||
command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
|
||||
command.cb.lba48 = ata->lba48;
|
||||
if ( ! ata->lba48 )
|
||||
command.cb.device |= command.cb.lba.bytes.low_prev;
|
||||
command.cb.cmd_stat = ( ata->lba48 ? ATA_CMD_READ_EXT : ATA_CMD_READ );
|
||||
command.data_in = buffer;
|
||||
return ata_command ( ata, &command );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write block to ATA device
|
||||
*
|
||||
* @v blockdev Block device
|
||||
* @v block LBA block number
|
||||
* @v count Block count
|
||||
* @v buffer Data buffer
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int ata_write ( struct block_device *blockdev, uint64_t block,
|
||||
unsigned long count, userptr_t buffer ) {
|
||||
struct ata_device *ata = block_to_ata ( blockdev );
|
||||
struct ata_command command;
|
||||
|
||||
memset ( &command, 0, sizeof ( command ) );
|
||||
command.cb.lba.native = block;
|
||||
command.cb.count.native = count;
|
||||
command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
|
||||
command.cb.lba48 = ata->lba48;
|
||||
if ( ! ata->lba48 )
|
||||
command.cb.device |= command.cb.lba.bytes.low_prev;
|
||||
command.cb.cmd_stat = ( ata->lba48 ?
|
||||
ATA_CMD_WRITE_EXT : ATA_CMD_WRITE );
|
||||
command.data_out = buffer;
|
||||
return ata_command ( ata, &command );
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify ATA device
|
||||
*
|
||||
* @v blockdev Block device
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int ata_identify ( struct block_device *blockdev ) {
|
||||
struct ata_device *ata = block_to_ata ( blockdev );
|
||||
struct ata_command command;
|
||||
struct ata_identity identity;
|
||||
int rc;
|
||||
|
||||
/* Issue IDENTIFY */
|
||||
memset ( &command, 0, sizeof ( command ) );
|
||||
command.cb.count.native = 1;
|
||||
command.cb.device = ( ata->device | ATA_DEV_OBSOLETE | ATA_DEV_LBA );
|
||||
command.cb.cmd_stat = ATA_CMD_IDENTIFY;
|
||||
command.data_in = virt_to_user ( &identity );
|
||||
linker_assert ( sizeof ( identity ) == ATA_SECTOR_SIZE,
|
||||
__ata_identity_bad_size__ );
|
||||
if ( ( rc = ata_command ( ata, &command ) ) != 0 )
|
||||
return rc;
|
||||
|
||||
/* Fill in block device parameters */
|
||||
blockdev->blksize = ATA_SECTOR_SIZE;
|
||||
if ( identity.supports_lba48 & cpu_to_le16 ( ATA_SUPPORTS_LBA48 ) ) {
|
||||
ata->lba48 = 1;
|
||||
blockdev->blocks = le64_to_cpu ( identity.lba48_sectors );
|
||||
if ( op ) {
|
||||
tag = op ( object, data, command );
|
||||
} else {
|
||||
blockdev->blocks = le32_to_cpu ( identity.lba_sectors );
|
||||
/* Default is to fail to issue the command */
|
||||
tag = -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
|
||||
intf_put ( dest );
|
||||
return tag;
|
||||
}
|
||||
|
||||
static struct block_device_operations ata_operations = {
|
||||
.read = ata_read,
|
||||
.write = ata_write
|
||||
/******************************************************************************
|
||||
*
|
||||
* 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 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise ATA device
|
||||
* Get reference to ATA device
|
||||
*
|
||||
* @v ata ATA device
|
||||
* @ret rc Return status code
|
||||
*
|
||||
* Initialises an ATA device. The ata_device::command field and the
|
||||
* @c ATA_FL_SLAVE portion of the ata_device::flags field must already
|
||||
* be filled in. This function will configure ata_device::blockdev,
|
||||
* including issuing an IDENTIFY DEVICE call to determine the block
|
||||
* size and total device size.
|
||||
* @v atadev ATA device
|
||||
* @ret atadev ATA device
|
||||
*/
|
||||
int init_atadev ( struct ata_device *ata ) {
|
||||
/** Fill in read and write methods, and get device capacity */
|
||||
ata->blockdev.op = &ata_operations;
|
||||
return ata_identify ( &ata->blockdev );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 ),
|
||||
};
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user