summaryrefslogblamecommitdiffstats
path: root/src/drivers/block/ata.c
blob: b1c6855a0c113b5bdfd477631dd38a3d96c1428c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                      

                                                                



                                                                    

   
                                       
 
                   
                   
                   
                   
                  
                     

                           
                          
                     
                     






                   





                                                                               



                    

                                             
                                   
                                                      
   









































































































































































































































                                                                               
 





































































                                                                              
 

                                                                    
 


















                                                                         

         





                                                                              
         






                                                                            
 




                                                     

 






























                                                                   
   
                     
  




                                                      
                                   
                                             

                                          






















                                                                          
 








                                                                               
                                                   
                                    
                                        


                                                                                
                                                                   


































                                                                               


   
                       
  



                                                      
                                   
                                             
                                          
 
   





                                                            


   
                        
  





                                                      

                                          






                                                             
 





































                                                                       
         

 
















                                                                                






                                                                   
                                                                           

  














                                                                  
   
                  
  



                                                                    
                                          
   




















                                                                          
 
/*
 * 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;
}