| /* | |
| * Copyright (C) 2017 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 ); | |
| /** | |
| * @file | |
| * | |
| * SAN booting | |
| * | |
| */ | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <assert.h> | |
| #include <ipxe/xfer.h> | |
| #include <ipxe/open.h> | |
| #include <ipxe/timer.h> | |
| #include <ipxe/process.h> | |
| #include <ipxe/iso9660.h> | |
| #include <ipxe/dhcp.h> | |
| #include <ipxe/settings.h> | |
| #include <ipxe/quiesce.h> | |
| #include <ipxe/sanboot.h> | |
| /** | |
| * Timeout for block device commands (in ticks) | |
| * | |
| * Underlying devices should ideally never become totally stuck. | |
| * However, if they do, then the blocking SAN APIs provide no means | |
| * for the caller to cancel the operation, and the machine appears to | |
| * hang. Use an overall timeout for all commands to avoid this | |
| * problem and bounce timeout failures to the caller. | |
| */ | |
| #define SAN_COMMAND_TIMEOUT ( 15 * TICKS_PER_SEC ) | |
| /** | |
| * Default number of times to retry commands | |
| * | |
| * We may need to retry commands. For example, the underlying | |
| * connection may be closed by the SAN target due to an inactivity | |
| * timeout, or the SAN target may return pointless "error" messages | |
| * such as "SCSI power-on occurred". | |
| */ | |
| #define SAN_DEFAULT_RETRIES 10 | |
| /** | |
| * Delay between reopening attempts | |
| * | |
| * Some SAN targets will always accept connections instantly and | |
| * report a temporary unavailability by e.g. failing the TEST UNIT | |
| * READY command. Avoid bombarding such targets by introducing a | |
| * small delay between attempts. | |
| */ | |
| #define SAN_REOPEN_DELAY_SECS 5 | |
| /** List of SAN devices */ | |
| LIST_HEAD ( san_devices ); | |
| /** Number of times to retry commands */ | |
| static unsigned long san_retries = SAN_DEFAULT_RETRIES; | |
| /** | |
| * Find SAN device by drive number | |
| * | |
| * @v drive Drive number | |
| * @ret sandev SAN device, or NULL | |
| */ | |
| struct san_device * sandev_find ( unsigned int drive ) { | |
| struct san_device *sandev; | |
| list_for_each_entry ( sandev, &san_devices, list ) { | |
| if ( sandev->drive == drive ) | |
| return sandev; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Find next SAN device by drive number | |
| * | |
| * @v drive Minimum drive number | |
| * @ret sandev SAN device, or NULL | |
| */ | |
| struct san_device * sandev_next ( unsigned int drive ) { | |
| struct san_device *sandev; | |
| list_for_each_entry ( sandev, &san_devices, list ) { | |
| if ( sandev->drive >= drive ) | |
| return sandev; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Free SAN device | |
| * | |
| * @v refcnt Reference count | |
| */ | |
| static void sandev_free ( struct refcnt *refcnt ) { | |
| struct san_device *sandev = | |
| container_of ( refcnt, struct san_device, refcnt ); | |
| unsigned int i; | |
| assert ( ! timer_running ( &sandev->timer ) ); | |
| assert ( ! sandev->active ); | |
| assert ( list_empty ( &sandev->opened ) ); | |
| for ( i = 0 ; i < sandev->paths ; i++ ) { | |
| uri_put ( sandev->path[i].uri ); | |
| assert ( sandev->path[i].desc == NULL ); | |
| } | |
| free ( sandev ); | |
| } | |
| /** | |
| * Close SAN device command | |
| * | |
| * @v sandev SAN device | |
| * @v rc Reason for close | |
| */ | |
| static void sandev_command_close ( struct san_device *sandev, int rc ) { | |
| /* Stop timer */ | |
| stop_timer ( &sandev->timer ); | |
| /* Restart interface */ | |
| intf_restart ( &sandev->command, rc ); | |
| /* Record command status */ | |
| sandev->command_rc = rc; | |
| } | |
| /** | |
| * Record SAN device capacity | |
| * | |
| * @v sandev SAN device | |
| * @v capacity SAN device capacity | |
| */ | |
| static void sandev_command_capacity ( struct san_device *sandev, | |
| struct block_device_capacity *capacity ) { | |
| /* Record raw capacity information */ | |
| memcpy ( &sandev->capacity, capacity, sizeof ( sandev->capacity ) ); | |
| } | |
| /** SAN device command interface operations */ | |
| static struct interface_operation sandev_command_op[] = { | |
| INTF_OP ( intf_close, struct san_device *, sandev_command_close ), | |
| INTF_OP ( block_capacity, struct san_device *, | |
| sandev_command_capacity ), | |
| }; | |
| /** SAN device command interface descriptor */ | |
| static struct interface_descriptor sandev_command_desc = | |
| INTF_DESC ( struct san_device, command, sandev_command_op ); | |
| /** | |
| * Handle SAN device command timeout | |
| * | |
| * @v retry Retry timer | |
| */ | |
| static void sandev_command_expired ( struct retry_timer *timer, | |
| int over __unused ) { | |
| struct san_device *sandev = | |
| container_of ( timer, struct san_device, timer ); | |
| sandev_command_close ( sandev, -ETIMEDOUT ); | |
| } | |
| /** | |
| * Open SAN path | |
| * | |
| * @v sanpath SAN path | |
| * @ret rc Return status code | |
| */ | |
| static int sanpath_open ( struct san_path *sanpath ) { | |
| struct san_device *sandev = sanpath->sandev; | |
| int rc; | |
| /* Sanity check */ | |
| list_check_contains_entry ( sanpath, &sandev->closed, list ); | |
| /* Open interface */ | |
| if ( ( rc = xfer_open_uri ( &sanpath->block, sanpath->uri ) ) != 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x.%d could not (re)open URI: " | |
| "%s\n", sandev->drive, sanpath->index, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Update ACPI descriptor, if applicable */ | |
| if ( ! ( sandev->flags & SAN_NO_DESCRIBE ) ) { | |
| if ( sanpath->desc ) | |
| acpi_del ( sanpath->desc ); | |
| sanpath->desc = acpi_describe ( &sanpath->block ); | |
| if ( sanpath->desc ) | |
| acpi_add ( sanpath->desc ); | |
| } | |
| /* Start process */ | |
| process_add ( &sanpath->process ); | |
| /* Mark as opened */ | |
| list_del ( &sanpath->list ); | |
| list_add_tail ( &sanpath->list, &sandev->opened ); | |
| /* Record as in progress */ | |
| sanpath->path_rc = -EINPROGRESS; | |
| return 0; | |
| } | |
| /** | |
| * Close SAN path | |
| * | |
| * @v sanpath SAN path | |
| * @v rc Reason for close | |
| */ | |
| static void sanpath_close ( struct san_path *sanpath, int rc ) { | |
| struct san_device *sandev = sanpath->sandev; | |
| /* Record status */ | |
| sanpath->path_rc = rc; | |
| /* Mark as closed */ | |
| list_del ( &sanpath->list ); | |
| list_add_tail ( &sanpath->list, &sandev->closed ); | |
| /* Stop process */ | |
| process_del ( &sanpath->process ); | |
| /* Restart interfaces, avoiding potential loops */ | |
| if ( sanpath == sandev->active ) { | |
| intfs_restart ( rc, &sandev->command, &sanpath->block, NULL ); | |
| sandev->active = NULL; | |
| sandev_command_close ( sandev, rc ); | |
| } else { | |
| intf_restart ( &sanpath->block, rc ); | |
| } | |
| } | |
| /** | |
| * Handle closure of underlying block device interface | |
| * | |
| * @v sanpath SAN path | |
| * @v rc Reason for close | |
| */ | |
| static void sanpath_block_close ( struct san_path *sanpath, int rc ) { | |
| struct san_device *sandev = sanpath->sandev; | |
| /* Any closure is an error from our point of view */ | |
| if ( rc == 0 ) | |
| rc = -ENOTCONN; | |
| DBGC ( sandev->drive, "SAN %#02x.%d closed: %s\n", | |
| sandev->drive, sanpath->index, strerror ( rc ) ); | |
| /* Close path */ | |
| sanpath_close ( sanpath, rc ); | |
| } | |
| /** | |
| * Check flow control window | |
| * | |
| * @v sanpath SAN path | |
| */ | |
| static size_t sanpath_block_window ( struct san_path *sanpath __unused ) { | |
| /* We are never ready to receive data via this interface. | |
| * This prevents objects that support both block and stream | |
| * interfaces from attempting to send us stream data. | |
| */ | |
| return 0; | |
| } | |
| /** | |
| * SAN path process | |
| * | |
| * @v sanpath SAN path | |
| */ | |
| static void sanpath_step ( struct san_path *sanpath ) { | |
| struct san_device *sandev = sanpath->sandev; | |
| /* Ignore if we are already the active device */ | |
| if ( sanpath == sandev->active ) | |
| return; | |
| /* Wait until path has become available */ | |
| if ( ! xfer_window ( &sanpath->block ) ) | |
| return; | |
| /* Record status */ | |
| sanpath->path_rc = 0; | |
| /* Mark as active path or close as applicable */ | |
| if ( ! sandev->active ) { | |
| DBGC ( sandev->drive, "SAN %#02x.%d is active\n", | |
| sandev->drive, sanpath->index ); | |
| sandev->active = sanpath; | |
| } else { | |
| DBGC ( sandev->drive, "SAN %#02x.%d is available\n", | |
| sandev->drive, sanpath->index ); | |
| sanpath_close ( sanpath, 0 ); | |
| } | |
| } | |
| /** SAN path block interface operations */ | |
| static struct interface_operation sanpath_block_op[] = { | |
| INTF_OP ( intf_close, struct san_path *, sanpath_block_close ), | |
| INTF_OP ( xfer_window, struct san_path *, sanpath_block_window ), | |
| INTF_OP ( xfer_window_changed, struct san_path *, sanpath_step ), | |
| }; | |
| /** SAN path block interface descriptor */ | |
| static struct interface_descriptor sanpath_block_desc = | |
| INTF_DESC ( struct san_path, block, sanpath_block_op ); | |
| /** SAN path process descriptor */ | |
| static struct process_descriptor sanpath_process_desc = | |
| PROC_DESC_ONCE ( struct san_path, process, sanpath_step ); | |
| /** | |
| * Restart SAN device interface | |
| * | |
| * @v sandev SAN device | |
| * @v rc Reason for restart | |
| */ | |
| static void sandev_restart ( struct san_device *sandev, int rc ) { | |
| struct san_path *sanpath; | |
| /* Restart all block device interfaces */ | |
| while ( ( sanpath = list_first_entry ( &sandev->opened, | |
| struct san_path, list ) ) ) { | |
| sanpath_close ( sanpath, rc ); | |
| } | |
| /* Clear active path */ | |
| sandev->active = NULL; | |
| /* Close any outstanding command */ | |
| sandev_command_close ( sandev, rc ); | |
| } | |
| /** | |
| * (Re)open SAN device | |
| * | |
| * @v sandev SAN device | |
| * @ret rc Return status code | |
| * | |
| * This function will block until the device is available. | |
| */ | |
| int sandev_reopen ( struct san_device *sandev ) { | |
| struct san_path *sanpath; | |
| int rc; | |
| /* Unquiesce system */ | |
| unquiesce(); | |
| /* Close any outstanding command and restart interfaces */ | |
| sandev_restart ( sandev, -ECONNRESET ); | |
| assert ( sandev->active == NULL ); | |
| assert ( list_empty ( &sandev->opened ) ); | |
| /* Open all paths */ | |
| while ( ( sanpath = list_first_entry ( &sandev->closed, | |
| struct san_path, list ) ) ) { | |
| if ( ( rc = sanpath_open ( sanpath ) ) != 0 ) | |
| goto err_open; | |
| } | |
| /* Wait for any device to become available, or for all devices | |
| * to fail. | |
| */ | |
| while ( sandev->active == NULL ) { | |
| step(); | |
| if ( list_empty ( &sandev->opened ) ) { | |
| /* Get status of the first device to be | |
| * closed. Do this on the basis that earlier | |
| * errors (e.g. "invalid IQN") are probably | |
| * more interesting than later errors | |
| * (e.g. "TCP timeout"). | |
| */ | |
| rc = -ENODEV; | |
| list_for_each_entry ( sanpath, &sandev->closed, list ) { | |
| rc = sanpath->path_rc; | |
| break; | |
| } | |
| DBGC ( sandev->drive, "SAN %#02x never became " | |
| "available: %s\n", sandev->drive, | |
| strerror ( rc ) ); | |
| goto err_none; | |
| } | |
| } | |
| assert ( ! list_empty ( &sandev->opened ) ); | |
| return 0; | |
| err_none: | |
| err_open: | |
| sandev_restart ( sandev, rc ); | |
| return rc; | |
| } | |
| /** SAN device read/write command parameters */ | |
| struct san_command_rw_params { | |
| /** SAN device read/write operation */ | |
| int ( * block_rw ) ( struct interface *control, struct interface *data, | |
| uint64_t lba, unsigned int count, void *buffer, | |
| size_t len ); | |
| /** Data buffer */ | |
| void *buffer; | |
| /** Starting LBA */ | |
| uint64_t lba; | |
| /** Block count */ | |
| unsigned int count; | |
| }; | |
| /** SAN device command parameters */ | |
| union san_command_params { | |
| /** Read/write command parameters */ | |
| struct san_command_rw_params rw; | |
| }; | |
| /** | |
| * Initiate SAN device read/write command | |
| * | |
| * @v sandev SAN device | |
| * @v params Command parameters | |
| * @ret rc Return status code | |
| */ | |
| static int sandev_command_rw ( struct san_device *sandev, | |
| const union san_command_params *params ) { | |
| struct san_path *sanpath = sandev->active; | |
| size_t len = ( params->rw.count * sandev->capacity.blksize ); | |
| int rc; | |
| /* Sanity check */ | |
| assert ( sanpath != NULL ); | |
| /* Initiate read/write command */ | |
| if ( ( rc = params->rw.block_rw ( &sanpath->block, &sandev->command, | |
| params->rw.lba, params->rw.count, | |
| params->rw.buffer, len ) ) != 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x.%d could not initiate " | |
| "read/write: %s\n", sandev->drive, sanpath->index, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Initiate SAN device read capacity command | |
| * | |
| * @v sandev SAN device | |
| * @v params Command parameters | |
| * @ret rc Return status code | |
| */ | |
| static int | |
| sandev_command_read_capacity ( struct san_device *sandev, | |
| const union san_command_params *params __unused){ | |
| struct san_path *sanpath = sandev->active; | |
| int rc; | |
| /* Sanity check */ | |
| assert ( sanpath != NULL ); | |
| /* Initiate read capacity command */ | |
| if ( ( rc = block_read_capacity ( &sanpath->block, | |
| &sandev->command ) ) != 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x.%d could not initiate read " | |
| "capacity: %s\n", sandev->drive, sanpath->index, | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Execute a single SAN device command and wait for completion | |
| * | |
| * @v sandev SAN device | |
| * @v command Command | |
| * @v params Command parameters (if required) | |
| * @ret rc Return status code | |
| */ | |
| static int | |
| sandev_command ( struct san_device *sandev, | |
| int ( * command ) ( struct san_device *sandev, | |
| const union san_command_params *params ), | |
| const union san_command_params *params ) { | |
| unsigned int retries = 0; | |
| int rc; | |
| /* Sanity check */ | |
| assert ( ! timer_running ( &sandev->timer ) ); | |
| /* Unquiesce system */ | |
| unquiesce(); | |
| /* (Re)try command */ | |
| do { | |
| /* Reopen block device if applicable */ | |
| if ( sandev_needs_reopen ( sandev ) && | |
| ( ( rc = sandev_reopen ( sandev ) ) != 0 ) ) { | |
| /* Delay reopening attempts */ | |
| sleep_fixed ( SAN_REOPEN_DELAY_SECS ); | |
| /* Retry opening indefinitely for multipath devices */ | |
| if ( sandev->paths <= 1 ) | |
| retries++; | |
| continue; | |
| } | |
| /* Initiate command */ | |
| if ( ( rc = command ( sandev, params ) ) != 0 ) { | |
| retries++; | |
| continue; | |
| } | |
| /* Start expiry timer */ | |
| start_timer_fixed ( &sandev->timer, SAN_COMMAND_TIMEOUT ); | |
| /* Wait for command to complete */ | |
| while ( timer_running ( &sandev->timer ) ) | |
| step(); | |
| /* Check command status */ | |
| if ( ( rc = sandev->command_rc ) != 0 ) { | |
| retries++; | |
| continue; | |
| } | |
| return 0; | |
| } while ( retries <= san_retries ); | |
| /* Sanity check */ | |
| assert ( ! timer_running ( &sandev->timer ) ); | |
| return rc; | |
| } | |
| /** | |
| * Reset SAN device | |
| * | |
| * @v sandev SAN device | |
| * @ret rc Return status code | |
| */ | |
| int sandev_reset ( struct san_device *sandev ) { | |
| int rc; | |
| DBGC ( sandev->drive, "SAN %#02x reset\n", sandev->drive ); | |
| /* Close and reopen underlying block device */ | |
| if ( ( rc = sandev_reopen ( sandev ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Read from or write to SAN device | |
| * | |
| * @v sandev SAN device | |
| * @v lba Starting logical block address | |
| * @v count Number of logical blocks | |
| * @v buffer Data buffer | |
| * @v block_rw Block read/write method | |
| * @ret rc Return status code | |
| */ | |
| static int sandev_rw ( struct san_device *sandev, uint64_t lba, | |
| unsigned int count, void *buffer, | |
| int ( * block_rw ) ( struct interface *control, | |
| struct interface *data, | |
| uint64_t lba, unsigned int count, | |
| void *buffer, size_t len ) ) { | |
| union san_command_params params; | |
| unsigned int remaining; | |
| size_t frag_len; | |
| int rc; | |
| /* Initialise command parameters */ | |
| params.rw.block_rw = block_rw; | |
| params.rw.buffer = buffer; | |
| params.rw.lba = ( lba << sandev->blksize_shift ); | |
| params.rw.count = sandev->capacity.max_count; | |
| remaining = ( count << sandev->blksize_shift ); | |
| /* Read/write fragments */ | |
| while ( remaining ) { | |
| /* Determine fragment length */ | |
| if ( params.rw.count > remaining ) | |
| params.rw.count = remaining; | |
| /* Execute command */ | |
| if ( ( rc = sandev_command ( sandev, sandev_command_rw, | |
| ¶ms ) ) != 0 ) | |
| return rc; | |
| /* Move to next fragment */ | |
| frag_len = ( sandev->capacity.blksize * params.rw.count ); | |
| params.rw.buffer += frag_len; | |
| params.rw.lba += params.rw.count; | |
| remaining -= params.rw.count; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Read from SAN device | |
| * | |
| * @v sandev SAN device | |
| * @v lba Starting logical block address | |
| * @v count Number of logical blocks | |
| * @v buffer Data buffer | |
| * @ret rc Return status code | |
| */ | |
| int sandev_read ( struct san_device *sandev, uint64_t lba, | |
| unsigned int count, void *buffer ) { | |
| int rc; | |
| /* Read from device */ | |
| if ( ( rc = sandev_rw ( sandev, lba, count, buffer, | |
| block_read ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Write to SAN device | |
| * | |
| * @v sandev SAN device | |
| * @v lba Starting logical block address | |
| * @v count Number of logical blocks | |
| * @v buffer Data buffer | |
| * @ret rc Return status code | |
| */ | |
| int sandev_write ( struct san_device *sandev, uint64_t lba, | |
| unsigned int count, void *buffer ) { | |
| int rc; | |
| /* Write to device */ | |
| if ( ( rc = sandev_rw ( sandev, lba, count, buffer, | |
| block_write ) ) != 0 ) | |
| return rc; | |
| /* Quiesce system. This is a heuristic designed to ensure | |
| * that the system is quiesced before Windows starts up, since | |
| * a Windows SAN boot will typically write a status flag to | |
| * the disk as its last action before transferring control to | |
| * the native drivers. | |
| */ | |
| quiesce(); | |
| return 0; | |
| } | |
| /** | |
| * Describe SAN device | |
| * | |
| * @v sandev SAN device | |
| * @ret rc Return status code | |
| * | |
| * Allow connections to progress until all existent path descriptors | |
| * are complete. | |
| */ | |
| static int sandev_describe ( struct san_device *sandev ) { | |
| struct san_path *sanpath; | |
| struct acpi_descriptor *desc; | |
| int rc; | |
| /* Wait for all paths to be either described or closed */ | |
| while ( 1 ) { | |
| /* Allow connections to progress */ | |
| step(); | |
| /* Fail if any closed path has an incomplete descriptor */ | |
| list_for_each_entry ( sanpath, &sandev->closed, list ) { | |
| desc = sanpath->desc; | |
| if ( ! desc ) | |
| continue; | |
| if ( ( rc = desc->model->complete ( desc ) ) != 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x.%d could not " | |
| "be described: %s\n", sandev->drive, | |
| sanpath->index, strerror ( rc ) ); | |
| return rc; | |
| } | |
| } | |
| /* Succeed if no paths have an incomplete descriptor */ | |
| rc = 0; | |
| list_for_each_entry ( sanpath, &sandev->opened, list ) { | |
| desc = sanpath->desc; | |
| if ( ! desc ) | |
| continue; | |
| if ( ( rc = desc->model->complete ( desc ) ) != 0 ) | |
| break; | |
| } | |
| if ( rc == 0 ) | |
| return 0; | |
| } | |
| } | |
| /** | |
| * Remove SAN device descriptors | |
| * | |
| * @v sandev SAN device | |
| */ | |
| static void sandev_undescribe ( struct san_device *sandev ) { | |
| struct san_path *sanpath; | |
| unsigned int i; | |
| /* Remove all ACPI descriptors */ | |
| for ( i = 0 ; i < sandev->paths ; i++ ) { | |
| sanpath = &sandev->path[i]; | |
| if ( sanpath->desc ) { | |
| acpi_del ( sanpath->desc ); | |
| sanpath->desc = NULL; | |
| } | |
| } | |
| } | |
| /** | |
| * Configure SAN device as a CD-ROM, if applicable | |
| * | |
| * @v sandev SAN device | |
| * @ret rc Return status code | |
| * | |
| * Both BIOS and UEFI require SAN devices to be accessed with a block | |
| * size of 2048. While we could require the user to configure the | |
| * block size appropriately, this is non-trivial and would impose a | |
| * substantial learning effort on the user. Instead, we check for the | |
| * presence of the ISO9660 primary volume descriptor and, if found, | |
| * then we force a block size of 2048 and map read/write requests | |
| * appropriately. | |
| */ | |
| static int sandev_parse_iso9660 ( struct san_device *sandev ) { | |
| static const struct iso9660_primary_descriptor_fixed primary_check = { | |
| .type = ISO9660_TYPE_PRIMARY, | |
| .id = ISO9660_ID, | |
| }; | |
| union { | |
| struct iso9660_primary_descriptor primary; | |
| char bytes[ISO9660_BLKSIZE]; | |
| } *scratch; | |
| unsigned int blksize; | |
| unsigned int blksize_shift; | |
| unsigned int lba; | |
| unsigned int count; | |
| int rc; | |
| /* Calculate required blocksize shift for potential CD-ROM access */ | |
| blksize = sandev->capacity.blksize; | |
| blksize_shift = 0; | |
| while ( blksize < ISO9660_BLKSIZE ) { | |
| blksize <<= 1; | |
| blksize_shift++; | |
| } | |
| if ( blksize > ISO9660_BLKSIZE ) { | |
| /* Cannot be a CD-ROM. This is not an error. */ | |
| rc = 0; | |
| goto invalid_blksize; | |
| } | |
| lba = ( ISO9660_PRIMARY_LBA << blksize_shift ); | |
| count = ( 1 << blksize_shift ); | |
| /* Allocate scratch area */ | |
| scratch = malloc ( ISO9660_BLKSIZE ); | |
| if ( ! scratch ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Read primary volume descriptor */ | |
| if ( ( rc = sandev_read ( sandev, lba, count, scratch ) ) != 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x could not read ISO9660 " | |
| "primary volume descriptor: %s\n", | |
| sandev->drive, strerror ( rc ) ); | |
| goto err_rw; | |
| } | |
| /* Configure as CD-ROM if applicable */ | |
| if ( memcmp ( &scratch->primary.fixed, &primary_check, | |
| sizeof ( primary_check ) ) == 0 ) { | |
| DBGC ( sandev->drive, "SAN %#02x contains an ISO9660 " | |
| "filesystem; treating as CD-ROM\n", sandev->drive ); | |
| sandev->blksize_shift = blksize_shift; | |
| sandev->is_cdrom = 1; | |
| } | |
| err_rw: | |
| free ( scratch ); | |
| err_alloc: | |
| invalid_blksize: | |
| return rc; | |
| } | |
| /** | |
| * Allocate SAN device | |
| * | |
| * @v uris List of URIs | |
| * @v count Number of URIs | |
| * @v priv_size Size of private data | |
| * @ret sandev SAN device, or NULL | |
| */ | |
| struct san_device * alloc_sandev ( struct uri **uris, unsigned int count, | |
| size_t priv_size ) { | |
| struct san_device *sandev; | |
| struct san_path *sanpath; | |
| size_t size; | |
| unsigned int i; | |
| /* Allocate and initialise structure */ | |
| size = ( sizeof ( *sandev ) + ( count * sizeof ( sandev->path[0] ) ) ); | |
| sandev = zalloc ( size + priv_size ); | |
| if ( ! sandev ) | |
| return NULL; | |
| ref_init ( &sandev->refcnt, sandev_free ); | |
| intf_init ( &sandev->command, &sandev_command_desc, &sandev->refcnt ); | |
| timer_init ( &sandev->timer, sandev_command_expired, &sandev->refcnt ); | |
| sandev->priv = ( ( ( void * ) sandev ) + size ); | |
| sandev->paths = count; | |
| INIT_LIST_HEAD ( &sandev->opened ); | |
| INIT_LIST_HEAD ( &sandev->closed ); | |
| for ( i = 0 ; i < count ; i++ ) { | |
| sanpath = &sandev->path[i]; | |
| sanpath->sandev = sandev; | |
| sanpath->index = i; | |
| sanpath->uri = uri_get ( uris[i] ); | |
| list_add_tail ( &sanpath->list, &sandev->closed ); | |
| intf_init ( &sanpath->block, &sanpath_block_desc, | |
| &sandev->refcnt ); | |
| process_init_stopped ( &sanpath->process, &sanpath_process_desc, | |
| &sandev->refcnt ); | |
| sanpath->path_rc = -EINPROGRESS; | |
| } | |
| return sandev; | |
| } | |
| /** | |
| * Register SAN device | |
| * | |
| * @v sandev SAN device | |
| * @v drive Drive number | |
| * @v flags Flags | |
| * @ret rc Return status code | |
| */ | |
| int register_sandev ( struct san_device *sandev, unsigned int drive, | |
| unsigned int flags ) { | |
| struct san_device *before; | |
| int rc; | |
| /* Check that drive number is not in use */ | |
| if ( sandev_find ( drive ) != NULL ) { | |
| DBGC ( sandev->drive, "SAN %#02x is already in use\n", drive ); | |
| rc = -EADDRINUSE; | |
| goto err_in_use; | |
| } | |
| /* Record drive number and flags */ | |
| sandev->drive = drive; | |
| sandev->flags = flags; | |
| /* Check that device is capable of being opened (i.e. that all | |
| * URIs are well-formed and that at least one path is | |
| * working). | |
| */ | |
| if ( ( rc = sandev_reopen ( sandev ) ) != 0 ) | |
| goto err_reopen; | |
| /* Describe device */ | |
| if ( ( rc = sandev_describe ( sandev ) ) != 0 ) | |
| goto err_describe; | |
| /* Read device capacity */ | |
| if ( ( rc = sandev_command ( sandev, sandev_command_read_capacity, | |
| NULL ) ) != 0 ) | |
| goto err_capacity; | |
| /* Configure as a CD-ROM, if applicable */ | |
| if ( ( rc = sandev_parse_iso9660 ( sandev ) ) != 0 ) | |
| goto err_iso9660; | |
| /* Add to list of SAN devices, in drive order */ | |
| for_each_sandev ( before ) { | |
| if ( before->drive > sandev->drive ) | |
| break; | |
| } | |
| list_add_tail ( &sandev->list, &before->list ); | |
| DBGC ( sandev->drive, "SAN %#02x registered\n", sandev->drive ); | |
| return 0; | |
| list_del ( &sandev->list ); | |
| err_iso9660: | |
| err_capacity: | |
| err_describe: | |
| err_reopen: | |
| sandev_restart ( sandev, rc ); | |
| sandev_undescribe ( sandev ); | |
| err_in_use: | |
| return rc; | |
| } | |
| /** | |
| * Unregister SAN device | |
| * | |
| * @v sandev SAN device | |
| */ | |
| void unregister_sandev ( struct san_device *sandev ) { | |
| /* Sanity check */ | |
| assert ( ! timer_running ( &sandev->timer ) ); | |
| /* Remove from list of SAN devices */ | |
| list_del ( &sandev->list ); | |
| /* Shut down interfaces */ | |
| sandev_restart ( sandev, 0 ); | |
| /* Remove ACPI descriptors */ | |
| sandev_undescribe ( sandev ); | |
| DBGC ( sandev->drive, "SAN %#02x unregistered\n", sandev->drive ); | |
| } | |
| /** The "san-drive" setting */ | |
| const struct setting san_drive_setting __setting ( SETTING_SANBOOT_EXTRA, | |
| san-drive ) = { | |
| .name = "san-drive", | |
| .description = "SAN drive number", | |
| .tag = DHCP_EB_SAN_DRIVE, | |
| .type = &setting_type_uint8, | |
| }; | |
| /** | |
| * Get default SAN drive number | |
| * | |
| * @ret drive Default drive number | |
| */ | |
| unsigned int san_default_drive ( void ) { | |
| unsigned long drive; | |
| /* Use "san-drive" setting, if specified */ | |
| if ( fetch_uint_setting ( NULL, &san_drive_setting, &drive ) >= 0 ) | |
| return drive; | |
| /* Otherwise, default to booting from first hard disk */ | |
| return SAN_DEFAULT_DRIVE; | |
| } | |
| /** The "san-retries" setting */ | |
| const struct setting san_retries_setting __setting ( SETTING_SANBOOT_EXTRA, | |
| san-retries ) = { | |
| .name = "san-retries", | |
| .description = "SAN retry count", | |
| .tag = DHCP_EB_SAN_RETRY, | |
| .type = &setting_type_int8, | |
| }; | |
| /** | |
| * Apply SAN boot settings | |
| * | |
| * @ret rc Return status code | |
| */ | |
| static int sandev_apply ( void ) { | |
| /* Apply "san-retries" setting */ | |
| if ( fetch_uint_setting ( NULL, &san_retries_setting, | |
| &san_retries ) < 0 ) { | |
| san_retries = SAN_DEFAULT_RETRIES; | |
| } | |
| return 0; | |
| } | |
| /** Settings applicator */ | |
| struct settings_applicator sandev_applicator __settings_applicator = { | |
| .apply = sandev_apply, | |
| }; |