This commit fixes security concerns regarding exploitation of a netlink socket inside the daemons event handler, that did not verify the sender of messages before handling them. This would put iprutils in a situation where any user capable of creating a AF_NETLINK socket would be able to generate events in our daemon. This patch modifies the event handler to verify the origin of each message received, and discard those that didn't come from the kernel itself. Signed-off-by: Gabriel Krisman Bertazi <krisman@linux.vnet.ibm.com> Signed-off-by: Brian King <brking@linux.vnet.ibm.com>
9841 lines
225 KiB
C
9841 lines
225 KiB
C
/**
|
|
* IBM IPR adapter utility library
|
|
*
|
|
* (C) Copyright 2000, 2004
|
|
* International Business Machines Corporation and others.
|
|
* All Rights Reserved. This program and the accompanying
|
|
* materials are made available under the terms of the
|
|
* Common Public License v1.0 which accompanies this distribution.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* $Header: /cvsroot/iprdd/iprutils/iprlib.c,v 1.127 2009/10/30 18:46:39 klebers Exp $
|
|
*/
|
|
|
|
#ifndef iprlib_h
|
|
#include "iprlib.h"
|
|
#endif
|
|
|
|
static void default_exit_func()
|
|
{
|
|
}
|
|
|
|
struct ipr_array_query_data *ipr_qac_data = NULL;
|
|
int num_ioas = 0;
|
|
struct ipr_ioa *ipr_ioa_head = NULL;
|
|
struct ipr_ioa *ipr_ioa_tail = NULL;
|
|
void (*exit_func) (void) = default_exit_func;
|
|
int daemonize = 0;
|
|
int ipr_debug = 0;
|
|
int ipr_force = 0;
|
|
int ipr_sg_required = 0;
|
|
int polling_mode = 0;
|
|
int ipr_fast = 0;
|
|
int format_done = 0;
|
|
static int ipr_mode5_write_buffer = 0;
|
|
static int first_time_check_zeroed_dev = 0;
|
|
|
|
struct sysfs_dev *head_zdev = NULL;
|
|
struct sysfs_dev *tail_zdev = NULL;
|
|
|
|
static int ipr_force_polling = 0;
|
|
static int ipr_force_uevents = 0;
|
|
static char *hotplug_dir = NULL;
|
|
static struct scsi_dev_data *scsi_dev_table = NULL;
|
|
|
|
/* This table includes both unsupported 522 disks and disks that support
|
|
being formatted to 522, but require a minimum microcode level. The disks
|
|
that require a minimum level of microcode will be marked by the
|
|
supported_with_min_ucode_level flag set to true. */
|
|
struct unsupported_af_dasd unsupported_af[] =
|
|
{
|
|
{
|
|
/* 00 Mako & Hammerhead */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"DRVS "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {" "},
|
|
min_ucode_mask: {0, 0, 0, 0}
|
|
},
|
|
{
|
|
/* 01 Swordfish */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"DRHS "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {" "},
|
|
min_ucode_mask: {0, 0, 0, 0}
|
|
},
|
|
{
|
|
/* 02 Neptune */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"DNES "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {" "},
|
|
min_ucode_mask: {0, 0, 0, 0}
|
|
},
|
|
{
|
|
/* 03 Thornback, Stingray, & Manta */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"DMVS "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {" "},
|
|
min_ucode_mask: {0, 0, 0, 0}
|
|
},
|
|
{
|
|
/* 04 Discovery I */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"DDYS "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {" "},
|
|
min_ucode_mask: {0, 0, 0, 0}
|
|
},
|
|
{
|
|
/* 05 US73 (Discovery II) - fixme - are we supporting this drive? */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"IC35L D2 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"S5DE"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 06 Apollo (Ext. Scallop 9GB) - LID: SC09 */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST318305L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"SC09"},
|
|
lid_mask: {1, 1, 1, 1},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {"C749"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 07 Apollo (Ext. Scallop 18GB) - LID: SC18 */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST318305L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"SC18"},
|
|
lid_mask: {1, 1, 1, 1},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {"C709"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 08 Apollo (Ext. Scallop 36GB) - LID: SC36 */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST336605L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"SC36"},
|
|
lid_mask: {1, 1, 1, 1},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {"C709"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 09 Apollo (Ext. Scallop 73GB) - LID: SC73 */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST373405L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"SC73"},
|
|
lid_mask: {1, 1, 1, 1},
|
|
supported_with_min_ucode_level: false,
|
|
min_ucode_level: {"C709"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 10 Apollo (non-external 9GB) - LID: AT1x */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST318305L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"AT1 "},
|
|
lid_mask: {1, 1, 1, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C54E"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 11 Apollo (non-external 18GB) - LID: AT0x */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST318305L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"AT0 "},
|
|
lid_mask: {1, 1, 1, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C50E"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 12 Apollo (non-external 36GB) - LID: AT0x */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST336605L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"AT0 "},
|
|
lid_mask: {1, 1, 1, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C50E"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 13 Apollo (non-external 73GB) - LID: AT0x */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST373405L "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {"AT0 "},
|
|
lid_mask: {1, 1, 1, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C50E"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 14 Odyssey (18, 36, 73GB) */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST3 53L "},
|
|
compare_product_id_byte: {1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C51A"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 15 Odyssey (146+GB) - fixme - not in drive list; remove from table? */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST3 53L "},
|
|
compare_product_id_byte: {1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C51A"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 16 Gemini (18, 36, 73GB) */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST3 07L "},
|
|
compare_product_id_byte: {1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C50F"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 17 Gemini (146+GB) */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"ST3 07L "},
|
|
compare_product_id_byte: {1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"C50F"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
},
|
|
{
|
|
/* 18 Daytona */
|
|
vendor_id: {"IBM "},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"IC35L DY "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
|
|
lid: {" "},
|
|
lid_mask: {0, 0, 0, 0},
|
|
supported_with_min_ucode_level: true,
|
|
min_ucode_level: {"S28C"},
|
|
min_ucode_mask: {1, 1, 1, 1}
|
|
}
|
|
};
|
|
|
|
struct unsupported_dasd unsupported_dasd [] = {
|
|
{ /* Piranha 18 GB, 15k RPM 160 MB/s - UCPR018 - 4322 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"UCPR018 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Piranha 18 GB, 15k RPM 320 MB/s - XCPR018 - 4325 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"XCPR018 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Piranha 35 GB, 15k RPM 160 MB/s - UCPR036 - 4323 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"UCPR036 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Piranha 35 GB, 15k RPM 320 MB/s - XCPR036 - 4326 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"XCPR036 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Monza 70 GB, 15k RPM 320 MB/s - XCPR073 - 4327 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"XCPR073 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Monza 141 GB, 15k RPM 320 MB/s - XCPR146 - 4328 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"XCPR146 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
},
|
|
{ /* Monaco 282 GB, 15k RPM 320 MB/s - XCPR282 - 4329 */
|
|
vendor_id: {"IBMAS400"},
|
|
compare_vendor_id_byte: {1, 1, 1, 1, 1, 1, 1, 1},
|
|
product_id: {"XCPR282 "},
|
|
compare_product_id_byte: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
|
}
|
|
};
|
|
|
|
struct ses_table_entry {
|
|
char product_id[17];
|
|
char compare_product_id_byte[17];
|
|
u32 max_bus_speed_limit;
|
|
int block_15k_devices;
|
|
};
|
|
|
|
static const struct ses_table_entry ses_table[] = {
|
|
{"2104-DL1 ", "XXXXXXXXXXXXXXXX", 80, 0},
|
|
{"2104-TL1 ", "XXXXXXXXXXXXXXXX", 80, 0},
|
|
{"HSBP07M P U2SCSI", "XXXXXXXXXXXXXXXX", 80, 1},
|
|
{"HSBP05M P U2SCSI", "XXXXXXXXXXXXXXXX", 80, 1},
|
|
{"HSBP05M S U2SCSI", "XXXXXXXXXXXXXXXX", 80, 1},
|
|
{"HSBP06E ASU2SCSI", "XXXXXXXXXXXXXXXX", 80, 1},
|
|
{"2104-DU3 ", "XXXXXXXXXXXXXXXX", 160, 0},
|
|
{"2104-TU3 ", "XXXXXXXXXXXXXXXX", 160, 0},
|
|
{"HSBP04C RSU2SCSI", "XXXXXXX*XXXXXXXX", 160, 0},
|
|
{"HSBP06E RSU2SCSI", "XXXXXXX*XXXXXXXX", 160, 0},
|
|
{"St V1S2 ", "XXXXXXXXXXXXXXXX", 160, 0},
|
|
{"HSBPD4M PU3SCSI", "XXXXXXX*XXXXXXXX", 160, 0},
|
|
{"VSBPD1H U3SCSI", "XXXXXXX*XXXXXXXX", 160, 0}
|
|
};
|
|
|
|
|
|
/*---------- static subroutine/function declearation starts here --------*/
|
|
|
|
static int sg_ioctl_by_name(char *, u8 *, void *, u32, u32,
|
|
struct sense_data_t *, u32);
|
|
|
|
/*---------- subroutine/function code starts here ---------*/
|
|
|
|
/**
|
|
* ses_table_entry -
|
|
* @ioa: ipr ioa struct
|
|
* @bus:
|
|
*
|
|
* Returns:
|
|
* ses_table_entry pointer if success / NULL on failure
|
|
**/
|
|
static const struct ses_table_entry *get_ses_entry(struct ipr_ioa *ioa, int bus)
|
|
{
|
|
int i, j, matches;
|
|
struct ipr_dev *dev = ioa->dev;
|
|
const struct ses_table_entry *ste = ses_table;
|
|
|
|
for (i = 0; i < ioa->num_devices; i++, dev++) {
|
|
if (!dev->scsi_dev_data)
|
|
continue;
|
|
if (dev->scsi_dev_data->channel == bus &&
|
|
dev->scsi_dev_data->type == TYPE_ENCLOSURE)
|
|
break;
|
|
}
|
|
|
|
if (i == ioa->num_devices)
|
|
return NULL;
|
|
if (strncmp(dev->scsi_dev_data->vendor_id, "IBM", 3))
|
|
return NULL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ses_table); i++, ste++) {
|
|
for (j = 0, matches = 0; j < IPR_PROD_ID_LEN; j++) {
|
|
if (ste->compare_product_id_byte[j] == 'X') {
|
|
if (dev->scsi_dev_data->product_id[j] == ste->product_id[j])
|
|
matches++;
|
|
else
|
|
break;
|
|
} else
|
|
matches++;
|
|
}
|
|
|
|
if (matches == IPR_PROD_ID_LEN)
|
|
return ste;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_cap_entry - Returns a pointer to a capability entry that corresponds to
|
|
* the given raid level.
|
|
* @supported_arrays: ipr_supported_arrays struct
|
|
* @raid_level: character string representation of the raid level
|
|
*
|
|
* Returns:
|
|
* ipr_array_cap_entry pointer if success / NULL on failure
|
|
**/
|
|
struct ipr_array_cap_entry *
|
|
get_cap_entry(struct ipr_supported_arrays *supported_arrays, char *raid_level)
|
|
{
|
|
struct ipr_array_cap_entry *cap;
|
|
|
|
for_each_cap_entry(cap, supported_arrays) {
|
|
if (!strcmp((char *)cap->prot_level_str, raid_level))
|
|
return cap;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_raid_cap_entry - Returns a pointer to a capability entry that corresponds
|
|
* to the given protection level.
|
|
* @supported_arrays: ipr_supported_arrays struct
|
|
* @prot_level: protection level
|
|
*
|
|
* Returns:
|
|
* ipr_array_cap_entry pointer if success / NULL on failure
|
|
**/
|
|
struct ipr_array_cap_entry *
|
|
get_raid_cap_entry(struct ipr_supported_arrays *supported_arrays, u8 prot_level)
|
|
{
|
|
struct ipr_array_cap_entry *cap;
|
|
|
|
if (!supported_arrays)
|
|
return NULL;
|
|
|
|
for_each_cap_entry(cap, supported_arrays) {
|
|
if (cap->prot_level == prot_level)
|
|
return cap;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_prot_level_str - Returns the string representation of the protection
|
|
* level the given the numeric protection level.
|
|
* @supported_arrays: ipr_supported_arrays struct
|
|
* @prot_level: protection level
|
|
*
|
|
* Returns:
|
|
* protection level string if success / NULL on failure
|
|
**/
|
|
char *get_prot_level_str(struct ipr_supported_arrays *supported_arrays,
|
|
int prot_level)
|
|
{
|
|
struct ipr_array_cap_entry *cap;
|
|
|
|
cap = get_raid_cap_entry(supported_arrays, prot_level);
|
|
|
|
if (cap)
|
|
return (char *)cap->prot_level_str;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_vset_from_array - Given an array, return the corresponding
|
|
* volume set device.
|
|
* @ioa: ipr ioa struct
|
|
* @array: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
struct ipr_dev *get_vset_from_array(struct ipr_ioa *ioa, struct ipr_dev *array)
|
|
{
|
|
struct ipr_dev *vset;
|
|
|
|
for_each_vset(ioa, vset)
|
|
if (vset->array_id == array->array_id)
|
|
return vset;
|
|
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* get_array_from_vset - Given a volume set, return the corresponding
|
|
* array device.
|
|
* @ioa: ipr ioa struct
|
|
* @vset: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
struct ipr_dev *get_array_from_vset(struct ipr_ioa *ioa, struct ipr_dev *vset)
|
|
{
|
|
struct ipr_dev *array;
|
|
|
|
for_each_array(ioa, array)
|
|
if (array->array_id == vset->array_id)
|
|
return array;
|
|
|
|
return vset;
|
|
}
|
|
|
|
static ssize_t sysfs_read_attr(const char *devpath, const char *attr,
|
|
void *value, size_t len)
|
|
{
|
|
char attrpath[256];
|
|
ssize_t ret;
|
|
int fd;
|
|
|
|
sprintf(attrpath, "%s/%s", devpath, attr);
|
|
fd = open(attrpath, O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
ret = read(fd, value, len);
|
|
close(fd);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t sysfs_write_attr(const char *devpath, const char *attr,
|
|
void *value, size_t len)
|
|
{
|
|
char attrpath[256];
|
|
ssize_t ret;
|
|
int fd;
|
|
|
|
sprintf(attrpath, "%s/%s", devpath, attr);
|
|
fd = open(attrpath, O_WRONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
ret = write(fd, value, len);
|
|
close(fd);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ipr_find_sysfs_dev -
|
|
* @dev: ipr dev struct
|
|
* @head: sysfs dev struct
|
|
*
|
|
* Returns:
|
|
* sysfs_dev pointer if success / NULL on failure
|
|
**/
|
|
struct sysfs_dev * ipr_find_sysfs_dev(struct ipr_dev *dev, struct sysfs_dev *head)
|
|
{
|
|
struct sysfs_dev *sdev;
|
|
|
|
if (!dev->scsi_dev_data)
|
|
return NULL;
|
|
|
|
for (sdev = head; sdev; sdev = sdev->next) {
|
|
if (!strcmp(sdev->sysfs_device_name,
|
|
dev->scsi_dev_data->sysfs_device_name))
|
|
break;
|
|
}
|
|
|
|
return sdev;
|
|
}
|
|
|
|
/**
|
|
* ipr_sysfs_dev_to_dev -
|
|
* @sdev: sysfs dev struct
|
|
*
|
|
* Returns:
|
|
* ipr_dev pointer if success / NULL on failure
|
|
**/
|
|
struct ipr_dev *ipr_sysfs_dev_to_dev(struct sysfs_dev *sdev)
|
|
{
|
|
struct ipr_dev *dev;
|
|
struct ipr_ioa *ioa;
|
|
|
|
for_each_ioa(ioa) {
|
|
for_each_dev(ioa, dev) {
|
|
if (!dev->scsi_dev_data)
|
|
continue;
|
|
if (!strcmp(sdev->sysfs_device_name,
|
|
dev->scsi_dev_data->sysfs_device_name))
|
|
return dev;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ipr_find_zeroed_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* results of call to ipr_find_sysfs_dev
|
|
**/
|
|
struct sysfs_dev * ipr_find_zeroed_dev(struct ipr_dev *dev)
|
|
{
|
|
return ipr_find_sysfs_dev(dev, head_zdev);
|
|
}
|
|
|
|
/**
|
|
* ipr_device_is_zeroed -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 1 if the given devices is zeroed / 0 if the device is not zeroed
|
|
**/
|
|
int ipr_device_is_zeroed(struct ipr_dev *dev)
|
|
{
|
|
if (ipr_find_zeroed_dev(dev))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_add_sysfs_dev -
|
|
* @dev: ipr dev struct
|
|
* @head: sysfs dev struct
|
|
* @tail: sysfs dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_add_sysfs_dev(struct ipr_dev *dev, struct sysfs_dev **head,
|
|
struct sysfs_dev **tail)
|
|
{
|
|
struct sysfs_dev *sdev = ipr_find_sysfs_dev(dev, *head);
|
|
|
|
if (!dev->scsi_dev_data)
|
|
return;
|
|
|
|
if (!sdev) {
|
|
sdev = calloc(1, sizeof(struct sysfs_dev));
|
|
strcpy(sdev->sysfs_device_name,
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
|
|
if (!(*head)) {
|
|
*tail = *head = sdev;
|
|
} else {
|
|
(*tail)->next = sdev;
|
|
sdev->prev = *tail;
|
|
*tail = sdev;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_add_zeroed_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_add_zeroed_dev(struct ipr_dev *dev)
|
|
{
|
|
ipr_add_sysfs_dev(dev, &head_zdev, &tail_zdev);
|
|
}
|
|
|
|
/**
|
|
* ipr_del_sysfs_dev -
|
|
* @dev: ipr dev struct
|
|
* @head: sysfs dev struct
|
|
* @tail: sysfs dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_del_sysfs_dev(struct ipr_dev *dev, struct sysfs_dev **head,
|
|
struct sysfs_dev **tail)
|
|
{
|
|
struct sysfs_dev *sdev = ipr_find_sysfs_dev(dev, *head);
|
|
|
|
if (!sdev || !dev->scsi_dev_data)
|
|
return;
|
|
|
|
if (sdev == *head) {
|
|
*head = (*head)->next;
|
|
|
|
if (!(*head))
|
|
*tail = NULL;
|
|
else
|
|
(*head)->prev = NULL;
|
|
} else if (sdev == *tail) {
|
|
*tail = (*tail)->prev;
|
|
(*tail)->next = NULL;
|
|
} else {
|
|
sdev->next->prev = sdev->prev;
|
|
sdev->prev->next = sdev->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_del_zeroed_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_del_zeroed_dev(struct ipr_dev *dev)
|
|
{
|
|
ipr_del_sysfs_dev(dev, &head_zdev, &tail_zdev);
|
|
}
|
|
|
|
/**
|
|
* ipr_update_qac_with_zeroed_devs
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_update_qac_with_zeroed_devs(struct ipr_ioa *ioa)
|
|
{
|
|
struct sysfs_dev *zdev;
|
|
struct ipr_dev_record *dev_rcd;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_ioa_mode_page *page;
|
|
int i;
|
|
|
|
if (!ioa->qac_data)
|
|
return;
|
|
|
|
for (i = 0; i < ioa->num_devices; i++) {
|
|
zdev = ipr_find_zeroed_dev(&ioa->dev[i]);
|
|
if (!zdev && ipr_is_af_dasd_device(&ioa->dev[i])) {
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
ipr_mode_sense(&ioa->dev[i], 0x20, &mode_pages);
|
|
|
|
page = (struct ipr_ioa_mode_page *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
if (page->format_completed) {
|
|
dev_rcd = (struct ipr_dev_record *)ioa->dev[i].qac_entry;
|
|
dev_rcd->known_zeroed = 1;
|
|
}
|
|
}
|
|
else if (zdev && ioa->dev[i].qac_entry) {
|
|
dev_rcd = (struct ipr_dev_record *)ioa->dev[i].qac_entry;
|
|
dev_rcd->known_zeroed = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_devs_format_completed()
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *dev;
|
|
|
|
if (format_done) {
|
|
for_each_ioa(ioa) {
|
|
for_each_dev(ioa, dev) {
|
|
if (ipr_is_af_dasd_device(dev) && ipr_device_is_zeroed(dev))
|
|
ipr_set_format_completed_bit(dev);
|
|
}
|
|
}
|
|
format_done = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_cleanup_zeroed_devs -
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_cleanup_zeroed_devs()
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *dev;
|
|
struct sysfs_dev *zdev;
|
|
|
|
for_each_ioa(ioa) {
|
|
for_each_dev(ioa, dev) {
|
|
zdev = ipr_find_zeroed_dev(dev);
|
|
if (!zdev)
|
|
continue;
|
|
|
|
if (dev->scsi_dev_data && dev->scsi_dev_data->type == TYPE_DISK)
|
|
ipr_del_zeroed_dev(dev);
|
|
else if (ipr_is_array_member(dev))
|
|
ipr_del_zeroed_dev(dev);
|
|
else if (ipr_is_hot_spare(dev))
|
|
ipr_del_zeroed_dev(dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_max_bus_speed -
|
|
* @ioa: ipr ioa struct
|
|
* @bus: bus number
|
|
*
|
|
* Returns:
|
|
* maximum bus speed value
|
|
**/
|
|
int get_max_bus_speed(struct ipr_ioa *ioa, int bus)
|
|
{
|
|
const struct ses_table_entry *ste = get_ses_entry(ioa, bus);
|
|
u32 fw_version;
|
|
int max_xfer_rate = IPR_MAX_XFER_RATE;
|
|
char devpath[PATH_MAX];
|
|
char value[16];
|
|
ssize_t len;
|
|
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_read_attr(devpath, "fw_version", value, 16);
|
|
if (len < 0)
|
|
return -1;
|
|
sscanf(value, "%8X", &fw_version);
|
|
|
|
if (ipr_force)
|
|
return IPR_MAX_XFER_RATE;
|
|
|
|
if (fw_version < ioa->msl)
|
|
max_xfer_rate = IPR_SAFE_XFER_RATE;
|
|
|
|
if (ste && ste->max_bus_speed_limit < max_xfer_rate)
|
|
return ste->max_bus_speed_limit;
|
|
|
|
return max_xfer_rate;
|
|
}
|
|
|
|
struct ioa_parms {
|
|
int ccin;
|
|
int scsi_id_changeable;
|
|
u32 msl;
|
|
char *fw_name;
|
|
};
|
|
|
|
static const struct ioa_parms ioa_parms [] = {
|
|
{.ccin = 0x5702, .scsi_id_changeable = 1,
|
|
.msl = 0x02080029, .fw_name = "44415254" },
|
|
{.ccin = 0x1974, .scsi_id_changeable = 1,
|
|
.msl = 0x02080037, .fw_name = "44415254" },
|
|
{.ccin = 0x5703, .scsi_id_changeable = 1,
|
|
.msl = 0x03090059, .fw_name = "5052414D" },
|
|
{.ccin = 0x1975, .scsi_id_changeable = 1,
|
|
.msl = 0x03090068, .fw_name = "5052414D" },
|
|
{.ccin = 0x5709, .scsi_id_changeable = 0,
|
|
.msl = 0x030D0047, .fw_name = "5052414E" },
|
|
{.ccin = 0x1976, .scsi_id_changeable = 0,
|
|
.msl = 0x030D0056, .fw_name = "5052414E" },
|
|
{.ccin = 0x570A, .scsi_id_changeable = 0,
|
|
.msl = 0x020A004E, .fw_name = "44415255" },
|
|
{.ccin = 0x570B, .scsi_id_changeable = 0,
|
|
.msl = 0x020A004E, .fw_name = "44415255" },
|
|
{.ccin = 0x2780, .scsi_id_changeable = 0,
|
|
.msl = 0x030E0040, .fw_name = "5349530E" },
|
|
};
|
|
|
|
struct chip_details {
|
|
u16 vendor;
|
|
u16 device;
|
|
const char *desc;
|
|
};
|
|
|
|
static const struct chip_details chip_details []= {
|
|
{ .vendor = 0x1069, .device = 0xB166, "PCI-X" }, /* Gemstone */
|
|
{ .vendor = 0x1014, .device = 0x028C, "PCI-X" }, /* Citrine */
|
|
{ .vendor = 0x1014, .device = 0x0180, "PCI-X" }, /* Snipe */
|
|
{ .vendor = 0x1014, .device = 0x02BD, "PCI-X" }, /* Obsidian */
|
|
{ .vendor = 0x1014, .device = 0x0339, "PCI-E" }, /* Obsidian-E */
|
|
{ .vendor = 0x9005, .device = 0x0503, "PCI-X" }, /* Scamp */
|
|
{ .vendor = 0x1014, .device = 0x033D, "PCI-E" }, /* CRoC-FPGA */
|
|
{ .vendor = 0x1014, .device = 0x034A, "PCI-E" }, /* CRoCodile */
|
|
};
|
|
|
|
/**
|
|
* get_chip_details -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* chip_details struct pointer if success / NULL on failure
|
|
**/
|
|
static const struct chip_details *get_chip_details(struct ipr_ioa *ioa)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chip_details); i++) {
|
|
if (ioa->pci_vendor == chip_details[i].vendor &&
|
|
ioa->pci_device == chip_details[i].device)
|
|
return &chip_details[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct ioa_details {
|
|
u16 subsystem_vendor;
|
|
u16 subsystem_device;
|
|
const char *ioa_desc;
|
|
int is_spi:1;
|
|
};
|
|
|
|
static const struct ioa_details ioa_details [] = {
|
|
{.subsystem_vendor = 0x1014,
|
|
.subsystem_device = 0x028D,
|
|
"PCI-X SAS RAID Adapter", .is_spi = 0}
|
|
};
|
|
|
|
/**
|
|
* get_ioa_details -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* ioa_details struct pointer if success / NULL on failure
|
|
**/
|
|
static const struct ioa_details *get_ioa_details(struct ipr_ioa *ioa)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ioa_details); i++) {
|
|
if (ioa->subsystem_vendor == ioa_details[i].subsystem_vendor &&
|
|
ioa->subsystem_device == ioa_details[i].subsystem_device)
|
|
return &ioa_details[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool ipr_is_af_blk_size(struct ipr_ioa *ioa, int blk_sz)
|
|
{
|
|
|
|
if (blk_sz == ioa->af_block_size ||
|
|
(ioa->support_4k && blk_sz == IPR_AF_4K_BLOCK_SIZE))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* ipr_improper_device_type -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 1 if the device is improper / 0 if the device is not improper
|
|
**/
|
|
int ipr_improper_device_type(struct ipr_dev *dev)
|
|
{
|
|
if (dev->rescan)
|
|
return 1;
|
|
if (!dev->scsi_dev_data)
|
|
return 0;
|
|
if (!dev->ioa->qac_data || !dev->ioa->qac_data->num_records)
|
|
return 0;
|
|
if (dev->scsi_dev_data->type == IPR_TYPE_AF_DISK && !dev->qac_entry &&
|
|
(ipr_get_blk_size(dev) == IPR_JBOD_BLOCK_SIZE ||
|
|
(dev->ioa->support_4k && ipr_get_blk_size(dev) == IPR_JBOD_4K_BLOCK_SIZE)))
|
|
return 1;
|
|
if (dev->scsi_dev_data->type == TYPE_DISK && dev->qac_entry && ipr_is_af_blk_size(dev->ioa, ipr_get_blk_size(dev)))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* mode_sense -
|
|
* @dev: ipr dev struct
|
|
* @page: page number
|
|
* @buff: buffer
|
|
* @sense_data sense_data_t struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int mode_sense(struct ipr_dev *dev, u8 page, void *buff,
|
|
struct sense_data_t *sense_data)
|
|
{
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
u8 length = IPR_MODE_SENSE_LENGTH; /* xxx FIXME? */
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
scsi_dbg(dev, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = MODE_SENSE;
|
|
cdb[2] = page;
|
|
cdb[4] = length;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_tcq_mode -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* the tcq mode type
|
|
**/
|
|
static enum ipr_tcq_mode ioa_get_tcq_mode(struct ipr_ioa *ioa)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
rc = mode_sense(&ioa->ioa, 0x28, &mode_pages, &sense_data);
|
|
|
|
if (rc && sense_data.sense_key == ILLEGAL_REQUEST)
|
|
return IPR_TCQ_NACA;
|
|
if (rc)
|
|
return IPR_TCQ_DISABLE;
|
|
return IPR_TCQ_FROZEN;
|
|
}
|
|
|
|
/**
|
|
* __ioa_is_spi - Determine if the IOA is a SCSI Parallel Interface (SPI)
|
|
* adapter.
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 1 if the IOA is an SPI adapter / 0 otherwise
|
|
**/
|
|
int __ioa_is_spi(struct ipr_ioa *ioa)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
rc = mode_sense(&ioa->ioa, 0x28, &mode_pages, &sense_data);
|
|
|
|
if (rc && sense_data.sense_key == ILLEGAL_REQUEST)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* ioa_is_spi - Determine if the IOA is a SCSI Parallel Interface (SPI) adapter.
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 1 if the IOA is an SPI adapter / 0 otherwise
|
|
**/
|
|
int ioa_is_spi(struct ipr_ioa *ioa)
|
|
{
|
|
const struct ioa_details *details = get_ioa_details(ioa);
|
|
|
|
if (details)
|
|
return details->is_spi;
|
|
return __ioa_is_spi(ioa);
|
|
}
|
|
|
|
static const char *raid_desc = "SCSI RAID Adapter";
|
|
static const char *jbod_desc = "SCSI Adapter";
|
|
static const char *sas_raid_desc = "SAS RAID Adapter";
|
|
static const char *sas_jbod_desc = "SAS Adapter";
|
|
static const char *aux_cache_desc = "Aux Cache Adapter";
|
|
static const char *def_chip_desc = "PCI";
|
|
|
|
/**
|
|
* get_bus_desc -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* FIXME
|
|
**/
|
|
const char *get_bus_desc(struct ipr_ioa *ioa)
|
|
{
|
|
const struct chip_details *chip = get_chip_details(ioa);
|
|
|
|
if (!chip)
|
|
return def_chip_desc;
|
|
return chip->desc;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_desc -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* FIXME
|
|
**/
|
|
const char *get_ioa_desc(struct ipr_ioa *ioa)
|
|
{
|
|
const struct ioa_details *details = get_ioa_details(ioa);
|
|
|
|
if (details)
|
|
return details->ioa_desc;
|
|
else if (ioa->is_aux_cache)
|
|
return aux_cache_desc;
|
|
else if (!ioa_is_spi(ioa)) {
|
|
if (ioa->qac_data->num_records)
|
|
return sas_raid_desc;
|
|
return sas_jbod_desc;
|
|
} else if (ioa->qac_data->num_records)
|
|
return raid_desc;
|
|
|
|
return jbod_desc;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_fw -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* pointer to ioa_parms entry / NULL on failure
|
|
**/
|
|
static const struct ioa_parms *get_ioa_fw(struct ipr_ioa *ioa)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ioa_parms); i++) {
|
|
if (ioa->ccin == ioa_parms[i].ccin)
|
|
return &ioa_parms[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* setup_ioa_parms
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void setup_ioa_parms(struct ipr_ioa *ioa)
|
|
{
|
|
const struct ioa_parms *ioa_parms = get_ioa_fw(ioa);
|
|
|
|
ioa->scsi_id_changeable = 1;
|
|
|
|
if (ioa_parms) {
|
|
ioa->scsi_id_changeable = ioa_parms->scsi_id_changeable;
|
|
ioa->msl = ioa_parms->msl;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* find_ioa -
|
|
* @host_no: host number int
|
|
*
|
|
* Returns:
|
|
* pointer to ipr_ioa / NULL on failure
|
|
**/
|
|
struct ipr_ioa *find_ioa(int host_no)
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
|
|
for_each_ioa(ioa)
|
|
if (ioa->host_num == host_no)
|
|
return ioa;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* _find_dev -
|
|
* @blk: ipr ioa struct
|
|
* @compare: pointer to a comparison function
|
|
*
|
|
* Returns:
|
|
* ipr_dev pointer if success / NULL on failure
|
|
**/
|
|
static struct ipr_dev *_find_dev(char *blk, int (*compare) (struct ipr_dev *, char *))
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *dev;
|
|
char *name = malloc(strlen(_PATH_DEV) + strlen(blk) + 1);
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
if (strncmp(blk, _PATH_DEV, strlen(_PATH_DEV)))
|
|
sprintf(name, _PATH_DEV"%s", blk);
|
|
else
|
|
sprintf(name, "%s", blk);
|
|
|
|
for_each_ioa(ioa) {
|
|
if (!compare(&ioa->ioa, name)) {
|
|
free(name);
|
|
return &ioa->ioa;
|
|
}
|
|
|
|
for_each_dev(ioa, dev) {
|
|
if (!compare(dev, name)) {
|
|
free(name);
|
|
return dev;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* blk_compare -
|
|
* @dev: ipr ioa struct
|
|
* @name: character string to compare
|
|
*
|
|
* Returns:
|
|
* integer result of the strcmp()
|
|
**/
|
|
static int blk_compare(struct ipr_dev *dev, char *name)
|
|
{
|
|
return strcmp(dev->dev_name, name);
|
|
}
|
|
|
|
/**
|
|
* find_blk_dev -
|
|
* @blk: block device name
|
|
*
|
|
* Returns:
|
|
* result of _find_dev()
|
|
**/
|
|
struct ipr_dev *find_blk_dev(char *blk)
|
|
{
|
|
return _find_dev(blk, blk_compare);
|
|
}
|
|
|
|
/**
|
|
* gen_compare -
|
|
* @dev: ipr dev struct
|
|
* @name: character string to compare
|
|
*
|
|
* Returns:
|
|
* integer result of the strcmp()
|
|
**/
|
|
static int gen_compare(struct ipr_dev *dev, char *name)
|
|
{
|
|
return strcmp(dev->gen_name, name);
|
|
}
|
|
|
|
/**
|
|
* find_gen_dev -
|
|
* @gen: generic device name
|
|
*
|
|
* Returns:
|
|
* result of _find_dev()
|
|
**/
|
|
struct ipr_dev *find_gen_dev(char *gen)
|
|
{
|
|
return _find_dev(gen, gen_compare);
|
|
}
|
|
|
|
/**
|
|
* find_dev -
|
|
* @name: device name to find
|
|
*
|
|
* Returns:
|
|
* ipr_dev pointer
|
|
**/
|
|
struct ipr_dev *find_dev(char *name)
|
|
{
|
|
struct ipr_dev *dev = find_blk_dev(name);
|
|
|
|
if (!dev)
|
|
dev = find_gen_dev(name);
|
|
return dev;
|
|
}
|
|
|
|
/**
|
|
* ipr_uevents_supported - indicate if uevents are supported
|
|
*
|
|
* Returns:
|
|
* 1 if success / 0 on failure
|
|
**/
|
|
static int ipr_uevents_supported()
|
|
{
|
|
struct ipr_ioa *ioa = ipr_ioa_head;
|
|
char devpath[PATH_MAX];
|
|
char value[16];
|
|
ssize_t len;
|
|
|
|
if (!ioa)
|
|
return 0;
|
|
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_read_attr(devpath, "uevent", value, 16);
|
|
return len > 0;
|
|
}
|
|
|
|
#define NETLINK_KOBJECT_UEVENT 15
|
|
|
|
/**
|
|
* poll_forever -
|
|
* @poll_func: pointer to polling function
|
|
* @poll_delay: integer sleep delay value
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void poll_forever(void (*poll_func) (void), int poll_delay)
|
|
{
|
|
while (1) {
|
|
sleep(poll_delay);
|
|
poll_func();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* handle_events -
|
|
* @poll_func: pointer to polling function
|
|
* @poll_delay: integer sleep delay value
|
|
* @prot_level: pointer to event handler function
|
|
*
|
|
* Returns:
|
|
* 0 if poll_forever() ever returns
|
|
**/
|
|
int handle_events(void (*poll_func) (void), int poll_delay,
|
|
void (*kevent_handler) (char *))
|
|
{
|
|
struct sockaddr_nl snl;
|
|
int sock, rc, len, flags;
|
|
int uevent_rcvd = ipr_force_uevents;
|
|
char buf[1024];
|
|
|
|
if (ipr_force_polling) {
|
|
poll_forever(poll_func, poll_delay);
|
|
return 0;
|
|
}
|
|
|
|
memset(&snl, 0, sizeof(snl));
|
|
snl.nl_family = AF_NETLINK;
|
|
snl.nl_pid = getpid();
|
|
snl.nl_groups = 1;
|
|
|
|
sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
|
|
if (sock == -1) {
|
|
syslog_dbg("Failed to get socket\n");
|
|
poll_forever(poll_func, poll_delay);
|
|
return 0;
|
|
}
|
|
|
|
rc = bind(sock, (struct sockaddr *)&snl, sizeof(snl));
|
|
|
|
if (rc < 0) {
|
|
syslog_dbg("Failed to bind to socket\n");
|
|
close(sock);
|
|
poll_forever(poll_func, poll_delay);
|
|
return 0;
|
|
}
|
|
|
|
if (!ipr_force_uevents && !ipr_uevents_supported()) {
|
|
if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
|
|
syslog(LOG_ERR, "Failed to fcntl socket\n");
|
|
close(sock);
|
|
poll_forever(poll_func, poll_delay);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
struct sockaddr_nl src_addr;
|
|
socklen_t addrlen = sizeof(struct sockaddr_nl);
|
|
|
|
len = recvfrom(sock, &buf, sizeof(buf), 0,
|
|
(struct sockaddr *) &src_addr, &addrlen);
|
|
|
|
if (!uevent_rcvd && len < 0) {
|
|
poll_func();
|
|
sleep(poll_delay);
|
|
continue;
|
|
}
|
|
|
|
if (len < 0) {
|
|
syslog_dbg("kevent recv failed.\n");
|
|
poll_func();
|
|
sleep(poll_delay);
|
|
continue;
|
|
}
|
|
|
|
if (src_addr.nl_pid != 0) {
|
|
syslog_dbg("received packet from unknown pid %d.\n",
|
|
src_addr.nl_pid);
|
|
poll_func();
|
|
sleep(poll_delay);
|
|
continue;
|
|
}
|
|
|
|
if (!uevent_rcvd) {
|
|
flags = fcntl(sock, F_GETFL);
|
|
if (flags == -1) {
|
|
syslog_dbg("F_GETFL Failed\n");
|
|
poll_func();
|
|
sleep(poll_delay);
|
|
continue;
|
|
}
|
|
|
|
if (flags & O_NONBLOCK) {
|
|
if (fcntl(sock, F_SETFL, (flags & ~O_NONBLOCK)) == -1) {
|
|
syslog_dbg("F_SETFL Failed\n");
|
|
poll_func();
|
|
sleep(poll_delay);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
uevent_rcvd = 1;
|
|
}
|
|
|
|
kevent_handler(buf);
|
|
}
|
|
|
|
close(sock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* parse_option - parse options from the command line
|
|
* @opt: character string option value
|
|
*
|
|
* Returns:
|
|
* 1 if success / 0 if the option isn't recognized
|
|
**/
|
|
int parse_option(char *opt)
|
|
{
|
|
if (strcmp(opt, "--version") == 0) {
|
|
printf("%s: %s\n", tool_name, PACKAGE_VERSION);
|
|
exit(0);
|
|
}
|
|
|
|
if (strcmp(opt, "--daemon") == 0)
|
|
daemonize = 1;
|
|
else if (strcmp(opt, "--debug") == 0)
|
|
ipr_debug = 1;
|
|
else if (strcmp(opt, "--force") == 0)
|
|
ipr_force = 1;
|
|
else if (strcmp(opt, "--use-polling") == 0)
|
|
ipr_force_polling = 1;
|
|
else if (strcmp(opt, "--use-uevents") == 0)
|
|
ipr_force_uevents = 1;
|
|
else if (strcmp(opt, "--fast") == 0)
|
|
ipr_fast = 1;
|
|
else if (strcmp(opt, "--deferred-write-buffer") == 0)
|
|
ipr_mode5_write_buffer = 0;
|
|
else if (strcmp(opt, "--mode5-write-buffer") == 0)
|
|
ipr_mode5_write_buffer = 1;
|
|
else
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* get_pci_attr -
|
|
* @sysfs_pci_device: sysfs_device struct
|
|
* @attr: attribute string
|
|
*
|
|
* Returns:
|
|
* pci attribute value if success / 0 on failure
|
|
**/
|
|
static int get_pci_attr(char *devpath, char *attr)
|
|
{
|
|
int temp, fd, len;
|
|
char path[PATH_MAX];
|
|
char data[200];
|
|
|
|
sprintf(path, "%s/%s", devpath, attr);
|
|
|
|
if ((fd = open(path, O_RDONLY)) < 0) {
|
|
syslog_dbg("Failed to open %s\n", path);
|
|
return 0;
|
|
}
|
|
|
|
len = read(fd, data, 200);
|
|
if (len < 0) {
|
|
syslog_dbg("Failed to read %s\n", path);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
sscanf(data, "0x%4X", &temp);
|
|
close(fd);
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* get_pci_attrs - sets vendor and device information
|
|
* @ioa: ipr ioa struct
|
|
* @sysfs_pci_device: sysfs_device struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_pci_attrs(struct ipr_ioa *ioa, char *pci_path)
|
|
{
|
|
ioa->subsystem_vendor = get_pci_attr(pci_path, "subsystem_vendor");
|
|
ioa->subsystem_device = get_pci_attr(pci_path, "subsystem_device");
|
|
ioa->pci_vendor = get_pci_attr(pci_path, "vendor");
|
|
ioa->pci_device = get_pci_attr(pci_path, "device");
|
|
}
|
|
|
|
static struct ipr_ioa *old_ioa_head;
|
|
static struct ipr_ioa *old_ioa_tail;
|
|
static int old_num_ioas;
|
|
static struct scsi_dev_data *old_scsi_dev_table;
|
|
struct ipr_array_query_data *old_qac_data;
|
|
|
|
/**
|
|
* free_current_config - frees current configuration data structures
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void free_current_config()
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
|
|
for (ioa = ipr_ioa_head; ioa;) {
|
|
ioa = ioa->next;
|
|
free(ipr_ioa_head);
|
|
ipr_ioa_head = ioa;
|
|
}
|
|
|
|
free(ipr_qac_data);
|
|
free(scsi_dev_table);
|
|
|
|
ipr_ioa_head = NULL;
|
|
ipr_ioa_tail = NULL;
|
|
num_ioas = 0;
|
|
scsi_dev_table = NULL;
|
|
ipr_qac_data = NULL;
|
|
}
|
|
|
|
/**
|
|
* save_old_config - saves current configuration data structures
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void save_old_config()
|
|
{
|
|
if (old_ioa_head) {
|
|
free_current_config();
|
|
return;
|
|
}
|
|
|
|
old_ioa_head = ipr_ioa_head;
|
|
old_ioa_tail = ipr_ioa_tail;
|
|
old_num_ioas = num_ioas;
|
|
old_scsi_dev_table = scsi_dev_table;
|
|
old_qac_data = ipr_qac_data;
|
|
|
|
ipr_qac_data = NULL;
|
|
scsi_dev_table = NULL;
|
|
ipr_ioa_head = NULL;
|
|
ipr_ioa_tail = NULL;
|
|
num_ioas = 0;
|
|
}
|
|
|
|
/**
|
|
* free_old_config - frees old configuration data structures
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void free_old_config()
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
|
|
/* Free up all the old memory */
|
|
for (ioa = old_ioa_head; ioa;) {
|
|
ioa = ioa->next;
|
|
free(old_ioa_head);
|
|
old_ioa_head = ioa;
|
|
}
|
|
|
|
free(old_qac_data);
|
|
free(old_scsi_dev_table);
|
|
|
|
old_ioa_head = NULL;
|
|
old_ioa_tail = NULL;
|
|
old_num_ioas = 0;
|
|
old_scsi_dev_table = NULL;
|
|
old_qac_data = NULL;
|
|
}
|
|
|
|
/**
|
|
* same_ioa - compares two ioas to determine if they are the same
|
|
* @first: ipr ioa struct
|
|
* @second: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if ioas are not the same / 1 if ioas are the same
|
|
**/
|
|
static int same_ioa(struct ipr_ioa *first, struct ipr_ioa *second)
|
|
{
|
|
if (strcmp(first->pci_address, second->pci_address))
|
|
return 0;
|
|
if (strcmp(first->host_name, second->host_name))
|
|
return 0;
|
|
if (first->ccin != second->ccin)
|
|
return 0;
|
|
if (first->host_num != second->host_num)
|
|
return 0;
|
|
if (first->pci_vendor != second->pci_vendor)
|
|
return 0;
|
|
if (first->pci_device != second->pci_device)
|
|
return 0;
|
|
if (first->subsystem_vendor != second->subsystem_vendor)
|
|
return 0;
|
|
if (first->subsystem_device != second->subsystem_device)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* same_scsi_dev - compares two scsi devices to determin if they are the same
|
|
* @first: scsi_dev_data struct
|
|
* @second: scsi_dev_data struct
|
|
*
|
|
* Returns:
|
|
* 0 if scsi devs are not the same / 1 if scsi devs are the same
|
|
**/
|
|
static int same_scsi_dev(struct scsi_dev_data *first, struct scsi_dev_data *second)
|
|
{
|
|
if (!first || !second)
|
|
return 0;
|
|
if (first->host != second->host)
|
|
return 0;
|
|
if (first->channel != second->channel)
|
|
return 0;
|
|
if (first->id != second->id)
|
|
return 0;
|
|
if (first->lun != second->lun)
|
|
return 0;
|
|
if (first->type != second->type)
|
|
return 0;
|
|
if (first->online != second->online)
|
|
return 0;
|
|
if (strcmp(first->vendor_id, second->vendor_id))
|
|
return 0;
|
|
if (strcmp(first->product_id, second->product_id))
|
|
return 0;
|
|
if (strcmp(first->sysfs_device_name, second->sysfs_device_name))
|
|
return 0;
|
|
if (strcmp(first->dev_name, second->dev_name))
|
|
return 0;
|
|
if (strcmp(first->gen_name, second->gen_name))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* same_dev_rcd - compares two device records to determine if they are the same
|
|
* @first: ipr_dev struct
|
|
* @second: ipr_dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if devices are not the same / 1 if devices are the same
|
|
**/
|
|
static int same_dev_rcd(struct ipr_dev *first, struct ipr_dev *second)
|
|
{
|
|
if (memcmp(&first->res_addr, &second->res_addr,
|
|
sizeof(first->res_addr)))
|
|
return 0;
|
|
if (memcmp(first->vendor_id, second->vendor_id, IPR_VENDOR_ID_LEN))
|
|
return 0;
|
|
if (memcmp(first->product_id, second->product_id, IPR_PROD_ID_LEN))
|
|
return 0;
|
|
if (memcmp(first->serial_number, second->serial_number, IPR_SERIAL_NUM_LEN))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* same_dev - compares two devices to determine if they are the same
|
|
* @first: ipr_dev struct
|
|
* @second: ipr_dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if devices are not the same / 1 if devices are the same
|
|
**/
|
|
static int same_dev(struct ipr_dev *first, struct ipr_dev *second)
|
|
{
|
|
if (strcmp(first->dev_name, second->dev_name))
|
|
return 0;
|
|
if (strcmp(first->gen_name, second->gen_name))
|
|
return 0;
|
|
if (!first->scsi_dev_data && !second->scsi_dev_data) {
|
|
if (!ipr_is_af_dasd_device(first) || !ipr_is_af_dasd_device(second))
|
|
return 0;
|
|
if (!same_dev_rcd(first, second))
|
|
return 0;
|
|
} else if (!same_scsi_dev(first->scsi_dev_data, second->scsi_dev_data))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* dev_init_allowed -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 1 if initialization is allowed / 0 if initialization is not allowed
|
|
**/
|
|
static int dev_init_allowed(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_query_res_state res_state;
|
|
|
|
if (!ipr_is_af_dasd_device(dev))
|
|
return 1;
|
|
if (!ipr_query_resource_state(dev, &res_state)) {
|
|
if (!res_state.read_write_prot && !res_state.prot_dev_failed)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* resolve_dev -
|
|
* @new: ipr dev struct
|
|
* @old: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void resolve_dev(struct ipr_dev *new, struct ipr_dev *old)
|
|
{
|
|
new->init_not_allowed = !dev_init_allowed(new);
|
|
if (!old->init_not_allowed || new->init_not_allowed)
|
|
new->should_init = 0;
|
|
if (!new->ioa->sis64 && new->ioa->is_secondary &&
|
|
!old->ioa->is_secondary && ipr_is_af_dasd_device(new) &&
|
|
new->scsi_dev_data)
|
|
new->rescan = 1;
|
|
if (!new->ioa->sis64 && !new->ioa->is_secondary &&
|
|
old->ioa->is_secondary && ipr_is_af_dasd_device(new) &&
|
|
!new->scsi_dev_data)
|
|
new->rescan = 1;
|
|
}
|
|
|
|
/**
|
|
* resolve_ioa -
|
|
* @ioa: ipr ioa struct
|
|
* @old_ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void resolve_ioa(struct ipr_ioa *ioa, struct ipr_ioa *old_ioa)
|
|
{
|
|
struct ipr_dev *dev, *old_dev;
|
|
|
|
ioa->should_init = 0;
|
|
|
|
for_each_dev(ioa, dev) {
|
|
for_each_dev(old_ioa, old_dev) {
|
|
if (!same_dev(dev, old_dev))
|
|
continue;
|
|
memcpy(&dev->attr, &old_dev->attr, sizeof(dev->attr));
|
|
dev->rescan = old_dev->rescan;
|
|
resolve_dev(dev, old_dev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* resolve_old_config -
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void resolve_old_config()
|
|
{
|
|
struct ipr_ioa *ioa, *old_ioa;
|
|
struct ipr_dev *dev;
|
|
|
|
for_each_ioa(ioa) {
|
|
ioa->should_init = 1;
|
|
for_each_dev(ioa, dev)
|
|
dev->should_init = 1;
|
|
}
|
|
|
|
for_each_ioa(ioa) {
|
|
__for_each_ioa(old_ioa, old_ioa_head) {
|
|
if (!same_ioa(ioa, old_ioa))
|
|
continue;
|
|
resolve_ioa(ioa, old_ioa);
|
|
break;
|
|
}
|
|
}
|
|
|
|
free_old_config();
|
|
}
|
|
|
|
struct ipr_pci_slot {
|
|
char slot_name[PATH_MAX];
|
|
char physical_name[PATH_MAX];
|
|
char pci_device[PATH_MAX];
|
|
};
|
|
|
|
static struct ipr_pci_slot *pci_slot;
|
|
static unsigned int num_pci_slots;
|
|
|
|
/**
|
|
* ipr_select_phy_location -
|
|
* @dirent: dirent struct
|
|
*
|
|
* Returns:
|
|
* 1 if d_name == "phy_location"/ 0 otherwise
|
|
**/
|
|
static int ipr_select_phy_location(const struct dirent *dirent)
|
|
{
|
|
if (strstr(dirent->d_name, "phy_location"))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int ipr_select_pci_address(const struct dirent *dirent)
|
|
{
|
|
if (strstr(dirent->d_name, "address"))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* read_attr_file -
|
|
* @path: path name of device/file to open
|
|
* @out: character array
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int read_attr_file(char *path, char *out, int size)
|
|
{
|
|
int fd, len;
|
|
|
|
if ((fd = open(path, O_RDONLY)) < 0) {
|
|
syslog_dbg("Failed to open %s\n", path);
|
|
return -EIO;
|
|
}
|
|
|
|
len = read(fd, out, size);
|
|
if (len < 0) {
|
|
syslog_dbg("Failed to read %s\n", path);
|
|
close(fd);
|
|
return -EIO;
|
|
}
|
|
|
|
if (out[strlen(out) - 1] == '\n')
|
|
out[strlen(out) - 1] = '\0';
|
|
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_add_slot_location -
|
|
* @path: path of device/file to open
|
|
* @name: name of device/file to open
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_add_slot_location(char *path, char *name)
|
|
{
|
|
struct ipr_pci_slot *slot;
|
|
char fpath[PATH_MAX];
|
|
int rc;
|
|
|
|
sprintf(fpath, "%s%s/phy_location", path, name);
|
|
|
|
pci_slot = realloc(pci_slot, sizeof(*pci_slot) * (num_pci_slots + 1));
|
|
slot = &pci_slot[num_pci_slots];
|
|
memset(slot, 0, sizeof(*slot));
|
|
|
|
strcpy(slot->slot_name, name);
|
|
rc = read_attr_file(fpath, slot->physical_name,
|
|
sizeof(slot->physical_name));
|
|
|
|
if (rc) {
|
|
pci_slot = realloc(pci_slot, sizeof(*pci_slot) * num_pci_slots);
|
|
return rc;
|
|
}
|
|
|
|
num_pci_slots++;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_add_slot_address -
|
|
* @path: path of device/file to open
|
|
* @name: name of device/file to open
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_add_slot_address(char *path, char *name)
|
|
{
|
|
struct ipr_pci_slot *slot;
|
|
char fpath[PATH_MAX];
|
|
int rc;
|
|
|
|
sprintf(fpath, "%s%s/address", path, name);
|
|
|
|
pci_slot = realloc(pci_slot, sizeof(*pci_slot) * (num_pci_slots + 1));
|
|
slot = &pci_slot[num_pci_slots];
|
|
memset(slot, 0, sizeof(*slot));
|
|
|
|
strcpy(slot->slot_name, name);
|
|
strcpy(slot->physical_name, name);
|
|
rc = read_attr_file(fpath, slot->pci_device,
|
|
sizeof(slot->pci_device));
|
|
|
|
if (rc) {
|
|
pci_slot = realloc(pci_slot, sizeof(*pci_slot) * num_pci_slots);
|
|
return rc;
|
|
}
|
|
|
|
strcat(slot->pci_device, ".0");
|
|
|
|
num_pci_slots++;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_pci_slots -
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_get_pci_slots()
|
|
{
|
|
char rootslot[PATH_MAX], slot[PATH_MAX];
|
|
char slotpath[PATH_MAX], attr[PATH_MAX];
|
|
char devspec[PATH_MAX], locpath[PATH_MAX];
|
|
char loc_code[1024], *last_hyphen, *prev_hyphen;
|
|
int num_slots, i, j, rc, num_attrs;
|
|
int loc_code_not_found = 0;
|
|
struct dirent **slotdir, **dirent;
|
|
struct stat statbuf;
|
|
struct ipr_ioa *ioa;
|
|
|
|
for_each_ioa(ioa)
|
|
ioa->physical_location[0] = '\0';
|
|
|
|
for_each_ioa(ioa) {
|
|
memset(devspec, 0, sizeof(devspec));
|
|
sprintf(attr, "/sys/bus/pci/devices/%s/devspec",
|
|
ioa->pci_address);
|
|
rc = read_attr_file(attr, devspec, PATH_MAX);
|
|
|
|
if (rc)
|
|
continue;
|
|
|
|
memset(loc_code, 0, sizeof(loc_code));
|
|
sprintf(locpath, "/proc/device-tree%s/ibm,loc-code",
|
|
devspec);
|
|
rc = read_attr_file(locpath, loc_code,
|
|
sizeof(loc_code));
|
|
|
|
if (rc) {
|
|
loc_code_not_found = 1;
|
|
continue;
|
|
}
|
|
|
|
last_hyphen = strrchr(loc_code, '-');
|
|
if (last_hyphen && last_hyphen[1] == 'T') {
|
|
*last_hyphen = '\0';
|
|
prev_hyphen = strrchr(loc_code, '-');
|
|
if (prev_hyphen && prev_hyphen[1] != 'C')
|
|
*last_hyphen = '-';
|
|
}
|
|
|
|
strcpy(ioa->physical_location, loc_code);
|
|
}
|
|
|
|
if (loc_code_not_found == 0)
|
|
return;
|
|
|
|
sprintf(rootslot, "/sys/bus/pci/slots/");
|
|
|
|
num_slots = scandir(rootslot, &slotdir, NULL, alphasort);
|
|
if (num_slots <= 0) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < num_slots; i++) {
|
|
sprintf(slot, "%s%s", rootslot, slotdir[i]->d_name);
|
|
rc = scandir(slot, &dirent, ipr_select_phy_location, alphasort);
|
|
if (rc > 0) {
|
|
ipr_add_slot_location(rootslot, slotdir[i]->d_name);
|
|
while (rc--)
|
|
free(dirent[rc]);
|
|
free(dirent);
|
|
} else {
|
|
rc = scandir(slot, &dirent, ipr_select_pci_address,
|
|
alphasort);
|
|
|
|
if (rc <= 0)
|
|
continue;
|
|
|
|
ipr_add_slot_address(rootslot, slotdir[i]->d_name);
|
|
while (rc--)
|
|
free(dirent[rc]);
|
|
free(dirent);
|
|
}
|
|
}
|
|
|
|
while (num_slots--)
|
|
free(slotdir[num_slots]);
|
|
free(slotdir);
|
|
|
|
for (i = 0; i < num_pci_slots; i++) {
|
|
|
|
if (!pci_slot[i].pci_device)
|
|
continue;
|
|
|
|
sprintf(slotpath, "/sys/bus/pci/devices/%s/",
|
|
pci_slot[i].slot_name);
|
|
|
|
num_attrs = scandir(slotpath, &slotdir, NULL, alphasort);
|
|
if (num_attrs <= 0)
|
|
continue;
|
|
|
|
for (j = 0; j < num_attrs; j++) {
|
|
sprintf(attr, "%s/%s", slotpath, slotdir[j]->d_name);
|
|
rc = stat(attr, &statbuf);
|
|
if (rc || !S_ISDIR(statbuf.st_mode))
|
|
continue;
|
|
if (!strcmp(slotdir[j]->d_name, "."))
|
|
continue;
|
|
if (!strcmp(slotdir[j]->d_name, ".."))
|
|
continue;
|
|
strcpy(pci_slot[i].pci_device, slotdir[j]->d_name);
|
|
break;
|
|
}
|
|
|
|
while (num_attrs--)
|
|
free(slotdir[num_attrs]);
|
|
free(slotdir);
|
|
}
|
|
|
|
for_each_ioa(ioa) {
|
|
if (strlen(ioa->physical_location) == 0) {
|
|
for (i = 0; i < num_pci_slots; i++) {
|
|
if (strcmp(pci_slot[i].pci_device, ioa->pci_address) &&
|
|
strcmp(pci_slot[i].slot_name, ioa->pci_address))
|
|
continue;
|
|
strcpy(ioa->physical_location,
|
|
pci_slot[i].physical_name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(pci_slot);
|
|
pci_slot = NULL;
|
|
num_pci_slots = 0;
|
|
}
|
|
|
|
/**
|
|
* tool_init -
|
|
* @save_state: integer flag - whether or not to save the old config
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static int __tool_init(int save_state)
|
|
{
|
|
int temp, fw_type;
|
|
struct ipr_ioa *ipr_ioa;
|
|
DIR *dirfd, *host_dirfd;
|
|
struct dirent *dent, *host_dent;
|
|
ssize_t len;
|
|
|
|
save_old_config();
|
|
|
|
dirfd = opendir("/sys/bus/pci/drivers/ipr");
|
|
if (!dirfd) {
|
|
syslog_dbg("Failed to open ipr driver bus. %m\n");
|
|
dirfd = opendir("/sys/bus/pci/drivers/ipr");
|
|
if (!dirfd) {
|
|
syslog_dbg("Failed to open ipr driver bus on second attempt. %m\n");
|
|
return -ENXIO;
|
|
}
|
|
}
|
|
while ((dent = readdir(dirfd)) != NULL) {
|
|
int channel, bus, device, function;
|
|
char devpath[PATH_MAX];
|
|
char buff[256];
|
|
|
|
if (sscanf(dent->d_name, "%x:%x:%x.%x",
|
|
&channel, &bus, &device, &function) != 4)
|
|
continue;
|
|
|
|
ipr_ioa = (struct ipr_ioa*)malloc(sizeof(struct ipr_ioa));
|
|
memset(ipr_ioa,0,sizeof(struct ipr_ioa));
|
|
|
|
/* PCI address */
|
|
strcpy(ipr_ioa->pci_address, dent->d_name);
|
|
ipr_ioa->host_num = -1;
|
|
sprintf(devpath, "/sys/bus/pci/drivers/ipr/%s",
|
|
dent->d_name);
|
|
host_dirfd = opendir(devpath);
|
|
if (!host_dirfd) {
|
|
syslog_dbg("Failed to open scsi_host class.\n");
|
|
return -EAGAIN;
|
|
}
|
|
while ((host_dent = readdir(host_dirfd)) != NULL) {
|
|
char scsipath[PATH_MAX];
|
|
char fw_str[256];
|
|
|
|
if (strncmp(host_dent->d_name, "host", 4))
|
|
continue;
|
|
|
|
if (sscanf(host_dent->d_name, "host%d",
|
|
&ipr_ioa->host_num) != 1)
|
|
continue;
|
|
|
|
strcpy(ipr_ioa->host_name, host_dent->d_name);
|
|
get_pci_attrs(ipr_ioa, devpath);
|
|
|
|
sprintf(scsipath, "%s/%s/scsi_host/%s", devpath,
|
|
ipr_ioa->host_name, ipr_ioa->host_name);
|
|
len = sysfs_read_attr(scsipath, "fw_type", fw_str, 256);
|
|
if (len < 0)
|
|
fw_type = 0;
|
|
else
|
|
sscanf(fw_str, "%d", &fw_type);
|
|
if (ipr_debug)
|
|
syslog(LOG_INFO, "tool_init: fw_type attr = %d.\n", fw_type);
|
|
break;
|
|
}
|
|
closedir(host_dirfd);
|
|
if (ipr_ioa->host_num < 0) {
|
|
syslog_dbg("No SCSI Host found on IPR device %s\n",
|
|
ipr_ioa->pci_address);
|
|
free(ipr_ioa);
|
|
continue;
|
|
}
|
|
if (fw_type == IPR_SIS64) {
|
|
sprintf(devpath, "/sys/bus/scsi/devices/%d:%d:0:0",
|
|
ipr_ioa->host_num, IPR_IOAFP_VIRTUAL_BUS);
|
|
ipr_ioa->sis64 = 1;
|
|
} else
|
|
sprintf(devpath, "/sys/bus/scsi/devices/%d:255:255:255",
|
|
ipr_ioa->host_num);
|
|
len = sysfs_read_attr(devpath, "model", buff, 16);
|
|
if (len < 0 || (sscanf(buff, "%4X", &temp) != 1)) {
|
|
syslog_dbg("Cannot read SCSI device model.\n");
|
|
return -EAGAIN;
|
|
}
|
|
ipr_ioa->ccin = temp;
|
|
setup_ioa_parms(ipr_ioa);
|
|
|
|
ipr_ioa->next = NULL;
|
|
ipr_ioa->num_raid_cmds = 0;
|
|
|
|
if (ipr_ioa_tail) {
|
|
ipr_ioa_tail->next = ipr_ioa;
|
|
ipr_ioa_tail = ipr_ioa;
|
|
} else
|
|
ipr_ioa_tail = ipr_ioa_head = ipr_ioa;
|
|
|
|
num_ioas++;
|
|
}
|
|
closedir(dirfd);
|
|
|
|
if (!save_state)
|
|
free_old_config();
|
|
|
|
ipr_get_pci_slots();
|
|
return 0;
|
|
}
|
|
|
|
int tool_init(int save_state)
|
|
{
|
|
int i, rc_err = -EAGAIN;
|
|
|
|
for (i = 0; i < 4 && rc_err == -EAGAIN; i++) {
|
|
rc_err = __tool_init(save_state);
|
|
if (rc_err == -EAGAIN)
|
|
sleep(2);
|
|
}
|
|
if (rc_err) {
|
|
syslog_dbg("Failed to initialize adapters. Timeout reached");
|
|
return rc_err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_reset_adapter -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_reset_adapter(struct ipr_ioa *ioa)
|
|
{
|
|
char devpath[PATH_MAX];
|
|
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
sysfs_write_attr(devpath, "reset_host", "1", 1);
|
|
}
|
|
|
|
/**
|
|
* ipr_read_host_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @name: attribute name
|
|
* @value: value to be set
|
|
* @value_len: length of value string
|
|
*
|
|
* Returns:
|
|
* >= 0 if success / < 0 on failure
|
|
**/
|
|
int ipr_read_host_attr(struct ipr_ioa *ioa, char *name,
|
|
void *value, size_t value_len)
|
|
{
|
|
ssize_t len;
|
|
char path[PATH_MAX];
|
|
|
|
sprintf(path, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_read_attr(path, name, value, value_len);
|
|
if (len < 0) {
|
|
ioa_dbg(ioa, "Failed to read %s attribute. %m\n", name);
|
|
return -EIO;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* ipr_write_host_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @name: attribute to be written
|
|
* @value: new value
|
|
* @value_len: length of value string
|
|
*
|
|
* Returns:
|
|
* >= 0 if success / < 0 on failure
|
|
**/
|
|
int ipr_write_host_attr(struct ipr_ioa *ioa, char *name,
|
|
void *value, size_t value_len)
|
|
{
|
|
ssize_t len;
|
|
char path[PATH_MAX];
|
|
|
|
sprintf(path, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_write_attr(path, name, value, value_len);
|
|
if (len < 0) {
|
|
ioa_dbg(ioa, "Failed to write %s attribute. %m\n", name);
|
|
return -EIO;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* ipr_cmds_per_lun -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* number of commands per lun if success / 6 on failure
|
|
**/
|
|
int ipr_cmds_per_lun(struct ipr_ioa *ioa)
|
|
{
|
|
char value[100];
|
|
int rc;
|
|
|
|
rc = ipr_read_host_attr(ioa, "cmd_per_lun", value, 100);
|
|
|
|
if (rc)
|
|
return 6;
|
|
|
|
return strtoul(value, NULL, 10);
|
|
}
|
|
|
|
/**
|
|
* ipr_scan -
|
|
* @ioa: ipr ioa struct
|
|
* @bus: bus number
|
|
* @id: id number
|
|
* @lun: lun number (RAID) level for new array
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_scan(struct ipr_ioa *ioa, int bus, int id, int lun)
|
|
{
|
|
char buf[100];
|
|
char devpath[PATH_MAX];
|
|
|
|
if (lun < 0) {
|
|
if (id < 0)
|
|
sprintf(buf, "%d - -", bus);
|
|
else
|
|
sprintf(buf, "%d %d -", bus, id);
|
|
} else {
|
|
if (id < 0)
|
|
sprintf(buf, "%d - %d", bus, lun);
|
|
else
|
|
sprintf(buf, "%d %d %d", bus, id, lun);
|
|
}
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
sysfs_write_attr(devpath, "scan", buf, 100);
|
|
}
|
|
|
|
/**
|
|
* __ipr_query_array_config - perform a query array configuration ioctl
|
|
* @ioa: ipr ioa struct
|
|
* @fd: file descriptor
|
|
* @allow_rebuld_refresh: allow rebuild refresh flag
|
|
* @set_array_id: set array id flag
|
|
* @array_id: array id number
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int __ipr_query_array_config(struct ipr_ioa *ioa, int fd,
|
|
bool allow_rebuild_refresh, bool set_array_id,
|
|
bool vset_migrate_query, int array_id,
|
|
struct ipr_array_query_data *buff)
|
|
{
|
|
int length = IPR_QAC_BUFFER_SIZE;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
if (!ioa->sis64)
|
|
length = IPR_SIS32_QAC_BUFFER_SIZE;
|
|
|
|
ioa->nr_ioa_microcode = 0;
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_QUERY_ARRAY_CONFIG;
|
|
|
|
if (vset_migrate_query) {
|
|
cdb[1] = 0x08;
|
|
cdb[2] = array_id;
|
|
if (ioa->sis64)
|
|
cdb[3] = 0x80;
|
|
} else if (set_array_id) {
|
|
cdb[1] = 0x01;
|
|
cdb[2] = array_id;
|
|
if (ioa->sis64)
|
|
cdb[3] = 0x80;
|
|
} else if (allow_rebuild_refresh)
|
|
cdb[1] = 0;
|
|
else
|
|
cdb[1] = 0x80; /* Prohibit Rebuild Candidate Refresh */
|
|
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, buff->u.buf,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0) {
|
|
if (sense_data.sense_key != ILLEGAL_REQUEST || ipr_debug)
|
|
ioa_cmd_err(ioa, &sense_data, "Query Array Config", rc);
|
|
else if (sense_data.sense_key == NOT_READY &&
|
|
sense_data.add_sense_code == 0x40 &&
|
|
sense_data.add_sense_code_qual == 0x85)
|
|
ioa->nr_ioa_microcode = 1;
|
|
}
|
|
|
|
if (ioa->sis64) {
|
|
buff->resp_len = ntohl(buff->u.buf64.resp_len);
|
|
buff->num_records = ntohl(buff->u.buf64.num_records);
|
|
buff->data = buff->u.buf64.data;
|
|
buff->hdr_len = 8;
|
|
} else {
|
|
buff->resp_len = ntohs(buff->u.buf32.resp_len);
|
|
buff->num_records = ntohs(buff->u.buf32.num_records);
|
|
buff->data = buff->u.buf32.data;
|
|
buff->hdr_len = 4;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_array_config - entry point to query array configuration ioctl call
|
|
* @ioa: ipr ioa struct
|
|
* @allow_rebuld_refresh: allow rebuild refresh flag
|
|
* @set_array_id: set array id flag
|
|
* @array_id: array id number
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_array_config(struct ipr_ioa *ioa,
|
|
bool allow_rebuild_refresh, bool set_array_id,
|
|
bool vset_migrate_query, int array_id,
|
|
struct ipr_array_query_data *buff)
|
|
{
|
|
int fd, rc;
|
|
|
|
ioa->nr_ioa_microcode = 0;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0) {
|
|
scsi_err((&ioa->ioa), "Adapter sg device does not exist\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
rc = flock(fd, LOCK_EX);
|
|
if (rc) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not lock %s. %m\n", ioa->ioa.gen_name);
|
|
close(fd);
|
|
return errno;
|
|
}
|
|
|
|
rc = __ipr_query_array_config(ioa, fd, allow_rebuild_refresh,
|
|
set_array_id, vset_migrate_query,
|
|
array_id, buff);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_multi_ioa_status -
|
|
* @ioa: ipr ioa struct
|
|
* @buff: data buffer
|
|
* @len: length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_multi_ioa_status(struct ipr_ioa *ioa, void *buff, u32 len)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_MAINTENANCE_IN;
|
|
cdb[1] = IPR_QUERY_MULTI_ADAPTER_STATUS;
|
|
cdb[6] = len >> 24;
|
|
cdb[7] = (len >> 16) & 0xff;
|
|
cdb[8] = (len >> 8) & 0xff;
|
|
cdb[9] = len & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff, len, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0) {
|
|
if (sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(ioa, &sense_data, "Query Multi Adapter Status", rc);
|
|
else if (sense_data.sense_key == NOT_READY &&
|
|
sense_data.add_sense_code == 0x40 &&
|
|
sense_data.add_sense_code_qual == 0x85)
|
|
ioa->nr_ioa_microcode = 1;
|
|
}
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_start_array - Start array protection for an array
|
|
* @ioa: ipr ioa struct
|
|
* @cmd: char pointer
|
|
* @stripe_size: stripe size for new array
|
|
* @prot_level: protection (RAID) level for new array
|
|
* @hot_spare: flag to indicate hot spare usage
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_start_array(struct ipr_ioa *ioa, char *cmd,
|
|
int stripe_size, int prot_level, int hot_spare)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = ioa->qac_data->resp_len;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
ipr_update_qac_with_zeroed_devs(ioa);
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_START_ARRAY_PROTECTION;
|
|
if (hot_spare)
|
|
cdb[1] = 0x01;
|
|
|
|
cdb[4] = (u8)(stripe_size >> 8);
|
|
cdb[5] = (u8)(stripe_size & 0xff);
|
|
cdb[6] = prot_level;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, ioa->qac_data->u.buf,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, "Start Array Protection", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_start_array_protection - Start array protection for an array
|
|
* @ioa: ipr ioa struct
|
|
* @stripe_size: stripe size for new array
|
|
* @prot_level: protection (RAID) level for new array
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_start_array_protection(struct ipr_ioa *ioa,
|
|
int stripe_size, int prot_level)
|
|
{
|
|
return ipr_start_array(ioa, "Start Array Protection",
|
|
stripe_size, prot_level, 0);
|
|
}
|
|
|
|
/**
|
|
* ipr_migrate_array_protection - Migrate array protection for an array
|
|
* @ioa: ipr ioa struct
|
|
* @qac_data: struct ipr_array_query_data
|
|
* @fd: file descriptor
|
|
* @stripe_size: new or existing stripe size for array
|
|
* @prot_level: new protection (RAID) level for array
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_migrate_array_protection(struct ipr_ioa *ioa,
|
|
struct ipr_array_query_data *qac_data,
|
|
int fd, int stripe_size, int prot_level)
|
|
{
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
char *cmd = "migrate array protection";
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = qac_data->resp_len;
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_MIGRATE_ARRAY_PROTECTION;
|
|
|
|
cdb[4] = (u8)(stripe_size >> 8);
|
|
cdb[5] = (u8)(stripe_size & 0xff);
|
|
cdb[6] = prot_level;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, qac_data->u.buf,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, cmd, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_add_hot_spare - Adds a hot spare to an array
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_add_hot_spare(struct ipr_ioa *ioa)
|
|
{
|
|
return ipr_start_array(ioa, "Create hot spare", 0, 0, 1);
|
|
}
|
|
|
|
/**
|
|
* ipr_stop_array - Stop array protection for an array
|
|
* @ioa: ipr ioa struct
|
|
* @cmd: command string
|
|
* @hot_spare: flag to indicate hot spare usage
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_stop_array(struct ipr_ioa *ioa, char *cmd, int hot_spare)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = ioa->qac_data->resp_len;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_STOP_ARRAY_PROTECTION;
|
|
if (hot_spare)
|
|
cdb[1] = 0x01;
|
|
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, ioa->qac_data->u.buf,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, cmd, rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_stop_array_protection - Stop array protection for an array
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_stop_array_protection(struct ipr_ioa *ioa)
|
|
{
|
|
return ipr_stop_array(ioa, "Stop Array Protection", 0);
|
|
}
|
|
|
|
/**
|
|
* ipr_remove_hot_spare - remove a hot spare from an array
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_remove_hot_spare(struct ipr_ioa *ioa)
|
|
{
|
|
return ipr_stop_array(ioa, "Delete hot spare", 1);
|
|
}
|
|
|
|
/**
|
|
* ipr_rebuild_device_data -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_rebuild_device_data(struct ipr_ioa *ioa)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = ioa->qac_data->resp_len;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_REBUILD_DEVICE_DATA;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, ioa->qac_data->u.buf, length,
|
|
SG_DXFER_TO_DEV, &sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, "Rebuild Device Data", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_resync_array -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_resync_array(struct ipr_ioa *ioa)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = ioa->qac_data->resp_len;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_RESYNC_ARRAY_PROTECTION;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, ioa->qac_data->u.buf, length,
|
|
SG_DXFER_TO_DEV, &sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, "Resync Array", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_add_array_device -
|
|
* @ioa: ipr ioa struct
|
|
* @fd: device/file descriptor
|
|
* @qac_data: query array configuration info
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_add_array_device(struct ipr_ioa *ioa, int fd,
|
|
struct ipr_array_query_data *qac_data)
|
|
{
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = qac_data->resp_len;
|
|
|
|
ipr_update_qac_with_zeroed_devs(ioa);
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_ADD_ARRAY_DEVICE;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, qac_data->u.buf,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(ioa, &sense_data, "Add Array Device", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_command_status -
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_command_status(struct ipr_dev *dev, void *buff)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int length = sizeof(struct ipr_cmd_status);
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_QUERY_COMMAND_STATUS;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if ((rc != 0) && (errno != EINVAL || ipr_debug))
|
|
scsi_cmd_err(dev, &sense_data, "Query Command Status", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_resource_state
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_resource_state(struct ipr_dev *dev, void *buff)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int length = sizeof(struct ipr_query_res_state);
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_QUERY_RESOURCE_STATE;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Query Resource State", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_mod_sense - issue a mode sense command
|
|
* @dev: ipr dev struct
|
|
* @page: mode page
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_mode_sense(struct ipr_dev *dev, u8 page, void *buff)
|
|
{
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
memset(&sense_data, 0, sizeof(sense_data));
|
|
rc = mode_sense(dev, page, buff, &sense_data);
|
|
|
|
/* post log if error unless error is device format in progress */
|
|
if ((rc != 0) && (rc != ENOENT) &&
|
|
(((sense_data.error_code & 0x7F) != 0x70) ||
|
|
((sense_data.sense_key & 0x0F) != 0x02) || /* NOT READY */
|
|
(sense_data.add_sense_code != 0x04) || /* LOGICAL UNIT NOT READY */
|
|
(sense_data.add_sense_code_qual != 0x04))) /* FORMAT IN PROGRESS */
|
|
scsi_cmd_err(dev, &sense_data, "Mode Sense", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_log_sense - issue a log sense command
|
|
* @dev: ipr dev struct
|
|
* @page: mode page
|
|
* @buff: data buffer
|
|
* @length: buffer length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_log_sense(struct ipr_dev *dev, u8 page, void *buff, u16 length)
|
|
{
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
char *name = dev->gen_name;
|
|
|
|
memset(&sense_data, 0, sizeof(sense_data));
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
scsi_dbg(dev, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = LOG_SENSE;
|
|
cdb[2] = 0x40 | page;
|
|
cdb[7] = (length >> 8) & 0xff;
|
|
cdb[8] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
scsi_cmd_dbg(dev, &sense_data, cdb, rc);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_is_log_page_supported - is the give page supported?
|
|
* @dev: ipr dev struct
|
|
* @page: mode page
|
|
*
|
|
* Returns:
|
|
* 1 if supported / 0 if not supported
|
|
**/
|
|
int ipr_is_log_page_supported(struct ipr_dev *dev, u8 page)
|
|
{
|
|
struct ipr_supp_log_pages pages;
|
|
int rc, i;
|
|
|
|
memset(&pages, 0, sizeof(pages));
|
|
rc = ipr_log_sense(dev, 0, &pages, sizeof(pages));
|
|
|
|
if (rc)
|
|
return -EIO;
|
|
|
|
for (i = 0; i < ntohs(pages.length); i++) {
|
|
if (pages.page[i] == page)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_blk_size - return the block size for the given device
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* block size if success / non-zero on failure
|
|
**/
|
|
int ipr_get_blk_size(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_block_desc *block_desc;
|
|
int rc;
|
|
|
|
rc = ipr_mode_sense(dev, 0, &mode_pages);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (mode_pages.hdr.block_desc_len > 0) {
|
|
block_desc = (struct ipr_block_desc *)mode_pages.data;
|
|
return ((block_desc->block_length[1] << 8) | block_desc->block_length[2]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_mode_select - issue a mode select command
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
* @length: length of buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_mode_select(struct ipr_dev *dev, void *buff, int length)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = MODE_SELECT;
|
|
cdb[1] = 0x10; /* PF = 1, SP = 0 */
|
|
cdb[4] = length;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_INTERNAL_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Mode Select", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* page0x0a_setup - Start array protection for an array
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 1 if success / 0 on failure
|
|
**/
|
|
int page0x0a_setup(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_control_mode_page *page;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x0A, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_control_mode_page *)(((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
if (page->queue_algorithm_modifier != 1)
|
|
return 0;
|
|
if (page->tst == 1 && page->qerr != 3)
|
|
return 0;
|
|
if (page->qerr != 1)
|
|
return 0;
|
|
if (page->dque != 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* ipr_setup_af_qdepth -
|
|
* @dev: ipr dev struct
|
|
* @qdepth: queue depth
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_setup_af_qdepth(struct ipr_dev *dev, int qdepth)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_ioa_mode_page *page;
|
|
int len;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x20, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_ioa_mode_page *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
page->max_tcq_depth = qdepth;
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (ipr_mode_select(dev, &mode_pages, len))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/**
|
|
* ipr_reset_device - issue a SG_SCSI_RESET to a device
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_reset_device(struct ipr_dev *dev)
|
|
{
|
|
int fd, rc, arg;
|
|
|
|
if (strlen(dev->gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(dev->gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", dev->gen_name);
|
|
return errno;
|
|
}
|
|
|
|
arg = SG_SCSI_RESET_DEVICE;
|
|
rc = ioctl(fd, SG_SCSI_RESET, &arg);
|
|
|
|
if (rc != 0)
|
|
scsi_err(dev, "Reset Device failed. %m\n");
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_re_read_partition -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_re_read_partition(struct ipr_dev *dev)
|
|
{
|
|
int fd, rc;
|
|
|
|
if (strlen(dev->dev_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(dev->dev_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", dev->gen_name);
|
|
return errno;
|
|
}
|
|
|
|
rc = ioctl(fd, BLKRRPART, NULL);
|
|
|
|
if (rc != 0)
|
|
scsi_err(dev, "Re-read partition table failed. %m\n");
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_read_capacity - issue a read capacity command to a device
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_read_capacity(struct ipr_dev *dev, void *buff)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
int length = sizeof(struct ipr_read_cap);
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = READ_CAPACITY;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Read Capacity", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_read_capacity_16 - issue a read capacity 16 to a device
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_read_capacity_16(struct ipr_dev *dev, void *buff)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int length = sizeof(struct ipr_read_cap16);
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_SERVICE_ACTION_IN;
|
|
cdb[1] = IPR_READ_CAPACITY_16;
|
|
|
|
cdb[10] = length >> 24;
|
|
cdb[11] = length>> 16 & 0xff;
|
|
cdb[12] = length >> 8 & 0xff;
|
|
cdb[13] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Read Capacity", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_reclaim_cache_store -
|
|
* @ioa: ipr ioa struct
|
|
* @action: action value
|
|
* @buff: data buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_reclaim_cache_store(struct ipr_ioa *ioa, int action, void *buff)
|
|
{
|
|
char *file = ioa->ioa.gen_name;
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int timeout, rc;
|
|
int length = sizeof(struct ipr_reclaim_query_data);
|
|
|
|
if (strlen(file) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(file, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n",file);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_RECLAIM_CACHE_STORE;
|
|
cdb[1] = (u8)action;
|
|
cdb[7] = (length & 0xff00) >> 8;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if ((action & IPR_RECLAIM_ACTION) == IPR_RECLAIM_PERFORM)
|
|
timeout = IPR_CACHE_RECLAIM_TIMEOUT;
|
|
else
|
|
timeout = IPR_INTERNAL_DEV_TIMEOUT;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, timeout);
|
|
|
|
if (rc != 0) {
|
|
if (sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(ioa, &sense_data, "Reclaim Cache", rc);
|
|
}
|
|
else if ((action & IPR_RECLAIM_ACTION) == IPR_RECLAIM_PERFORM)
|
|
ipr_reset_adapter(ioa);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_start_stop -
|
|
* @dev: ipr dev struct
|
|
* @start: start or stop flag
|
|
* @cmd: command buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_start_stop(struct ipr_dev *dev, u8 start, char *cmd)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = START_STOP;
|
|
cdb[4] = start;
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
0, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, cmd, rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_start_stop_start -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_start_stop_start(struct ipr_dev *dev)
|
|
{
|
|
return ipr_start_stop(dev, IPR_START_STOP_START, "Start Unit");
|
|
}
|
|
|
|
/**
|
|
* ipr_check_allow_restart - check the allow_restart flag for a device
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Return value:
|
|
* none
|
|
**/
|
|
int ipr_check_allow_restart(struct ipr_dev *dev)
|
|
{
|
|
char path[PATH_MAX];
|
|
char value[8];
|
|
ssize_t len;
|
|
|
|
sprintf(path,"/sys/class/scsi_disk/%s",
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
len = sysfs_read_attr(path, "allow_restart", value, 8);
|
|
if (len < 1) {
|
|
syslog_dbg("Failed to open allow_restart parameter.\n");
|
|
return -1;
|
|
}
|
|
|
|
return atoi(value);
|
|
}
|
|
|
|
/**
|
|
* ipr_allow_restart - set or clear the allow_restart flag for a device
|
|
* @dev: ipr dev struct
|
|
* @allow: value to set
|
|
*
|
|
* Return value:
|
|
* none
|
|
**/
|
|
void ipr_allow_restart(struct ipr_dev *dev, int allow)
|
|
{
|
|
char path[PATH_MAX];
|
|
char value_str[8];
|
|
ssize_t len;
|
|
|
|
sprintf(path,"/sys/class/scsi_disk/%s",
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
len = sysfs_read_attr(path, "allow_restart", value_str, 8);
|
|
if (len < 1) {
|
|
syslog_dbg("Failed to open allow_restart parameter.\n");
|
|
return;
|
|
}
|
|
|
|
if (atoi(value_str) == allow) {
|
|
return;
|
|
}
|
|
|
|
snprintf(value_str, 8, "%d", allow);
|
|
|
|
if (sysfs_write_attr(path, "allow_restart", value_str, 1) < 0)
|
|
syslog_dbg("Failed to write allow_restart parameter.\n");
|
|
}
|
|
|
|
/**
|
|
* ipr_start_stop_stop -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_start_stop_stop(struct ipr_dev *dev)
|
|
{
|
|
return ipr_start_stop(dev, IPR_START_STOP_STOP, "Stop Unit");
|
|
}
|
|
|
|
/**
|
|
* ipr_set_dev_cache_policy
|
|
*
|
|
* Set the default cache policy for a device.
|
|
*
|
|
* @ipr_dev: Device to change policy.
|
|
* @policy: New policy
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_set_dev_wcache_policy (const struct ipr_dev *dev, int policy)
|
|
{
|
|
char path[PATH_MAX];
|
|
char *cache_type;
|
|
char current_type[100];
|
|
|
|
sprintf(path, "/sys/class/scsi_disk/%s",
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
|
|
switch (policy) {
|
|
case IPR_DEV_CACHE_WRITE_THROUGH:
|
|
cache_type = "write through\n";
|
|
break;
|
|
case IPR_DEV_CACHE_WRITE_BACK:
|
|
cache_type = "write back\n";
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sysfs_write_attr(path, "cache_type", cache_type,
|
|
strlen(cache_type)) < 0) {
|
|
syslog_dbg("Failed to write cache_type parameter of %s",
|
|
dev->dev_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sysfs_read_attr(path, "cache_type", current_type, 100) < 0) {
|
|
syslog_dbg("Failed to read cache_type parameter of %s",
|
|
dev->dev_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strncmp (current_type, cache_type, 100) != 0) {
|
|
syslog_dbg("Failed to set cache_type parameter of %s",
|
|
dev->dev_name);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_dev_wcache_policy
|
|
*
|
|
* Check the default cache policy of a device.
|
|
*
|
|
* @ipr_dev: Device to change policy.
|
|
* @policy: Returned value. New policy.
|
|
*
|
|
* Returns:
|
|
* policy = [0, 1] / < 0 if failure.
|
|
**/
|
|
static int ipr_dev_wcache_policy (const struct ipr_dev *dev)
|
|
{
|
|
char path[PATH_MAX];
|
|
char cache_type[100];
|
|
|
|
sprintf(path, "/sys/class/scsi_disk/%s",
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
|
|
memset(cache_type, '\0', ARRAY_SIZE(cache_type));
|
|
|
|
if (sysfs_read_attr(path, "cache_type", cache_type, 100) < 0) {
|
|
syslog_dbg("Failed to read cache_type parameter of %s",
|
|
dev->dev_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strncmp (cache_type, "write through\n", 100) == 0)
|
|
return IPR_DEV_CACHE_WRITE_THROUGH;
|
|
else if (strncmp (cache_type, "write back\n", 100) == 0)
|
|
return IPR_DEV_CACHE_WRITE_BACK;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* __ipr_test_unit_ready - issue a test unit ready command
|
|
* @dev: ipr dev struct
|
|
* @sense_data: sense data struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int __ipr_test_unit_ready(struct ipr_dev *dev,
|
|
struct sense_data_t *sense_data)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int length = 0, allow_restart = 0;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = TEST_UNIT_READY;
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
length, SG_DXFER_FROM_DEV,
|
|
sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
close(fd);
|
|
|
|
if (rc && sense_data->sense_key == NOT_READY &&
|
|
sense_data->add_sense_code == 0x4 &&
|
|
sense_data->add_sense_code_qual == 0x2) {
|
|
allow_restart = ipr_check_allow_restart(dev);
|
|
if (allow_restart)
|
|
ipr_start_stop(dev, IPR_START_STOP_START, "Start Unit");
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_test_unit_ready - issue a test unit ready command
|
|
* @dev: ipr dev struct
|
|
* @sense_data: sense data struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_test_unit_ready(struct ipr_dev *dev,
|
|
struct sense_data_t *sense_data)
|
|
{
|
|
int rc = __ipr_test_unit_ready(dev, sense_data);
|
|
|
|
if (rc != 0 && sense_data->sense_key != NOT_READY &&
|
|
sense_data->sense_key != UNIT_ATTENTION &&
|
|
(!strcmp(tool_name, "iprconfig") || ipr_debug))
|
|
scsi_cmd_err(dev, sense_data, "Test Unit Ready", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct ipr_dev *find_multipath_jbod(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *multipath_dev;
|
|
|
|
for_each_sas_ioa(ioa) {
|
|
if (ioa == dev->ioa)
|
|
continue;
|
|
|
|
for_each_dev(ioa, multipath_dev) {
|
|
if (dev->scsi_dev_data->device_id == multipath_dev->scsi_dev_data->device_id)
|
|
return multipath_dev;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ipr_format_unit -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_format_unit(struct ipr_dev *dev)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
u8 *defect_list_hdr;
|
|
int length = IPR_DEFECT_LIST_HDR_LEN;
|
|
char *name = dev->gen_name;
|
|
char cmnd[1000];
|
|
struct ipr_dev *multipath_dev;
|
|
|
|
if (strlen(dev->dev_name) && dev->scsi_dev_data->device_id) {
|
|
sprintf(cmnd, "/sbin/multipathd -k\"del path %s\" > /dev/null 2>&1", strrchr(dev->dev_name, '/') + 1);
|
|
system(cmnd);
|
|
sprintf(cmnd, "/bin/rm -rf %s %s%s", dev->dev_name, dev->dev_name, "[0-9]*");
|
|
system(cmnd);
|
|
|
|
multipath_dev = find_multipath_jbod(dev);
|
|
if (multipath_dev) {
|
|
sprintf(cmnd, "/sbin/multipathd -k\"del path %s\" > /dev/null 2>&1", strrchr(multipath_dev->dev_name, '/') + 1);
|
|
system(cmnd);
|
|
sprintf(cmnd, "/bin/rm -rf %s %s%s", multipath_dev->dev_name, multipath_dev->dev_name , "[0-9]*");
|
|
system(cmnd);
|
|
}
|
|
system("/sbin/multipath -F > /dev/null 2>&1");
|
|
}
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
defect_list_hdr = calloc(1, IPR_DEFECT_LIST_HDR_LEN);
|
|
|
|
cdb[0] = FORMAT_UNIT;
|
|
cdb[1] = IPR_FORMAT_DATA; /* lun = 0, fmtdata = 1, cmplst = 0, defect list format = 0 */
|
|
cdb[4] = 1;
|
|
|
|
defect_list_hdr[1] = IPR_FORMAT_IMMED; /* FOV = 0, DPRY = 0, DCRT = 0, STPF = 0, IP = 0, DSP = 0, Immed = 1, VS = 0 */
|
|
|
|
rc = sg_ioctl(fd, cdb, defect_list_hdr,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
free(defect_list_hdr);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Format Unit", rc);
|
|
|
|
close(fd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_evaluate_device -
|
|
* @dev: ipr dev struct
|
|
* @res_handle: resource handle
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_evaluate_device(struct ipr_dev *dev, u32 res_handle)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
u32 resource_handle;
|
|
int length = 0;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
resource_handle = ntohl(res_handle);
|
|
|
|
cdb[0] = IPR_EVALUATE_DEVICE;
|
|
cdb[2] = (u8)(resource_handle >> 24);
|
|
cdb[3] = (u8)(resource_handle >> 16);
|
|
cdb[4] = (u8)(resource_handle >> 8);
|
|
cdb[5] = (u8)(resource_handle);
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_EVALUATE_DEVICE_TIMEOUT);
|
|
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
scsi_cmd_err(dev, &sense_data, "Evaluate Device Capabilities", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_disrupt_device -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_disrupt_device(struct ipr_dev *dev)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
u32 res_handle;
|
|
char *name = dev->ioa->ioa.gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
if (!dev->dev_rcd)
|
|
return -EINVAL;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
res_handle = ntohl(dev->resource_handle);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_DISRUPT_DEVICE;
|
|
if (!ipr_force)
|
|
cdb[2] = 0x40;
|
|
cdb[6] = (u8)(res_handle >> 24);
|
|
cdb[7] = (u8)(res_handle >> 16);
|
|
cdb[8] = (u8)(res_handle >> 8);
|
|
cdb[9] = (u8)(res_handle);
|
|
|
|
scsi_info(dev, "Attempting to force device failure "
|
|
"with disrupt device command\n");
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
0, SG_DXFER_NONE,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
scsi_cmd_err(dev, &sense_data, "Disrupt Device", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_inquiry - issue an inquiry command
|
|
* @dev: ipr dev struct
|
|
* @page: mode page
|
|
* @buff: data buffer
|
|
* @length buffer length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_inquiry(struct ipr_dev *dev, u8 page, void *buff, u8 length)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = INQUIRY;
|
|
if (page != IPR_STD_INQUIRY) {
|
|
cdb[1] = 0x01;
|
|
cdb[2] = page;
|
|
}
|
|
cdb[4] = length;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && (ipr_debug || sense_data.sense_key != ILLEGAL_REQUEST))
|
|
scsi_cmd_err(dev, &sense_data, "Inquiry", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* __ipr_get_fw_version - get the firmware version
|
|
* @dev: ipr dev struct
|
|
* @pag3_ing: page 3 inquiry data
|
|
* @release_level: location to copy version info into
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void __ipr_get_fw_version(struct ipr_dev *dev,
|
|
struct ipr_dasd_inquiry_page3 *page3_inq, u8 release_level[4])
|
|
{
|
|
if (ipr_is_ses(dev) && !__ioa_is_spi(dev->ioa))
|
|
memcpy(release_level, page3_inq->load_id, 4);
|
|
else
|
|
memcpy(release_level, page3_inq->release_level, 4);
|
|
}
|
|
|
|
/**
|
|
* ipr_get_fw_version - get the firmware version entry point
|
|
* @dev: ipr dev struct
|
|
* @release_level: location to copy version info into
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_get_fw_version(struct ipr_dev *dev, u8 release_level[4])
|
|
{
|
|
struct ipr_dasd_inquiry_page3 page3_inq;
|
|
int rc;
|
|
|
|
memset(&page3_inq, 0, sizeof(page3_inq));
|
|
rc = ipr_inquiry(dev, 3, &page3_inq, sizeof(page3_inq));
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
__ipr_get_fw_version(dev, &page3_inq, release_level);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_init_res_addr_aliases -
|
|
* @aliases: ipr_res_addr_aliases struct
|
|
* @res_addr: ipr_res_addr struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_init_res_addr_aliases(struct ipr_res_addr_aliases *aliases,
|
|
struct ipr_res_addr *res_addr)
|
|
{
|
|
struct ipr_res_addr *ra;
|
|
|
|
aliases->length = htonl(sizeof(*res_addr) * IPR_DEV_MAX_PATHS);
|
|
for_each_ra_alias(ra, aliases)
|
|
memcpy(ra, res_addr, sizeof(*ra));
|
|
}
|
|
|
|
/**
|
|
* ipr_query_res_addr_aliases -
|
|
* @ioa: ipr ioa struct
|
|
* @res_addr: ipr_res_addr struct
|
|
* @aliases: ipr_res_addr_aliases struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_res_addr_aliases(struct ipr_ioa *ioa, struct ipr_res_addr *res_addr,
|
|
struct ipr_res_addr_aliases *aliases)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
char *name = ioa->ioa.gen_name;
|
|
struct ipr_res_addr *ra;
|
|
|
|
ipr_init_res_addr_aliases(aliases, res_addr);
|
|
for_each_ra_alias(ra, aliases)
|
|
ra->host = ioa->host_num;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_QUERY_RES_ADDR_ALIASES;
|
|
cdb[3] = res_addr->bus;
|
|
cdb[4] = res_addr->target;
|
|
cdb[5] = res_addr->lun;
|
|
cdb[10] = sizeof(*aliases) >> 24;
|
|
cdb[11] = (sizeof(*aliases) >> 16) & 0xff;
|
|
cdb[12] = (sizeof(*aliases) >> 8) & 0xff;
|
|
cdb[13] = sizeof(*aliases) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, aliases,
|
|
sizeof(*aliases), SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc || (ntohl(aliases->length) <= 4))
|
|
ipr_init_res_addr_aliases(aliases, res_addr);
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(ioa, &sense_data, "Query Resource Address Aliases", rc);
|
|
if (rc && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
rc = 0;
|
|
|
|
for_each_ra_alias(ra, aliases)
|
|
ra->host = ioa->host_num;
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_init_res_path_aliases -
|
|
* @aliases: ipr_res_path_aliases struct
|
|
* @res_addr: ipr_res_path
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_init_res_path_aliases(struct ipr_res_path_aliases *aliases,
|
|
struct ipr_res_path *res_path)
|
|
{
|
|
struct ipr_res_path *rp;
|
|
|
|
aliases->length = htonl(sizeof(*res_path) * IPR_DEV_MAX_PATHS);
|
|
for_each_rp_alias(rp, aliases) {
|
|
memcpy(rp, res_path, sizeof(*rp));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_query_res_path_aliases -
|
|
* @ioa: ipr ioa struct
|
|
* @res_addr: ipr_res_path struct
|
|
* @aliases: ipr_res_ipr_aliases struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_res_path_aliases(struct ipr_ioa *ioa, struct ipr_res_path *res_path,
|
|
struct ipr_res_path_aliases *aliases)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
char *name = ioa->ioa.gen_name;
|
|
|
|
ipr_init_res_path_aliases(aliases, res_path);
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_QUERY_RES_PATH_ALIASES;
|
|
|
|
memcpy(&cdb[2], res_path->res_path_bytes, sizeof(struct ipr_res_path));
|
|
cdb[10] = sizeof(*aliases) >> 24;
|
|
cdb[11] = (sizeof(*aliases) >> 16) & 0xff;
|
|
cdb[12] = (sizeof(*aliases) >> 8) & 0xff;
|
|
cdb[13] = sizeof(*aliases) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, aliases,
|
|
sizeof(*aliases), SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc || (ntohl(aliases->length) <= 8))
|
|
ipr_init_res_path_aliases(aliases, res_path);
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(ioa, &sense_data, "Query Resource Path Aliases", rc);
|
|
if (rc && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
rc = 0;
|
|
|
|
close(fd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_sas_expander_info -
|
|
* @ioa: ipr ioa struct
|
|
* @info: ipr_query_sas_expander_info struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_sas_expander_info(struct ipr_ioa *ioa,
|
|
struct ipr_query_sas_expander_info *info)
|
|
{
|
|
char *name = ioa->ioa.gen_name;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_QUERY_SAS_EXPANDER_INFO;
|
|
cdb[10] = sizeof(*info) >> 24;
|
|
cdb[11] = (sizeof(*info) >> 16) & 0xff;
|
|
cdb[12] = (sizeof(*info) >> 8) & 0xff;
|
|
cdb[13] = sizeof(*info) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, info, sizeof(*info), SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(ioa, &sense_data, "Query SAS Expander Info", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_change_cache_parameters
|
|
* @ioa: ipr ioa struct
|
|
* @mode: caching mode to set
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_change_cache_parameters(struct ipr_ioa *ioa, int mode)
|
|
{
|
|
char *name = ioa->ioa.gen_name;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_CHANGE_CACHE_PARAMETERS;
|
|
cdb[2] = mode;
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL, 0, SG_DXFER_NONE,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && (sense_data.sense_key != ILLEGAL_REQUEST || ipr_debug))
|
|
ioa_cmd_err(ioa, &sense_data, "change cache params failed", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_cache_parameters -
|
|
* @ioa: ipr ioa struct
|
|
* @buf: buffer for returned cache data
|
|
* @len: length of buffer
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_cache_parameters(struct ipr_ioa *ioa, void *buf, int len)
|
|
{
|
|
char *name = ioa->ioa.gen_name;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_QUERY_CACHE_PARAMETERS;
|
|
cdb[10] = len >> 24;
|
|
cdb[11] = len >> 16 & 0xff;
|
|
cdb[12] = len >> 8 & 0xff;
|
|
cdb[13] = len & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buf, len, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && (sense_data.sense_key != ILLEGAL_REQUEST || ipr_debug))
|
|
ioa_cmd_err(ioa, &sense_data, "Query Cache Parameters", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_res_redundancy_info -
|
|
* @dev: ipr dev struct
|
|
* @info: ipr_res_redundancy_info struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_res_redundancy_info(struct ipr_dev *dev,
|
|
struct ipr_res_redundancy_info *info)
|
|
{
|
|
struct scsi_dev_data *scsi_dev_data = dev->scsi_dev_data;
|
|
char *name = dev->ioa->ioa.gen_name;
|
|
struct ipr_dev_record *dev_record;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
u32 res_handle;
|
|
int fd, rc;
|
|
|
|
if (scsi_dev_data)
|
|
res_handle = ntohl(scsi_dev_data->handle);
|
|
else if (ipr_is_af_dasd_device(dev)) {
|
|
dev_record = dev->dev_rcd;
|
|
if (dev_record->no_cfgte_dev)
|
|
return -EINVAL;
|
|
|
|
res_handle = ntohl(dev->resource_handle);
|
|
} else
|
|
return -EINVAL;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
if (dev->ioa->sis64)
|
|
cdb[1] = IPR_QUERY_RES_REDUNDANCY_INFO64;
|
|
else
|
|
cdb[1] = IPR_QUERY_RES_REDUNDANCY_INFO;
|
|
cdb[2] = (u8)(res_handle >> 24);
|
|
cdb[3] = (u8)(res_handle >> 16);
|
|
cdb[4] = (u8)(res_handle >> 8);
|
|
cdb[5] = (u8)(res_handle);
|
|
cdb[10] = sizeof(*info) >> 24;
|
|
cdb[11] = (sizeof(*info) >> 16) & 0xff;
|
|
cdb[12] = (sizeof(*info) >> 8) & 0xff;
|
|
cdb[13] = sizeof(*info) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, info, sizeof(*info), SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && sense_data.sense_key != ILLEGAL_REQUEST)
|
|
ioa_cmd_err(dev->ioa, &sense_data, "Query Resource Redundancy Info", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* get_livedump_fname -
|
|
* @ioa: ipr_ioa struct
|
|
* @fname: string to return the filename
|
|
* @max: max size of fname
|
|
*
|
|
* Returns:
|
|
* negative on failure / otherwise length of the file name
|
|
*
|
|
**/
|
|
static int get_livedump_fname(struct ipr_ioa *ioa, char *fname, int max)
|
|
{
|
|
time_t cur_time, rc;
|
|
struct tm *cur_tm;
|
|
char time_str[100];
|
|
int len;
|
|
|
|
fname[0] = '\0';
|
|
rc = time(&cur_time);
|
|
if (rc == ((time_t)-1))
|
|
return -EIO;
|
|
|
|
cur_tm = localtime(&cur_time);
|
|
if (!cur_tm)
|
|
return -EIO;
|
|
strftime(time_str, sizeof(time_str), "%Y%m%d.%H%M", cur_tm);
|
|
|
|
len = snprintf(fname, max, "ipr-%04X-%s-dump-%s", ioa->ccin,
|
|
ioa->pci_address, time_str);
|
|
|
|
return len;
|
|
}
|
|
|
|
#define IPR_MAX_LIVE_DUMP_SIZE (16 * 1024 * 1024)
|
|
|
|
/**
|
|
* get_scsi_max_xfer_len -
|
|
* @fd: IOA sg file descriptor
|
|
*
|
|
* Returns:
|
|
* -1 on failure / otherwise max scatter-gather transfer length
|
|
*
|
|
**/
|
|
static int get_scsi_max_xfer_len(int fd)
|
|
{
|
|
int rc, max_tablesize, max_scsi_len;
|
|
char elem_str[64];
|
|
ssize_t len;
|
|
|
|
len = sysfs_read_attr("/sys/module/sg/parameters", "scatter_elem_sz",
|
|
elem_str, 64);
|
|
if (len < 0) {
|
|
syslog_dbg("Failed to read scatter_elem_sz parameter from sg module. %m\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = ioctl(fd, SG_GET_SG_TABLESIZE, &max_tablesize);
|
|
if (rc) {
|
|
syslog_dbg("Unable to get device scatter-gather table size. %m\n");
|
|
return -1;
|
|
}
|
|
|
|
max_scsi_len = max_tablesize * atoi(elem_str);
|
|
|
|
if (max_scsi_len < IPR_MAX_LIVE_DUMP_SIZE)
|
|
return max_scsi_len;
|
|
else
|
|
return IPR_MAX_LIVE_DUMP_SIZE;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_live_dump -
|
|
* @dev: ipr dev struct
|
|
* @info: ipr_res_redundancy_info struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
*
|
|
**/
|
|
int ipr_get_live_dump(struct ipr_ioa *ioa)
|
|
{
|
|
char *name = ioa->ioa.gen_name;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN], *buf;
|
|
char dump_file[100], dump_path[100];
|
|
int rc, fd, fd_dump, buff_len;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
buff_len = get_scsi_max_xfer_len(fd);
|
|
if (buff_len <= 0) {
|
|
rc = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_LIVE_DUMP;
|
|
cdb[10] = buff_len >> 24;
|
|
cdb[11] = (buff_len >> 16) & 0xff;
|
|
cdb[12] = (buff_len >> 8) & 0xff;
|
|
cdb[13] = buff_len & 0xff;
|
|
|
|
buf = (u8*) malloc(buff_len);
|
|
|
|
rc = sg_ioctl(fd, cdb, buf, buff_len, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc) {
|
|
ioa_cmd_err(ioa, &sense_data, "Live Dump", rc);
|
|
goto out_free;
|
|
}
|
|
|
|
rc = get_livedump_fname(ioa, dump_file, sizeof(dump_file));
|
|
if (rc < 0) {
|
|
syslog_dbg("Failed to create a live dump file name.\n");
|
|
goto out_free;
|
|
}
|
|
|
|
snprintf(dump_path, sizeof(dump_path), "%s%s", IPRDUMP_DIR, dump_file);
|
|
fd_dump = creat(dump_path, S_IRUSR);
|
|
if (fd_dump < 0) {
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", dump_path);
|
|
rc = fd_dump;
|
|
goto out_free;
|
|
}
|
|
|
|
rc = write(fd_dump, buf, buff_len);
|
|
if (rc != buff_len)
|
|
syslog(LOG_ERR, "Could not write dump on file %s. %m\n", dump_path);
|
|
|
|
close(fd_dump);
|
|
|
|
out_free:
|
|
free(buf);
|
|
out:
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_array_asym_access -
|
|
* @dev: ipr_dev struct
|
|
* @qac: ipr_array_query_data struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_array_asym_access(struct ipr_ioa *ioa)
|
|
{
|
|
char *name = ioa->ioa.gen_name;
|
|
struct sense_data_t sense_data;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int fd, rc;
|
|
int length = ioa->qac_data->resp_len;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_SET_ARRAY_ASYMMETRIC_ACCESS;
|
|
cdb[7] = (length >> 8) & 0xff;
|
|
cdb[8] = length & 0xff;
|
|
|
|
if (ioa->sis64) {
|
|
cdb[10] = (length & 0xff000000) >> 24;
|
|
cdb[11] = (length & 0xff0000) >> 16;
|
|
}
|
|
rc = sg_ioctl(fd, cdb, ioa->qac_data->u.buf, length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_ARRAY_CMD_TIMEOUT);
|
|
|
|
if (rc != 0 && (sense_data.sense_key != ILLEGAL_REQUEST || ipr_debug))
|
|
ioa_cmd_err(ioa, &sense_data, "Set Array Asymmetric Access", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* get_write_buffer_dev - FIXME - probably need to remove this routine.
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* device generic name for now - see FIXME below
|
|
**/
|
|
#if 0
|
|
static char *get_write_buffer_dev(struct ipr_dev *dev)
|
|
{
|
|
struct sysfs_class_device *class_device;
|
|
struct sysfs_attribute *dev_attr;
|
|
int rc, val;
|
|
char path[SYSFS_PATH_MAX];
|
|
|
|
/* xxx FIXME */
|
|
return dev->gen_name;
|
|
|
|
if (!strlen(dev->dev_name) || !dev->scsi_dev_data)
|
|
return dev->gen_name;
|
|
|
|
class_device = sysfs_open_class_device("block",
|
|
&dev->dev_name[5]);
|
|
if (!class_device) {
|
|
scsi_dbg(dev, "Failed to open block class device for %s. %m\n",
|
|
dev->dev_name);
|
|
return dev->gen_name;
|
|
}
|
|
|
|
sprintf(path, "%s/queue/max_sectors_kb", class_device->path);
|
|
|
|
dev_attr = sysfs_open_attribute(path);
|
|
if (!dev_attr) {
|
|
scsi_dbg(dev, "Failed to get max_sectors_kb attribute. %m\n");
|
|
goto fail;
|
|
}
|
|
|
|
rc = sysfs_read_attribute(dev_attr);
|
|
|
|
if (rc) {
|
|
sysfs_close_attribute(dev_attr);
|
|
scsi_dbg(dev, "Failed to read max_sectors_kb attribute. %m\n");
|
|
goto fail;
|
|
}
|
|
|
|
sscanf(dev_attr->value, "%d", &val);
|
|
sysfs_close_attribute(dev_attr);
|
|
sysfs_close_class_device(class_device);
|
|
|
|
if (val > 256)
|
|
return dev->dev_name;
|
|
|
|
return dev->gen_name;
|
|
fail:
|
|
sysfs_close_class_device(class_device);
|
|
return dev->gen_name;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* ipr_resume_device_bus -
|
|
* @ioa: ipr ioa struct
|
|
* @res_addr: ipr_res_addr struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_resume_device_bus(struct ipr_dev *dev,
|
|
struct ipr_res_addr *res_addr)
|
|
{
|
|
int fd, rc, cdb_num;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int length = 0;
|
|
struct ipr_ioa *ioa = dev->ioa;
|
|
char *rp, *endptr;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_RESUME_DEV_BUS;
|
|
cdb[1] = IPR_RDB_UNQUIESCE_AND_REBALANCE;
|
|
if (ioa->sis64) {
|
|
/* convert res path string to bytes */
|
|
cdb_num = 2;
|
|
rp = dev->res_path_name;
|
|
do {
|
|
cdb[cdb_num++] = (u8)strtol(rp, &endptr, 16);
|
|
rp = endptr+1;
|
|
} while (*endptr != '\0' && cdb_num < 10);
|
|
|
|
while (cdb_num < 10) cdb[cdb_num++] = 0xff;
|
|
} else {
|
|
cdb[3] = res_addr->bus;
|
|
cdb[4] = res_addr->target;
|
|
cdb[5] = res_addr->lun;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_SUSPEND_DEV_BUS_TIMEOUT);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* __ipr_write_buffer - Issue WRITE_BUFFER command.
|
|
* In older kernels, max_sectors_kb is not big enough and
|
|
* would return -EIO when using dev_name to issue
|
|
* SG_IO ioctl(). In that case, we have to use gen_name.
|
|
* Also, in case dev_name does not exist, we need to use
|
|
* gen_name. Hence, we try dev_name first and switch to
|
|
* gen_name if we get -ENOENT or -EIO.
|
|
* @dev: ipr dev struct
|
|
* @mode: mode value
|
|
* @buff: data buffer
|
|
* @length: buffer length
|
|
* @sense_data: sense data struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int __ipr_write_buffer(struct ipr_dev *dev, u8 mode, void *buff, int length,
|
|
struct sense_data_t *sense_data)
|
|
{
|
|
u32 direction = length ? SG_DXFER_TO_DEV : SG_DXFER_NONE;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
int rc;
|
|
|
|
ENTER;
|
|
if ((strlen(dev->dev_name) == 0) && (strlen(dev->gen_name) == 0))
|
|
return -ENOENT;
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = WRITE_BUFFER;
|
|
cdb[1] = mode;
|
|
cdb[6] = (length & 0xff0000) >> 16;
|
|
cdb[7] = (length & 0x00ff00) >> 8;
|
|
cdb[8] = length & 0x0000ff;
|
|
|
|
rc = sg_ioctl_by_name(dev->dev_name, cdb, buff,
|
|
length, direction,
|
|
sense_data, IPR_WRITE_BUFFER_TIMEOUT);
|
|
|
|
if (rc != 0) {
|
|
if ((rc == -ENOENT) || (rc == -EIO) || (rc == -EINVAL)) {
|
|
syslog_dbg("Write buffer failed on sd device %s. %m\n", dev->dev_name);
|
|
rc = sg_ioctl_by_name(dev->gen_name, cdb, buff,
|
|
length, direction,
|
|
sense_data, IPR_WRITE_BUFFER_TIMEOUT);
|
|
if (rc != 0) {
|
|
scsi_cmd_err(dev, sense_data,
|
|
"Write buffer using sg device", rc);
|
|
if (rc == -ENOMEM)
|
|
syslog(LOG_ERR,
|
|
"Cannot get enough memory to "
|
|
"perform microcode download "
|
|
"through %s. Reduce system "
|
|
"memory usage and try again.\n",
|
|
dev->gen_name);
|
|
}
|
|
} else
|
|
scsi_cmd_err(dev, sense_data,
|
|
"Write buffer using sd device", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_write_buffer -
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
* @length: buffer length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_write_buffer(struct ipr_dev *dev, void *buff, int length)
|
|
{
|
|
int rc, i;
|
|
struct sense_data_t sense_data;
|
|
struct ipr_disk_attr attr;
|
|
int old_qdepth;
|
|
int sas_ses = 0;
|
|
int sas_ses_retries = 5;
|
|
int mode5 = 1;
|
|
|
|
ENTER;
|
|
if ((rc = ipr_get_dev_attr(dev, &attr))) {
|
|
scsi_dbg(dev, "Failed to get device attributes. rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (ipr_is_ses(dev) && !__ioa_is_spi(dev->ioa))
|
|
sas_ses = 1;
|
|
if (sas_ses && !ipr_mode5_write_buffer)
|
|
mode5 = 0;
|
|
|
|
/*
|
|
* Set the queue depth to 1 for the duration of the code download.
|
|
* This prevents us from getting I/O errors during the code update
|
|
*/
|
|
old_qdepth = attr.queue_depth;
|
|
attr.queue_depth = 1;
|
|
if ((rc = ipr_set_dev_attr(dev, &attr, 0))) {
|
|
scsi_dbg(dev, "Failed to set queue depth to 1. rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (sas_ses) {
|
|
if (!mode5) {
|
|
for (i = 0; i < (sas_ses_retries + 1); i++) {
|
|
rc = __ipr_write_buffer(dev, 0x0e, buff, length, &sense_data);
|
|
if (!rc) {
|
|
break;
|
|
} else if (rc && sense_data.sense_key == UNIT_ATTENTION &&
|
|
sense_data.add_sense_code == 0x29 &&
|
|
sense_data.add_sense_code_qual == 0x00) {
|
|
continue;
|
|
} else if (rc && sense_data.sense_key == ILLEGAL_REQUEST) {
|
|
mode5 = 1;
|
|
break;
|
|
} else if (rc) {
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = ipr_suspend_device_bus(dev, &dev->res_addr[0],
|
|
IPR_SDB_CHECK_AND_QUIESCE_ENC);
|
|
|
|
if (rc) {
|
|
scsi_dbg(dev, "Failed to quiesce the SAS port.\n");
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < (sas_ses_retries + 1); i++) {
|
|
if (mode5)
|
|
rc = __ipr_write_buffer(dev, 5, buff, length, &sense_data);
|
|
else
|
|
rc = __ipr_write_buffer(dev, 0x0f, NULL, 0, &sense_data);
|
|
|
|
if (rc && sense_data.sense_key == UNIT_ATTENTION &&
|
|
sense_data.add_sense_code == 0x29 &&
|
|
sense_data.add_sense_code_qual == 0x00) {
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc) {
|
|
scsi_dbg(dev, "Write buffer %sfailed.\n", mode5 ? "" : "activate ");
|
|
goto out_resume;
|
|
}
|
|
} else {
|
|
if ((rc = __ipr_write_buffer(dev, 5, buff, length, &sense_data)))
|
|
goto out;
|
|
}
|
|
|
|
scsi_dbg(dev, "Waiting for device to come ready after download.\n");
|
|
sleep(sas_ses ? 120 : 5);
|
|
|
|
for (i = 0, rc = -1; rc && (i < 60); i++, sleep(1))
|
|
rc = __ipr_test_unit_ready(dev, &sense_data);
|
|
|
|
out_resume:
|
|
if (sas_ses)
|
|
ipr_resume_device_bus(dev, &dev->res_addr[0]);
|
|
|
|
out:
|
|
attr.queue_depth = old_qdepth;
|
|
ipr_set_dev_attr(dev, &attr, 0);
|
|
LEAVE;
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_suspend_device_bus -
|
|
* @ioa: ipr ioa struct
|
|
* @res_addr: ipr_res_addr struct
|
|
* @option: option value
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_suspend_device_bus(struct ipr_dev *dev,
|
|
struct ipr_res_addr *res_addr, u8 option)
|
|
{
|
|
int fd, rc, cdb_num;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int length = 0;
|
|
struct ipr_ioa *ioa = dev->ioa;
|
|
char *rp, *endptr;
|
|
|
|
ENTER;
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_SUSPEND_DEV_BUS;
|
|
cdb[1] = option;
|
|
if (ioa->sis64) {
|
|
/* convert res path string to bytes */
|
|
cdb_num = 2;
|
|
rp = dev->res_path_name;
|
|
do {
|
|
cdb[cdb_num++] = (u8)strtol(rp, &endptr, 16);
|
|
rp = endptr+1;
|
|
} while (*endptr != '\0' && cdb_num < 10);
|
|
|
|
while (cdb_num < 10) cdb[cdb_num++] = 0xff;
|
|
} else {
|
|
cdb[3] = res_addr->bus;
|
|
cdb[4] = res_addr->target;
|
|
cdb[5] = res_addr->lun;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_SUSPEND_DEV_BUS_TIMEOUT);
|
|
|
|
close(fd);
|
|
LEAVE;
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_can_remove_device - indicate whether or not a device can be removed
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 1 if device can be removed / 0 if device can not be removed
|
|
**/
|
|
int ipr_can_remove_device(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_res_addr *ra;
|
|
|
|
for_each_ra(ra, dev) {
|
|
if (ipr_suspend_device_bus(dev, ra, IPR_SDB_CHECK_ONLY))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* ipr_receive_diagnostics -
|
|
* @dev: ipr dev struct
|
|
* @page: page number
|
|
* @buff: data buffer
|
|
* @length: buffer length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_receive_diagnostics(struct ipr_dev *dev,
|
|
u8 page, void *buff, int length)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = RECEIVE_DIAGNOSTIC;
|
|
cdb[1] = 0x01;
|
|
cdb[2] = page;
|
|
cdb[3] = (length >> 8) & 0xff;
|
|
cdb[4] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Receive diagnostics", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_send_diagnostics -
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
* @length: buffer length
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_send_diagnostics(struct ipr_dev *dev, void *buff, int length)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
char *name = dev->gen_name;
|
|
|
|
if (strlen(name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = SEND_DIAGNOSTIC;
|
|
cdb[1] = 0x10;
|
|
cdb[3] = (length >> 8) & 0xff;
|
|
cdb[4] = length & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Send diagnostics", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* set_supported_devs -
|
|
* @dev: ipr dev struct
|
|
* @std_inq: ipr_std_inq_data struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int set_supported_devs(struct ipr_dev *dev,
|
|
struct ipr_std_inq_data *std_inq)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
struct ipr_supported_device supported_dev;
|
|
|
|
fd = open(dev->ioa->ioa.gen_name, O_RDWR);
|
|
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n",
|
|
dev->ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(&supported_dev, 0, sizeof(struct ipr_supported_device));
|
|
memcpy(&supported_dev.vpids, &std_inq->vpids, sizeof(std_inq->vpids));
|
|
supported_dev.num_records = 1;
|
|
supported_dev.data_length = htons(sizeof(supported_dev));
|
|
supported_dev.reserved = 0;
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
cdb[0] = SET_SUPPORTED_DEVICES;
|
|
cdb[7] = (sizeof(supported_dev) >> 8) & 0xff;
|
|
cdb[8] = sizeof(supported_dev) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, &supported_dev,
|
|
sizeof(supported_dev), SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
ioa_cmd_err(dev->ioa, &sense_data, "Set supported devices", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* __device_supported -
|
|
* @dev: ipr dev struct
|
|
* @inq: ipr_std_inq_data
|
|
*
|
|
* Returns:
|
|
* 0 if device is supported / 1 if device is not supported
|
|
**/
|
|
static int __device_supported(struct ipr_dev *dev, struct ipr_std_inq_data *inq)
|
|
{
|
|
int i, j;
|
|
const struct ses_table_entry *ste;
|
|
|
|
if (!dev->scsi_dev_data)
|
|
return 1;
|
|
|
|
ste = get_ses_entry(dev->ioa, dev->scsi_dev_data->channel);
|
|
|
|
if (!ste)
|
|
return 1;
|
|
if (!ste->block_15k_devices)
|
|
return 1;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(unsupported_dasd); i++) {
|
|
for (j = 0; j < IPR_VENDOR_ID_LEN; j++) {
|
|
if (unsupported_dasd[i].compare_vendor_id_byte[j] &&
|
|
unsupported_dasd[i].vendor_id[j] != inq->vpids.vendor_id[j])
|
|
break;
|
|
}
|
|
|
|
if (j != IPR_VENDOR_ID_LEN)
|
|
continue;
|
|
|
|
for (j = 0; j < IPR_PROD_ID_LEN; j++) {
|
|
if (unsupported_dasd[i].compare_product_id_byte[j] &&
|
|
unsupported_dasd[i].product_id[j] != inq->vpids.product_id[j])
|
|
break;
|
|
}
|
|
|
|
if (j != IPR_PROD_ID_LEN)
|
|
continue;
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* device_supported -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if device is supported / 1 if device is not supported
|
|
**/
|
|
int device_supported(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_std_inq_data std_inq;
|
|
|
|
if (ipr_inquiry(dev, IPR_STD_INQUIRY, &std_inq, sizeof(std_inq)))
|
|
return -EIO;
|
|
|
|
return __device_supported(dev, &std_inq);
|
|
}
|
|
|
|
/**
|
|
* enable_af -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int enable_af(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_std_inq_data std_inq;
|
|
|
|
if (ipr_inquiry(dev, IPR_STD_INQUIRY, &std_inq, sizeof(std_inq)))
|
|
return -EIO;
|
|
|
|
if (!__device_supported(dev, &std_inq)) {
|
|
scsi_dbg(dev, "Unsupported device attached\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (set_supported_devs(dev, &std_inq))
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* get_blk_size - return the block size of the device
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* block size / non-zero on failure
|
|
**/
|
|
static int get_blk_size(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages modes;
|
|
struct ipr_block_desc *block_desc;
|
|
int rc;
|
|
|
|
memset(&modes, 0, sizeof(modes));
|
|
|
|
rc = ipr_mode_sense(dev, 0x0a, &modes);
|
|
|
|
if (!rc) {
|
|
if (modes.hdr.block_desc_len > 0) {
|
|
block_desc = (struct ipr_block_desc *)(modes.data);
|
|
return ((block_desc->block_length[1] << 8) | block_desc->block_length[2]);
|
|
}
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* format_req -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int format_req(struct ipr_dev *dev)
|
|
{
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
rc = ipr_test_unit_ready(dev, &sense_data);
|
|
|
|
if (rc == CHECK_CONDITION &&
|
|
sense_data.add_sense_code == 0x31 &&
|
|
sense_data.add_sense_code_qual == 0x00)
|
|
return 1;
|
|
|
|
rc = get_blk_size(dev);
|
|
|
|
if (rc < 0)
|
|
return 0;
|
|
|
|
if (ipr_is_gscsi(dev) && (rc != 512 || rc != 4096))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Scatter/gather list buffers are checked against the value returned
|
|
* by queue_dma_alignment(), which defaults to 511 in Linux 2.6,
|
|
* for alignment if a SG_IO ioctl request is sent through a /dev/sdX device.
|
|
*/
|
|
#define IPR_S_G_BUFF_ALIGNMENT 512
|
|
#define IPR_MAX_XFER 0x8000
|
|
|
|
const int cdb_size[] ={6, 10, 10, 0, 16, 12, 16, 16};
|
|
/**
|
|
* _sg_ioctl -
|
|
* @fd: file descriptor
|
|
* @cdb: cdb
|
|
* @data: data pointer
|
|
* @xfer_len transfer length
|
|
* @data_direction transfer to dev or from dev
|
|
* @sense_data sense data pointer
|
|
* @timeout_in_sec timeout value
|
|
* @retries number of retries
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int _sg_ioctl(int fd, u8 cdb[IPR_CCB_CDB_LEN],
|
|
void *data, u32 xfer_len, u32 data_direction,
|
|
struct sense_data_t *sense_data,
|
|
u32 timeout_in_sec, int retries)
|
|
{
|
|
int rc = 0;
|
|
sg_io_hdr_t io_hdr_t;
|
|
sg_iovec_t *iovec = NULL;
|
|
int iovec_count = 0;
|
|
int i;
|
|
int buff_len, segment_size;
|
|
void *dxferp;
|
|
u8 *buf;
|
|
|
|
/* check if scatter gather should be used */
|
|
if (xfer_len > IPR_MAX_XFER) {
|
|
iovec_count = (xfer_len/IPR_MAX_XFER) + 1;
|
|
iovec = malloc(iovec_count * sizeof(sg_iovec_t));
|
|
|
|
buff_len = xfer_len;
|
|
segment_size = IPR_MAX_XFER;
|
|
|
|
for (i = 0; (i < iovec_count) && (buff_len != 0); i++) {
|
|
posix_memalign(&(iovec[i].iov_base), IPR_S_G_BUFF_ALIGNMENT, segment_size);
|
|
if (data_direction == SG_DXFER_TO_DEV)
|
|
memcpy(iovec[i].iov_base, data + (IPR_MAX_XFER * i), segment_size);
|
|
iovec[i].iov_len = segment_size;
|
|
|
|
buff_len -= segment_size;
|
|
if (buff_len < segment_size)
|
|
segment_size = buff_len;
|
|
}
|
|
|
|
iovec_count = i;
|
|
dxferp = (void *)iovec;
|
|
} else {
|
|
iovec_count = 0;
|
|
dxferp = data;
|
|
}
|
|
|
|
for (i = 0; i < (retries + 1); i++) {
|
|
memset(&io_hdr_t, 0, sizeof(io_hdr_t));
|
|
memset(sense_data, 0, sizeof(struct sense_data_t));
|
|
|
|
io_hdr_t.interface_id = 'S';
|
|
io_hdr_t.cmd_len = cdb_size[(cdb[0] >> 5) & 0x7];
|
|
io_hdr_t.iovec_count = iovec_count;
|
|
io_hdr_t.flags = 0;
|
|
io_hdr_t.pack_id = 0;
|
|
io_hdr_t.usr_ptr = 0;
|
|
io_hdr_t.sbp = (unsigned char *)sense_data;
|
|
io_hdr_t.mx_sb_len = sizeof(struct sense_data_t);
|
|
io_hdr_t.timeout = timeout_in_sec * 1000;
|
|
io_hdr_t.cmdp = cdb;
|
|
io_hdr_t.dxfer_direction = data_direction;
|
|
io_hdr_t.dxfer_len = xfer_len;
|
|
io_hdr_t.dxferp = dxferp;
|
|
|
|
rc = ioctl(fd, SG_IO, &io_hdr_t);
|
|
|
|
if (rc == -1 && errno == EINVAL) {
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (rc == 0 && io_hdr_t.masked_status == CHECK_CONDITION)
|
|
rc = CHECK_CONDITION;
|
|
else if (rc == 0 && (io_hdr_t.host_status || io_hdr_t.driver_status))
|
|
rc = -EIO;
|
|
|
|
if (rc == 0 || io_hdr_t.host_status == 1)
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (iovec_count) {
|
|
for (i = 0, buf = (u8 *)data; i < iovec_count; i++) {
|
|
if (data_direction == SG_DXFER_FROM_DEV)
|
|
memcpy(buf, iovec[i].iov_base, iovec[i].iov_len);
|
|
buf += iovec[i].iov_len;
|
|
free(iovec[i].iov_base);
|
|
}
|
|
free(iovec);
|
|
}
|
|
|
|
return rc;
|
|
};
|
|
|
|
/**
|
|
* sg_ioctl -
|
|
* @fd: file descriptor
|
|
* @cdb: cdb
|
|
* @data: data pointer
|
|
* @xfer_len transfer length
|
|
* @data_direction transfer to dev or from dev
|
|
* @sense_data sense data pointer
|
|
* @timeout_in_sec timeout value
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int sg_ioctl(int fd, u8 cdb[IPR_CCB_CDB_LEN],
|
|
void *data, u32 xfer_len, u32 data_direction,
|
|
struct sense_data_t *sense_data,
|
|
u32 timeout_in_sec)
|
|
{
|
|
return _sg_ioctl(fd, cdb,
|
|
data, xfer_len, data_direction,
|
|
sense_data, timeout_in_sec, 1);
|
|
};
|
|
|
|
/**
|
|
* sg_ioctl_noretry -
|
|
* @fd: file descriptor
|
|
* @cdb: cdb
|
|
* @data: data pointer
|
|
* @xfer_len transfer length
|
|
* @data_direction transfer to dev or from dev
|
|
* @sense_data sense data pointer
|
|
* @timeout_in_sec timeout value
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int sg_ioctl_noretry(int fd, u8 cdb[IPR_CCB_CDB_LEN],
|
|
void *data, u32 xfer_len, u32 data_direction,
|
|
struct sense_data_t *sense_data,
|
|
u32 timeout_in_sec)
|
|
{
|
|
return _sg_ioctl(fd, cdb,
|
|
data, xfer_len, data_direction,
|
|
sense_data, timeout_in_sec, 0);
|
|
};
|
|
|
|
/**
|
|
* sg_ioctl_by_name -
|
|
* @name: file name
|
|
* @cdb: cdb
|
|
* @data: data pointer
|
|
* @xfer_len transfer length
|
|
* @data_direction transfer to dev or from dev
|
|
* @sense_data sense data pointer
|
|
* @timeout_in_sec timeout value
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int sg_ioctl_by_name(char *name, u8 cdb[IPR_CCB_CDB_LEN],
|
|
void *data, u32 xfer_len, u32 data_direction,
|
|
struct sense_data_t *sense_data,
|
|
u32 timeout_in_sec)
|
|
{
|
|
int fd, rc;
|
|
|
|
if (strlen(name) <= 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s.\n", name);
|
|
return errno;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, data,
|
|
xfer_len, data_direction,
|
|
sense_data, timeout_in_sec);
|
|
close(fd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_ha_mode -
|
|
* @ioa: ipr ioa struct
|
|
* @gscsi_only_ha: process gscsi only flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_ha_mode(struct ipr_ioa *ioa, int gscsi_only_ha)
|
|
{
|
|
int rc, len;
|
|
struct sense_data_t sense_data;
|
|
struct ipr_config_term_hdr *hdr;
|
|
struct ipr_subsys_config_term *term;
|
|
struct ipr_mode_pages pages;
|
|
struct ipr_mode_page25 *page;
|
|
|
|
memset(&sense_data, 0, sizeof(sense_data));
|
|
rc = mode_sense(&ioa->ioa, 0x25, &pages, &sense_data);
|
|
|
|
if (!rc) {
|
|
page = (struct ipr_mode_page25 *)(pages.data + pages.hdr.block_desc_len);
|
|
|
|
for_each_page25_term(hdr, page) {
|
|
if (hdr->term_id != IPR_SUBSYS_CONFIG_TERM_ID)
|
|
continue;
|
|
|
|
term = (struct ipr_subsys_config_term *)hdr;
|
|
if (gscsi_only_ha)
|
|
term->config = IPR_GSCSI_ONLY_HA_SUBSYS;
|
|
else
|
|
term->config = IPR_AFDASD_SUBSYS;
|
|
|
|
len = pages.hdr.length + 1;
|
|
pages.hdr.length = 0;
|
|
|
|
return ipr_mode_select(&ioa->ioa, &pages, len);
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_change_multi_adapter_assignment - set the preferred primary state and/or
|
|
* the asymmetric access state
|
|
* @ioa: ipr ioa struct
|
|
* @preferred_primary: set preferred primary flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_change_multi_adapter_assignment(struct ipr_ioa *ioa, int preferred_primary,
|
|
int asym_access)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
struct ipr_dev *dev = &ioa->ioa;
|
|
|
|
if (strlen(dev->gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(dev->gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", dev->gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_MAINTENANCE_OUT;
|
|
cdb[1] = IPR_CHANGE_MULTI_ADAPTER_ASSIGNMENT;
|
|
if (asym_access)
|
|
cdb[2] = IPR_PRESERVE_ASYMMETRIC_STATE;
|
|
if (preferred_primary)
|
|
cdb[3] = IPR_IOA_STATE_PRIMARY;
|
|
else
|
|
cdb[3] = IPR_IOA_STATE_NO_PREFERENCE;
|
|
|
|
rc = sg_ioctl(fd, cdb, NULL, 0, SG_DXFER_NONE,
|
|
&sense_data, IPR_INTERNAL_DEV_TIMEOUT);
|
|
|
|
if (rc != 0 && (sense_data.sense_key != ILLEGAL_REQUEST || ipr_debug))
|
|
scsi_cmd_err(dev, &sense_data, "Change Multi Adapter Assignment", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
static void ipr_save_ioa_attr(struct ipr_ioa *ioa, char *field,
|
|
char *value, int update);
|
|
|
|
/**
|
|
* set_preferred_primary - set the preferred primary state
|
|
* @ioa: ipr ioa struct
|
|
* @preferred_primary: set preferred primary flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int set_preferred_primary(struct ipr_ioa *ioa, int preferred_primary)
|
|
{
|
|
char temp[100];
|
|
|
|
sprintf(temp, "%d", preferred_primary);
|
|
if (ipr_change_multi_adapter_assignment(ioa, preferred_primary, IPR_PRESERVE_ASYMMETRIC_STATE))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* set_ha_mode -
|
|
* @ioa: ipr ioa struct
|
|
* @gscsi_only: gscsi only flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int set_ha_mode(struct ipr_ioa *ioa, int gscsi_only)
|
|
{
|
|
char temp[100];
|
|
int reset_needed = (gscsi_only != ioa->in_gscsi_only_ha);
|
|
|
|
sprintf(temp, "%d", gscsi_only);
|
|
if (ipr_set_ha_mode(ioa, gscsi_only))
|
|
return -EIO;
|
|
ipr_save_ioa_attr(ioa, IPR_GSCSI_HA_ONLY, temp, 1);
|
|
|
|
if (reset_needed)
|
|
ipr_reset_adapter(ioa);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_array_rebuild_verify
|
|
* @ioa: ipr ioa struct
|
|
* @disable_verify: Whether to disable Verify Rebuild (1, 0).
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_array_rebuild_verify(struct ipr_ioa *ioa, u8 disable_verify)
|
|
{
|
|
int len, rc;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_mode_page24 *page24;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (rc)
|
|
return rc;
|
|
|
|
page24 = ((struct ipr_mode_page24 *)
|
|
(((u8 *)&mode_pages)
|
|
+ mode_pages.hdr.block_desc_len
|
|
+ sizeof(mode_pages.hdr)));
|
|
|
|
if (page24->rebuild_without_verify == disable_verify)
|
|
return 0;
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
page24->rebuild_without_verify = disable_verify;
|
|
|
|
rc = ipr_mode_select(&ioa->ioa, &mode_pages, len);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Check if rebuild rate value was correctly set */
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (page24->rebuild_without_verify != disable_verify)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_array_rebuild_rate
|
|
* @ioa: ipr ioa struct
|
|
* @check_rate checkrate policy
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_array_rebuild_rate(struct ipr_ioa *ioa, u8 rebuild_rate)
|
|
{
|
|
int len, rc;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_mode_page24 *page24;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (rc)
|
|
return rc;
|
|
|
|
page24 = (struct ipr_mode_page24 *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
page24->rebuild_rate = rebuild_rate;
|
|
|
|
rc = ipr_mode_select(&ioa->ioa, &mode_pages, len);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Verify if rebuild rate value was correctly set */
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (rebuild_rate != page24->rebuild_rate)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_active_active_mode -
|
|
* @ioa: ipr ioa struct
|
|
* @mode: enable or disable
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_active_active_mode(struct ipr_ioa *ioa)
|
|
{
|
|
int len, rc;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_mode_page24 *page24;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (!rc) {
|
|
page24 = (struct ipr_mode_page24 *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
len = mode_pages.hdr.length +1;
|
|
mode_pages.hdr.length = 0;
|
|
page24->dual_adapter_af = ENABLE_DUAL_IOA_ACTIVE_ACTIVE;
|
|
return ipr_mode_select(&ioa->ioa, &mode_pages, len);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* set_active_active_mode -
|
|
* @ioa: ipr ioa struct
|
|
* @mode: enable or disable
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int set_active_active_mode(struct ipr_ioa *ioa, int mode)
|
|
{
|
|
struct ipr_ioa_attr attr;
|
|
int rc;
|
|
|
|
/* Get the current ioa attributes. */
|
|
rc = ipr_get_ioa_attr(ioa, &attr);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Get the saved ioa attributes from the config file. */
|
|
rc = ipr_modify_ioa_attr(ioa, &attr);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (attr.active_active == mode) {
|
|
/* We should never get here. */
|
|
ioa_dbg(ioa, "Saved and current asymmetric access mode are not the same.\n");
|
|
}
|
|
|
|
attr.active_active = mode;
|
|
return ipr_set_ioa_attr(ioa, &attr, 1);
|
|
}
|
|
|
|
|
|
int ipr_set_ses_mode(struct ipr_dev *dev, int mode) {
|
|
int rc;
|
|
struct sense_data_t sense_data;
|
|
u8 buff = (u8)mode;
|
|
|
|
rc = __ipr_write_buffer(dev, 0x1f, &buff, sizeof(buff), &sense_data);
|
|
return rc;
|
|
}
|
|
|
|
|
|
/**
|
|
* get_scsi_dev_data - get scsi device data
|
|
* @scsi_dev_ref: scsi_dev_data struct
|
|
*
|
|
* Returns:
|
|
* number of devices if success / 0 or -1 on failure
|
|
**/
|
|
int get_scsi_dev_data(struct scsi_dev_data **scsi_dev_ref)
|
|
{
|
|
int num_devs = 0;
|
|
DIR *dirfd;
|
|
struct dirent *dent;
|
|
struct scsi_dev_data *scsi_dev_data;
|
|
struct scsi_dev_data *scsi_dev_base = *scsi_dev_ref;
|
|
|
|
dirfd = opendir("/sys/class/scsi_device");
|
|
if (!dirfd) {
|
|
syslog_dbg("Failed to open scsi_device class. %m\n");
|
|
return 0;
|
|
}
|
|
while ((dent = readdir(dirfd)) != NULL) {
|
|
int host, channel, lun;
|
|
signed char id;
|
|
char devpath[PATH_MAX];
|
|
char buff[256];
|
|
ssize_t len;
|
|
|
|
if (sscanf(dent->d_name,"%d:%d:%hhd:%d",
|
|
&host, &channel, &id, &lun) != 4)
|
|
continue;
|
|
|
|
scsi_dev_base = realloc(scsi_dev_base,
|
|
sizeof(struct scsi_dev_data) * (num_devs + 1));
|
|
scsi_dev_data = &scsi_dev_base[num_devs];
|
|
scsi_dev_data->host = host;
|
|
scsi_dev_data->channel = channel;
|
|
scsi_dev_data->id = id;
|
|
scsi_dev_data->lun = lun;
|
|
strcpy(scsi_dev_data->sysfs_device_name, dent->d_name);
|
|
sprintf(devpath, "/sys/class/scsi_device/%s/device",
|
|
dent->d_name);
|
|
len = sysfs_read_attr(devpath, "type", buff, 256);
|
|
if (len > 0)
|
|
sscanf(buff, "%d", &scsi_dev_data->type);
|
|
|
|
//FIXME WHERE TO GET OPENS!!!
|
|
/*
|
|
sysfs_attr = sysfs_get_device_attr(sysfs_device_device, "opens");
|
|
sscanf(sysfs_attr->value "%d", &scsi_dev_data->opens);
|
|
*/ scsi_dev_data->opens = 0;
|
|
|
|
len = sysfs_read_attr(devpath, "state", buff, 256);
|
|
if (len > 0 && strstr(buff, "offline"))
|
|
scsi_dev_data->online = 0;
|
|
else
|
|
scsi_dev_data->online = 1;
|
|
|
|
len = sysfs_read_attr(devpath, "queue_depth", buff, 256);
|
|
if (len > 0)
|
|
sscanf(buff, "%d", &scsi_dev_data->qdepth);
|
|
|
|
len = sysfs_read_attr(devpath, "adapter_handle", buff, 256);
|
|
if (len > 0)
|
|
sscanf(buff, "%X", &scsi_dev_data->handle);
|
|
|
|
len = sysfs_read_attr(devpath, "vendor", buff, 256);
|
|
if (len > 0)
|
|
ipr_strncpy_0n(scsi_dev_data->vendor_id,
|
|
buff, IPR_VENDOR_ID_LEN);
|
|
|
|
len = sysfs_read_attr(devpath, "model", buff, 256);
|
|
if (len > 0)
|
|
ipr_strncpy_0n(scsi_dev_data->product_id,
|
|
buff, IPR_PROD_ID_LEN);
|
|
|
|
len = sysfs_read_attr(devpath, "resource_path", buff, 256);
|
|
if (len > 0)
|
|
ipr_strncpy_0n(scsi_dev_data->res_path,
|
|
buff, IPR_MAX_RES_PATH_LEN);
|
|
|
|
len = sysfs_read_attr(devpath, "device_id", buff, 256);
|
|
if (len > 0)
|
|
sscanf(buff, "%lX", &scsi_dev_data->device_id);
|
|
|
|
strcpy(scsi_dev_data->dev_name,"");
|
|
strcpy(scsi_dev_data->gen_name,"");
|
|
num_devs++;
|
|
}
|
|
closedir(dirfd);
|
|
|
|
*scsi_dev_ref = scsi_dev_base;
|
|
return num_devs;
|
|
}
|
|
|
|
/**
|
|
* wait_for_dev - wait 20 seconds for the device to become available
|
|
* @name: device name
|
|
*
|
|
* Returns:
|
|
* -ETIMEDOUT
|
|
**/
|
|
static int wait_for_dev(char *name)
|
|
{
|
|
int fd, delay;
|
|
|
|
for (delay = 20, fd = 0; delay; delay--) {
|
|
fd = open(name, O_RDWR);
|
|
if (fd != -1) {
|
|
close(fd);
|
|
break;
|
|
}
|
|
|
|
syslog_dbg("Waiting for %s to show up\n", name);
|
|
sleep(1);
|
|
}
|
|
|
|
if (fd == -1)
|
|
syslog_dbg("Failed to open %s.\n", name);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/**
|
|
* get_sg_names - waits for sg devices to become available
|
|
* @num_devs: number of devices
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_sg_names(int num_devs)
|
|
{
|
|
int i;
|
|
DIR *dirfd;
|
|
struct dirent *dent;
|
|
char devpath[PATH_MAX];
|
|
|
|
for (i = 0; i < num_devs; i++) {
|
|
sprintf(devpath, "/sys/class/scsi_device/%s/device/scsi_generic",
|
|
scsi_dev_table[i].sysfs_device_name);
|
|
dirfd = opendir(devpath);
|
|
if (!dirfd)
|
|
continue;
|
|
while((dent = readdir(dirfd)) != NULL) {
|
|
if (dent->d_name[0] == '.')
|
|
continue;
|
|
if (strncmp(dent->d_name, "sg", 2))
|
|
continue;
|
|
sprintf(scsi_dev_table[i].gen_name, "/dev/%s",
|
|
dent->d_name);
|
|
break;
|
|
}
|
|
closedir(dirfd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_sd_names - populate the scsi_dev_table dev_name entries
|
|
* @num_devs: number of devices
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_sd_names(int num_devs)
|
|
{
|
|
int i;
|
|
DIR *dirfd;
|
|
struct dirent *dent;
|
|
char devpath[PATH_MAX];
|
|
|
|
for (i = 0; i < num_devs; i++) {
|
|
sprintf(devpath, "/sys/class/scsi_device/%s/device/block",
|
|
scsi_dev_table[i].sysfs_device_name);
|
|
dirfd = opendir(devpath);
|
|
if (!dirfd)
|
|
continue;
|
|
while((dent = readdir(dirfd)) != NULL) {
|
|
if (dent->d_name[0] == '.')
|
|
continue;
|
|
if (strncmp(dent->d_name, "sd", 2))
|
|
continue;
|
|
sprintf(scsi_dev_table[i].dev_name, "/dev/%s",
|
|
dent->d_name);
|
|
break;
|
|
}
|
|
closedir(dirfd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_ioa_name - populate adapter information in the scsi_dev_table
|
|
* @ioa: ipr ioa struct
|
|
* @num_sg_devices: number of sg devices
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_ioa_name(struct ipr_ioa *ioa,
|
|
int num_sg_devices)
|
|
{
|
|
int i;
|
|
int host_no;
|
|
int channel = IPR_IOAFP_VIRTUAL_BUS;
|
|
int id = 0;
|
|
int lun = 0;
|
|
|
|
sscanf(ioa->host_name, "host%d", &host_no);
|
|
|
|
if (!ioa->sis64)
|
|
channel = id = lun = 255;
|
|
|
|
for (i = 0; i < num_sg_devices; i++) {
|
|
if (scsi_dev_table[i].host == host_no &&
|
|
scsi_dev_table[i].channel == channel &&
|
|
scsi_dev_table[i].id == id &&
|
|
scsi_dev_table[i].lun == lun &&
|
|
scsi_dev_table[i].type == IPR_TYPE_ADAPTER) {
|
|
strcpy(ioa->ioa.dev_name, scsi_dev_table[i].dev_name);
|
|
strcpy(ioa->ioa.gen_name, scsi_dev_table[i].gen_name);
|
|
ioa->ioa.scsi_dev_data = &scsi_dev_table[i];
|
|
ioa->ioa.ioa = ioa;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ipr_dual_ioa_state {
|
|
u8 state;
|
|
char *desc;
|
|
};
|
|
|
|
static struct ipr_dual_ioa_state dual_ioa_states [] = {
|
|
{IPR_IOA_STATE_PRIMARY, "Primary"},
|
|
{IPR_IOA_STATE_SECONDARY, "Secondary"},
|
|
{IPR_IOA_STATE_NO_PREFERENCE, "No Preference"}
|
|
};
|
|
|
|
/**
|
|
* print_ioa_state - copy the ioa state into buf
|
|
* @buf: data buffer
|
|
* @state: state
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void print_ioa_state(char *buf, u8 state)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dual_ioa_states); i++) {
|
|
if (dual_ioa_states[i].state == state) {
|
|
strcpy(buf, dual_ioa_states[i].desc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
sprintf(buf, "Unknown (%d)", state);
|
|
}
|
|
|
|
/**
|
|
* get_subsys_config -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_subsys_config(struct ipr_ioa *ioa)
|
|
{
|
|
int rc;
|
|
struct ipr_mode_pages pages;
|
|
struct ipr_mode_page25 *page;
|
|
struct ipr_config_term_hdr *hdr;
|
|
struct ipr_subsys_config_term *term;
|
|
struct sense_data_t sense_data;
|
|
|
|
ioa->in_gscsi_only_ha = 0;
|
|
|
|
if (!ioa->gscsi_only_ha)
|
|
return;
|
|
|
|
memset(&sense_data, 0, sizeof(sense_data));
|
|
rc = mode_sense(&ioa->ioa, 0x25, &pages, &sense_data);
|
|
|
|
if (!rc) {
|
|
page = (struct ipr_mode_page25 *)(pages.data + pages.hdr.block_desc_len);
|
|
|
|
for_each_page25_term(hdr, page) {
|
|
if (hdr->term_id != IPR_SUBSYS_CONFIG_TERM_ID)
|
|
continue;
|
|
|
|
term = (struct ipr_subsys_config_term *)hdr;
|
|
if (term->config == IPR_GSCSI_ONLY_HA_SUBSYS)
|
|
ioa->in_gscsi_only_ha = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* get_dual_ioa_state - set ioa->is_secondary to indicate the dual ioa state
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_dual_ioa_state(struct ipr_ioa *ioa)
|
|
{
|
|
int rc;
|
|
struct ipr_dual_ioa_entry *ioa_entry;
|
|
|
|
sprintf(ioa->dual_state, "Primary");
|
|
sprintf(ioa->preferred_dual_state, "No Preference");
|
|
ioa->is_secondary = 0;
|
|
|
|
if (!ioa->dual_raid_support)
|
|
return;
|
|
|
|
rc = ipr_query_multi_ioa_status(ioa, &ioa->ioa_status, sizeof(ioa->ioa_status));
|
|
|
|
if (rc)
|
|
return;
|
|
|
|
print_ioa_state(ioa->preferred_dual_state, ioa->ioa_status.cap.preferred_role);
|
|
|
|
if (ntohl(ioa->ioa_status.num_entries)) {
|
|
ioa_entry = (struct ipr_dual_ioa_entry *)
|
|
(((unsigned long)&ioa->ioa_status.cap) + ntohl(ioa->ioa_status.cap.length));
|
|
print_ioa_state(ioa->dual_state, ioa_entry->cur_state);
|
|
if (ioa_entry->cur_state == IPR_IOA_STATE_SECONDARY)
|
|
ioa->is_secondary = 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_af_block_size - return the af block size
|
|
* @ioa_cap: ipr_inquiry_ioa_cap struct
|
|
*
|
|
* Returns:
|
|
* the actual af_block_size or IPR_DEFAULT_AF_BLOCK_SIZE
|
|
**/
|
|
static u16 get_af_block_size(struct ipr_inquiry_ioa_cap *ioa_cap)
|
|
{
|
|
int sz_off = offsetof(struct ipr_inquiry_ioa_cap, af_block_size);
|
|
int len_off = offsetof(struct ipr_inquiry_ioa_cap, page_length);
|
|
|
|
if (ioa_cap->page_length > (sz_off - len_off))
|
|
return ntohs(ioa_cap->af_block_size);
|
|
return IPR_DEFAULT_AF_BLOCK_SIZE;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_cap - get the capability information for the ioa (inquiry page D0)
|
|
* Also, if page 01 is supported, then there is cache on the card.
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_ioa_cap(struct ipr_ioa *ioa)
|
|
{
|
|
int rc, j;
|
|
struct ipr_inquiry_page0 page0_inq;
|
|
struct ipr_inquiry_ioa_cap ioa_cap;
|
|
struct ipr_mode_page24 *page24;
|
|
struct ipr_mode_pages mode_pages;
|
|
|
|
ioa->af_block_size = IPR_DEFAULT_AF_BLOCK_SIZE;
|
|
ioa->tcq_mode = ioa_get_tcq_mode(ioa);
|
|
|
|
rc = ipr_inquiry(&ioa->ioa, 0, &page0_inq, sizeof(page0_inq));
|
|
|
|
if (rc) {
|
|
ioa->ioa_dead = 1;
|
|
return;
|
|
}
|
|
|
|
for (j = 0; j < page0_inq.page_length; j++) {
|
|
if (page0_inq.supported_page_codes[j] == 0x01) {
|
|
ioa->has_cache = 1;
|
|
continue;
|
|
}
|
|
if (page0_inq.supported_page_codes[j] == 0xC4) {
|
|
ioa->has_cache = 1;
|
|
continue;
|
|
}
|
|
if (page0_inq.supported_page_codes[j] != 0xD0)
|
|
continue;
|
|
rc = ipr_inquiry(&ioa->ioa, 0xD0, &ioa_cap, sizeof(ioa_cap));
|
|
if (rc)
|
|
break;
|
|
|
|
ioa->af_block_size = get_af_block_size(&ioa_cap);
|
|
ioa->support_4k = ioa_cap.af_4k_support;
|
|
|
|
if (ioa_cap.is_aux_cache)
|
|
ioa->is_aux_cache = 1;
|
|
if (ioa_cap.can_attach_to_aux_cache && ioa_cap.is_dual_wide)
|
|
ioa->protect_last_bus = 1;
|
|
if (ioa_cap.gscsi_only_ha)
|
|
ioa->gscsi_only_ha = 1;
|
|
|
|
if (ioa_cap.sis_format == IPR_SIS64)
|
|
ioa->sis64 = 1;
|
|
else {
|
|
if (ioa_cap.ra_id_encoding == IPR_2BIT_HOP)
|
|
ioa->hop_count = IPR_2BIT_HOP;
|
|
else
|
|
ioa->hop_count = IPR_3BIT_HOP;
|
|
}
|
|
|
|
if (ioa_cap.dual_ioa_raid || ioa_cap.dual_ioa_asymmetric_access) {
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x24, &mode_pages);
|
|
if (rc)
|
|
break;
|
|
|
|
page24 = (struct ipr_mode_page24 *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
ioa->rebuild_rate = page24->rebuild_rate;
|
|
if (ioa_cap.disable_array_rebuild_verify) {
|
|
ioa->configure_rebuild_verify = 1;
|
|
ioa->disable_rebuild_verify =
|
|
page24->rebuild_without_verify;
|
|
}
|
|
|
|
if (ioa_cap.dual_ioa_raid && page24->dual_adapter_af == ENABLE_DUAL_IOA_AF)
|
|
ioa->dual_raid_support = 1;
|
|
|
|
if (ioa_cap.dual_ioa_asymmetric_access) {
|
|
ioa->asymmetric_access = 1;
|
|
if (page24->dual_adapter_af == ENABLE_DUAL_IOA_ACTIVE_ACTIVE) {
|
|
ioa->dual_raid_support = 1;
|
|
ioa->asymmetric_access_enabled = 1;
|
|
} else {
|
|
ioa->asymmetric_access_enabled = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_prot_levels - populate the prot_level_str field for each array, for each
|
|
* vset and each device in the vset
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_prot_levels(struct ipr_ioa *ioa)
|
|
{
|
|
struct ipr_dev *array, *vset, *dev;
|
|
char *prot_level_str;
|
|
|
|
for_each_array(ioa, array) {
|
|
prot_level_str = get_prot_level_str(ioa->supported_arrays,
|
|
array->raid_level);
|
|
strncpy(array->prot_level_str, prot_level_str, 8);
|
|
}
|
|
|
|
for_each_vset(ioa, vset) {
|
|
prot_level_str = get_prot_level_str(ioa->supported_arrays,
|
|
vset->raid_level);
|
|
strncpy(vset->prot_level_str, prot_level_str, 8);
|
|
for_each_dev_in_vset(vset, dev)
|
|
strncpy(dev->prot_level_str, prot_level_str, 8);
|
|
}
|
|
}
|
|
|
|
void ipr_convert_res_path_to_bytes(struct ipr_dev *dev)
|
|
{
|
|
|
|
struct scsi_dev_data *scsi_dev_data = dev->scsi_dev_data;
|
|
int i;
|
|
char *startptr, *endptr;
|
|
|
|
if (scsi_dev_data) {
|
|
startptr = dev->res_path_name;
|
|
i = 0;
|
|
do {
|
|
dev->res_path[0].res_path_bytes[i++] = (u8)strtol(startptr, &endptr, 16);
|
|
startptr = endptr + 1;
|
|
} while (*endptr != '\0' && i < 8);
|
|
|
|
while ( i < 8 ) dev->res_path[0].res_path_bytes[i++] = 0xff;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_res_addr -
|
|
* @dev: ipr dev struct
|
|
* @res_addr: ipr_res_addr struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / -1 on failure
|
|
**/
|
|
static int get_res_addr(struct ipr_dev *dev, struct ipr_res_addr *res_addr)
|
|
{
|
|
struct ipr_dev_record *dev_record = dev->dev_rcd;
|
|
struct ipr_array_record *array_record = dev->array_rcd;
|
|
|
|
if (dev->scsi_dev_data) {
|
|
res_addr->host = dev->scsi_dev_data->host;
|
|
res_addr->bus = dev->scsi_dev_data->channel;
|
|
res_addr->target = dev->scsi_dev_data->id;
|
|
res_addr->lun = dev->scsi_dev_data->lun;
|
|
if (dev->ioa->sis64) {
|
|
strncpy(dev->res_path_name, dev->scsi_dev_data->res_path, strlen(dev->scsi_dev_data->res_path));
|
|
ipr_convert_res_path_to_bytes(dev);
|
|
}
|
|
} else if (ipr_is_af_dasd_device(dev)) {
|
|
if (dev_record && dev_record->no_cfgte_dev) {
|
|
res_addr->host = dev->ioa->host_num;
|
|
res_addr->bus = dev_record->type2.last_resource_addr.bus;
|
|
res_addr->target = dev_record->type2.last_resource_addr.target;
|
|
res_addr->lun = dev_record->type2.last_resource_addr.lun;
|
|
} else if (dev_record) {
|
|
res_addr->host = dev->ioa->host_num;
|
|
res_addr->bus = dev_record->type2.resource_addr.bus;
|
|
res_addr->target = dev_record->type2.resource_addr.target;
|
|
res_addr->lun = dev_record->type2.resource_addr.lun;
|
|
if (dev->ioa->sis64) {
|
|
ipr_format_res_path(dev_record->type3.res_path, dev->res_path_name, IPR_MAX_RES_PATH_LEN);
|
|
ipr_convert_res_path_to_bytes(dev);
|
|
}
|
|
} else
|
|
return -1;
|
|
} else if (ipr_is_volume_set(dev)) {
|
|
if (array_record && array_record->no_config_entry) {
|
|
res_addr->host = dev->ioa->host_num;
|
|
res_addr->bus = array_record->type2.last_resource_addr.bus;
|
|
res_addr->target = array_record->type2.last_resource_addr.target;
|
|
res_addr->lun = array_record->type2.last_resource_addr.lun;
|
|
} else if (array_record) {
|
|
res_addr->host = dev->ioa->host_num;
|
|
res_addr->bus = array_record->type2.resource_addr.bus;
|
|
res_addr->target = array_record->type2.resource_addr.target;
|
|
res_addr->lun = array_record->type2.resource_addr.lun;
|
|
} else
|
|
return -1;
|
|
} else
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* find_multipath_vset - return the other vset of a given multipath vset
|
|
* @vset1: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* ipr_dev if success / NULL on failure
|
|
**/
|
|
static struct ipr_dev *find_multipath_vset(struct ipr_dev *vset1)
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *vset2;
|
|
|
|
for_each_ioa(ioa) {
|
|
if (ioa == vset1->ioa)
|
|
continue;
|
|
for_each_vset(ioa, vset2) {
|
|
if (memcmp(vset1->vendor_id,
|
|
vset2->vendor_id,
|
|
IPR_VENDOR_ID_LEN))
|
|
continue;
|
|
if (memcmp(vset1->product_id,
|
|
vset2->product_id,
|
|
IPR_PROD_ID_LEN))
|
|
continue;
|
|
if (memcmp(vset1->serial_number,
|
|
vset2->serial_number,
|
|
IPR_SERIAL_NUM_LEN))
|
|
continue;
|
|
return vset2;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* link_multipath_vsets - set the alt_path entries for multipath vsets
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void link_multipath_vsets()
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_dev *vset1, *vset2;
|
|
|
|
for_each_ioa(ioa) {
|
|
for_each_vset(ioa, vset1) {
|
|
vset2 = find_multipath_vset(vset1);
|
|
if (!vset2)
|
|
continue;
|
|
vset1->alt_path = vset2;
|
|
vset2->alt_path = vset1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_format_res_path - Format the resource path into a string.
|
|
* @res_path: resource path
|
|
* @buf: buffer
|
|
* @len: buffer length
|
|
*
|
|
* Return value:
|
|
* none
|
|
**/
|
|
void ipr_format_res_path(u8 *res_path, char *buffer, int len)
|
|
{
|
|
int i;
|
|
char *p = buffer;
|
|
|
|
*p = '\0';
|
|
p += snprintf(p, buffer + len - p, "%02X", res_path[0]);
|
|
for (i = 1; res_path[i] != 0xff && ((i * 3) < len); i++)
|
|
p += snprintf(p, buffer + len - p, "-%02X", res_path[i]);
|
|
}
|
|
|
|
/**
|
|
* ipr_format_res_path_wo_hyphen - Format the resource path into a string.
|
|
* @res_path: resource path
|
|
* @buf: buffer
|
|
* @len: buffer length
|
|
*
|
|
* Return value:
|
|
* none
|
|
**/
|
|
void ipr_format_res_path_wo_hyphen(u8 *res_path, char *buffer, int len)
|
|
{
|
|
int i;
|
|
char *p = buffer;
|
|
|
|
*p = '\0';
|
|
p += snprintf(p, buffer + len - p, "%02X", res_path[0]);
|
|
for (i = 1; res_path[i] != 0xff && ((i * 3) < len); i++)
|
|
p += snprintf(p, buffer + len - p, "%02X", res_path[i]);
|
|
}
|
|
|
|
/**
|
|
* ipr_res_path_cmp - compare two resource paths
|
|
* @dev_res_path
|
|
* @scsi_res_path
|
|
*
|
|
* Returns:
|
|
* 1 if the paths are the same, 0 otherwise
|
|
**/
|
|
int ipr_res_path_cmp(u8 *dev_res_path, char *scsi_res_path)
|
|
{
|
|
char buffer[IPR_MAX_RES_PATH_LEN];
|
|
|
|
ipr_format_res_path(dev_res_path, buffer, IPR_MAX_RES_PATH_LEN);
|
|
return !strncmp(buffer, scsi_res_path, IPR_MAX_RES_PATH_LEN);
|
|
}
|
|
|
|
/**
|
|
* ipr_debug_dump_rcd - dump out a device record
|
|
* @rcd: record structure
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
*/
|
|
void ipr_debug_dump_rcd(struct ipr_common_record *rcd)
|
|
{
|
|
int i;
|
|
u8 *rcd_ptr = (u8 *)rcd;
|
|
|
|
dprintf("===========\n");
|
|
dprintf("Record ID = %d, Length = %d\n", rcd->record_id, rcd->record_len);
|
|
for (i=0; i<rcd->record_len; i++) {
|
|
dprintf("%02x", rcd_ptr[i]);
|
|
if ((i+1)%8 == 0)
|
|
dprintf(" ");
|
|
if ((i+1)%32 == 0)
|
|
dprintf("\n");
|
|
}
|
|
dprintf("\n");
|
|
}
|
|
|
|
/**
|
|
* ipr_get_logical_block_size - check the logical block size
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Return value:
|
|
* none
|
|
**/
|
|
int ipr_get_logical_block_size(struct ipr_dev *dev)
|
|
{
|
|
char path[PATH_MAX], *first_hyphen;
|
|
char buff[16];
|
|
ssize_t len;
|
|
int rc;
|
|
|
|
first_hyphen = strchr(dev->dev_name, 's');
|
|
sprintf(path, "/sys/block/%s/queue", first_hyphen);
|
|
|
|
len = sysfs_read_attr(path, "logical_block_size", buff, 16);
|
|
if (len < 0) {
|
|
syslog_dbg("Failed to open logical_block_size parameter.\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = atoi(buff);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* check_current_config - populates the ioa configuration data
|
|
* @allow_rebuild_refresh: allow_rebuild_refresh flag
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void check_current_config(bool allow_rebuild_refresh)
|
|
{
|
|
struct scsi_dev_data *scsi_dev_data;
|
|
int num_sg_devices, rc, device_count, j, k;
|
|
struct ipr_ioa *ioa;
|
|
struct ipr_array_query_data *qac_data;
|
|
struct ipr_common_record *common_record;
|
|
struct ipr_dev_record *device_record;
|
|
struct ipr_array_record *array_record;
|
|
struct ipr_std_inq_data std_inq_data;
|
|
struct sense_data_t sense_data;
|
|
struct ipr_res_addr res_addr, *ra;
|
|
struct ipr_dev *dev;
|
|
int *qac_entry_ref;
|
|
struct ipr_dev_identify_vpd di_vpd;
|
|
char *pchr;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_ioa_mode_page *page;
|
|
|
|
if (ipr_qac_data == NULL) {
|
|
ipr_qac_data =
|
|
(struct ipr_array_query_data *)
|
|
calloc(num_ioas, sizeof(struct ipr_array_query_data));
|
|
}
|
|
|
|
/* Get sg data via sysfs */
|
|
num_sg_devices = get_scsi_dev_data(&scsi_dev_table);
|
|
get_sg_names(num_sg_devices);
|
|
get_sd_names(num_sg_devices);
|
|
|
|
for(ioa = ipr_ioa_head, qac_data = ipr_qac_data;
|
|
ioa; ioa = ioa->next, qac_data++) {
|
|
get_ioa_name(ioa, num_sg_devices);
|
|
|
|
ioa->num_devices = 0;
|
|
|
|
rc = ipr_inquiry(&ioa->ioa, IPR_STD_INQUIRY,
|
|
&std_inq_data, sizeof(std_inq_data));
|
|
|
|
if (rc)
|
|
ioa->ioa_dead = 1;
|
|
|
|
get_ioa_cap(ioa);
|
|
get_dual_ioa_state(ioa);
|
|
get_subsys_config(ioa);
|
|
|
|
/* Get Query Array Config Data */
|
|
rc = ipr_query_array_config(ioa, allow_rebuild_refresh, 0, 0, 0, qac_data);
|
|
|
|
if (rc != 0) {
|
|
qac_data->num_records = 0;
|
|
qac_data->resp_len = qac_data->hdr_len;
|
|
}
|
|
|
|
ioa->qac_data = qac_data;
|
|
ioa->start_array_qac_entry = NULL;
|
|
|
|
device_count = 0;
|
|
memset(ioa->dev, 0, IPR_MAX_IOA_DEVICES * sizeof(struct ipr_dev));
|
|
qac_entry_ref = calloc(1, sizeof(int) * qac_data->num_records);
|
|
|
|
/* now assemble data pertaining to each individual device */
|
|
for (j = 0, scsi_dev_data = scsi_dev_table;
|
|
j < num_sg_devices; j++, scsi_dev_data++) {
|
|
if (scsi_dev_data->host != ioa->host_num)
|
|
continue;
|
|
|
|
if (scsi_dev_data->type == TYPE_DISK ||
|
|
scsi_dev_data->type == IPR_TYPE_AF_DISK ||
|
|
scsi_dev_data->type == IPR_TYPE_ADAPTER ||
|
|
scsi_dev_data->type == TYPE_ENCLOSURE ||
|
|
scsi_dev_data->type == TYPE_ROM ||
|
|
scsi_dev_data->type == TYPE_TAPE ||
|
|
scsi_dev_data->type == TYPE_PROCESSOR) {
|
|
ioa->dev[device_count].ioa = ioa;
|
|
ioa->dev[device_count].scsi_dev_data = scsi_dev_data;
|
|
ioa->dev[device_count].qac_entry = NULL;
|
|
strcpy(ioa->dev[device_count].dev_name,
|
|
scsi_dev_data->dev_name);
|
|
strcpy(ioa->dev[device_count].gen_name,
|
|
scsi_dev_data->gen_name);
|
|
|
|
if (scsi_dev_data->type == TYPE_DISK) {
|
|
if (ipr_dev_wcache_policy(&(ioa->dev[device_count])) == IPR_DEV_CACHE_WRITE_BACK)
|
|
ioa->dev[device_count].write_cache_policy =
|
|
IPR_DEV_CACHE_WRITE_BACK;
|
|
else
|
|
ioa->dev[device_count].write_cache_policy =
|
|
IPR_DEV_CACHE_WRITE_THROUGH;
|
|
}
|
|
|
|
if (ioa->support_4k && scsi_dev_data->type == TYPE_DISK) {
|
|
if (ipr_get_logical_block_size(&ioa->dev[device_count]) == IPR_JBOD_4K_BLOCK_SIZE)
|
|
ioa->dev[device_count].block_dev_class |= IPR_BLK_DEV_CLASS_4K;
|
|
}
|
|
/* find array config data matching resource entry */
|
|
k = 0;
|
|
for_each_qac_entry(common_record, qac_data) {
|
|
if (common_record->record_id == IPR_RECORD_ID_DEVICE_RECORD) {
|
|
device_record = (struct ipr_dev_record *)common_record;
|
|
if (device_record->type2.resource_addr.bus == scsi_dev_data->channel &&
|
|
device_record->type2.resource_addr.target == scsi_dev_data->id &&
|
|
device_record->type2.resource_addr.lun == scsi_dev_data->lun) {
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
qac_entry_ref[k]++;
|
|
break;
|
|
}
|
|
} else if (common_record->record_id == IPR_RECORD_ID_ARRAY_RECORD) {
|
|
array_record = (struct ipr_array_record *)common_record;
|
|
if (array_record->type2.resource_addr.bus == scsi_dev_data->channel &&
|
|
array_record->type2.resource_addr.target == scsi_dev_data->id &&
|
|
array_record->type2.resource_addr.lun == scsi_dev_data->lun) {
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
qac_entry_ref[k]++;
|
|
break;
|
|
}
|
|
} else if (common_record->record_id == IPR_RECORD_ID_DEVICE_RECORD_3) {
|
|
device_record = (struct ipr_dev_record *)common_record;
|
|
if (ipr_res_path_cmp(device_record->type3.res_path, scsi_dev_data->res_path)) {
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
qac_entry_ref[k]++;
|
|
break;
|
|
}
|
|
} else if (common_record->record_id == IPR_RECORD_ID_VSET_RECORD_3) {
|
|
array_record = (struct ipr_array_record *)common_record;
|
|
if (ipr_res_path_cmp(array_record->type3.res_path, scsi_dev_data->res_path)) {
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
qac_entry_ref[k]++;
|
|
break;
|
|
}
|
|
} else if (common_record->record_id == IPR_RECORD_ID_ARRAY_RECORD_3) {
|
|
array_record = (struct ipr_array_record *)common_record;
|
|
if (ipr_res_path_cmp(array_record->type3.res_path, scsi_dev_data->res_path)) {
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
qac_entry_ref[k]++;
|
|
break;
|
|
}
|
|
}
|
|
k++;
|
|
}
|
|
|
|
/* Send Test Unit Ready to start device if its a volume set */
|
|
/* xxx TODO try to remove this */
|
|
if (!ipr_fast && ipr_is_volume_set(&ioa->dev[device_count]))
|
|
__ipr_test_unit_ready(&ioa->dev[device_count], &sense_data);
|
|
|
|
device_count++;
|
|
} else if (scsi_dev_data->type == IPR_TYPE_ADAPTER) {
|
|
ioa->ioa.ioa = ioa;
|
|
ioa->ioa.scsi_dev_data = scsi_dev_data;
|
|
}
|
|
}
|
|
|
|
/* now scan qac device and array entries to see which ones have
|
|
not been referenced */
|
|
k = 0;
|
|
for_each_qac_entry(common_record, qac_data) {
|
|
if (qac_entry_ref[k] > 1)
|
|
syslog(LOG_ERR,
|
|
"Query Array Config entry referenced more than once\n");
|
|
|
|
if (common_record->record_id == IPR_RECORD_ID_SUPPORTED_ARRAYS) {
|
|
ioa->supported_arrays = (struct ipr_supported_arrays *)common_record;
|
|
} else if (!qac_entry_ref[k] &&
|
|
(ipr_is_device_record(common_record->record_id) ||
|
|
ipr_is_vset_record(common_record->record_id))) {
|
|
// TODO - type 3 array records????
|
|
array_record = (struct ipr_array_record *)common_record;
|
|
if (ipr_is_vset_record(common_record->record_id) &&
|
|
array_record->start_cand) {
|
|
ioa->start_array_qac_entry = array_record;
|
|
} else {
|
|
/* add phantom qac entry to ioa device list */
|
|
ioa->dev[device_count].scsi_dev_data = NULL;
|
|
ioa->dev[device_count].qac_entry = common_record;
|
|
ioa->dev[device_count].ioa = ioa;
|
|
strcpy(ioa->dev[device_count].dev_name, "");
|
|
strcpy(ioa->dev[device_count].gen_name, "");
|
|
device_count++;
|
|
}
|
|
}
|
|
k++;
|
|
}
|
|
|
|
ioa->num_devices = device_count;
|
|
free(qac_entry_ref);
|
|
}
|
|
|
|
for_each_ioa(ioa) {
|
|
for_each_dev(ioa, dev) {
|
|
get_res_addr(dev, &res_addr);
|
|
for_each_ra(ra, dev)
|
|
memcpy(ra, &res_addr, sizeof(*ra));
|
|
|
|
if (!dev || !dev->qac_entry)
|
|
continue;
|
|
|
|
if (dev->qac_entry->record_id == IPR_RECORD_ID_DEVICE_RECORD) {
|
|
dev->vendor_id = dev->dev_rcd->type2.vendor_id;
|
|
dev->product_id = dev->dev_rcd->type2.product_id;
|
|
dev->serial_number = dev->dev_rcd->type2.serial_number;
|
|
dev->array_id = dev->dev_rcd->type2.array_id;
|
|
dev->resource_handle = dev->dev_rcd->type2.resource_handle;
|
|
dev->block_dev_class = dev->dev_rcd->type2.block_dev_class;
|
|
if (dev->block_dev_class & IPR_SSD)
|
|
dev->read_intensive = dev->dev_rcd->type2.read_intensive;
|
|
} else if (dev->qac_entry->record_id == IPR_RECORD_ID_DEVICE_RECORD_3) {
|
|
dev->vendor_id = dev->dev_rcd->type3.vendor_id;
|
|
dev->product_id = dev->dev_rcd->type3.product_id;
|
|
dev->serial_number = dev->dev_rcd->type3.serial_number;
|
|
dev->array_id = dev->dev_rcd->type3.array_id;
|
|
dev->resource_handle = dev->dev_rcd->type3.resource_handle;
|
|
dev->block_dev_class = dev->dev_rcd->type3.block_dev_class;
|
|
if (dev->block_dev_class & IPR_SSD)
|
|
dev->read_intensive = dev->dev_rcd->type3.read_intensive;
|
|
} else if (dev->qac_entry->record_id == IPR_RECORD_ID_ARRAY_RECORD) {
|
|
dev->vendor_id = dev->array_rcd->type2.vendor_id;
|
|
dev->product_id = dev->array_rcd->type2.product_id;
|
|
dev->serial_number = dev->array_rcd->type2.serial_number;
|
|
dev->array_id = dev->array_rcd->type2.array_id;
|
|
dev->raid_level = dev->array_rcd->type2.raid_level;
|
|
dev->stripe_size = dev->array_rcd->type2.stripe_size;
|
|
dev->resource_handle = dev->array_rcd->type2.resource_handle;
|
|
dev->block_dev_class = dev->array_rcd->type2.block_dev_class;
|
|
if (dev->block_dev_class & IPR_SSD)
|
|
dev->read_intensive = dev->dev_rcd->type2.read_intensive;
|
|
} else if (dev->qac_entry->record_id == IPR_RECORD_ID_VSET_RECORD_3) {
|
|
dev->vendor_id = dev->array_rcd->type3.vendor_id;
|
|
dev->product_id = dev->array_rcd->type3.product_id;
|
|
dev->serial_number = dev->array_rcd->type3.serial_number;
|
|
dev->array_id = dev->array_rcd->type3.array_id;
|
|
dev->raid_level = dev->array_rcd->type3.raid_level;
|
|
dev->stripe_size = dev->array_rcd->type3.stripe_size;
|
|
dev->resource_handle = dev->array_rcd->type3.resource_handle;
|
|
dev->block_dev_class = dev->array_rcd->type3.block_dev_class;
|
|
if (dev->block_dev_class & IPR_SSD)
|
|
dev->read_intensive = dev->dev_rcd->type3.read_intensive;
|
|
} else if (dev->qac_entry->record_id == IPR_RECORD_ID_ARRAY_RECORD_3) {
|
|
dev->vendor_id = dev->array_rcd->type3.vendor_id;
|
|
dev->product_id = dev->array_rcd->type3.product_id;
|
|
dev->serial_number = dev->array_rcd->type3.serial_number;
|
|
dev->array_id = dev->array_rcd->type3.array_id;
|
|
dev->raid_level = dev->array_rcd->type3.raid_level;
|
|
dev->stripe_size = dev->array_rcd->type3.stripe_size;
|
|
dev->resource_handle = dev->array_rcd->type3.resource_handle;
|
|
dev->block_dev_class = dev->array_rcd->type3.block_dev_class;
|
|
if (dev->block_dev_class & IPR_SSD)
|
|
dev->read_intensive = dev->dev_rcd->type3.read_intensive;
|
|
}
|
|
}
|
|
get_prot_levels(ioa);
|
|
}
|
|
|
|
for_each_ioa(ioa) {
|
|
if (strlen((char *)ioa->yl_serial_num) == 0) {
|
|
memset(&di_vpd, 0, sizeof(di_vpd));
|
|
rc = ipr_inquiry(&ioa->ioa, 0x83, &di_vpd, sizeof(di_vpd));
|
|
if (!rc && ntohs(di_vpd.add_page_len) > 120) {
|
|
pchr = strstr((char *)&di_vpd.dev_identify_contxt[0],"SN");
|
|
if (pchr)
|
|
strncpy((char *)ioa->yl_serial_num, (pchr + 3), YL_SERIAL_NUM_LEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
set_devs_format_completed();
|
|
link_multipath_vsets();
|
|
ipr_cleanup_zeroed_devs();
|
|
resolve_old_config();
|
|
|
|
if (!first_time_check_zeroed_dev) {
|
|
for_each_ioa(ioa) {
|
|
for_each_dev(ioa, dev) {
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
ipr_mode_sense(dev, 0x20, &mode_pages);
|
|
|
|
page = (struct ipr_ioa_mode_page *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
if (page->format_completed)
|
|
ipr_add_zeroed_dev(dev);
|
|
}
|
|
}
|
|
}
|
|
first_time_check_zeroed_dev = 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* num_devices_opens - return usage count (number of opens) for a given device
|
|
* @host_num: host number
|
|
* @channel: channel number
|
|
* @id: id number
|
|
* @lun: lun number
|
|
*
|
|
* Returns:
|
|
* usage count (number of opens) for a given device
|
|
**/
|
|
/* xxx TODO delete */
|
|
int num_device_opens(int host_num, int channel, int id, int lun)
|
|
{
|
|
struct scsi_dev_data *scsi_dev_base = NULL;
|
|
int opens = 0;
|
|
int k, num_sg_devices;
|
|
|
|
/* Get sg data via sg proc file system */
|
|
num_sg_devices = get_scsi_dev_data(&scsi_dev_base);
|
|
|
|
/* find usage counts in scsi_dev_data */
|
|
for (k = 0; k < num_sg_devices; k++)
|
|
{
|
|
if ((host_num == scsi_dev_base[k].host) &&
|
|
(channel == scsi_dev_base[k].channel) &&
|
|
(id == scsi_dev_base[k].id) &&
|
|
(lun == scsi_dev_base[k].lun))
|
|
{
|
|
opens = scsi_dev_base[k].opens;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(scsi_dev_base);
|
|
return opens;
|
|
}
|
|
|
|
/**
|
|
* open_and_lock - Open and device file and lock it.
|
|
* @file_name: the name of the device to open
|
|
*
|
|
* Returns:
|
|
* file descriptor if success / -1 on failure
|
|
**/
|
|
int open_and_lock(char *file_name)
|
|
{
|
|
int fd;
|
|
int rc;
|
|
|
|
fd = open(file_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", file_name);
|
|
return fd;
|
|
}
|
|
|
|
rc = flock(fd, LOCK_EX);
|
|
if (rc) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not lock %s. %m\n", file_name);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
/**
|
|
* exit_on_error - exits program or error after cleaning up
|
|
* @s: string
|
|
* @...: arguments
|
|
*
|
|
* Returns:
|
|
* nothing - exits program on error
|
|
**/
|
|
void exit_on_error(char *s, ...)
|
|
{
|
|
va_list args;
|
|
char usr_str[256];
|
|
|
|
exit_func();
|
|
|
|
va_start(args, s);
|
|
vsprintf(usr_str, s, args);
|
|
va_end(args);
|
|
|
|
closelog();
|
|
openlog(tool_name, LOG_PERROR | LOG_PID | LOG_CONS, LOG_USER);
|
|
syslog(LOG_ERR,"%s",usr_str);
|
|
closelog();
|
|
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* ipr_config_file_hdr - prints the header to the ipr config file
|
|
* @file_name: file name
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_config_file_hdr(char *file_name)
|
|
{
|
|
FILE *fd;
|
|
char cmd_str[64];
|
|
|
|
if (strlen(file_name) == 0)
|
|
return;
|
|
|
|
/* check if file currently exists */
|
|
fd = fopen(file_name, "r");
|
|
if (fd) {
|
|
fclose(fd);
|
|
return;
|
|
}
|
|
|
|
/* be sure directory is present */
|
|
sprintf(cmd_str,"install -d %s",IPR_CONFIG_DIR);
|
|
system(cmd_str);
|
|
|
|
/* otherwise, create new file */
|
|
fd = fopen(file_name, "w");
|
|
if (fd == NULL) {
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", file_name);
|
|
return;
|
|
}
|
|
|
|
fprintf(fd,"# DO NOT EDIT! Software generated configuration file for\n");
|
|
fprintf(fd,"# ipr SCSI device subsystem\n\n");
|
|
fprintf(fd,"# Use iprconfig to configure\n");
|
|
fprintf(fd,"# See \"man iprconfig\" for more information\n\n");
|
|
fclose(fd);
|
|
}
|
|
|
|
/**
|
|
* ipr_save_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @category:
|
|
* @field:
|
|
* @value:
|
|
* @update:
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_save_attr(struct ipr_ioa *ioa, char *category,
|
|
char *field, char *value, int update)
|
|
{
|
|
char fname[64];
|
|
FILE *fd, *temp_fd;
|
|
char temp_fname[64], line[64];
|
|
int bus_found = 0;
|
|
int field_found = 0;
|
|
|
|
sprintf(fname, "%s%x_%s", IPR_CONFIG_DIR, ioa->ccin, ioa->pci_address);
|
|
ipr_config_file_hdr(fname);
|
|
|
|
fd = fopen(fname, "r");
|
|
if (fd == NULL)
|
|
return;
|
|
|
|
sprintf(temp_fname, "%s.1", fname);
|
|
temp_fd = fopen(temp_fname, "w");
|
|
if (temp_fd == NULL) {
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", temp_fname);
|
|
return;
|
|
}
|
|
|
|
while (fgets(line, 64, fd)) {
|
|
if (strstr(line, category) && line[0] != '#') {
|
|
bus_found = 1;
|
|
} else {
|
|
if (bus_found) {
|
|
if (line[0] == '[') {
|
|
bus_found = 0;
|
|
if (!field_found) {
|
|
if (!update)
|
|
fprintf(temp_fd, "# ");
|
|
|
|
fprintf(temp_fd,"%s %s\n",field,value);
|
|
}
|
|
}
|
|
|
|
if (strstr(line, field)) {
|
|
if (update)
|
|
sprintf(line,"%s %s\n",field,value);
|
|
|
|
field_found = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fputs(line, temp_fd);
|
|
}
|
|
|
|
if (!field_found) {
|
|
if (!bus_found)
|
|
fprintf(temp_fd,"\n%s\n", category);
|
|
|
|
if (!update)
|
|
fprintf(temp_fd, "# ");
|
|
|
|
fprintf(temp_fd,"%s %s\n", field, value);
|
|
}
|
|
|
|
if (rename(temp_fname, fname))
|
|
syslog(LOG_ERR, "Could not save %s.\n", fname);
|
|
fclose(fd);
|
|
fclose(temp_fd);
|
|
}
|
|
|
|
/**
|
|
* ipr_save_bus_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @bus: bus
|
|
* @field:
|
|
* @value:
|
|
* @update:
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_save_bus_attr(struct ipr_ioa *ioa, int bus,
|
|
char *field, char *value, int update)
|
|
{
|
|
char category[16];
|
|
sprintf(category,"[%s %d]", IPR_CATEGORY_BUS, bus);
|
|
ipr_save_attr(ioa, category, field, value, update);
|
|
}
|
|
|
|
/**
|
|
* ipr_save_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @field:
|
|
* @value:
|
|
* @update:
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_save_dev_attr(struct ipr_dev *dev, char *field,
|
|
char *value, int update)
|
|
{
|
|
char category[100];
|
|
if (dev->scsi_dev_data->device_id)
|
|
sprintf(category,"[%s %lx]", IPR_CATEGORY_DEVICE,
|
|
dev->scsi_dev_data->device_id);
|
|
else
|
|
sprintf(category,"[%s %d:%d:%d]", IPR_CATEGORY_DISK,
|
|
dev->scsi_dev_data->channel, dev->scsi_dev_data->id,
|
|
dev->scsi_dev_data->lun);
|
|
|
|
ipr_save_attr(dev->ioa, category, field, value, update);
|
|
}
|
|
|
|
/**
|
|
* ipr_save_ioa_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @field:
|
|
* @value:
|
|
* @update:
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_save_ioa_attr(struct ipr_ioa *ioa, char *field,
|
|
char *value, int update)
|
|
{
|
|
char category[16];
|
|
sprintf(category,"[%s]", IPR_CATEGORY_IOA);
|
|
ipr_save_attr(ioa, category, field, value, update);
|
|
}
|
|
|
|
/**
|
|
* ipr_get_saved_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @category:
|
|
* @field:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / RC_FAILED on failure
|
|
**/
|
|
static int ipr_get_saved_attr(struct ipr_ioa *ioa, char *category,
|
|
char *field, char *value)
|
|
{
|
|
FILE *fd;
|
|
char fname[100], line[100], *str_ptr;
|
|
int bus_found = 0;
|
|
|
|
sprintf(fname, "%s%x_%s", IPR_CONFIG_DIR, ioa->ccin, ioa->pci_address);
|
|
|
|
fd = fopen(fname, "r");
|
|
if (fd == NULL)
|
|
return RC_FAILED;
|
|
|
|
while (NULL != fgets(line, 100, fd)) {
|
|
if (line[0] != '#') {
|
|
if (strstr(line, category))
|
|
bus_found = 1;
|
|
else if (bus_found) {
|
|
if (line[0] == '[')
|
|
bus_found = 0;
|
|
|
|
if ((str_ptr = strstr(line, field))) {
|
|
str_ptr += strlen(field);
|
|
while (str_ptr[0] == ' ')
|
|
str_ptr++;
|
|
sscanf(str_ptr, "%s\n", value);
|
|
fclose(fd);
|
|
return RC_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fd);
|
|
return RC_FAILED;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_saved_bus_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @bus:
|
|
* @field:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / RC_FAILED on failure
|
|
**/
|
|
static int ipr_get_saved_bus_attr(struct ipr_ioa *ioa, int bus,
|
|
char *field, char *value)
|
|
{
|
|
char category[16];
|
|
sprintf(category,"[%s %d]", IPR_CATEGORY_BUS, bus);
|
|
return ipr_get_saved_attr(ioa, category, field, value);
|
|
}
|
|
|
|
/**
|
|
* ipr_get_saved_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @field:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / RC_FAILED on failure
|
|
**/
|
|
static int ipr_get_saved_dev_attr(struct ipr_dev *dev,
|
|
char *field, char *value)
|
|
{
|
|
char category[100];
|
|
int rc = RC_FAILED;
|
|
|
|
if (!dev->scsi_dev_data)
|
|
return -ENXIO;
|
|
|
|
if (dev->scsi_dev_data->device_id) {
|
|
sprintf(category,"[%s %lx]", IPR_CATEGORY_DEVICE,
|
|
dev->scsi_dev_data->device_id);
|
|
|
|
rc = ipr_get_saved_attr(dev->ioa, category, field, value);
|
|
}
|
|
|
|
if (rc) {
|
|
sprintf(category,"[%s %d:%d:%d]", IPR_CATEGORY_DISK,
|
|
dev->scsi_dev_data->channel, dev->scsi_dev_data->id,
|
|
dev->scsi_dev_data->lun);
|
|
rc = ipr_get_saved_attr(dev->ioa, category, field, value);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_saved_ioa_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @field:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / RC_FAILED on failure
|
|
**/
|
|
static int ipr_get_saved_ioa_attr(struct ipr_ioa *ioa,
|
|
char *field, char *value)
|
|
{
|
|
char category[16];
|
|
sprintf(category,"[%s]", IPR_CATEGORY_IOA);
|
|
return ipr_get_saved_attr(ioa, category, field, value);
|
|
}
|
|
|
|
#define GSCSI_TCQ_DEPTH 3
|
|
#define GSCSI_SAS_TCQ_DEPTH 16
|
|
#define AS400_TCQ_DEPTH 16
|
|
#define DEFAULT_TCQ_DEPTH 64
|
|
|
|
/**
|
|
* get_tcq_depth - return the proper queue depth for the given device
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* GSCSI_TCQ_DEPTH, GSCSI_SAS_TCQ_DEPTH, AS400_TCQ_DEPTH or DEFAULT_TCQ_DEPTH
|
|
**/
|
|
static int get_tcq_depth(struct ipr_dev *dev)
|
|
{
|
|
if (ipr_is_gscsi(dev)) {
|
|
if (ioa_is_spi(dev->ioa))
|
|
return GSCSI_TCQ_DEPTH;
|
|
else
|
|
return GSCSI_SAS_TCQ_DEPTH;
|
|
}
|
|
if (!dev->scsi_dev_data)
|
|
return AS400_TCQ_DEPTH;
|
|
if (!strncmp(dev->scsi_dev_data->vendor_id, "IBMAS400", 8))
|
|
return AS400_TCQ_DEPTH;
|
|
|
|
return DEFAULT_TCQ_DEPTH;
|
|
}
|
|
|
|
/**
|
|
* is_tagged -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
*
|
|
**/
|
|
static int is_tagged(struct ipr_dev *dev)
|
|
{
|
|
char temp[100];
|
|
|
|
if (!ipr_read_dev_attr(dev, "tcq_enable", temp, 100))
|
|
return strtoul(temp, NULL, 10);
|
|
else if (!ipr_read_dev_attr(dev, "queue_type", temp, 100))
|
|
return (strstr(temp, "none") ? 0 : 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* set_tagged -
|
|
* @dev: ipr dev struct
|
|
* @tcq_enabled:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int set_tagged(struct ipr_dev *dev, int tcq_enabled)
|
|
{
|
|
char temp[100];
|
|
|
|
if (!ipr_read_dev_attr(dev, "tcq_enable", temp, 100)) {
|
|
sprintf(temp, "%d", tcq_enabled);
|
|
return ipr_write_dev_attr(dev, "tcq_enable", temp);
|
|
}
|
|
|
|
if (!ipr_read_dev_attr(dev, "queue_type", temp, 100)) {
|
|
if (!tcq_enabled)
|
|
return ipr_write_dev_attr(dev, "queue_type", "none");
|
|
|
|
if (page0x0a_setup(dev))
|
|
return ipr_write_dev_attr(dev, "queue_type", "ordered");
|
|
else
|
|
return ipr_write_dev_attr(dev, "queue_type", "simple");
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* get_format_timeout -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* timeout value
|
|
**/
|
|
static int get_format_timeout(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_query_dasd_timeouts tos;
|
|
int rc, i, records, timeout;
|
|
char temp[100];
|
|
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
rc = ipr_query_dasd_timeouts(dev, &tos);
|
|
|
|
if (!rc) {
|
|
records = (ntohl(tos.length) - sizeof(tos.length)) / sizeof(tos.record[0]);
|
|
|
|
for (i = 0; i < records; i++) {
|
|
if (tos.record[i].op_code != FORMAT_UNIT)
|
|
continue;
|
|
if (IPR_TIMEOUT_RADIX_IS_MINUTE(tos.record[i].timeout))
|
|
return ((tos.record[i].timeout & IPR_TIMEOUT_MASK) * 60);
|
|
if (IPR_TIMEOUT_RADIX_IS_SECONDS(tos.record[i].timeout))
|
|
return tos.record[i].timeout & IPR_TIMEOUT_MASK;
|
|
scsi_dbg(dev, "Unknown timeout radix: %X\n",
|
|
(tos.record[i].timeout & IPR_TIMEOUT_RADIX_MASK));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
timeout = IPR_FORMAT_UNIT_TIMEOUT;
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_FORMAT_TIMEOUT, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &timeout);
|
|
|
|
return timeout;
|
|
}
|
|
|
|
static const struct ipr_dasd_timeout_record ipr_dasd_timeouts[] = {
|
|
{READ_10, 0, __constant_cpu_to_be16(30)},
|
|
{WRITE_10, 0, __constant_cpu_to_be16(30)},
|
|
{WRITE_VERIFY, 0, __constant_cpu_to_be16(30)},
|
|
{SKIP_READ, 0, __constant_cpu_to_be16(30)},
|
|
{SKIP_WRITE, 0, __constant_cpu_to_be16(30)}
|
|
};
|
|
|
|
struct ipr_dasd_timeouts {
|
|
u32 length;
|
|
struct ipr_dasd_timeout_record record[ARRAY_SIZE(ipr_dasd_timeouts) + 1];
|
|
};
|
|
|
|
/**
|
|
* ipr_set_dasd_timeouts -
|
|
* @dev: ipr dev struct
|
|
* @format_timeout:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_set_dasd_timeouts(struct ipr_dev *dev, int format_timeout)
|
|
{
|
|
int fd, rc, len;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
struct ipr_dasd_timeouts timeouts;
|
|
struct ipr_disk_attr attr;
|
|
char *name = dev->gen_name;
|
|
|
|
fd = open(name, O_RDWR);
|
|
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memcpy(timeouts.record, ipr_dasd_timeouts, sizeof(ipr_dasd_timeouts));
|
|
len = sizeof(timeouts) - sizeof(timeouts.record[0]);
|
|
|
|
if (!ipr_get_dev_attr(dev, &attr)) {
|
|
len = sizeof(timeouts);
|
|
|
|
if (!format_timeout)
|
|
format_timeout = attr.format_timeout;
|
|
|
|
timeouts.record[ARRAY_SIZE(ipr_dasd_timeouts)].op_code = FORMAT_UNIT;
|
|
if (format_timeout >= IPR_TIMEOUT_MASK) {
|
|
timeouts.record[ARRAY_SIZE(ipr_dasd_timeouts)].timeout =
|
|
(format_timeout / 60) | IPR_TIMEOUT_MINUTE_RADIX;
|
|
} else {
|
|
timeouts.record[ARRAY_SIZE(ipr_dasd_timeouts)].timeout =
|
|
format_timeout;
|
|
}
|
|
}
|
|
|
|
timeouts.length = htonl(len);
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
cdb[0] = SET_DASD_TIMEOUTS;
|
|
cdb[7] = (len >> 8) & 0xff;
|
|
cdb[8] = len & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, &timeouts,
|
|
len, SG_DXFER_TO_DEV,
|
|
&sense_data, SET_DASD_TIMEOUTS_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Set DASD timeouts", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_dev_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @attr: ipr_disk_attr struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_get_dev_attr(struct ipr_dev *dev, struct ipr_disk_attr *attr)
|
|
{
|
|
char temp[100];
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_ioa_mode_page *page;
|
|
|
|
if (ipr_read_dev_attr(dev, "queue_depth", temp, 100))
|
|
return -EIO;
|
|
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x20, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_ioa_mode_page *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
attr->queue_depth = page->max_tcq_depth;
|
|
} else
|
|
attr->queue_depth = strtoul(temp, NULL, 10);
|
|
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
if (attr->queue_depth < 2)
|
|
attr->tcq_enabled = 0;
|
|
else
|
|
attr->tcq_enabled = 1;
|
|
} else
|
|
attr->tcq_enabled = is_tagged(dev);
|
|
|
|
attr->format_timeout = get_format_timeout(dev);
|
|
attr->write_cache_policy = dev->write_cache_policy;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ipr_set_format_completed_bit(struct ipr_dev *dev)
|
|
{
|
|
int len;
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_ioa_mode_page *page;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x20, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_ioa_mode_page *) (((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
|
|
page->format_completed = 1;
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (ipr_mode_select(dev, &mode_pages, len))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
/**
|
|
* get_ioa_caching -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0
|
|
**/
|
|
int get_ioa_caching(struct ipr_ioa *ioa)
|
|
{
|
|
int rc;
|
|
int found = 0;
|
|
struct ipr_query_ioa_caching_info info;
|
|
struct ipr_global_cache_params_term *term;
|
|
int term_size = sizeof(struct ipr_query_ioa_caching_info);
|
|
|
|
memset(&info, 0, term_size);
|
|
|
|
rc = ipr_query_cache_parameters(ioa, &info, term_size);
|
|
if (rc)
|
|
return IPR_IOA_REQUESTED_CACHING_DEFAULT;
|
|
|
|
for_each_cache_term(term, &info)
|
|
if (term && term->term_id == IPR_CACHE_PARAM_TERM_ID) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
|
|
if (found == 1)
|
|
if (term->enable_caching_dual_ioa_failure == IPR_IOA_CACHING_DUAL_FAILURE_ENABLED)
|
|
if (term->disable_caching_requested == IPR_IOA_REQUESTED_CACHING_DISABLED)
|
|
return IPR_IOA_CACHING_DISABLED_DUAL_ENABLED;
|
|
else
|
|
return IPR_IOA_CACHING_DEFAULT_DUAL_ENABLED;
|
|
else
|
|
if (term->disable_caching_requested == IPR_IOA_REQUESTED_CACHING_DISABLED)
|
|
return IPR_IOA_REQUESTED_CACHING_DISABLED;
|
|
else
|
|
return IPR_IOA_REQUESTED_CACHING_DEFAULT;
|
|
else
|
|
return IPR_IOA_REQUESTED_CACHING_DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_ioa_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @attr: ipr_ioa_attr struct
|
|
*
|
|
* Returns:
|
|
* 0
|
|
**/
|
|
int ipr_get_ioa_attr(struct ipr_ioa *ioa, struct ipr_ioa_attr *attr)
|
|
{
|
|
attr->preferred_primary = 0;
|
|
attr->gscsi_only_ha = ioa->in_gscsi_only_ha;
|
|
attr->active_active = ioa->asymmetric_access_enabled;
|
|
attr->caching = get_ioa_caching(ioa);
|
|
attr->rebuild_rate = ioa->rebuild_rate;
|
|
attr->disable_rebuild_verify = ioa->disable_rebuild_verify;
|
|
|
|
if (!ioa->dual_raid_support)
|
|
return 0;
|
|
|
|
if (ioa->ioa_status.cap.preferred_role == IPR_IOA_STATE_PRIMARY)
|
|
attr->preferred_primary = 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_modify_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @attr: ipr_disk_attr struct
|
|
*
|
|
* Returns:
|
|
* 0
|
|
**/
|
|
int ipr_modify_dev_attr(struct ipr_dev *dev, struct ipr_disk_attr *attr)
|
|
{
|
|
char temp[100];
|
|
int rc;
|
|
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_QUEUE_DEPTH, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->queue_depth);
|
|
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_TCQ_ENABLED, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->tcq_enabled);
|
|
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_FORMAT_TIMEOUT, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->format_timeout);
|
|
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_WRITE_CACHE_POLICY, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->write_cache_policy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_modify_ioa_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @attr: ipr_ioa_attr struct
|
|
*
|
|
* Returns:
|
|
* 0
|
|
**/
|
|
int ipr_modify_ioa_attr(struct ipr_ioa *ioa, struct ipr_ioa_attr *attr)
|
|
{
|
|
char temp[100];
|
|
int rc;
|
|
|
|
rc = ipr_get_saved_ioa_attr(ioa, IPR_GSCSI_HA_ONLY, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->gscsi_only_ha);
|
|
|
|
rc = ipr_get_saved_ioa_attr(ioa, IPR_DUAL_ADAPTER_ACTIVE_ACTIVE, temp);
|
|
if (rc == RC_SUCCESS)
|
|
sscanf(temp, "%d", &attr->active_active);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @attr: ipr_disk_attr struct
|
|
* @save: save flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_dev_attr(struct ipr_dev *dev, struct ipr_disk_attr *attr, int save)
|
|
{
|
|
struct ipr_disk_attr old_attr;
|
|
char temp[100];
|
|
|
|
if (ipr_get_dev_attr(dev, &old_attr))
|
|
return -EIO;
|
|
|
|
if (attr->queue_depth != old_attr.queue_depth) {
|
|
sprintf(temp, "%d", attr->queue_depth);
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
if (ipr_setup_af_qdepth(dev, attr->queue_depth))
|
|
return -EIO;
|
|
} else {
|
|
if (ipr_write_dev_attr(dev, "queue_depth", temp))
|
|
return -EIO;
|
|
}
|
|
if (save)
|
|
ipr_save_dev_attr(dev, IPR_QUEUE_DEPTH, temp, 1);
|
|
}
|
|
|
|
if (attr->format_timeout != old_attr.format_timeout) {
|
|
if (ipr_is_af_dasd_device(dev)) {
|
|
sprintf(temp, "%d", attr->format_timeout);
|
|
if (ipr_set_dasd_timeouts(dev, attr->format_timeout))
|
|
return -EIO;
|
|
if (save)
|
|
ipr_save_dev_attr(dev, IPR_FORMAT_TIMEOUT, temp, 1);
|
|
}
|
|
}
|
|
|
|
if (attr->tcq_enabled != old_attr.tcq_enabled) {
|
|
if (!ipr_is_af_dasd_device(dev)) {
|
|
if (set_tagged(dev, attr->tcq_enabled))
|
|
return -EIO;
|
|
sprintf(temp, "%d", attr->tcq_enabled);
|
|
if (save)
|
|
ipr_save_dev_attr(dev, IPR_TCQ_ENABLED, temp, 1);
|
|
}
|
|
}
|
|
|
|
if (attr->write_cache_policy != old_attr.write_cache_policy) {
|
|
ipr_set_dev_wcache_policy(dev, attr->write_cache_policy);
|
|
if (save) {
|
|
sprintf(temp, "%d", attr->write_cache_policy);
|
|
ipr_save_dev_attr(dev, IPR_WRITE_CACHE_POLICY, temp, 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_ioa_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @attr: ipr_ioa_attr struct
|
|
* @save: save flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_ioa_attr(struct ipr_ioa *ioa, struct ipr_ioa_attr *attr, int save)
|
|
{
|
|
struct ipr_ioa_attr old_attr;
|
|
char temp[100];
|
|
int mode;
|
|
int rc;
|
|
|
|
if (ipr_get_ioa_attr(ioa, &old_attr))
|
|
return -EIO;
|
|
|
|
/* FIXME - preferred_primary and active_active may change at the same
|
|
* time. This code may need to change.
|
|
*/
|
|
|
|
if (attr->preferred_primary != old_attr.preferred_primary)
|
|
if (ipr_change_multi_adapter_assignment(ioa, attr->preferred_primary, IPR_PRESERVE_ASYMMETRIC_STATE))
|
|
return -EIO;
|
|
|
|
if (attr->gscsi_only_ha != old_attr.gscsi_only_ha) {
|
|
sprintf(temp, "%d", attr->gscsi_only_ha);
|
|
|
|
if (ipr_set_ha_mode(ioa, attr->gscsi_only_ha))
|
|
return -EIO;
|
|
if (save)
|
|
ipr_save_ioa_attr(ioa, IPR_GSCSI_HA_ONLY, temp, 1);
|
|
ipr_reset_adapter(ioa);
|
|
}
|
|
|
|
if (attr->active_active != old_attr.active_active && ioa->asymmetric_access) {
|
|
sprintf(temp, "%d", attr->active_active);
|
|
|
|
/* If setting active/active, use mode page 24.
|
|
* If clearing, reset the adapter and then use
|
|
* Change Multi Adapter Assignment. */
|
|
if (attr->active_active) {
|
|
if (ipr_set_active_active_mode(ioa))
|
|
return -EIO;
|
|
if (save)
|
|
ipr_save_ioa_attr(ioa, IPR_DUAL_ADAPTER_ACTIVE_ACTIVE, temp, 1);
|
|
} else {
|
|
if (save)
|
|
ipr_save_ioa_attr(ioa, IPR_DUAL_ADAPTER_ACTIVE_ACTIVE, temp, 1);
|
|
ipr_reset_adapter(ioa);
|
|
if (ipr_change_multi_adapter_assignment(ioa,
|
|
attr->preferred_primary,
|
|
attr->active_active))
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
if (attr->caching != old_attr.caching) {
|
|
if (attr->caching == IPR_IOA_REQUESTED_CACHING_DEFAULT)
|
|
mode = IPR_IOA_SET_CACHING_DEFAULT;
|
|
else
|
|
mode = IPR_IOA_SET_CACHING_DISABLED;
|
|
ipr_change_cache_parameters(ioa, mode);
|
|
}
|
|
|
|
if (attr->rebuild_rate != old_attr.rebuild_rate) {
|
|
rc = ipr_set_array_rebuild_rate(ioa, attr->rebuild_rate);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (save) {
|
|
sprintf(temp, "%d", attr->rebuild_rate);
|
|
ipr_save_ioa_attr(ioa, IPR_ARRAY_REBUILD_RATE, temp, 1);
|
|
}
|
|
}
|
|
|
|
if (attr->disable_rebuild_verify != old_attr.disable_rebuild_verify) {
|
|
rc = ipr_set_array_rebuild_verify(ioa,
|
|
attr->disable_rebuild_verify);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (save) {
|
|
sprintf(temp, "%d", attr->disable_rebuild_verify);
|
|
ipr_save_ioa_attr(ioa, IPR_ARRAY_DISABLE_REBUILD_VERIFY,
|
|
temp, 1);
|
|
}
|
|
}
|
|
|
|
get_dual_ioa_state(ioa); /* for preferred_primary */
|
|
get_subsys_config(ioa); /* for gscsi_only_ha */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_query_dasd_timeouts -
|
|
* @dev: ipr dev struct
|
|
* @timeouts: ipr_query_dasd_timesouts struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_dasd_timeouts(struct ipr_dev *dev,
|
|
struct ipr_query_dasd_timeouts *timeouts)
|
|
{
|
|
int fd, rc;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
char *name = dev->gen_name;
|
|
|
|
fd = open(name, O_RDWR);
|
|
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(timeouts, 0, sizeof(*timeouts));
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
cdb[0] = QUERY_DASD_TIMEOUTS;
|
|
cdb[7] = (sizeof(*timeouts) >> 8) & 0xff;
|
|
cdb[8] = sizeof(*timeouts) & 0xff;
|
|
|
|
rc = sg_ioctl(fd, cdb, timeouts,
|
|
sizeof(*timeouts), SG_DXFER_FROM_DEV,
|
|
&sense_data, SET_DASD_TIMEOUTS_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Query DASD timeouts", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_get_bus_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @sbus: ipr_scsi_buses struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_get_bus_attr(struct ipr_ioa *ioa, struct ipr_scsi_buses *sbus)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_mode_page_28 *page_28;
|
|
struct ipr_mode_page_28_scsi_dev_bus_attr *bus;
|
|
int rc, i, busno;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
memset(sbus, 0, sizeof(*sbus));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x28, &mode_pages);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
page_28 = (struct ipr_mode_page_28 *)(mode_pages.data + mode_pages.hdr.block_desc_len);
|
|
|
|
for_each_bus_attr(bus, page_28, i) {
|
|
busno = bus->res_addr.bus;
|
|
sbus->bus[busno].max_xfer_rate = ntohl(bus->max_xfer_rate);
|
|
sbus->bus[busno].qas_capability = bus->qas_capability;
|
|
sbus->bus[busno].scsi_id = bus->scsi_id;
|
|
sbus->bus[busno].bus_width = bus->bus_width;
|
|
sbus->bus[busno].extended_reset_delay = bus->extended_reset_delay;
|
|
sbus->bus[busno].min_time_delay = bus->min_time_delay;
|
|
sbus->num_buses++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_set_bus_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @sbus: ipr_scsi_buses struct
|
|
* @save: save flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_set_bus_attr(struct ipr_ioa *ioa, struct ipr_scsi_buses *sbus, int save)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_mode_page_28 *page_28;
|
|
struct ipr_mode_page_28_scsi_dev_bus_attr *bus;
|
|
struct ipr_scsi_buses old_settings;
|
|
int rc, i, busno, len;
|
|
int reset_needed = 0;
|
|
char value_str[64];
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
rc = ipr_mode_sense(&ioa->ioa, 0x28, &mode_pages);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ipr_get_bus_attr(ioa, &old_settings);
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
page_28 = (struct ipr_mode_page_28 *)
|
|
(mode_pages.data + mode_pages.hdr.block_desc_len);
|
|
|
|
for_each_bus_attr(bus, page_28, i) {
|
|
busno = bus->res_addr.bus;
|
|
bus->bus_width = sbus->bus[busno].bus_width;
|
|
if (save && bus->bus_width != old_settings.bus[busno].bus_width) {
|
|
sprintf(value_str, "%d", bus->bus_width);
|
|
ipr_save_bus_attr(ioa, i, IPR_BUS_WIDTH, value_str, 1);
|
|
}
|
|
|
|
bus->max_xfer_rate = htonl(sbus->bus[busno].max_xfer_rate);
|
|
if (save && bus->max_xfer_rate != old_settings.bus[busno].max_xfer_rate) {
|
|
sprintf(value_str, "%d",
|
|
IPR_BUS_XFER_RATE_TO_THRUPUT(sbus->bus[busno].max_xfer_rate,
|
|
bus->bus_width));
|
|
ipr_save_bus_attr(ioa, i, IPR_MAX_XFER_RATE_STR, value_str, 1);
|
|
}
|
|
|
|
bus->qas_capability = sbus->bus[busno].qas_capability;
|
|
if (save && bus->qas_capability != old_settings.bus[busno].qas_capability) {
|
|
sprintf(value_str, "%d", bus->qas_capability);
|
|
ipr_save_bus_attr(ioa, i, IPR_QAS_CAPABILITY, value_str, 1);
|
|
}
|
|
|
|
if (bus->scsi_id != sbus->bus[busno].scsi_id)
|
|
reset_needed = 1;
|
|
|
|
bus->scsi_id = sbus->bus[busno].scsi_id;
|
|
if (save && bus->scsi_id != old_settings.bus[busno].scsi_id) {
|
|
sprintf(value_str, "%d", bus->scsi_id);
|
|
ipr_save_bus_attr(ioa, i, IPR_HOST_SCSI_ID, value_str, 1);
|
|
}
|
|
|
|
bus->min_time_delay = sbus->bus[busno].min_time_delay;
|
|
if (save && bus->min_time_delay != old_settings.bus[busno].min_time_delay) {
|
|
sprintf(value_str, "%d", bus->min_time_delay);
|
|
ipr_save_bus_attr(ioa, i, IPR_MIN_TIME_DELAY, value_str, 1);
|
|
}
|
|
|
|
bus->extended_reset_delay = sbus->bus[busno].extended_reset_delay;
|
|
}
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
|
|
rc = ipr_mode_select(&ioa->ioa, &mode_pages, len);
|
|
|
|
if (reset_needed)
|
|
ipr_reset_adapter(ioa);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_modify_bus_attr -
|
|
* @ioa: ipr ioa struct
|
|
* @sbus: ipr_scsi_buses struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_modify_bus_attr(struct ipr_ioa *ioa, struct ipr_scsi_buses *sbus)
|
|
{
|
|
int i, rc, saved_value, max_xfer_rate;
|
|
char value_str[64];
|
|
|
|
for (i = 0; i < sbus->num_buses; i++) {
|
|
rc = ipr_get_saved_bus_attr(ioa, i, IPR_QAS_CAPABILITY, value_str);
|
|
if (rc == RC_SUCCESS) {
|
|
sscanf(value_str, "%d", &saved_value);
|
|
sbus->bus[i].qas_capability = saved_value;
|
|
}
|
|
|
|
if (ioa->scsi_id_changeable) {
|
|
rc = ipr_get_saved_bus_attr(ioa, i, IPR_HOST_SCSI_ID, value_str);
|
|
if (rc == RC_SUCCESS) {
|
|
sscanf(value_str, "%d", &saved_value);
|
|
sbus->bus[i].scsi_id = saved_value;
|
|
}
|
|
}
|
|
|
|
rc = ipr_get_saved_bus_attr(ioa, i, IPR_BUS_WIDTH, value_str);
|
|
if (rc == RC_SUCCESS) {
|
|
sscanf(value_str, "%d", &saved_value);
|
|
sbus->bus[i].bus_width = saved_value;
|
|
}
|
|
|
|
max_xfer_rate = get_max_bus_speed(ioa, i);
|
|
|
|
rc = ipr_get_saved_bus_attr(ioa, i, IPR_MAX_XFER_RATE_STR, value_str);
|
|
if (rc == RC_SUCCESS) {
|
|
sscanf(value_str, "%d", &saved_value);
|
|
|
|
if (saved_value <= max_xfer_rate) {
|
|
sbus->bus[i].max_xfer_rate =
|
|
IPR_BUS_THRUPUT_TO_XFER_RATE(saved_value, sbus->bus[i].bus_width);
|
|
} else {
|
|
sbus->bus[i].max_xfer_rate =
|
|
IPR_BUS_THRUPUT_TO_XFER_RATE(max_xfer_rate, sbus->bus[i].bus_width);
|
|
sprintf(value_str, "%d", max_xfer_rate);
|
|
ipr_save_bus_attr(ioa, i, IPR_MAX_XFER_RATE_STR, value_str, 1);
|
|
}
|
|
} else {
|
|
sbus->bus[i].max_xfer_rate =
|
|
IPR_BUS_THRUPUT_TO_XFER_RATE(max_xfer_rate, sbus->bus[i].bus_width);
|
|
}
|
|
|
|
rc = ipr_get_saved_bus_attr(ioa, i, IPR_MIN_TIME_DELAY, value_str);
|
|
|
|
if (rc == RC_SUCCESS) {
|
|
sscanf(value_str,"%d", &saved_value);
|
|
sbus->bus[i].min_time_delay = saved_value;
|
|
} else {
|
|
sbus->bus[i].min_time_delay = IPR_INIT_SPINUP_DELAY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_unsupp_af -
|
|
* @ing: ipr_std_ing_data struct
|
|
* @page3: ipr_dasd_inquiry_page3 struct
|
|
*
|
|
* Returns:
|
|
* unsupported_af_dasd struct
|
|
**/
|
|
struct unsupported_af_dasd *
|
|
get_unsupp_af(struct ipr_std_inq_data *inq,
|
|
struct ipr_dasd_inquiry_page3 *page3)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(unsupported_af); i++) {
|
|
for (j = 0; j < IPR_VENDOR_ID_LEN; j++) {
|
|
if (unsupported_af[i].compare_vendor_id_byte[j] &&
|
|
unsupported_af[i].vendor_id[j] != inq->vpids.vendor_id[j])
|
|
break;
|
|
}
|
|
|
|
if (j != IPR_VENDOR_ID_LEN)
|
|
continue;
|
|
|
|
for (j = 0; j < IPR_PROD_ID_LEN; j++) {
|
|
if (unsupported_af[i].compare_product_id_byte[j] &&
|
|
unsupported_af[i].product_id[j] != inq->vpids.product_id[j])
|
|
break;
|
|
}
|
|
|
|
if (j != IPR_PROD_ID_LEN)
|
|
continue;
|
|
|
|
for (j = 0; j < 4; j++) {
|
|
if (unsupported_af[i].lid_mask[j] &&
|
|
unsupported_af[i].lid[j] != page3->load_id[j])
|
|
break;
|
|
}
|
|
|
|
if (j != 4)
|
|
continue;
|
|
|
|
return &unsupported_af[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* disk_needs_msl -
|
|
* @unsupp_af: unsupported_af_dasd struct
|
|
* @inq: ipr_std_inq_data struct
|
|
*
|
|
* Returns:
|
|
* true if needs msl / false otherwise
|
|
**/
|
|
bool disk_needs_msl(struct unsupported_af_dasd *unsupp_af,
|
|
struct ipr_std_inq_data *inq)
|
|
{
|
|
u32 ros_rcv_ram_rsvd, min_ucode_level;
|
|
int j;
|
|
|
|
if (unsupp_af->supported_with_min_ucode_level) {
|
|
min_ucode_level = 0;
|
|
ros_rcv_ram_rsvd = 0;
|
|
|
|
for (j = 0; j < 4; j++) {
|
|
if (unsupp_af->min_ucode_mask[j]) {
|
|
min_ucode_level = (min_ucode_level << 8) |
|
|
unsupp_af->min_ucode_level[j];
|
|
ros_rcv_ram_rsvd = (ros_rcv_ram_rsvd << 8) |
|
|
inq->ros_rsvd_ram_rsvd[j];
|
|
}
|
|
}
|
|
|
|
if (min_ucode_level > ros_rcv_ram_rsvd)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* is_af_blocked -
|
|
* @dev: ipr dev struct
|
|
* @silent:
|
|
*
|
|
* Returns:
|
|
* true if blocked / false otherwise
|
|
**/
|
|
bool is_af_blocked(struct ipr_dev *dev, int silent)
|
|
{
|
|
int rc;
|
|
struct ipr_std_inq_data std_inq_data;
|
|
struct ipr_dasd_inquiry_page3 dasd_page3_inq;
|
|
struct unsupported_af_dasd *unsupp_af;
|
|
|
|
/* Zero out inquiry data */
|
|
memset(&std_inq_data, 0, sizeof(std_inq_data));
|
|
rc = ipr_inquiry(dev, IPR_STD_INQUIRY,
|
|
&std_inq_data, sizeof(std_inq_data));
|
|
|
|
if (rc != 0)
|
|
return false;
|
|
|
|
/* Issue page 3 inquiry */
|
|
memset(&dasd_page3_inq, 0, sizeof(dasd_page3_inq));
|
|
rc = ipr_inquiry(dev, 0x03,
|
|
&dasd_page3_inq, sizeof(dasd_page3_inq));
|
|
|
|
if (rc != 0)
|
|
return false;
|
|
|
|
unsupp_af = get_unsupp_af(&std_inq_data, &dasd_page3_inq);
|
|
|
|
if (!unsupp_af)
|
|
return false;
|
|
|
|
/* If we make it this far, we have a match into the table. Now,
|
|
determine if we need a certain level of microcode or if this
|
|
disk is not supported all together. */
|
|
if (unsupp_af->supported_with_min_ucode_level) {
|
|
if (disk_needs_msl(unsupp_af, &std_inq_data)) {
|
|
if (ipr_force) {
|
|
if (!silent)
|
|
scsi_err(dev, "Disk %s needs updated microcode "
|
|
"before transitioning to 522 bytes/sector "
|
|
"format. IGNORING SINCE --force USED!",
|
|
dev->gen_name);
|
|
return false;
|
|
} else {
|
|
if (!silent)
|
|
scsi_err(dev, "Disk %s needs updated microcode "
|
|
"before transitioning to 522 bytes/sector "
|
|
"format.", dev->gen_name);
|
|
return true;
|
|
}
|
|
}
|
|
} else {/* disk is not supported at all */
|
|
if (!silent)
|
|
syslog(LOG_ERR,"Disk %s canot be formatted to "
|
|
"522 bytes/sector.", dev->gen_name);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* ipr_read_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @attr:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_read_dev_attr(struct ipr_dev *dev, char *attr,
|
|
char *value, size_t value_len)
|
|
{
|
|
char *sysfs_dev_name;
|
|
char devpath[PATH_MAX];
|
|
ssize_t len;
|
|
|
|
if (!dev->scsi_dev_data) {
|
|
scsi_dbg(dev, "Cannot read dev attr %s. NULL scsi data\n", attr);
|
|
return -ENOENT;
|
|
}
|
|
sysfs_dev_name = dev->scsi_dev_data->sysfs_device_name;
|
|
sprintf(devpath, "/sys/class/scsi_device/%s/device", sysfs_dev_name);
|
|
len = sysfs_read_attr(devpath, attr, value, value_len);
|
|
if (len < 0) {
|
|
scsi_dbg(dev, "Failed to read %s attribute. %m\n", attr);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_write_dev_attr -
|
|
* @dev: ipr dev struct
|
|
* @attr:
|
|
* @value:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_write_dev_attr(struct ipr_dev *dev, char *attr, char *value)
|
|
{
|
|
char *sysfs_dev_name;
|
|
char devpath[PATH_MAX];
|
|
|
|
if (!dev->scsi_dev_data)
|
|
return -ENOENT;
|
|
|
|
sysfs_dev_name = dev->scsi_dev_data->sysfs_device_name;
|
|
sprintf(devpath, "/sys/class/scsi_device/%s/device", sysfs_dev_name);
|
|
if (sysfs_write_attr(devpath, attr, value, strlen(value)) < 0) {
|
|
scsi_dbg(dev, "Failed to write attribute: %s\n", attr);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* get_ucode_date -
|
|
* @ucode_file: microcode file name
|
|
* @ucode_date: microcode date string pointer
|
|
* @max_size: max size for the date field
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void get_ucode_date(char *ucode_file, char *ucode_date, int max_size)
|
|
{
|
|
struct stat st;
|
|
struct tm *file_tm;
|
|
|
|
ucode_date[0] = '\0';
|
|
if (stat(ucode_file, &st))
|
|
return;
|
|
|
|
file_tm = localtime(&st.st_mtime);
|
|
if (!file_tm)
|
|
return;
|
|
|
|
strftime(ucode_date, max_size, "%D", file_tm);
|
|
}
|
|
|
|
/**
|
|
* get_ioa_ucode_version -
|
|
* @ucode_file: microcode file name
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
u32 get_ioa_ucode_version(char *ucode_file)
|
|
{
|
|
int fd, rc;
|
|
struct stat ucode_stats;
|
|
struct ipr_ioa_ucode_header *image_hdr;
|
|
|
|
fd = open(ucode_file, O_RDONLY);
|
|
|
|
if (fd == -1)
|
|
return 0;
|
|
|
|
rc = fstat(fd, &ucode_stats);
|
|
|
|
if (rc != 0) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
image_hdr = mmap(NULL, ucode_stats.st_size,
|
|
PROT_READ, MAP_SHARED, fd, 0);
|
|
|
|
if (image_hdr == MAP_FAILED) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
rc = ntohl(image_hdr->rev_level);
|
|
munmap(image_hdr, ucode_stats.st_size);
|
|
close(fd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* fw_compare - compare two firmware images
|
|
* @parm1: pointer to firmware image
|
|
* @parm2: pointer to firmware image
|
|
*
|
|
* Returns:
|
|
* 0 if the images are the same / non-zero otherwise
|
|
**/
|
|
/* Sort in decending order */
|
|
static int fw_compare(const void *parm1,
|
|
const void *parm2)
|
|
{
|
|
struct ipr_fw_images *first = (struct ipr_fw_images *)parm1;
|
|
struct ipr_fw_images *second = (struct ipr_fw_images *)parm2;
|
|
|
|
return memcmp(&second->version, &first->version,
|
|
sizeof(second->version));
|
|
}
|
|
|
|
/**
|
|
* ipr_get_hotplug_dir -
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int ipr_get_hotplug_dir()
|
|
{
|
|
FILE *file;
|
|
char buf[100];
|
|
char *loc, *end;
|
|
|
|
file = fopen(FIRMWARE_HOTPLUG_CONFIG_FILE, "r");
|
|
|
|
if (!file) {
|
|
hotplug_dir = realloc(hotplug_dir, strlen(FIRMWARE_HOTPLUG_DEFAULT_DIR) + 1);
|
|
if (!hotplug_dir)
|
|
return -ENOMEM;
|
|
|
|
strcpy(hotplug_dir, FIRMWARE_HOTPLUG_DEFAULT_DIR);
|
|
return 0;
|
|
}
|
|
|
|
clearerr(file);
|
|
do {
|
|
if (feof(file)) {
|
|
syslog(LOG_ERR, "Failed parsing %s. Reached end of file.\n",
|
|
FIRMWARE_HOTPLUG_CONFIG_FILE);
|
|
return -EIO;
|
|
}
|
|
fgets(buf, 100, file);
|
|
loc = strstr(buf, FIRMWARE_HOTPLUG_DIR_TAG);
|
|
} while(!loc || buf[0] == '#');
|
|
|
|
loc = strchr(buf, '/');
|
|
|
|
fclose(file);
|
|
|
|
if (!loc) {
|
|
syslog(LOG_ERR, "Failed parsing %s.\n", FIRMWARE_HOTPLUG_CONFIG_FILE);
|
|
return -EIO;
|
|
}
|
|
|
|
end = strchr(loc, ' ');
|
|
|
|
if (!end)
|
|
end = strchr(loc, '"');
|
|
if (end)
|
|
*end = '\0';
|
|
|
|
hotplug_dir = realloc(hotplug_dir, strlen(loc) + 1);
|
|
|
|
if (!hotplug_dir)
|
|
return -ENOMEM;
|
|
|
|
strcpy(hotplug_dir, loc);
|
|
end = strchr(hotplug_dir, '\n');
|
|
if (end)
|
|
*end = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* get_dasd_ucode_version -
|
|
* @ucode_file: file name of microcode file
|
|
* @has_hdr: has header flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
u32 get_dasd_ucode_version(char *ucode_file, int has_hdr)
|
|
{
|
|
int fd;
|
|
unsigned int len;
|
|
struct stat ucode_stats;
|
|
struct ipr_dasd_ucode_header *hdr;
|
|
char *tmp;
|
|
u32 rc;
|
|
|
|
if (has_hdr) {
|
|
fd = open(ucode_file, O_RDONLY);
|
|
|
|
if (fd == -1)
|
|
return 0;
|
|
|
|
rc = fstat(fd, &ucode_stats);
|
|
|
|
if (rc != 0) {
|
|
fprintf(stderr, "Failed to stat %s\n", ucode_file);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
hdr = mmap(NULL, ucode_stats.st_size, PROT_READ,
|
|
MAP_SHARED, fd, 0);
|
|
|
|
if (hdr == MAP_FAILED) {
|
|
fprintf(stderr, "mmap of %s failed\n", ucode_file);
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
len = (hdr->length[0] << 16) | (hdr->length[1] << 8) | hdr->length[2];
|
|
|
|
if (len == ucode_stats.st_size) {
|
|
rc = (hdr->modification_level[0] << 24) |
|
|
(hdr->modification_level[1] << 16) |
|
|
(hdr->modification_level[2] << 8) |
|
|
hdr->modification_level[3];
|
|
|
|
munmap(hdr, ucode_stats.st_size);
|
|
close(fd);
|
|
return rc;
|
|
} else {
|
|
munmap(hdr, ucode_stats.st_size);
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
tmp = strrchr(ucode_file, '.');
|
|
|
|
if (!tmp)
|
|
return 0;
|
|
|
|
rc = strtoul(tmp+1, NULL, 16);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* get_ses_ucode_version -
|
|
* @ucode_file:
|
|
*
|
|
* Returns:
|
|
* ses microcode version / 0 if failure
|
|
**/
|
|
u32 get_ses_ucode_version(char *ucode_file)
|
|
{
|
|
char *tmp = strrchr(ucode_file, '.');
|
|
|
|
if (!tmp)
|
|
return 0;
|
|
if (strlen(tmp+1) < 4)
|
|
return 0;
|
|
|
|
return (tmp[1] << 24) | (tmp[2] << 16) | (tmp[3] << 8) | tmp[4];
|
|
}
|
|
|
|
/**
|
|
* get_dev_fw_version -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* device firmware version / 0 if failure
|
|
**/
|
|
u32 get_dev_fw_version(struct ipr_dev *dev)
|
|
{
|
|
u8 release_level[4];
|
|
int rc;
|
|
|
|
rc = ipr_get_fw_version(dev, release_level);
|
|
|
|
if (rc != 0) {
|
|
scsi_dbg(dev, "Inquiry failed\n");
|
|
return 0;
|
|
}
|
|
|
|
rc = release_level[0] << 24 | release_level[1] << 16 |
|
|
release_level[2] << 8 | release_level[3];
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_fw_version -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* ioa firmware version
|
|
**/
|
|
u32 get_ioa_fw_version(struct ipr_ioa *ioa)
|
|
{
|
|
char devpath[PATH_MAX];
|
|
char value[16];
|
|
ssize_t len;
|
|
u32 fw_version;
|
|
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_read_attr(devpath, "fw_version", value, 16);
|
|
if (len < 0)
|
|
return -1;
|
|
sscanf(value, "%8X", &fw_version);
|
|
|
|
return fw_version;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_image_type -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* ioa image type
|
|
**/
|
|
static u8 get_ioa_image_type(struct ipr_ioa *ioa)
|
|
{
|
|
u32 fw_version = get_ioa_fw_version(ioa);
|
|
return ((fw_version & 0x00ff0000) >> 16);
|
|
}
|
|
|
|
/**
|
|
* get_ioa_fw_name -
|
|
* @ioa: ipr ioa struct
|
|
* @buf: data buffer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_ioa_fw_name(struct ipr_ioa *ioa, char *buf)
|
|
{
|
|
const struct ioa_parms *ioa_parms = get_ioa_fw(ioa);
|
|
|
|
if (ioa_parms)
|
|
strcpy(buf, ioa_parms->fw_name);
|
|
else
|
|
sprintf(buf, "534953%02X", get_ioa_image_type(ioa));
|
|
}
|
|
|
|
/**
|
|
* get_linux_ioa_fw_name -
|
|
* @ioa: ipr ioa struct
|
|
* @buf: data buffer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_linux_ioa_fw_name(struct ipr_ioa *ioa, char *buf)
|
|
{
|
|
sprintf(buf, "pci.%04x%04x.%02x", ioa->pci_vendor, ioa->pci_device,
|
|
get_ioa_image_type(ioa));
|
|
}
|
|
|
|
/**
|
|
* get_linux_ioa_fw_name_capital -
|
|
* @ioa: ipr ioa struct
|
|
* @buf: data buffer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void get_linux_ioa_fw_name_capital(struct ipr_ioa *ioa, char *buf)
|
|
{
|
|
sprintf(buf, "pci.%04X%04X.%02X", ioa->pci_vendor, ioa->pci_device,
|
|
get_ioa_image_type(ioa));
|
|
}
|
|
|
|
/**
|
|
* init_ioa_ucode_entry -
|
|
* @img: ipr_fw_images struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void init_ioa_ucode_entry(struct ipr_fw_images *img)
|
|
{
|
|
img->version = get_ioa_ucode_version(img->file);
|
|
img->has_header = 0;
|
|
get_ucode_date(img->file, img->date, sizeof(img->date));
|
|
}
|
|
|
|
/**
|
|
* init_disk_ucode_entry -
|
|
* @img: ipr_fw_images
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void init_disk_ucode_entry(struct ipr_fw_images *img)
|
|
{
|
|
img->version = get_dasd_ucode_version(img->file, 1);
|
|
img->has_header = 1;
|
|
get_ucode_date(img->file, img->date, sizeof(img->date));
|
|
}
|
|
|
|
/**
|
|
* init_disk_ucode_entry_nohdr -
|
|
* @img: ipr_fw_images struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void init_disk_ucode_entry_nohdr(struct ipr_fw_images *img)
|
|
{
|
|
img->version = get_dasd_ucode_version(img->file, 0);
|
|
img->has_header = 0;
|
|
get_ucode_date(img->file, img->date, sizeof(img->date));
|
|
}
|
|
|
|
/**
|
|
* init_ses_ucode_entry_nohdr -
|
|
* @img: ipr_fw_images struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void init_ses_ucode_entry_nohdr(struct ipr_fw_images *img)
|
|
{
|
|
img->version = get_ses_ucode_version(img->file);
|
|
img->has_header = 0;
|
|
get_ucode_date(img->file, img->date, sizeof(img->date));
|
|
}
|
|
|
|
/**
|
|
* scan_fw_dir -
|
|
* @path:
|
|
* @name:
|
|
* @list:
|
|
* @len:
|
|
* @init function pointer to initialization function
|
|
*
|
|
* Returns:
|
|
* int len
|
|
**/
|
|
static int scan_fw_dir(char *path, char *name, struct ipr_fw_images **list, int len,
|
|
void (*init)(struct ipr_fw_images *))
|
|
{
|
|
struct dirent **dirent;
|
|
int rc, i;
|
|
struct ipr_fw_images *ret = *list;
|
|
|
|
rc = scandir(path, &dirent, NULL, alphasort);
|
|
|
|
for (i = 0; i < rc && rc > 0; i++) {
|
|
if (strstr(dirent[i]->d_name, name) == dirent[i]->d_name) {
|
|
ret = realloc(ret, sizeof(*ret) * (len + 1));
|
|
sprintf(ret[len].file, "%s/%s", path, dirent[i]->d_name);
|
|
init(&ret[len]);
|
|
len++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < rc; i++)
|
|
free(dirent[i]);
|
|
if (rc > 0)
|
|
free(dirent);
|
|
|
|
*list = ret;
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* get_ioa_firmware_image_list -
|
|
* @ioa: ipr ioa struct
|
|
* @list: ipr_fw_images struct
|
|
*
|
|
* Returns:
|
|
* length of list
|
|
**/
|
|
int get_ioa_firmware_image_list(struct ipr_ioa *ioa,
|
|
struct ipr_fw_images **list)
|
|
{
|
|
char buf[100];
|
|
struct ipr_fw_images *ret = NULL;
|
|
int len = 0;
|
|
|
|
*list = NULL;
|
|
|
|
get_ioa_fw_name(ioa, buf);
|
|
|
|
len = scan_fw_dir(UCODE_BASE_DIR, buf, &ret, len, init_ioa_ucode_entry);
|
|
|
|
get_linux_ioa_fw_name(ioa, buf);
|
|
|
|
len = scan_fw_dir(LINUX_UCODE_BASE_DIR, buf, &ret, len, init_ioa_ucode_entry);
|
|
|
|
get_linux_ioa_fw_name_capital(ioa, buf);
|
|
|
|
len = scan_fw_dir(LINUX_UCODE_BASE_DIR, buf, &ret, len, init_ioa_ucode_entry);
|
|
|
|
sprintf(buf, "ibmsis%X.img", ioa->ccin);
|
|
|
|
len = scan_fw_dir("/etc/microcode", buf, &ret, len, init_ioa_ucode_entry);
|
|
|
|
sprintf(buf, "ibmsis%X.img", get_ioa_image_type(ioa));
|
|
|
|
len = scan_fw_dir("/etc/microcode", buf, &ret, len, init_ioa_ucode_entry);
|
|
|
|
if (ret)
|
|
qsort(ret, len, sizeof(*ret), fw_compare);
|
|
|
|
*list = ret;
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* get_dasd_firmware_image_list-
|
|
* @dev: ipr dev struct
|
|
* @list: irp_fw_images struct
|
|
*
|
|
* Returns:
|
|
* length value if success / non-zero on failure
|
|
**/
|
|
int get_dasd_firmware_image_list(struct ipr_dev *dev,
|
|
struct ipr_fw_images **list)
|
|
{
|
|
char buf[100];
|
|
struct ipr_fw_images *ret = NULL;
|
|
struct ipr_dasd_inquiry_page3 page3_inq;
|
|
int rc;
|
|
int len = 0;
|
|
|
|
*list = NULL;
|
|
|
|
memset(&page3_inq, 0, sizeof(page3_inq));
|
|
rc = ipr_inquiry(dev, 3, &page3_inq, sizeof(page3_inq));
|
|
|
|
if (rc != 0) {
|
|
scsi_dbg(dev, "Inquiry failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
sprintf(buf, "%.7s.%02X%02X%02X%02X",
|
|
dev->scsi_dev_data->product_id,
|
|
page3_inq.load_id[0], page3_inq.load_id[1],
|
|
page3_inq.load_id[2], page3_inq.load_id[3]);
|
|
|
|
len = scan_fw_dir(UCODE_BASE_DIR, buf, &ret, len,
|
|
init_disk_ucode_entry_nohdr);
|
|
|
|
if (memcmp(dev->scsi_dev_data->vendor_id, "IBMAS400", 8) == 0)
|
|
sprintf(buf, "ibmsis%02X%02X%02X%02X.img",
|
|
page3_inq.load_id[0], page3_inq.load_id[1],
|
|
page3_inq.load_id[2], page3_inq.load_id[3]);
|
|
|
|
len = scan_fw_dir("/etc/microcode/device", buf, &ret, len,
|
|
init_disk_ucode_entry);
|
|
|
|
sprintf(buf, "IBM-%.7s.%02X%02X%02X%02X",
|
|
dev->scsi_dev_data->product_id,
|
|
page3_inq.load_id[0], page3_inq.load_id[1],
|
|
page3_inq.load_id[2], page3_inq.load_id[3]);
|
|
|
|
len = scan_fw_dir(LINUX_UCODE_BASE_DIR, buf, &ret, len,
|
|
init_disk_ucode_entry_nohdr);
|
|
|
|
if (len)
|
|
qsort(ret, len, sizeof(*ret), fw_compare);
|
|
else
|
|
scsi_dbg(dev, "Could not find device firmware file\n");
|
|
|
|
*list = ret;
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* get_ses_load_id-
|
|
* @dev: ipr dev struct
|
|
* @load_id:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int get_ses_load_id(struct ipr_dev *dev, u8 load_id[4])
|
|
{
|
|
struct ipr_dasd_inquiry_page3 page3_inq;
|
|
int rc;
|
|
|
|
memset(&page3_inq, 0, sizeof(page3_inq));
|
|
rc = ipr_inquiry(dev, 3, &page3_inq, sizeof(page3_inq));
|
|
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (ipr_is_ses(dev) && !__ioa_is_spi(dev->ioa))
|
|
memcpy(load_id, page3_inq.release_level, 4);
|
|
else
|
|
memcpy(load_id, page3_inq.load_id, 4);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* get_ses_firmware_image_list-
|
|
* @dev: ipr dev struct
|
|
* @list:
|
|
*
|
|
* Returns:
|
|
* length value if success / non-zero on failure
|
|
**/
|
|
int get_ses_firmware_image_list(struct ipr_dev *dev,
|
|
struct ipr_fw_images **list)
|
|
{
|
|
char buf[100];
|
|
struct ipr_fw_images *ret = NULL;
|
|
u8 load_id[4];
|
|
int rc;
|
|
int len = 0;
|
|
|
|
*list = NULL;
|
|
|
|
rc = get_ses_load_id(dev, load_id);
|
|
|
|
if (rc != 0) {
|
|
scsi_dbg(dev, "Inquiry failed\n");
|
|
return -EIO;
|
|
}
|
|
|
|
sprintf(buf, "%02X%02X%02X%02X", load_id[0], load_id[1],
|
|
load_id[2], load_id[3]);
|
|
|
|
len = scan_fw_dir(UCODE_BASE_DIR, buf, &ret, len,
|
|
init_ses_ucode_entry_nohdr);
|
|
|
|
sprintf(buf, "IBM-%02X%02X%02X%02X", load_id[0], load_id[1],
|
|
load_id[2], load_id[3]);
|
|
|
|
len = scan_fw_dir(LINUX_UCODE_BASE_DIR, buf, &ret, len,
|
|
init_ses_ucode_entry_nohdr);
|
|
|
|
if (len)
|
|
qsort(ret, len, sizeof(*ret), fw_compare);
|
|
else
|
|
scsi_dbg(dev, "Could not find device firmware file\n");
|
|
|
|
*list = ret;
|
|
return len;
|
|
}
|
|
|
|
struct ipr_ioa_desc {
|
|
u16 type;
|
|
const char *desc;
|
|
};
|
|
|
|
struct ipr_ioa_desc ioa_desc [] = {
|
|
{0x5702, "PCI-X Dual Channel Ultra320 SCSI Adapter [5702]"},
|
|
{0x1974, "PCI-X Dual Channel Ultra320 SCSI Adapter [1974]"},
|
|
{0x5703, "PCI-X Dual Channel Ultra320 SCSI RAID Adapter [5703]"},
|
|
{0x1975, "PCI-X Dual Channel Ultra320 SCSI RAID Adapter [1975]"},
|
|
{0x2780, "PCI-X Quad Channel Ultra320 SCSI RAID Adapter [2780]"},
|
|
{0x5709, "SCSI RAID Enablement Card for PCI-X Dual Channel Ultra320 SCSI Integrated Controller [5709]"},
|
|
{0x1976, "SCSI RAID Enablement Card for PCI-X Dual Channel Ultra320 SCSI Integrated Controller [1976]"},
|
|
{0x570A, "PCI-X Dual Channel SCSI Integrated Controller (Adapter bus) [570A]"},
|
|
{0x570B, "PCI-X Dual Channel SCSI Integrated Controller (Adapter bus) [570B]"}
|
|
};
|
|
|
|
/**
|
|
* get_long_ioa_desc-
|
|
* @type:
|
|
*
|
|
* Returns:
|
|
* IOA description if success / NULL on failure
|
|
**/
|
|
static const char *get_long_ioa_desc(u16 type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(ioa_desc)/sizeof(ioa_desc[0]); i++) {
|
|
if (type == ioa_desc[i].type)
|
|
return ioa_desc[i].desc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ipr_log_ucode_error - log a microcode error
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_log_ucode_error(struct ipr_ioa *ioa)
|
|
{
|
|
const char *desc = get_long_ioa_desc(ioa->ccin);
|
|
|
|
if (desc) {
|
|
syslog(LOG_ERR, "Could not find required level of microcode for IBM '%s'. "
|
|
"Please download the latest microcode from "
|
|
"http://techsupport.services.ibm.com/server/mdownload/download.html. "
|
|
"SCSI speeds will be limited to %d MB/s until updated microcode is downloaded.\n",
|
|
desc, IPR_SAFE_XFER_RATE);
|
|
} else {
|
|
syslog(LOG_ERR, "Could not find required level of microcode for IBM %04X. "
|
|
"Please download the latest microcode from "
|
|
"http://techsupport.services.ibm.com/server/mdownload/download.html. "
|
|
"SCSI speeds will be limited to %d MB/s until updated microcode is downloaded.\n",
|
|
ioa->ccin, IPR_SAFE_XFER_RATE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ipr_update_ioa_fw -
|
|
* @ioa: ipr ioa struct
|
|
* @image: pointer to fw image
|
|
* @force: force flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_update_ioa_fw(struct ipr_ioa *ioa,
|
|
struct ipr_fw_images *image, int force)
|
|
{
|
|
struct ipr_ioa_ucode_header *image_hdr;
|
|
struct ipr_ioa_ucode_ext_header *ext_hdr;
|
|
struct ipr_ioa_ucode_img_desc *img_desc;
|
|
struct stat ucode_stats;
|
|
u32 fw_version;
|
|
int fd, rc;
|
|
int ioafw = 1;
|
|
int host_num = ioa->host_num;
|
|
char *tmp;
|
|
char ucode_file[200];
|
|
DIR *dir;
|
|
char *img_file;
|
|
char cwd[200];
|
|
char devpath[PATH_MAX];
|
|
ssize_t len;
|
|
|
|
fw_version = get_ioa_fw_version(ioa);
|
|
|
|
if (fw_version >= ioa->msl && !force)
|
|
return 0;
|
|
|
|
if (ipr_get_hotplug_dir())
|
|
return 0;
|
|
|
|
fd = open(image->file, O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
syslog(LOG_ERR, "Could not open firmware file %s.\n", image->file);
|
|
return fd;
|
|
}
|
|
|
|
rc = fstat(fd, &ucode_stats);
|
|
|
|
if (rc != 0) {
|
|
syslog(LOG_ERR, "Failed to stat IOA firmware file: %s.\n", image->file);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
image_hdr = mmap(NULL, ucode_stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
|
|
|
if (image_hdr == MAP_FAILED) {
|
|
syslog(LOG_ERR, "Error mapping IOA firmware file: %s.\n", image->file);
|
|
close(fd);
|
|
return -EIO;
|
|
}
|
|
|
|
ext_hdr = (void *)image_hdr + ntohl(image_hdr->header_length);
|
|
img_desc = (void *)ext_hdr + ntohl(ext_hdr->img_desc_offset);
|
|
if (strncmp(img_desc->fw_type, IPR_IOAF_STR, 4))
|
|
ioafw = 0;
|
|
|
|
if (ntohl(image_hdr->rev_level) > fw_version || force) {
|
|
if (ioafw)
|
|
ioa_info(ioa, "Updating microcode from %08X to %08X.\n",
|
|
fw_version, ntohl(image_hdr->rev_level));
|
|
else
|
|
ioa_info(ioa, "Updating microcode to %08X.\n",
|
|
ntohl(image_hdr->rev_level));
|
|
|
|
/* Give the file name an absolute path if needed. */
|
|
if (image->file[0] != '/') {
|
|
getcwd(cwd, sizeof(cwd));
|
|
strcat(cwd, "/");
|
|
img_file = strcat(cwd, image->file);
|
|
} else
|
|
img_file = image->file;
|
|
|
|
tmp = strrchr(img_file, '/');
|
|
if (tmp)
|
|
tmp++;
|
|
else {
|
|
syslog(LOG_ERR, "Failed to find image name in %s\n",
|
|
img_file);
|
|
return -EIO;
|
|
}
|
|
|
|
dir = opendir(hotplug_dir);
|
|
if (!dir)
|
|
mkdir(hotplug_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
|
|
|
|
dir = opendir(hotplug_dir);
|
|
if (!dir) {
|
|
syslog(LOG_ERR, "Failed to open %s. %m\n", hotplug_dir);
|
|
munmap(image_hdr, ucode_stats.st_size);
|
|
close(fd);
|
|
return -EIO;
|
|
}
|
|
closedir(dir);
|
|
sprintf(ucode_file, "%s/.%s", hotplug_dir, tmp);
|
|
symlink(img_file, ucode_file);
|
|
sprintf(ucode_file, ".%s\n", tmp);
|
|
sprintf(devpath, "/sys/class/scsi_host/%s", ioa->host_name);
|
|
len = sysfs_write_attr(devpath, "update_fw",
|
|
ucode_file, strlen(ucode_file));
|
|
sprintf(ucode_file, "%s/.%s", hotplug_dir, tmp);
|
|
unlink(ucode_file);
|
|
|
|
if (len < 0)
|
|
ioa_err(ioa, "Microcode update failed. rc=%d\n",
|
|
(int)len);
|
|
check_current_config(false);
|
|
for_each_ioa(ioa) {
|
|
if (ioa->host_num != host_num)
|
|
continue;
|
|
ipr_init_ioa(ioa);
|
|
break;
|
|
}
|
|
} else
|
|
ipr_log_ucode_error(ioa);
|
|
|
|
munmap(image_hdr, ucode_stats.st_size);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ipr_update_disk_fw - update disk fw
|
|
* @dev: ipr dev struct
|
|
* @image: pointer to fw image
|
|
* @force: force flag
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
/* xxx TODO make a general routine to do write buffer that takes a
|
|
struct ipr_fw_images as input */
|
|
int ipr_update_disk_fw(struct ipr_dev *dev,
|
|
struct ipr_fw_images *image, int force)
|
|
{
|
|
int rc = 0;
|
|
struct stat ucode_stats;
|
|
int fd;
|
|
struct ipr_dasd_ucode_header *img_hdr;
|
|
struct ipr_dasd_inquiry_page3 page3_inq;
|
|
struct ipr_std_inq_data std_inq_data;
|
|
struct unsupported_af_dasd *unsupp_af;
|
|
u32 level;
|
|
u8 release_level[4];
|
|
|
|
memset(&std_inq_data, 0, sizeof(std_inq_data));
|
|
rc = ipr_inquiry(dev, IPR_STD_INQUIRY,
|
|
&std_inq_data, sizeof(std_inq_data));
|
|
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
rc = ipr_inquiry(dev, 3, &page3_inq, sizeof(page3_inq));
|
|
|
|
if (rc != 0) {
|
|
scsi_dbg(dev, "Inquiry failed\n");
|
|
return rc;
|
|
}
|
|
|
|
if (!force) {
|
|
unsupp_af = get_unsupp_af(&std_inq_data, &page3_inq);
|
|
if (!unsupp_af)
|
|
return 0;
|
|
|
|
if (!disk_needs_msl(unsupp_af, &std_inq_data))
|
|
return 0;
|
|
}
|
|
|
|
fd = open(image->file, O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
syslog_dbg("Could not open firmware file %s.\n", image->file);
|
|
return fd;
|
|
}
|
|
|
|
rc = fstat(fd, &ucode_stats);
|
|
|
|
if (rc != 0) {
|
|
syslog(LOG_ERR, "Failed to stat firmware file: %s.\n", image->file);
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
img_hdr = mmap(NULL, ucode_stats.st_size,
|
|
PROT_READ, MAP_SHARED, fd, 0);
|
|
|
|
if (img_hdr == MAP_FAILED) {
|
|
syslog(LOG_ERR, "Error reading firmware file: %s.\n", image->file);
|
|
close(fd);
|
|
return -EIO;
|
|
}
|
|
|
|
level = htonl(image->version);
|
|
__ipr_get_fw_version(dev, &page3_inq, release_level);
|
|
|
|
if (memcmp(&level, page3_inq.release_level, 4) > 0 || force) {
|
|
scsi_info(dev, "Updating device microcode using %s "
|
|
"from %02X%02X%02X%02X (%c%c%c%c) to %08X (%c%c%c%c)\n", image->file,
|
|
release_level[0], release_level[1], release_level[2], release_level[3],
|
|
release_level[0], release_level[1], release_level[2], release_level[3],
|
|
image->version, image->version >> 24, (image->version >> 16) & 0xff,
|
|
(image->version >> 8) & 0xff, image->version & 0xff);
|
|
|
|
rc = ipr_write_buffer(dev, img_hdr, ucode_stats.st_size);
|
|
ipr_init_dev(dev);
|
|
}
|
|
|
|
if (munmap(img_hdr, ucode_stats.st_size))
|
|
syslog(LOG_ERR, "munmap failed.\n");
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* mode_select - issue a mode select command
|
|
* @dev: ipr dev struct
|
|
* @buff: data buffer
|
|
* @length:
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int mode_select(struct ipr_dev *dev, void *buff, int length)
|
|
{
|
|
int fd;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
char *name = dev->gen_name;
|
|
|
|
fd = open(name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = MODE_SELECT;
|
|
cdb[1] = 0x11;
|
|
cdb[4] = length;
|
|
|
|
rc = sg_ioctl(fd, cdb, buff,
|
|
length, SG_DXFER_TO_DEV,
|
|
&sense_data, IPR_INTERNAL_TIMEOUT);
|
|
|
|
if (rc)
|
|
scsi_cmd_err(dev, &sense_data, "Mode Select", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* setup_page0x00 -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int setup_page0x00(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages, ch_mode_pages;
|
|
struct ipr_vendor_mode_page *page, *ch_page;
|
|
int len;
|
|
|
|
if (strncmp(dev->scsi_dev_data->vendor_id, "IBM", 3)) {
|
|
scsi_dbg(dev, "Not setting up mode page 0x00 for unknown device.\n");
|
|
return 0;
|
|
}
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
memset(&ch_mode_pages, 0, sizeof(ch_mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x00, &mode_pages))
|
|
return -EIO;
|
|
if (ipr_mode_sense(dev, 0x40, &ch_mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_vendor_mode_page *)(((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
ch_page = (struct ipr_vendor_mode_page *)(((u8 *)&ch_mode_pages) +
|
|
ch_mode_pages.hdr.block_desc_len +
|
|
sizeof(ch_mode_pages.hdr));
|
|
|
|
IPR_SET_MODE(ch_page->arhes, page->arhes, 1);
|
|
IPR_SET_MODE(ch_page->cmdac, page->cmdac, 1);
|
|
IPR_SET_MODE(ch_page->caen, page->caen, 1);
|
|
|
|
/* Use a 3 second command aging timer - units are 50 ms */
|
|
IPR_SET_MODE(ch_page->cmd_aging_limit_hi, page->cmd_aging_limit_hi, 0);
|
|
IPR_SET_MODE(ch_page->cmd_aging_limit_lo, page->cmd_aging_limit_lo, 60);
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (mode_select(dev, &mode_pages, len)) {
|
|
scsi_dbg(dev, "Failed to setup mode page 0x00\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* setup_page0x01 -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int setup_page0x01(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages, ch_mode_pages;
|
|
struct ipr_rw_err_mode_page *page, *ch_page;
|
|
int len;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
memset(&ch_mode_pages, 0, sizeof(ch_mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x01, &mode_pages))
|
|
return -EIO;
|
|
if (ipr_mode_sense(dev, 0x41, &ch_mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_rw_err_mode_page *)(((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
ch_page = (struct ipr_rw_err_mode_page *)(((u8 *)&ch_mode_pages) +
|
|
ch_mode_pages.hdr.block_desc_len +
|
|
sizeof(ch_mode_pages.hdr));
|
|
|
|
IPR_SET_MODE(ch_page->awre, page->awre, 1);
|
|
IPR_SET_MODE(ch_page->arre, page->arre, 1);
|
|
|
|
if (page->awre != 1)
|
|
goto error;
|
|
if (page->arre != 1)
|
|
goto error;
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (mode_select(dev, &mode_pages, len)) {
|
|
error:
|
|
scsi_err(dev, "Failed to setup mode page 0x01\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* setup_page0x08 - Perform initial configuration for mode page 0x8 of
|
|
* vset devices.
|
|
*
|
|
* This disables the Write Cache for vsets.
|
|
*
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int setup_page0x08(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages;
|
|
struct ipr_caching_parameters_page *page;
|
|
int len;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x08, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = ((struct ipr_caching_parameters_page *)
|
|
(((u8 *)&mode_pages)
|
|
+ mode_pages.hdr.block_desc_len
|
|
+ sizeof(mode_pages.hdr)));
|
|
|
|
if (page->wce == 0) {
|
|
/* Write cache is already disabled */
|
|
return 0;
|
|
}
|
|
|
|
page->wce = 0;
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (mode_select(dev, &mode_pages, len)) {
|
|
scsi_err(dev, "Failed to disable write cache.\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (ipr_mode_sense(dev, 0x08, &mode_pages))
|
|
return -EIO;
|
|
|
|
page = ((struct ipr_caching_parameters_page *)
|
|
(((u8 *)&mode_pages)
|
|
+ mode_pages.hdr.block_desc_len
|
|
+ sizeof(mode_pages.hdr)));
|
|
|
|
if (page->wce != 0) {
|
|
scsi_err(dev, "Failed to disable write cache.\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* setup_page0x0a -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
static int setup_page0x0a(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_mode_pages mode_pages, ch_mode_pages;
|
|
struct ipr_control_mode_page *page, *ch_page;
|
|
int len;
|
|
int rc = 0;
|
|
|
|
memset(&mode_pages, 0, sizeof(mode_pages));
|
|
memset(&ch_mode_pages, 0, sizeof(ch_mode_pages));
|
|
|
|
if (ipr_mode_sense(dev, 0x0A, &mode_pages))
|
|
return -EIO;
|
|
if (ipr_mode_sense(dev, 0x4A, &ch_mode_pages))
|
|
return -EIO;
|
|
|
|
page = (struct ipr_control_mode_page *)(((u8 *)&mode_pages) +
|
|
mode_pages.hdr.block_desc_len +
|
|
sizeof(mode_pages.hdr));
|
|
ch_page = (struct ipr_control_mode_page *)(((u8 *)&ch_mode_pages) +
|
|
ch_mode_pages.hdr.block_desc_len +
|
|
sizeof(ch_mode_pages.hdr));
|
|
|
|
IPR_SET_MODE(ch_page->queue_algorithm_modifier,
|
|
page->queue_algorithm_modifier, 1);
|
|
IPR_SET_MODE(ch_page->tst, page->tst, 1);
|
|
IPR_SET_MODE(ch_page->tas, page->tas, 1);
|
|
|
|
switch(dev->ioa->tcq_mode) {
|
|
case IPR_TCQ_FROZEN:
|
|
IPR_SET_MODE(ch_page->qerr, page->qerr, 3);
|
|
if (page->qerr != 3) {
|
|
IPR_SET_MODE(ch_page->qerr, page->qerr, 1);
|
|
if (page->tst != 1)
|
|
IPR_SET_MODE(ch_page->tst, page->tst, 0);
|
|
}
|
|
|
|
scsi_dbg(dev, "Using control mode settings: "
|
|
"TST=%d, QERR=%d, TAS=%d\n", page->tst, page->qerr, page->tas);
|
|
break;
|
|
case IPR_TCQ_NACA:
|
|
IPR_SET_MODE(ch_page->qerr, page->qerr, 0);
|
|
break;
|
|
case IPR_TCQ_DISABLE:
|
|
default:
|
|
rc = -EIO;
|
|
break;
|
|
};
|
|
|
|
IPR_SET_MODE(ch_page->dque, page->dque, 0);
|
|
|
|
if (page->dque != 0) {
|
|
scsi_dbg(dev, "Cannot set dque=0\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (page->queue_algorithm_modifier != 1)
|
|
scsi_dbg(dev, "Cannot set QAM=1\n");
|
|
|
|
len = mode_pages.hdr.length + 1;
|
|
mode_pages.hdr.length = 0;
|
|
mode_pages.hdr.medium_type = 0;
|
|
mode_pages.hdr.device_spec_parms = 0;
|
|
page->hdr.parms_saveable = 0;
|
|
|
|
if (mode_select(dev, &mode_pages, len)) {
|
|
scsi_err(dev, "Failed to setup mode page 0x0A\n");
|
|
return -EIO;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* init_vset_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
/*
|
|
* VSETs:
|
|
* 1. Adjust queue depth based on number of devices
|
|
*
|
|
*/
|
|
static void init_vset_dev(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_query_res_state res_state;
|
|
char q_depth[100];
|
|
char cur_depth[100], saved_depth[100];
|
|
int depth, rc;
|
|
|
|
memset(&res_state, 0, sizeof(res_state));
|
|
|
|
if (polling_mode && !dev->should_init)
|
|
return;
|
|
|
|
if (!ipr_query_resource_state(dev, &res_state)) {
|
|
depth = res_state.vset.num_devices_in_vset * 4;
|
|
snprintf(q_depth, sizeof(q_depth), "%d", depth);
|
|
q_depth[sizeof(q_depth)-1] = '\0';
|
|
if (ipr_read_dev_attr(dev, "queue_depth", cur_depth, 100))
|
|
return;
|
|
rc = ipr_get_saved_dev_attr(dev, IPR_QUEUE_DEPTH, saved_depth);
|
|
if (rc == RC_SUCCESS) {
|
|
if (!strcmp(saved_depth, q_depth))
|
|
return;
|
|
depth = strtoul(saved_depth, NULL, 10);
|
|
if (depth && depth <= 255)
|
|
strcpy(q_depth, saved_depth);
|
|
}
|
|
|
|
if (!strcmp(cur_depth, q_depth))
|
|
return;
|
|
if (ipr_write_dev_attr(dev, "queue_depth", q_depth))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* init_gpdd_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
/*
|
|
* GPDD DASD:
|
|
* 1. Setup Mode Page 0x0A for TCQing.
|
|
* 2. Enable TCQing
|
|
* 3. Adjust queue depth
|
|
*
|
|
*/
|
|
static void init_gpdd_dev(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_disk_attr attr;
|
|
struct sense_data_t sense_data;
|
|
int rc;
|
|
|
|
if (polling_mode && !dev->should_init)
|
|
return;
|
|
if (__ipr_test_unit_ready(dev, &sense_data)) {
|
|
if ((sense_data.sense_key != UNIT_ATTENTION) ||
|
|
__ipr_test_unit_ready(dev, &sense_data))
|
|
return;
|
|
}
|
|
if (enable_af(dev))
|
|
return;
|
|
if (ipr_get_dev_attr(dev, &attr))
|
|
return;
|
|
if (setup_page0x00(dev))
|
|
return;
|
|
if ((rc = setup_page0x0a(dev))) {
|
|
if (rc != -EINVAL) {
|
|
scsi_dbg(dev, "Failed to enable TCQing.\n");
|
|
return;
|
|
}
|
|
} else {
|
|
attr.queue_depth = get_tcq_depth(dev);
|
|
attr.tcq_enabled = 1;
|
|
}
|
|
|
|
if (ipr_modify_dev_attr(dev, &attr))
|
|
return;
|
|
if (ipr_set_dev_attr(dev, &attr, 0))
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* init_af_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
/*
|
|
* AF DASD:
|
|
* 1. Setup mode pages (pages 0x01, 0x0A, 0x20)
|
|
* 2. Send set supported devices
|
|
* 3. Set DASD timeouts
|
|
*/
|
|
static void init_af_dev(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_disk_attr attr;
|
|
int rc;
|
|
|
|
if (ipr_set_dasd_timeouts(dev, 0))
|
|
return;
|
|
if (polling_mode && (!dev->should_init && !memcmp(&attr, &dev->attr, sizeof(attr))))
|
|
return;
|
|
if (polling_mode && !dev_init_allowed(dev))
|
|
return;
|
|
if (setup_page0x00(dev))
|
|
return;
|
|
if (setup_page0x01(dev))
|
|
return;
|
|
if (setup_page0x08(dev))
|
|
return;
|
|
if (enable_af(dev))
|
|
return;
|
|
if (ipr_get_dev_attr(dev, &attr))
|
|
return;
|
|
|
|
if ((rc = setup_page0x0a(dev))) {
|
|
if (rc != -EINVAL) {
|
|
scsi_dbg(dev, "Failed to enable TCQing.\n");
|
|
return;
|
|
}
|
|
} else
|
|
attr.queue_depth = get_tcq_depth(dev);
|
|
|
|
if (ipr_modify_dev_attr(dev, &attr))
|
|
return;
|
|
memcpy(&dev->attr, &attr, sizeof(attr));
|
|
if (ipr_set_dev_attr(dev, &attr, 0))
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* init_ioa_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
/*
|
|
* IOA:
|
|
* 1. Load saved adapter configuration
|
|
*/
|
|
static void init_ioa_dev(struct ipr_dev *dev)
|
|
{
|
|
struct ipr_scsi_buses buses;
|
|
struct ipr_ioa_attr attr;
|
|
|
|
if (!dev->ioa)
|
|
return;
|
|
if (polling_mode && !dev->ioa->should_init)
|
|
return;
|
|
if (ipr_get_ioa_attr(dev->ioa, &attr))
|
|
return;
|
|
if (dev->ioa->asymmetric_access && dev->ioa->sis64)
|
|
attr.active_active = 1;
|
|
if (dev->ioa->configure_rebuild_verify)
|
|
attr.disable_rebuild_verify = 1;
|
|
ipr_modify_ioa_attr(dev->ioa, &attr);
|
|
if (ipr_set_ioa_attr(dev->ioa, &attr, 0))
|
|
return;
|
|
if (ipr_get_bus_attr(dev->ioa, &buses))
|
|
return;
|
|
ipr_modify_bus_attr(dev->ioa, &buses);
|
|
if (ipr_set_bus_attr(dev->ioa, &buses, 0))
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* ipr_init_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0
|
|
**/
|
|
int ipr_init_dev(struct ipr_dev *dev)
|
|
{
|
|
if (!dev->scsi_dev_data)
|
|
return 0;
|
|
|
|
switch (dev->scsi_dev_data->type) {
|
|
case TYPE_DISK:
|
|
if (ipr_is_volume_set(dev))
|
|
init_vset_dev(dev);
|
|
else
|
|
init_gpdd_dev(dev);
|
|
break;
|
|
case IPR_TYPE_AF_DISK:
|
|
init_af_dev(dev);
|
|
break;
|
|
case IPR_TYPE_ADAPTER:
|
|
if (&dev->ioa->ioa == dev)
|
|
init_ioa_dev(dev);
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_init_new_dev -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
int ipr_init_new_dev(struct ipr_dev *dev)
|
|
{
|
|
if (!dev->scsi_dev_data)
|
|
return 0;
|
|
|
|
switch (dev->scsi_dev_data->type) {
|
|
case TYPE_DISK:
|
|
if (!strlen(dev->dev_name))
|
|
return 1;
|
|
wait_for_dev(dev->dev_name);
|
|
break;
|
|
case IPR_TYPE_ADAPTER:
|
|
if (&dev->ioa->ioa != dev)
|
|
break;
|
|
case IPR_TYPE_AF_DISK:
|
|
wait_for_dev(dev->gen_name);
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
ipr_init_dev(dev);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ipr_scan_ra -
|
|
* @ioa: ipr ioa struct
|
|
* @ra: ipr_res_addr struct
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_scan_ra(struct ipr_ioa *ioa, struct ipr_res_addr *ra)
|
|
{
|
|
ra_dbg(ra, "Scanning for new device\n");
|
|
ipr_scan(ioa, ra->bus, ra->target, ra->lun);
|
|
}
|
|
|
|
/**
|
|
* ipr_for_each_unique_ra -
|
|
* @dev: ipr dev struct
|
|
* @func: function pointer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
static void ipr_for_each_unique_ra(struct ipr_dev *dev,
|
|
void (*func) (struct ipr_ioa *, struct ipr_res_addr *))
|
|
{
|
|
signed int i, j;
|
|
int dup;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dev->res_addr); i++) {
|
|
dup = 0;
|
|
for (j = i - 1; j >= 0; j--) {
|
|
if (!memcmp(&dev->res_addr[i], &dev->res_addr[j],
|
|
sizeof(struct ipr_res_addr))) {
|
|
dup = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dup)
|
|
func(dev->ioa, &dev->res_addr[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fixup_improper_devs -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if no improper devs / number of improper devs otherwise
|
|
**/
|
|
static int fixup_improper_devs(struct ipr_ioa *ioa)
|
|
{
|
|
struct ipr_dev *dev;
|
|
int improper = 0;
|
|
|
|
for_each_dev(ioa, dev) {
|
|
dev->local_flag = 0;
|
|
if (ipr_improper_device_type(dev)) {
|
|
improper++;
|
|
dev->local_flag = 1;
|
|
scsi_dbg(dev, "Deleting improper device\n");
|
|
ipr_write_dev_attr(dev, "delete", "1");
|
|
}
|
|
}
|
|
|
|
if (!improper)
|
|
return 0;
|
|
|
|
sleep(5);
|
|
for_each_dev(ioa, dev) {
|
|
if (!dev->local_flag)
|
|
continue;
|
|
dev->local_flag = 0;
|
|
dev->rescan = 0;
|
|
ipr_for_each_unique_ra(dev, ipr_scan_ra);
|
|
}
|
|
|
|
return improper;
|
|
}
|
|
|
|
/**
|
|
* ipr_init_ioa -
|
|
* @ioa: ipr ioa struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_init_ioa(struct ipr_ioa *ioa)
|
|
{
|
|
struct ipr_dev *dev;
|
|
|
|
if (ioa->ioa_dead)
|
|
return 0;
|
|
if (fixup_improper_devs(ioa))
|
|
return -EAGAIN;
|
|
|
|
for_each_dev(ioa, dev)
|
|
ipr_init_dev(dev);
|
|
|
|
init_ioa_dev(&ioa->ioa);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* scsi_dev_kevent -
|
|
* @buf: data buffer
|
|
* @find_device: function pointer
|
|
* @func: function pointer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void scsi_dev_kevent(char *buf, struct ipr_dev *(*find_device)(char *),
|
|
int (*func)(struct ipr_dev *))
|
|
{
|
|
struct ipr_dev *dev;
|
|
char *name;
|
|
int i, j, rc = -EAGAIN;
|
|
|
|
name = strrchr(buf, '/');
|
|
if (!name) {
|
|
syslog_dbg("Failed to handle %s kevent\n", buf);
|
|
return;
|
|
}
|
|
|
|
name++;
|
|
for (i = 0; i < 2 && rc == -EAGAIN; i++) {
|
|
for (j = 0; j < 10; j++) {
|
|
tool_init(1);
|
|
check_current_config(false);
|
|
dev = find_device(name);
|
|
if (dev)
|
|
break;
|
|
sleep(2);
|
|
}
|
|
|
|
if (!dev) {
|
|
syslog_dbg("Failed to find ipr dev %s\n", name);
|
|
return;
|
|
}
|
|
|
|
rc = func(dev);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* scsi_host_kevent -
|
|
* @buf: data buffer
|
|
* @func: funtion pointer
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void scsi_host_kevent(char *buf, int (*func)(struct ipr_ioa *))
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
char *c;
|
|
int host;
|
|
int i, j, rc = -EAGAIN;
|
|
|
|
c = strrchr(buf, '/');
|
|
if (!c) {
|
|
syslog_dbg("Failed to handle %s kevent\n", buf);
|
|
return;
|
|
}
|
|
|
|
c += strlen("/host");
|
|
|
|
host = strtoul(c, NULL, 10);
|
|
for (i = 0; i < 2 && rc == -EAGAIN; i++) {
|
|
for (j = 0; j < 10; j++) {
|
|
tool_init(1);
|
|
check_current_config(false);
|
|
ioa = find_ioa(host);
|
|
if (ioa)
|
|
break;
|
|
sleep(2);
|
|
}
|
|
|
|
if (!ioa) {
|
|
syslog_dbg("Failed to find ipr ioa %d\n", host);
|
|
return;
|
|
}
|
|
|
|
rc = func(ioa);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_dev_from_addr -
|
|
* @res_addr: ipr_res_addr struct
|
|
*
|
|
* Returns:
|
|
* ipr_dev struct if success / NULL otherwise
|
|
**/
|
|
struct ipr_dev *get_dev_from_addr(struct ipr_res_addr *res_addr)
|
|
{
|
|
struct ipr_ioa *ioa;
|
|
int j;
|
|
struct scsi_dev_data *scsi_dev_data;
|
|
|
|
for_each_ioa(ioa) {
|
|
for (j = 0; j < ioa->num_devices; j++) {
|
|
scsi_dev_data = ioa->dev[j].scsi_dev_data;
|
|
|
|
if (!scsi_dev_data)
|
|
continue;
|
|
|
|
if (scsi_dev_data->host == res_addr->host &&
|
|
scsi_dev_data->channel == res_addr->bus &&
|
|
scsi_dev_data->id == res_addr->target &&
|
|
scsi_dev_data->lun == res_addr->lun)
|
|
return &ioa->dev[j];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* get_dev_from_handle -
|
|
* @res_handle:
|
|
* @ioa: struct ipr_ioa
|
|
*
|
|
* Returns:
|
|
* ipr_dev struct if success / NULL otherwise
|
|
**/
|
|
struct ipr_dev *get_dev_from_handle(struct ipr_ioa *ioa, u32 res_handle)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < ioa->num_devices; j++) {
|
|
if (!ioa->dev[j].qac_entry)
|
|
continue;
|
|
|
|
if (ipr_is_device_record(ioa->dev[j].qac_entry->record_id) &&
|
|
ioa->dev[j].resource_handle == res_handle)
|
|
return &ioa->dev[j];
|
|
|
|
if (ipr_is_array_record(ioa->dev[j].qac_entry->record_id) &&
|
|
ioa->dev[j].resource_handle == res_handle)
|
|
return &ioa->dev[j];
|
|
|
|
if (ipr_is_vset_record(ioa->dev[j].qac_entry->record_id) &&
|
|
ioa->dev[j].resource_handle == res_handle)
|
|
return &ioa->dev[j];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ipr_daemonize -
|
|
*
|
|
* Returns:
|
|
* nothing
|
|
**/
|
|
void ipr_daemonize()
|
|
{
|
|
int rc = fork();
|
|
|
|
if (rc < 0) {
|
|
syslog(LOG_ERR, "Failed to daemonize\n");
|
|
exit(1);
|
|
} else if (rc) {
|
|
exit(0);
|
|
}
|
|
|
|
close(STDIN_FILENO);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
open(_PATH_DEVNULL,O_RDONLY);
|
|
open(_PATH_DEVNULL,O_WRONLY);
|
|
open(_PATH_DEVNULL,O_WRONLY);
|
|
setsid();
|
|
}
|
|
|
|
/**
|
|
* ipr_disable_qerr -
|
|
* @dev: ipr dev struct
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_disable_qerr(struct ipr_dev *dev)
|
|
{
|
|
u8 ioctl_buffer[IOCTL_BUFFER_SIZE];
|
|
u8 ioctl_buffer2[IOCTL_BUFFER_SIZE];
|
|
struct ipr_control_mode_page *control_mode_page;
|
|
struct ipr_control_mode_page *control_mode_page_changeable;
|
|
struct ipr_mode_parm_hdr *mode_parm_hdr;
|
|
int status;
|
|
u8 length;
|
|
|
|
/* Issue mode sense to get the control mode page */
|
|
status = ipr_mode_sense(dev, 0x0a, &ioctl_buffer);
|
|
|
|
if (status)
|
|
return -EIO;
|
|
|
|
/* Issue mode sense to get the control mode page */
|
|
status = ipr_mode_sense(dev, 0x4a, &ioctl_buffer2);
|
|
|
|
if (status)
|
|
return -EIO;
|
|
|
|
mode_parm_hdr = (struct ipr_mode_parm_hdr *)ioctl_buffer2;
|
|
|
|
control_mode_page_changeable = (struct ipr_control_mode_page *)
|
|
(((u8 *)(mode_parm_hdr+1)) + mode_parm_hdr->block_desc_len);
|
|
|
|
mode_parm_hdr = (struct ipr_mode_parm_hdr *)ioctl_buffer;
|
|
|
|
control_mode_page = (struct ipr_control_mode_page *)
|
|
(((u8 *)(mode_parm_hdr+1)) + mode_parm_hdr->block_desc_len);
|
|
|
|
/* Turn off QERR since some drives do not like QERR
|
|
and IMMED bit at the same time. */
|
|
IPR_SET_MODE(control_mode_page_changeable->qerr,
|
|
control_mode_page->qerr, 0);
|
|
|
|
/* Issue mode select to set page x0A */
|
|
length = mode_parm_hdr->length + 1;
|
|
|
|
mode_parm_hdr->length = 0;
|
|
control_mode_page->hdr.parms_saveable = 0;
|
|
mode_parm_hdr->medium_type = 0;
|
|
mode_parm_hdr->device_spec_parms = 0;
|
|
|
|
status = ipr_mode_select(dev, &ioctl_buffer, length);
|
|
|
|
if (status)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ipr_set_manage_start_stop(struct ipr_dev *dev)
|
|
{
|
|
char path[PATH_MAX];
|
|
ssize_t len;
|
|
char value_str[2];
|
|
int value;
|
|
|
|
sprintf(path, "/sys/class/scsi_disk/%s",
|
|
dev->scsi_dev_data->sysfs_device_name);
|
|
len = sysfs_read_attr(path, "manage_start_stop", value_str, 2);
|
|
if (len < 0) {
|
|
syslog_dbg("Failed to open manage_start_stop parameter.\n");
|
|
return;
|
|
}
|
|
|
|
value = atoi(value_str);
|
|
|
|
snprintf(value_str, 2, "%d", 1);
|
|
|
|
if (sysfs_write_attr(path, "manage_start_stop", value_str, 1) < 0)
|
|
syslog_dbg("Failed to write manage_start_stop parameter.\n");
|
|
|
|
len = sysfs_read_attr(path, "manage_start_stop", value_str, 2);
|
|
value = atoi(value_str);
|
|
}
|
|
|
|
/**
|
|
* ipr_query_ioa_device_port -
|
|
* @ioa: ipr ioa struct
|
|
* @res_addr: ipr_res_addr struct
|
|
* @option: option value
|
|
*
|
|
* Returns:
|
|
* 0 if success / non-zero on failure
|
|
**/
|
|
int ipr_query_io_dev_port(struct ipr_dev *dev, struct ipr_query_io_port *io_port)
|
|
{
|
|
int fd, rc, cdb_num;
|
|
u8 cdb[IPR_CCB_CDB_LEN];
|
|
struct ipr_ioa *ioa = dev->ioa;
|
|
struct sense_data_t sense_data;
|
|
char *rp, *endptr;
|
|
|
|
if (strlen(ioa->ioa.gen_name) == 0)
|
|
return -ENOENT;
|
|
|
|
fd = open(ioa->ioa.gen_name, O_RDWR);
|
|
if (fd <= 1) {
|
|
if (!strcmp(tool_name, "iprconfig") || ipr_debug)
|
|
syslog(LOG_ERR, "Could not open %s. %m\n", ioa->ioa.gen_name);
|
|
return errno;
|
|
}
|
|
|
|
memset(cdb, 0, IPR_CCB_CDB_LEN);
|
|
|
|
cdb[0] = IPR_IOA_SERVICE_ACTION;
|
|
cdb[1] = IPR_QUERY_IOA_DEV_PORT;
|
|
if (ioa->sis64) {
|
|
/* convert res path string to bytes */
|
|
cdb_num = 2;
|
|
rp = dev->res_path_name;
|
|
do {
|
|
cdb[cdb_num++] = (u8)strtol(rp, &endptr, 16);
|
|
rp = endptr+1;
|
|
} while (*endptr != '\0' && cdb_num < 10);
|
|
|
|
while (cdb_num < 10)
|
|
cdb[cdb_num++] = 0xff;
|
|
|
|
cdb[10] = sizeof(*io_port) >> 24;
|
|
cdb[11] = (sizeof(*io_port) >> 16) & 0xff;
|
|
cdb[12] = (sizeof(*io_port) >> 8) & 0xff;
|
|
cdb[13] = sizeof(*io_port) & 0xff;
|
|
}
|
|
else {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
rc = sg_ioctl(fd, cdb, io_port,
|
|
sizeof(*io_port), SG_DXFER_FROM_DEV,
|
|
&sense_data, IPR_INTERNAL_TIMEOUT);
|
|
|
|
if (rc != 0)
|
|
scsi_cmd_err(dev, &sense_data, "Query IO port status", rc);
|
|
|
|
close(fd);
|
|
return rc;
|
|
}
|
|
|