freebsd-skq/sys/dev/smartpqi/smartpqi_discovery.c
Sean Bruno 1e66f787c8 martpqi(4):
- Microsemi SCSI driver for PQI controllers.
- Found on newer model HP servers.
- Restrict to AMD64 only as per developer request.

The driver provides support for the new generation of PQI controllers
from Microsemi. This driver is the first SCSI driver to implement the PQI
queuing model and it will replace the aacraid driver for Adaptec Series 9
controllers.  HARDWARE Controllers supported by the driver include:

    HPE Gen10 Smart Array Controller Family
    OEM Controllers based on the Microsemi Chipset.

Submitted by:   deepak.ukey@microsemi.com
Relnotes:       yes
Sponsored by:   Microsemi
Differential Revision:   https://reviews.freebsd.org/D14514
2018-04-26 16:59:06 +00:00

1807 lines
49 KiB
C

/*-
* Copyright (c) 2018 Microsemi Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* $FreeBSD$ */
#include "smartpqi_includes.h"
/* Validate the scsi sense response code */
static inline boolean_t pqisrc_scsi_sense_valid(const struct sense_header_scsi *sshdr)
{
DBG_FUNC("IN\n");
if (!sshdr)
return false;
DBG_FUNC("OUT\n");
return (sshdr->response_code & 0x70) == 0x70;
}
/* Update scsi sense info to a local buffer*/
boolean_t pqisrc_update_scsi_sense(const uint8_t *buff, int len,
struct sense_header_scsi *header)
{
DBG_FUNC("IN\n");
if (!buff || !len)
return false;
memset(header, 0, sizeof(struct sense_header_scsi));
header->response_code = (buff[0] & 0x7f);
if (!pqisrc_scsi_sense_valid(header))
return false;
if (header->response_code >= 0x72) {
/* descriptor format */
if (len > 1)
header->sense_key = (buff[1] & 0xf);
if (len > 2)
header->asc = buff[2];
if (len > 3)
header->ascq = buff[3];
if (len > 7)
header->additional_length = buff[7];
} else {
/* fixed format */
if (len > 2)
header->sense_key = (buff[2] & 0xf);
if (len > 7) {
len = (len < (buff[7] + 8)) ?
len : (buff[7] + 8);
if (len > 12)
header->asc = buff[12];
if (len > 13)
header->ascq = buff[13];
}
}
DBG_FUNC("OUT\n");
return true;
}
/*
* Function used to build the internal raid request and analyze the response
*/
int pqisrc_build_send_raid_request(pqisrc_softstate_t *softs, pqisrc_raid_req_t *request,
void *buff, size_t datasize, uint8_t cmd, uint16_t vpd_page, uint8_t *scsi3addr,
raid_path_error_info_elem_t *error_info)
{
uint8_t *cdb;
int ret = PQI_STATUS_SUCCESS;
uint32_t tag = 0;
struct dma_mem device_mem;
sgt_t *sgd;
ib_queue_t *ib_q = &softs->op_raid_ib_q[PQI_DEFAULT_IB_QUEUE];
ob_queue_t *ob_q = &softs->op_ob_q[PQI_DEFAULT_IB_QUEUE];
rcb_t *rcb = NULL;
DBG_FUNC("IN\n");
memset(&device_mem, 0, sizeof(struct dma_mem));
/* for TUR datasize: 0 buff: NULL */
if (datasize) {
device_mem.tag = "device_mem";
device_mem.size = datasize;
device_mem.align = PQISRC_DEFAULT_DMA_ALIGN;
ret = os_dma_mem_alloc(softs, &device_mem);
if (ret) {
DBG_ERR("failed to allocate dma memory for device_mem return code %d\n", ret);
return ret;
}
sgd = (sgt_t *)&request->sg_descriptors[0];
sgd->addr = device_mem.dma_addr;
sgd->len = datasize;
sgd->flags = SG_FLAG_LAST;
}
/* Build raid path request */
request->header.iu_type = PQI_IU_TYPE_RAID_PATH_IO_REQUEST;
request->header.iu_length = LE_16(offsetof(pqisrc_raid_req_t,
sg_descriptors[1]) - PQI_REQUEST_HEADER_LENGTH);
request->buffer_length = LE_32(datasize);
memcpy(request->lun_number, scsi3addr, sizeof(request->lun_number));
request->task_attribute = SOP_TASK_ATTRIBUTE_SIMPLE;
request->additional_cdb_bytes_usage = PQI_ADDITIONAL_CDB_BYTES_0;
cdb = request->cdb;
switch (cmd) {
case SA_INQUIRY:
request->data_direction = SOP_DATA_DIR_TO_DEVICE;
cdb[0] = SA_INQUIRY;
if (vpd_page & VPD_PAGE) {
cdb[1] = 0x1;
cdb[2] = (uint8_t)vpd_page;
}
cdb[4] = (uint8_t)datasize;
break;
case SA_REPORT_LOG:
case SA_REPORT_PHYS:
request->data_direction = SOP_DATA_DIR_TO_DEVICE;
cdb[0] = cmd;
if (cmd == SA_REPORT_PHYS)
cdb[1] = SA_REPORT_PHYS_EXTENDED;
else
cdb[1] = SA_REPORT_LOG_EXTENDED;
cdb[8] = (uint8_t)((datasize) >> 8);
cdb[9] = (uint8_t)datasize;
break;
case TEST_UNIT_READY:
request->data_direction = SOP_DATA_DIR_NONE;
break;
case SA_GET_RAID_MAP:
request->data_direction = SOP_DATA_DIR_TO_DEVICE;
cdb[0] = SA_CISS_READ;
cdb[1] = cmd;
cdb[8] = (uint8_t)((datasize) >> 8);
cdb[9] = (uint8_t)datasize;
break;
case SA_CACHE_FLUSH:
request->data_direction = SOP_DATA_DIR_FROM_DEVICE;
cdb[0] = BMIC_WRITE;
cdb[6] = BMIC_CACHE_FLUSH;
cdb[7] = (uint8_t)((datasize) << 8);
cdb[8] = (uint8_t)((datasize) >> 8);
break;
case BMIC_IDENTIFY_CONTROLLER:
case BMIC_IDENTIFY_PHYSICAL_DEVICE:
request->data_direction = SOP_DATA_DIR_TO_DEVICE;
cdb[0] = BMIC_READ;
cdb[6] = cmd;
cdb[7] = (uint8_t)((datasize) << 8);
cdb[8] = (uint8_t)((datasize) >> 8);
break;
case BMIC_WRITE_HOST_WELLNESS:
request->data_direction = SOP_DATA_DIR_FROM_DEVICE;
memcpy(device_mem.virt_addr, buff, datasize);
cdb[0] = BMIC_WRITE;
cdb[6] = cmd;
cdb[7] = (uint8_t)((datasize) << 8);
cdb[8] = (uint8_t)((datasize) >> 8);
break;
case BMIC_SENSE_SUBSYSTEM_INFORMATION:
request->data_direction = SOP_DATA_DIR_TO_DEVICE;
cdb[0] = BMIC_READ;
cdb[6] = cmd;
cdb[7] = (uint8_t)((datasize) << 8);
cdb[8] = (uint8_t)((datasize) >> 8);
break;
default:
DBG_ERR("unknown command 0x%x", cmd);
break;
}
tag = pqisrc_get_tag(&softs->taglist);
if (INVALID_ELEM == tag) {
DBG_ERR("Tag not available\n");
ret = PQI_STATUS_FAILURE;
goto err_notag;
}
((pqisrc_raid_req_t *)request)->request_id = tag;
((pqisrc_raid_req_t *)request)->error_index = ((pqisrc_raid_req_t *)request)->request_id;
((pqisrc_raid_req_t *)request)->response_queue_id = ob_q->q_id;
rcb = &softs->rcb[tag];
rcb->success_cmp_callback = pqisrc_process_internal_raid_response_success;
rcb->error_cmp_callback = pqisrc_process_internal_raid_response_error;
rcb->req_pending = true;
rcb->tag = tag;
/* Submit Command */
ret = pqisrc_submit_cmnd(softs, ib_q, request);
if (ret != PQI_STATUS_SUCCESS) {
DBG_ERR("Unable to submit command\n");
goto err_out;
}
ret = pqisrc_wait_on_condition(softs, rcb);
if (ret != PQI_STATUS_SUCCESS) {
DBG_ERR("Internal RAID request timed out: cmd : 0x%c\n", cmd);
goto err_out;
}
if (datasize) {
if (buff) {
memcpy(buff, device_mem.virt_addr, datasize);
}
os_dma_mem_free(softs, &device_mem);
}
ret = rcb->status;
if (ret) {
if(error_info) {
memcpy(error_info,
rcb->error_info,
sizeof(*error_info));
if (error_info->data_out_result ==
PQI_RAID_DATA_IN_OUT_UNDERFLOW) {
ret = PQI_STATUS_SUCCESS;
}
else{
DBG_INFO("Error!! Bus=%u Target=%u, Cmd=0x%x,"
"Ret=%d\n", BMIC_GET_LEVEL_2_BUS(scsi3addr),
BMIC_GET_LEVEL_TWO_TARGET(scsi3addr),
cmd, ret);
ret = PQI_STATUS_FAILURE;
}
}
} else {
if(error_info) {
ret = PQI_STATUS_SUCCESS;
memset(error_info, 0, sizeof(*error_info));
}
}
os_reset_rcb(rcb);
pqisrc_put_tag(&softs->taglist, ((pqisrc_raid_req_t *)request)->request_id);
DBG_FUNC("OUT\n");
return ret;
err_out:
DBG_ERR("Error!! Bus=%u Target=%u, Cmd=0x%x, Ret=%d\n",
BMIC_GET_LEVEL_2_BUS(scsi3addr), BMIC_GET_LEVEL_TWO_TARGET(scsi3addr),
cmd, ret);
os_reset_rcb(rcb);
pqisrc_put_tag(&softs->taglist, ((pqisrc_raid_req_t *)request)->request_id);
err_notag:
if (datasize)
os_dma_mem_free(softs, &device_mem);
DBG_FUNC("FAILED \n");
return ret;
}
/* common function used to send report physical and logical luns cmnds*/
static int pqisrc_report_luns(pqisrc_softstate_t *softs, uint8_t cmd,
void *buff, size_t buf_len)
{
int ret;
pqisrc_raid_req_t request;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
ret = pqisrc_build_send_raid_request(softs, &request, buff,
buf_len, cmd, 0, (uint8_t *)RAID_CTLR_LUNID, NULL);
DBG_FUNC("OUT\n");
return ret;
}
/* subroutine used to get physical and logical luns of the device */
static int pqisrc_get_physical_logical_luns(pqisrc_softstate_t *softs, uint8_t cmd,
reportlun_data_ext_t **buff, size_t *data_length)
{
int ret;
size_t list_len;
size_t data_len;
size_t new_lun_list_length;
reportlun_data_ext_t *lun_data;
reportlun_header_t report_lun_header;
DBG_FUNC("IN\n");
ret = pqisrc_report_luns(softs, cmd, &report_lun_header,
sizeof(report_lun_header));
if (ret) {
DBG_ERR("failed return code: %d\n", ret);
return ret;
}
list_len = BE_32(report_lun_header.list_length);
retry:
data_len = sizeof(reportlun_header_t) + list_len;
*data_length = data_len;
lun_data = os_mem_alloc(softs, data_len);
if (!lun_data) {
DBG_ERR("failed to allocate memory for lun_data\n");
return PQI_STATUS_FAILURE;
}
if (list_len == 0) {
DBG_INFO("list_len is 0\n");
memcpy(lun_data, &report_lun_header, sizeof(report_lun_header));
goto out;
}
ret = pqisrc_report_luns(softs, cmd, lun_data, data_len);
if (ret) {
DBG_ERR("error\n");
goto error;
}
new_lun_list_length = BE_32(lun_data->header.list_length);
if (new_lun_list_length > list_len) {
list_len = new_lun_list_length;
os_mem_free(softs, (void *)lun_data, data_len);
goto retry;
}
out:
*buff = lun_data;
DBG_FUNC("OUT\n");
return 0;
error:
os_mem_free(softs, (void *)lun_data, data_len);
DBG_ERR("FAILED\n");
return ret;
}
/*
* Function used to get physical and logical device list
*/
static int pqisrc_get_phys_log_device_list(pqisrc_softstate_t *softs,
reportlun_data_ext_t **physical_dev_list,
reportlun_data_ext_t **logical_dev_list,
size_t *phys_data_length,
size_t *log_data_length)
{
int ret = PQI_STATUS_SUCCESS;
size_t logical_list_length;
size_t logdev_data_length;
size_t data_length;
reportlun_data_ext_t *local_logdev_list;
reportlun_data_ext_t *logdev_data;
reportlun_header_t report_lun_header;
DBG_FUNC("IN\n");
ret = pqisrc_get_physical_logical_luns(softs, SA_REPORT_PHYS, physical_dev_list, phys_data_length);
if (ret) {
DBG_ERR("report physical LUNs failed");
return ret;
}
ret = pqisrc_get_physical_logical_luns(softs, SA_REPORT_LOG, logical_dev_list, log_data_length);
if (ret) {
DBG_ERR("report logical LUNs failed");
return ret;
}
logdev_data = *logical_dev_list;
if (logdev_data) {
logical_list_length =
BE_32(logdev_data->header.list_length);
} else {
memset(&report_lun_header, 0, sizeof(report_lun_header));
logdev_data =
(reportlun_data_ext_t *)&report_lun_header;
logical_list_length = 0;
}
logdev_data_length = sizeof(reportlun_header_t) +
logical_list_length;
/* Adding LOGICAL device entry for controller */
local_logdev_list = os_mem_alloc(softs,
logdev_data_length + sizeof(reportlun_ext_entry_t));
if (!local_logdev_list) {
data_length = *log_data_length;
os_mem_free(softs, (char *)*logical_dev_list, data_length);
*logical_dev_list = NULL;
return PQI_STATUS_FAILURE;
}
memcpy(local_logdev_list, logdev_data, logdev_data_length);
memset((uint8_t *)local_logdev_list + logdev_data_length, 0,
sizeof(reportlun_ext_entry_t));
local_logdev_list->header.list_length = BE_32(logical_list_length +
sizeof(reportlun_ext_entry_t));
data_length = *log_data_length;
os_mem_free(softs, (char *)*logical_dev_list, data_length);
*log_data_length = logdev_data_length + sizeof(reportlun_ext_entry_t);
*logical_dev_list = local_logdev_list;
DBG_FUNC("OUT\n");
return ret;
}
/* Subroutine used to set Bus-Target-Lun for the requested device */
static inline void pqisrc_set_btl(pqi_scsi_dev_t *device,
int bus, int target, int lun)
{
DBG_FUNC("IN\n");
device->bus = bus;
device->target = target;
device->lun = lun;
DBG_FUNC("OUT\n");
}
inline boolean_t pqisrc_is_external_raid_device(pqi_scsi_dev_t *device)
{
return device->is_external_raid_device;
}
static inline boolean_t pqisrc_is_external_raid_addr(uint8_t *scsi3addr)
{
return scsi3addr[2] != 0;
}
/* Function used to assign Bus-Target-Lun for the requested device */
static void pqisrc_assign_btl(pqi_scsi_dev_t *device)
{
uint8_t *scsi3addr;
uint32_t lunid;
uint32_t bus;
uint32_t target;
uint32_t lun;
DBG_FUNC("IN\n");
scsi3addr = device->scsi3addr;
lunid = GET_LE32(scsi3addr);
if (pqisrc_is_hba_lunid(scsi3addr)) {
/* The specified device is the controller. */
pqisrc_set_btl(device, PQI_HBA_BUS, PQI_CTLR_INDEX, lunid & 0x3fff);
device->target_lun_valid = true;
return;
}
if (pqisrc_is_logical_device(device)) {
if (pqisrc_is_external_raid_device(device)) {
DBG_INFO("External Raid Device!!!");
bus = PQI_EXTERNAL_RAID_VOLUME_BUS;
target = (lunid >> 16) & 0x3fff;
lun = lunid & 0xff;
} else {
bus = PQI_RAID_VOLUME_BUS;
lun = 0;
target = lunid & 0x3fff;
}
pqisrc_set_btl(device, bus, target, lun);
device->target_lun_valid = true;
return;
}
/* physical device */
pqisrc_set_btl(device, PQI_PHYSICAL_DEVICE_BUS, PQI_PD_INDEX(scsi3addr[6]), 0);
DBG_FUNC("OUT\n");
}
/* Build and send the internal INQUIRY command to particular device */
static int pqisrc_send_scsi_inquiry(pqisrc_softstate_t *softs,
uint8_t *scsi3addr, uint16_t vpd_page, uint8_t *buff, int buf_len)
{
int ret = PQI_STATUS_SUCCESS;
pqisrc_raid_req_t request;
raid_path_error_info_elem_t error_info;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
ret = pqisrc_build_send_raid_request(softs, &request, buff, buf_len,
SA_INQUIRY, vpd_page, scsi3addr, &error_info);
DBG_FUNC("OUT\n");
return ret;
}
/* Function used to parse the sense information from response */
static void pqisrc_fetch_sense_info(const uint8_t *sense_data,
unsigned sense_data_length, uint8_t *sense_key, uint8_t *asc, uint8_t *ascq)
{
struct sense_header_scsi header;
DBG_FUNC("IN\n");
*sense_key = 0;
*ascq = 0;
*asc = 0;
if (pqisrc_update_scsi_sense(sense_data, sense_data_length, &header)) {
*sense_key = header.sense_key;
*asc = header.asc;
*ascq = header.ascq;
}
DBG_INFO("sense_key: %x asc: %x ascq: %x\n", *sense_key, *asc, *ascq);
DBG_FUNC("OUT\n");
}
/* Function used to validate volume offline status */
static uint8_t pqisrc_get_volume_offline_status(pqisrc_softstate_t *softs,
uint8_t *scsi3addr)
{
int ret = PQI_STATUS_SUCCESS;
uint8_t status = SA_LV_STATUS_VPD_UNSUPPORTED;
uint8_t size;
uint8_t *buff = NULL;
DBG_FUNC("IN\n");
buff = os_mem_alloc(softs, 64);
if (!buff)
return PQI_STATUS_FAILURE;
/* Get the size of the VPD return buff. */
ret = pqisrc_send_scsi_inquiry(softs, scsi3addr, VPD_PAGE | SA_VPD_LV_STATUS,
buff, SCSI_VPD_HEADER_LENGTH);
if (ret)
goto out;
size = buff[3];
/* Now get the whole VPD buff. */
ret = pqisrc_send_scsi_inquiry(softs, scsi3addr, VPD_PAGE | SA_VPD_LV_STATUS,
buff, size + SCSI_VPD_HEADER_LENGTH);
if (ret)
goto out;
status = buff[4];
out:
os_mem_free(softs, (char *)buff, 64);
DBG_FUNC("OUT\n");
return status;
}
/* Determine offline status of a volume. Returns appropriate SA_LV_* status.*/
static uint8_t pqisrc_get_dev_vol_status(pqisrc_softstate_t *softs,
uint8_t *scsi3addr)
{
int ret = PQI_STATUS_SUCCESS;
uint8_t *sense_data;
unsigned sense_data_len;
uint8_t sense_key;
uint8_t asc;
uint8_t ascq;
uint8_t off_status;
uint8_t scsi_status;
pqisrc_raid_req_t request;
raid_path_error_info_elem_t error_info;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
ret = pqisrc_build_send_raid_request(softs, &request, NULL, 0,
TEST_UNIT_READY, 0, scsi3addr, &error_info);
if (ret)
goto error;
sense_data = error_info.data;
sense_data_len = LE_16(error_info.sense_data_len);
if (sense_data_len > sizeof(error_info.data))
sense_data_len = sizeof(error_info.data);
pqisrc_fetch_sense_info(sense_data, sense_data_len, &sense_key, &asc,
&ascq);
scsi_status = error_info.status;
/* scsi status: "CHECK CONDN" / SK: "not ready" ? */
if (scsi_status != 2 ||
sense_key != 2 ||
asc != ASC_LUN_NOT_READY) {
return SA_LV_OK;
}
/* Determine the reason for not ready state. */
off_status = pqisrc_get_volume_offline_status(softs, scsi3addr);
DBG_INFO("offline_status 0x%x\n", off_status);
/* Keep volume offline in certain cases. */
switch (off_status) {
case SA_LV_UNDERGOING_ERASE:
case SA_LV_NOT_AVAILABLE:
case SA_LV_UNDERGOING_RPI:
case SA_LV_PENDING_RPI:
case SA_LV_ENCRYPTED_NO_KEY:
case SA_LV_PLAINTEXT_IN_ENCRYPT_ONLY_CONTROLLER:
case SA_LV_UNDERGOING_ENCRYPTION:
case SA_LV_UNDERGOING_ENCRYPTION_REKEYING:
case SA_LV_ENCRYPTED_IN_NON_ENCRYPTED_CONTROLLER:
return off_status;
case SA_LV_STATUS_VPD_UNSUPPORTED:
/*
* If the VPD status page isn't available,
* use ASC/ASCQ to determine state.
*/
if (ascq == ASCQ_LUN_NOT_READY_FORMAT_IN_PROGRESS ||
ascq == ASCQ_LUN_NOT_READY_INITIALIZING_CMD_REQ)
return off_status;
break;
}
DBG_FUNC("OUT\n");
return SA_LV_OK;
error:
return SA_LV_STATUS_VPD_UNSUPPORTED;
}
/* Validate the RAID map parameters */
static int pqisrc_raid_map_validation(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device, pqisrc_raid_map_t *raid_map)
{
char *error_msg;
uint32_t raidmap_size;
uint32_t r5or6_blocks_per_row;
unsigned phys_dev_num;
unsigned num_raidmap_entries;
DBG_FUNC("IN\n");
raidmap_size = LE_32(raid_map->structure_size);
if (raidmap_size < offsetof(pqisrc_raid_map_t, dev_data)) {
error_msg = "RAID map too small\n";
goto error;
}
if (raidmap_size > sizeof(*raid_map)) {
error_msg = "RAID map too large\n";
goto error;
}
phys_dev_num = LE_16(raid_map->layout_map_count) *
(LE_16(raid_map->data_disks_per_row) +
LE_16(raid_map->metadata_disks_per_row));
num_raidmap_entries = phys_dev_num *
LE_16(raid_map->row_cnt);
if (num_raidmap_entries > RAID_MAP_MAX_ENTRIES) {
error_msg = "invalid number of map entries in RAID map\n";
goto error;
}
if (device->raid_level == SA_RAID_1) {
if (LE_16(raid_map->layout_map_count) != 2) {
error_msg = "invalid RAID-1 map\n";
goto error;
}
} else if (device->raid_level == SA_RAID_ADM) {
if (LE_16(raid_map->layout_map_count) != 3) {
error_msg = "invalid RAID-1(ADM) map\n";
goto error;
}
} else if ((device->raid_level == SA_RAID_5 ||
device->raid_level == SA_RAID_6) &&
LE_16(raid_map->layout_map_count) > 1) {
/* RAID 50/60 */
r5or6_blocks_per_row =
LE_16(raid_map->strip_size) *
LE_16(raid_map->data_disks_per_row);
if (r5or6_blocks_per_row == 0) {
error_msg = "invalid RAID-5 or RAID-6 map\n";
goto error;
}
}
DBG_FUNC("OUT\n");
return 0;
error:
DBG_ERR("%s\n", error_msg);
return PQI_STATUS_FAILURE;
}
/* Get device raidmap for the requested device */
static int pqisrc_get_device_raidmap(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
int ret = PQI_STATUS_SUCCESS;
pqisrc_raid_req_t request;
pqisrc_raid_map_t *raid_map;
DBG_FUNC("IN\n");
raid_map = os_mem_alloc(softs, sizeof(*raid_map));
if (!raid_map)
return PQI_STATUS_FAILURE;
memset(&request, 0, sizeof(request));
ret = pqisrc_build_send_raid_request(softs, &request, raid_map, sizeof(*raid_map),
SA_GET_RAID_MAP, 0, device->scsi3addr, NULL);
if (ret) {
DBG_ERR("error in build send raid req ret=%d\n", ret);
goto err_out;
}
ret = pqisrc_raid_map_validation(softs, device, raid_map);
if (ret) {
DBG_ERR("error in raid map validation ret=%d\n", ret);
goto err_out;
}
device->raid_map = raid_map;
DBG_FUNC("OUT\n");
return 0;
err_out:
os_mem_free(softs, (char*)raid_map, sizeof(*raid_map));
DBG_FUNC("FAILED \n");
return ret;
}
/* Get device ioaccel_status to validate the type of device */
static void pqisrc_get_dev_ioaccel_status(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
int ret = PQI_STATUS_SUCCESS;
uint8_t *buff;
uint8_t ioaccel_status;
DBG_FUNC("IN\n");
buff = os_mem_alloc(softs, 64);
if (!buff)
return;
ret = pqisrc_send_scsi_inquiry(softs, device->scsi3addr,
VPD_PAGE | SA_VPD_LV_IOACCEL_STATUS, buff, 64);
if (ret) {
DBG_ERR("error in send scsi inquiry ret=%d\n", ret);
goto err_out;
}
ioaccel_status = buff[IOACCEL_STATUS_BYTE];
device->offload_config =
!!(ioaccel_status & OFFLOAD_CONFIGURED_BIT);
if (device->offload_config) {
device->offload_enabled_pending =
!!(ioaccel_status & OFFLOAD_ENABLED_BIT);
if (pqisrc_get_device_raidmap(softs, device))
device->offload_enabled_pending = false;
}
DBG_INFO("offload_config: 0x%x offload_enabled_pending: 0x%x \n",
device->offload_config, device->offload_enabled_pending);
err_out:
os_mem_free(softs, (char*)buff, 64);
DBG_FUNC("OUT\n");
}
/* Get RAID level of requested device */
static void pqisrc_get_dev_raid_level(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
uint8_t raid_level;
uint8_t *buff;
DBG_FUNC("IN\n");
raid_level = SA_RAID_UNKNOWN;
buff = os_mem_alloc(softs, 64);
if (buff) {
int ret;
ret = pqisrc_send_scsi_inquiry(softs, device->scsi3addr,
VPD_PAGE | SA_VPD_LV_DEVICE_GEOMETRY, buff, 64);
if (ret == 0) {
raid_level = buff[8];
if (raid_level > SA_RAID_MAX)
raid_level = SA_RAID_UNKNOWN;
}
os_mem_free(softs, (char*)buff, 64);
}
device->raid_level = raid_level;
DBG_INFO("RAID LEVEL: %x \n", raid_level);
DBG_FUNC("OUT\n");
}
/* Parse the inquiry response and determine the type of device */
static int pqisrc_get_dev_data(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
int ret = PQI_STATUS_SUCCESS;
uint8_t *inq_buff;
DBG_FUNC("IN\n");
inq_buff = os_mem_alloc(softs, OBDR_TAPE_INQ_SIZE);
if (!inq_buff)
return PQI_STATUS_FAILURE;
/* Send an inquiry to the device to see what it is. */
ret = pqisrc_send_scsi_inquiry(softs, device->scsi3addr, 0, inq_buff,
OBDR_TAPE_INQ_SIZE);
if (ret)
goto err_out;
pqisrc_sanitize_inquiry_string(&inq_buff[8], 8);
pqisrc_sanitize_inquiry_string(&inq_buff[16], 16);
device->devtype = inq_buff[0] & 0x1f;
memcpy(device->vendor, &inq_buff[8],
sizeof(device->vendor));
memcpy(device->model, &inq_buff[16],
sizeof(device->model));
DBG_INFO("DEV_TYPE: %x VENDOR: %s MODEL: %s\n", device->devtype, device->vendor, device->model);
if (pqisrc_is_logical_device(device) && device->devtype == DISK_DEVICE) {
if (pqisrc_is_external_raid_device(device)) {
device->raid_level = SA_RAID_UNKNOWN;
device->volume_status = SA_LV_OK;
device->volume_offline = false;
}
else {
pqisrc_get_dev_raid_level(softs, device);
pqisrc_get_dev_ioaccel_status(softs, device);
device->volume_status = pqisrc_get_dev_vol_status(softs,
device->scsi3addr);
device->volume_offline = device->volume_status != SA_LV_OK;
}
}
/*
* Check if this is a One-Button-Disaster-Recovery device
* by looking for "$DR-10" at offset 43 in the inquiry data.
*/
device->is_obdr_device = (device->devtype == ROM_DEVICE &&
memcmp(&inq_buff[OBDR_SIG_OFFSET], OBDR_TAPE_SIG,
OBDR_SIG_LEN) == 0);
err_out:
os_mem_free(softs, (char*)inq_buff, OBDR_TAPE_INQ_SIZE);
DBG_FUNC("OUT\n");
return ret;
}
/*
* BMIC (Basic Management And Interface Commands) command
* to get the controller identify params
*/
static int pqisrc_identify_ctrl(pqisrc_softstate_t *softs,
bmic_ident_ctrl_t *buff)
{
int ret = PQI_STATUS_SUCCESS;
pqisrc_raid_req_t request;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
ret = pqisrc_build_send_raid_request(softs, &request, buff, sizeof(*buff),
BMIC_IDENTIFY_CONTROLLER, 0, (uint8_t *)RAID_CTLR_LUNID, NULL);
DBG_FUNC("OUT\n");
return ret;
}
/* Get the adapter FW version using BMIC_IDENTIFY_CONTROLLER */
int pqisrc_get_ctrl_fw_version(pqisrc_softstate_t *softs)
{
int ret = PQI_STATUS_SUCCESS;
bmic_ident_ctrl_t *identify_ctrl;
DBG_FUNC("IN\n");
identify_ctrl = os_mem_alloc(softs, sizeof(*identify_ctrl));
if (!identify_ctrl) {
DBG_ERR("failed to allocate memory for identify_ctrl\n");
return PQI_STATUS_FAILURE;
}
memset(identify_ctrl, 0, sizeof(*identify_ctrl));
ret = pqisrc_identify_ctrl(softs, identify_ctrl);
if (ret)
goto out;
softs->fw_build_number = identify_ctrl->fw_build_number;
memcpy(softs->fw_version, identify_ctrl->fw_version,
sizeof(identify_ctrl->fw_version));
softs->fw_version[sizeof(identify_ctrl->fw_version)] = '\0';
snprintf(softs->fw_version +
strlen(softs->fw_version),
sizeof(softs->fw_version),
"-%u", identify_ctrl->fw_build_number);
out:
os_mem_free(softs, (char *)identify_ctrl, sizeof(*identify_ctrl));
DBG_INFO("Firmware version: %s Firmware build number: %d\n", softs->fw_version, softs->fw_build_number);
DBG_FUNC("OUT\n");
return ret;
}
/* BMIC command to determine scsi device identify params */
static int pqisrc_identify_physical_disk(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device,
bmic_ident_physdev_t *buff,
int buf_len)
{
int ret = PQI_STATUS_SUCCESS;
uint16_t bmic_device_index;
pqisrc_raid_req_t request;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
bmic_device_index = BMIC_GET_DRIVE_NUMBER(device->scsi3addr);
request.cdb[2] = (uint8_t)bmic_device_index;
request.cdb[9] = (uint8_t)(bmic_device_index >> 8);
ret = pqisrc_build_send_raid_request(softs, &request, buff, buf_len,
BMIC_IDENTIFY_PHYSICAL_DEVICE, 0, (uint8_t *)RAID_CTLR_LUNID, NULL);
DBG_FUNC("OUT\n");
return ret;
}
/*
* Function used to get the scsi device information using one of BMIC
* BMIC_IDENTIFY_PHYSICAL_DEVICE
*/
static void pqisrc_get_physical_device_info(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device,
bmic_ident_physdev_t *id_phys)
{
int ret = PQI_STATUS_SUCCESS;
DBG_FUNC("IN\n");
memset(id_phys, 0, sizeof(*id_phys));
ret= pqisrc_identify_physical_disk(softs, device,
id_phys, sizeof(*id_phys));
if (ret) {
device->queue_depth = PQI_PHYSICAL_DISK_DEFAULT_MAX_QUEUE_DEPTH;
return;
}
device->queue_depth =
LE_16(id_phys->current_queue_depth_limit);
device->device_type = id_phys->device_type;
device->active_path_index = id_phys->active_path_number;
device->path_map = id_phys->redundant_path_present_map;
memcpy(&device->box,
&id_phys->alternate_paths_phys_box_on_port,
sizeof(device->box));
memcpy(&device->phys_connector,
&id_phys->alternate_paths_phys_connector,
sizeof(device->phys_connector));
device->bay = id_phys->phys_bay_in_box;
DBG_INFO("BMIC DEV_TYPE: %x QUEUE DEPTH: 0x%x \n", device->device_type, device->queue_depth);
DBG_FUNC("OUT\n");
}
/* Function used to find the entry of the device in a list */
static device_status_t pqisrc_scsi_find_entry(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device_to_find,
pqi_scsi_dev_t **same_device)
{
pqi_scsi_dev_t *device;
int i,j;
DBG_FUNC("IN\n");
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
if (pqisrc_scsi3addr_equal(device_to_find->scsi3addr,
device->scsi3addr)) {
*same_device = device;
if (pqisrc_device_equal(device_to_find, device)) {
if (device_to_find->volume_offline)
return DEVICE_CHANGED;
return DEVICE_UNCHANGED;
}
return DEVICE_CHANGED;
}
}
}
DBG_FUNC("OUT\n");
return DEVICE_NOT_FOUND;
}
/* Update the newly added devices as existed device */
static void pqisrc_exist_device_update(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device_exist,
pqi_scsi_dev_t *new_device)
{
DBG_FUNC("IN\n");
device_exist->expose_device = new_device->expose_device;
memcpy(device_exist->vendor, new_device->vendor,
sizeof(device_exist->vendor));
memcpy(device_exist->model, new_device->model,
sizeof(device_exist->model));
device_exist->is_physical_device = new_device->is_physical_device;
device_exist->is_external_raid_device =
new_device->is_external_raid_device;
device_exist->sas_address = new_device->sas_address;
device_exist->raid_level = new_device->raid_level;
device_exist->queue_depth = new_device->queue_depth;
device_exist->ioaccel_handle = new_device->ioaccel_handle;
device_exist->volume_status = new_device->volume_status;
device_exist->active_path_index = new_device->active_path_index;
device_exist->path_map = new_device->path_map;
device_exist->bay = new_device->bay;
memcpy(device_exist->box, new_device->box,
sizeof(device_exist->box));
memcpy(device_exist->phys_connector, new_device->phys_connector,
sizeof(device_exist->phys_connector));
device_exist->offload_config = new_device->offload_config;
device_exist->offload_enabled = false;
device_exist->offload_enabled_pending =
new_device->offload_enabled_pending;
device_exist->offload_to_mirror = 0;
if (device_exist->raid_map)
os_mem_free(softs,
(char *)device_exist->raid_map,
sizeof(*device_exist->raid_map));
device_exist->raid_map = new_device->raid_map;
/* To prevent this from being freed later. */
new_device->raid_map = NULL;
DBG_FUNC("OUT\n");
}
/* Validate the ioaccel_handle for a newly added device */
static pqi_scsi_dev_t *pqisrc_identify_device_via_ioaccel(
pqisrc_softstate_t *softs, uint32_t ioaccel_handle)
{
pqi_scsi_dev_t *device;
int i,j;
DBG_FUNC("IN\n");
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
if (device->devtype != DISK_DEVICE)
continue;
if (pqisrc_is_logical_device(device))
continue;
if (device->ioaccel_handle == ioaccel_handle)
return device;
}
}
DBG_FUNC("OUT\n");
return NULL;
}
/* Get the scsi device queue depth */
static void pqisrc_update_log_dev_qdepth(pqisrc_softstate_t *softs)
{
unsigned i;
unsigned phys_dev_num;
unsigned num_raidmap_entries;
unsigned queue_depth;
pqisrc_raid_map_t *raid_map;
pqi_scsi_dev_t *device;
raidmap_data_t *dev_data;
pqi_scsi_dev_t *phys_disk;
unsigned j;
DBG_FUNC("IN\n");
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
if (device->devtype != DISK_DEVICE)
continue;
if (!pqisrc_is_logical_device(device))
continue;
if (pqisrc_is_external_raid_device(device))
continue;
device->queue_depth = PQI_LOGICAL_DISK_DEFAULT_MAX_QUEUE_DEPTH;
raid_map = device->raid_map;
if (!raid_map)
return;
dev_data = raid_map->dev_data;
phys_dev_num = LE_16(raid_map->layout_map_count) *
(LE_16(raid_map->data_disks_per_row) +
LE_16(raid_map->metadata_disks_per_row));
num_raidmap_entries = phys_dev_num *
LE_16(raid_map->row_cnt);
queue_depth = 0;
for (i = 0; i < num_raidmap_entries; i++) {
phys_disk = pqisrc_identify_device_via_ioaccel(softs,
dev_data[i].ioaccel_handle);
if (!phys_disk) {
DBG_WARN(
"Failed to find physical disk handle for logical drive %016llx\n",
(unsigned long long)BE_64(device->scsi3addr[0]));
device->offload_enabled = false;
device->offload_enabled_pending = false;
if (raid_map)
os_mem_free(softs, (char *)raid_map, sizeof(*raid_map));
device->raid_map = NULL;
return;
}
queue_depth += phys_disk->queue_depth;
}
device->queue_depth = queue_depth;
} /* end inner loop */
}/* end outer loop */
DBG_FUNC("OUT\n");
}
/* Function used to add a scsi device to OS scsi subsystem */
static int pqisrc_add_device(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
DBG_FUNC("IN\n");
DBG_INFO("vendor: %s model: %s bus:%d target:%d lun:%d is_physical_device:0x%x expose_device:0x%x volume_offline 0x%x volume_status 0x%x \n",
device->vendor, device->model, device->bus, device->target, device->lun, device->is_physical_device, device->expose_device, device->volume_offline, device->volume_status);
device->invalid = false;
if(device->expose_device) {
/* TBD: Call OS upper layer function to add the device entry */
os_add_device(softs,device);
}
DBG_FUNC("OUT\n");
return PQI_STATUS_SUCCESS;
}
/* Function used to remove a scsi device from OS scsi subsystem */
void pqisrc_remove_device(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
DBG_FUNC("IN\n");
DBG_INFO("vendor: %s model: %s bus:%d target:%d lun:%d is_physical_device:0x%x expose_device:0x%x volume_offline 0x%x volume_status 0x%x \n",
device->vendor, device->model, device->bus, device->target, device->lun, device->is_physical_device, device->expose_device, device->volume_offline, device->volume_status);
/* TBD: Call OS upper layer function to remove the device entry */
device->invalid = true;
os_remove_device(softs,device);
DBG_FUNC("OUT\n");
}
/*
* When exposing new device to OS fails then adjst list according to the
* mid scsi list
*/
static void pqisrc_adjust_list(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
DBG_FUNC("IN\n");
if (!device) {
DBG_ERR("softs = %p: device is NULL !!!\n", softs);
return;
}
OS_ACQUIRE_SPINLOCK(&softs->devlist_lock);
softs->device_list[device->target][device->lun] = NULL;
OS_RELEASE_SPINLOCK(&softs->devlist_lock);
pqisrc_device_mem_free(softs, device);
DBG_FUNC("OUT\n");
}
/* Debug routine used to display the RAID volume status of the device */
static void pqisrc_display_volume_status(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *device)
{
char *status;
DBG_FUNC("IN\n");
switch (device->volume_status) {
case SA_LV_OK:
status = "Volume is online.";
break;
case SA_LV_UNDERGOING_ERASE:
status = "Volume is undergoing background erase process.";
break;
case SA_LV_NOT_AVAILABLE:
status = "Volume is waiting for transforming volume.";
break;
case SA_LV_UNDERGOING_RPI:
status = "Volume is undergoing rapid parity initialization process.";
break;
case SA_LV_PENDING_RPI:
status = "Volume is queued for rapid parity initialization process.";
break;
case SA_LV_ENCRYPTED_NO_KEY:
status = "Volume is encrypted and cannot be accessed because key is not present.";
break;
case SA_LV_PLAINTEXT_IN_ENCRYPT_ONLY_CONTROLLER:
status = "Volume is not encrypted and cannot be accessed because controller is in encryption-only mode.";
break;
case SA_LV_UNDERGOING_ENCRYPTION:
status = "Volume is undergoing encryption process.";
break;
case SA_LV_UNDERGOING_ENCRYPTION_REKEYING:
status = "Volume is undergoing encryption re-keying process.";
break;
case SA_LV_ENCRYPTED_IN_NON_ENCRYPTED_CONTROLLER:
status = "Volume is encrypted and cannot be accessed because controller does not have encryption enabled.";
break;
case SA_LV_PENDING_ENCRYPTION:
status = "Volume is pending migration to encrypted state, but process has not started.";
break;
case SA_LV_PENDING_ENCRYPTION_REKEYING:
status = "Volume is encrypted and is pending encryption rekeying.";
break;
case SA_LV_STATUS_VPD_UNSUPPORTED:
status = "Volume status is not available through vital product data pages.";
break;
default:
status = "Volume is in an unknown state.";
break;
}
DBG_INFO("scsi BTL %d:%d:%d %s\n",
device->bus, device->target, device->lun, status);
DBG_FUNC("OUT\n");
}
void pqisrc_device_mem_free(pqisrc_softstate_t *softs, pqi_scsi_dev_t *device)
{
DBG_INFO("IN\n");
if (!device)
return;
if (device->raid_map) {
os_mem_free(softs, (char *)device->raid_map, sizeof(pqisrc_raid_map_t));
}
os_mem_free(softs, (char *)device,sizeof(*device));
DBG_INFO("OUT\n");
}
/* OS should call this function to free the scsi device */
void pqisrc_free_device(pqisrc_softstate_t * softs,pqi_scsi_dev_t *device)
{
OS_ACQUIRE_SPINLOCK(&softs->devlist_lock);
pqisrc_device_mem_free(softs, device);
OS_RELEASE_SPINLOCK(&softs->devlist_lock);
}
/* Update the newly added devices to the device list */
static void pqisrc_update_device_list(pqisrc_softstate_t *softs,
pqi_scsi_dev_t *new_device_list[], int num_new_devices)
{
int ret;
int i;
device_status_t dev_status;
pqi_scsi_dev_t *device;
pqi_scsi_dev_t *same_device;
pqi_scsi_dev_t **added = NULL;
pqi_scsi_dev_t **removed = NULL;
int nadded = 0, nremoved = 0;
int j;
DBG_INFO("IN\n");
added = os_mem_alloc(softs, sizeof(*added) * PQI_MAX_DEVICES);
removed = os_mem_alloc(softs, sizeof(*removed) * PQI_MAX_DEVICES);
if (!added || !removed) {
DBG_WARN("Out of memory \n");
goto free_and_out;
}
OS_ACQUIRE_SPINLOCK(&softs->devlist_lock);
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
device->device_gone = true;
}
}
DBG_IO("Device list used an array\n");
for (i = 0; i < num_new_devices; i++) {
device = new_device_list[i];
dev_status = pqisrc_scsi_find_entry(softs, device,
&same_device);
switch (dev_status) {
case DEVICE_UNCHANGED:
/* New Device present in existing device list */
device->new_device = false;
same_device->device_gone = false;
pqisrc_exist_device_update(softs, same_device, device);
break;
case DEVICE_NOT_FOUND:
/* Device not found in existing list */
device->new_device = true;
break;
case DEVICE_CHANGED:
/* Actual device gone need to add device to list*/
device->new_device = true;
break;
default:
break;
}
}
/* Process all devices that have gone away. */
for(i = 0, nremoved = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
if (device->device_gone) {
softs->device_list[device->target][device->lun] = NULL;
removed[nremoved] = device;
nremoved++;
}
}
}
/* Process all new devices. */
for (i = 0, nadded = 0; i < num_new_devices; i++) {
device = new_device_list[i];
if (!device->new_device)
continue;
if (device->volume_offline)
continue;
softs->device_list[device->target][device->lun] = device;
DBG_INFO("Added device %p at B : %d T : %d L : %d\n",device,
device->bus,device->target,device->lun);
/* To prevent this entry from being freed later. */
new_device_list[i] = NULL;
added[nadded] = device;
nadded++;
}
pqisrc_update_log_dev_qdepth(softs);
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
device->offload_enabled = device->offload_enabled_pending;
}
}
OS_RELEASE_SPINLOCK(&softs->devlist_lock);
for(i = 0; i < nremoved; i++) {
device = removed[i];
if (device == NULL)
continue;
pqisrc_remove_device(softs, device);
pqisrc_display_device_info(softs, "removed", device);
}
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if(softs->device_list[i][j] == NULL)
continue;
device = softs->device_list[i][j];
/*
* Notify the OS upper layer if the queue depth of any existing device has
* changed.
*/
if (device->queue_depth !=
device->advertised_queue_depth) {
device->advertised_queue_depth = device->queue_depth;
/* TBD: Call OS upper layer function to change device Q depth */
}
}
}
for(i = 0; i < nadded; i++) {
device = added[i];
if (device->expose_device) {
ret = pqisrc_add_device(softs, device);
if (ret) {
DBG_WARN("scsi %d:%d:%d addition failed, device not added\n",
device->bus, device->target,
device->lun);
pqisrc_adjust_list(softs, device);
continue;
}
}
pqisrc_display_device_info(softs, "added", device);
}
/* Process all volumes that are offline. */
for (i = 0; i < num_new_devices; i++) {
device = new_device_list[i];
if (!device)
continue;
if (!device->new_device)
continue;
if (device->volume_offline) {
pqisrc_display_volume_status(softs, device);
pqisrc_display_device_info(softs, "offline", device);
}
}
free_and_out:
if (added)
os_mem_free(softs, (char *)added,
sizeof(*added) * PQI_MAX_DEVICES);
if (removed)
os_mem_free(softs, (char *)removed,
sizeof(*removed) * PQI_MAX_DEVICES);
DBG_INFO("OUT\n");
}
/*
* Let the Adapter know about driver version using one of BMIC
* BMIC_WRITE_HOST_WELLNESS
*/
int pqisrc_write_driver_version_to_host_wellness(pqisrc_softstate_t *softs)
{
int rval = PQI_STATUS_SUCCESS;
struct bmic_host_wellness_driver_version *host_wellness_driver_ver;
size_t data_length;
pqisrc_raid_req_t request;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
data_length = sizeof(*host_wellness_driver_ver);
host_wellness_driver_ver = os_mem_alloc(softs, data_length);
if (!host_wellness_driver_ver) {
DBG_ERR("failed to allocate memory for host wellness driver_version\n");
return PQI_STATUS_FAILURE;
}
host_wellness_driver_ver->start_tag[0] = '<';
host_wellness_driver_ver->start_tag[1] = 'H';
host_wellness_driver_ver->start_tag[2] = 'W';
host_wellness_driver_ver->start_tag[3] = '>';
host_wellness_driver_ver->driver_version_tag[0] = 'D';
host_wellness_driver_ver->driver_version_tag[1] = 'V';
host_wellness_driver_ver->driver_version_length = LE_16(sizeof(host_wellness_driver_ver->driver_version));
strncpy(host_wellness_driver_ver->driver_version, softs->os_name,
sizeof(host_wellness_driver_ver->driver_version));
if (strlen(softs->os_name) < sizeof(host_wellness_driver_ver->driver_version) ) {
strncpy(host_wellness_driver_ver->driver_version + strlen(softs->os_name), PQISRC_DRIVER_VERSION,
sizeof(host_wellness_driver_ver->driver_version) - strlen(softs->os_name));
} else {
DBG_INFO("OS name length(%lu) is longer than buffer of driver_version\n",
strlen(softs->os_name));
}
host_wellness_driver_ver->driver_version[sizeof(host_wellness_driver_ver->driver_version) - 1] = '\0';
host_wellness_driver_ver->end_tag[0] = 'Z';
host_wellness_driver_ver->end_tag[1] = 'Z';
rval = pqisrc_build_send_raid_request(softs, &request, host_wellness_driver_ver,data_length,
BMIC_WRITE_HOST_WELLNESS, 0, (uint8_t *)RAID_CTLR_LUNID, NULL);
os_mem_free(softs, (char *)host_wellness_driver_ver, data_length);
DBG_FUNC("OUT");
return rval;
}
/*
* Write current RTC time from host to the adapter using
* BMIC_WRITE_HOST_WELLNESS
*/
int pqisrc_write_current_time_to_host_wellness(pqisrc_softstate_t *softs)
{
int rval = PQI_STATUS_SUCCESS;
struct bmic_host_wellness_time *host_wellness_time;
size_t data_length;
pqisrc_raid_req_t request;
DBG_FUNC("IN\n");
memset(&request, 0, sizeof(request));
data_length = sizeof(*host_wellness_time);
host_wellness_time = os_mem_alloc(softs, data_length);
if (!host_wellness_time) {
DBG_ERR("failed to allocate memory for host wellness time structure\n");
return PQI_STATUS_FAILURE;
}
host_wellness_time->start_tag[0] = '<';
host_wellness_time->start_tag[1] = 'H';
host_wellness_time->start_tag[2] = 'W';
host_wellness_time->start_tag[3] = '>';
host_wellness_time->time_tag[0] = 'T';
host_wellness_time->time_tag[1] = 'D';
host_wellness_time->time_length = LE_16(offsetof(struct bmic_host_wellness_time, time_length) -
offsetof(struct bmic_host_wellness_time, century));
os_get_time(host_wellness_time);
host_wellness_time->dont_write_tag[0] = 'D';
host_wellness_time->dont_write_tag[1] = 'W';
host_wellness_time->end_tag[0] = 'Z';
host_wellness_time->end_tag[1] = 'Z';
rval = pqisrc_build_send_raid_request(softs, &request, host_wellness_time,data_length,
BMIC_WRITE_HOST_WELLNESS, 0, (uint8_t *)RAID_CTLR_LUNID, NULL);
os_mem_free(softs, (char *)host_wellness_time, data_length);
DBG_FUNC("OUT");
return rval;
}
/*
* Function used to perform a rescan of scsi devices
* for any config change events
*/
int pqisrc_scan_devices(pqisrc_softstate_t *softs)
{
boolean_t is_physical_device;
int ret = PQI_STATUS_FAILURE;
int i;
int new_dev_cnt;
int phy_log_dev_cnt;
uint8_t *scsi3addr;
uint32_t physical_cnt;
uint32_t logical_cnt;
uint32_t ndev_allocated = 0;
size_t phys_data_length, log_data_length;
reportlun_data_ext_t *physical_dev_list = NULL;
reportlun_data_ext_t *logical_dev_list = NULL;
reportlun_ext_entry_t *lun_ext_entry = NULL;
bmic_ident_physdev_t *bmic_phy_info = NULL;
pqi_scsi_dev_t **new_device_list = NULL;
pqi_scsi_dev_t *device = NULL;
DBG_FUNC("IN\n");
ret = pqisrc_get_phys_log_device_list(softs, &physical_dev_list, &logical_dev_list,
&phys_data_length, &log_data_length);
if (ret)
goto err_out;
physical_cnt = BE_32(physical_dev_list->header.list_length)
/ sizeof(physical_dev_list->lun_entries[0]);
logical_cnt = BE_32(logical_dev_list->header.list_length)
/ sizeof(logical_dev_list->lun_entries[0]);
DBG_INFO("physical_cnt %d logical_cnt %d\n", physical_cnt, logical_cnt);
if (physical_cnt) {
bmic_phy_info = os_mem_alloc(softs, sizeof(*bmic_phy_info));
if (bmic_phy_info == NULL) {
ret = PQI_STATUS_FAILURE;
DBG_ERR("failed to allocate memory for BMIC ID PHYS Device : %d\n", ret);
goto err_out;
}
}
phy_log_dev_cnt = physical_cnt + logical_cnt;
new_device_list = os_mem_alloc(softs,
sizeof(*new_device_list) * phy_log_dev_cnt);
if (new_device_list == NULL) {
ret = PQI_STATUS_FAILURE;
DBG_ERR("failed to allocate memory for device list : %d\n", ret);
goto err_out;
}
for (i = 0; i < phy_log_dev_cnt; i++) {
new_device_list[i] = os_mem_alloc(softs,
sizeof(*new_device_list[i]));
if (new_device_list[i] == NULL) {
ret = PQI_STATUS_FAILURE;
DBG_ERR("failed to allocate memory for device list : %d\n", ret);
ndev_allocated = i;
goto err_out;
}
}
ndev_allocated = phy_log_dev_cnt;
new_dev_cnt = 0;
for (i = 0; i < phy_log_dev_cnt; i++) {
if (i < physical_cnt) {
is_physical_device = true;
lun_ext_entry = &physical_dev_list->lun_entries[i];
} else {
is_physical_device = false;
lun_ext_entry =
&logical_dev_list->lun_entries[i - physical_cnt];
}
scsi3addr = lun_ext_entry->lunid;
/* Skip masked physical non-disk devices. */
if (MASKED_DEVICE(scsi3addr) && is_physical_device)
continue;
device = new_device_list[new_dev_cnt];
memset(device, 0, sizeof(*device));
memcpy(device->scsi3addr, scsi3addr, sizeof(device->scsi3addr));
device->wwid = lun_ext_entry->wwid;
device->is_physical_device = is_physical_device;
if (!is_physical_device)
device->is_external_raid_device =
pqisrc_is_external_raid_addr(scsi3addr);
/* Get device type, vendor, model, device ID. */
ret = pqisrc_get_dev_data(softs, device);
if (ret) {
DBG_WARN("Inquiry failed, skipping device %016llx\n",
(unsigned long long)BE_64(device->scsi3addr[0]));
DBG_INFO("INQUIRY FAILED \n");
continue;
}
pqisrc_assign_btl(device);
/*
* Expose all devices except for physical devices that
* are masked.
*/
if (device->is_physical_device &&
MASKED_DEVICE(scsi3addr))
device->expose_device = false;
else
device->expose_device = true;
if (device->is_physical_device &&
(lun_ext_entry->device_flags &
REPORT_LUN_DEV_FLAG_AIO_ENABLED) &&
lun_ext_entry->ioaccel_handle) {
device->aio_enabled = true;
}
switch (device->devtype) {
case ROM_DEVICE:
/*
* We don't *really* support actual CD-ROM devices,
* but we do support the HP "One Button Disaster
* Recovery" tape drive which temporarily pretends to
* be a CD-ROM drive.
*/
if (device->is_obdr_device)
new_dev_cnt++;
break;
case DISK_DEVICE:
case ZBC_DEVICE:
if (device->is_physical_device) {
device->ioaccel_handle =
lun_ext_entry->ioaccel_handle;
device->sas_address = BE_64(lun_ext_entry->wwid);
pqisrc_get_physical_device_info(softs, device,
bmic_phy_info);
}
/* Logical device doesn't have SAS address
* so requires target SAS address for MSA.
*/
if(device->is_external_raid_device)
device->sas_address = BE_64((uint64_t)lun_ext_entry->lunid);
new_dev_cnt++;
break;
case ENCLOSURE_DEVICE:
if (device->is_physical_device) {
device->sas_address = BE_64(lun_ext_entry->wwid);
}
new_dev_cnt++;
break;
case TAPE_DEVICE:
case MEDIUM_CHANGER_DEVICE:
new_dev_cnt++;
break;
case RAID_DEVICE:
/*
* Only present the HBA controller itself as a RAID
* controller. If it's a RAID controller other than
* the HBA itself (an external RAID controller, MSA500
* or similar), don't present it.
*/
if (pqisrc_is_hba_lunid(scsi3addr))
new_dev_cnt++;
break;
}
}
DBG_INFO("new_dev_cnt %d\n", new_dev_cnt);
pqisrc_update_device_list(softs, new_device_list, new_dev_cnt);
err_out:
if (new_device_list) {
for (i = 0; i < ndev_allocated; i++) {
if (new_device_list[i]) {
if(new_device_list[i]->raid_map)
os_mem_free(softs, (char *)new_device_list[i]->raid_map,
sizeof(pqisrc_raid_map_t));
os_mem_free(softs, (char*)new_device_list[i],
sizeof(*new_device_list[i]));
}
}
os_mem_free(softs, (char *)new_device_list,
sizeof(*new_device_list) * ndev_allocated);
}
if(physical_dev_list)
os_mem_free(softs, (char *)physical_dev_list, phys_data_length);
if(logical_dev_list)
os_mem_free(softs, (char *)logical_dev_list, log_data_length);
if (bmic_phy_info)
os_mem_free(softs, (char *)bmic_phy_info, sizeof(*bmic_phy_info));
DBG_FUNC("OUT \n");
return ret;
}
/*
* Clean up memory allocated for devices.
*/
void pqisrc_cleanup_devices(pqisrc_softstate_t *softs)
{
int i = 0,j = 0;
pqi_scsi_dev_t *dvp = NULL;
DBG_FUNC("IN\n");
for(i = 0; i < PQI_MAX_DEVICES; i++) {
for(j = 0; j < PQI_MAX_MULTILUN; j++) {
if (softs->device_list[i][j] == NULL)
continue;
dvp = softs->device_list[i][j];
pqisrc_device_mem_free(softs, dvp);
}
}
DBG_FUNC("OUT\n");
}