scsi: qla2xxx: Add flash read/write interface for 29xx

The 29xx series uses a different flash access mechanism than earlier
adapters.  Add the mailbox wrappers and qla_sup helpers needed for flash
read and write operations, including the necessary hooks in isp_ops so
that the existing flash infrastructure can drive the new hardware.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202607141520.k9T31Dpp-lkp@intel.com/
Signed-off-by: Manish Rangankar <mrangankar@marvell.com>
Signed-off-by: Nilesh Javali <njavali@marvell.com>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Link: https://lore.kernel.org/oe-kbuild-all/202607141520.k9T31Dpp-lkp@intel.com/
Link: https://patch.msgid.link/20260723050413.3897522-3-njavali@marvell.com
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
This commit is contained in:
Manish Rangankar 2026-07-23 10:33:19 +05:30 committed by Martin K. Petersen (Oracle)
parent af711d68e6
commit 3d2e901768
6 changed files with 898 additions and 13 deletions

View File

@ -1278,6 +1278,7 @@ static inline bool qla2xxx_is_valid_mbs(unsigned int mbs)
#define MBC_LOAD_RISC_RAM 9 /* Load RAM command. */
#define MBC_DUMP_RISC_RAM 0xa /* Dump RAM command. */
#define MBC_SECURE_FLASH_UPDATE 0xa /* Secure Flash Update(28xx) */
#define MBC_RD_WR_FLASH 0xa /* Read/write Dword/block Flash(29xx) */
#define MBC_LOAD_RISC_RAM_EXTENDED 0xb /* Load RAM extended. */
#define MBC_DUMP_RISC_RAM_EXTENDED 0xc /* Dump RAM extended. */
#define MBC_WRITE_RAM_WORD_EXTENDED 0xd /* Write RAM word extended */
@ -4170,6 +4171,7 @@ struct qla_hw_data {
#define EEH_FLUSH_RDY 1
#define EEH_FLUSH_DONE 2
uint32_t secure_mcu:1;
uint32_t valid_flt:1;
} flags;
uint16_t max_exchg;
@ -4498,6 +4500,8 @@ struct qla_hw_data {
struct qla_flt_header *flt;
dma_addr_t flt_dma;
struct qla_flash_layout *flt_data;
uint32_t fw_dump_tmplt_len;
#define XGMAC_DATA_SIZE 4096
void *xgmac_data;
@ -4722,6 +4726,9 @@ struct qla_hw_data {
uint32_t fdt_protect_sec_cmd;
uint32_t fdt_wrt_sts_reg_cmd;
#define QLA_SEGMENT_LENGTH 0x25000
uint32_t flt_segment_length;
struct {
uint32_t flt_region_flt;
uint32_t flt_region_fdt;
@ -5348,6 +5355,14 @@ static inline bool qla_vha_mark_busy(scsi_qla_host_t *vha)
/*
* Flash support definitions
*/
#define check_and_set_mbc_bits(bopt, dopt, bit_to_check, bit_to_set) { \
if (bopt & bit_to_check) \
dopt |= bit_to_set; \
}
#define SET_FW_BIT(__opts, bit) ((__opts) |= (bit))
#define CLEAR_FW_BIT(__opts, bit) ((__opts) &= ~(bit))
#define OPTROM_SIZE_2300 0x20000
#define OPTROM_SIZE_2322 0x100000
#define OPTROM_SIZE_24XX 0x100000
@ -5612,4 +5627,9 @@ struct ql_vnd_tgt_stats_resp {
(!_fcport || IS_SESSION_DELETED(_fcport) || atomic_read(&_fcport->state) != FCS_ONLINE || \
!_fcport->vha->hw->flags.fw_started)
#define is_flash_read(_opt) \
(!(_opt & BIT_9) && !(_opt & BIT_6))
#define is_flash_write(_opt) \
(!(_opt & BIT_9) && (_opt & BIT_6))
#endif

View File

@ -1695,6 +1695,10 @@ struct qla_flt_location {
#define FLT_REG_NVME_PARAMS_PRI_28XX 0x14E
#define FLT_REG_NVME_PARAMS_SEC_28XX 0x179
/* 29xx */
#define FLT_REG_MINI_FLT 0x201
#define FLT_REG_FW_DUMP_TMPLT 0x1A0
struct qla_flt_region {
__le16 code;
uint8_t attribute;
@ -1716,6 +1720,56 @@ struct qla_flt_header {
#define FLT_MAX_REGIONS 0xFF
#define FLT_REGIONS_SIZE (FLT_REGION_SIZE * FLT_MAX_REGIONS)
/* 29xx */
#define FLT_HDR_VERSION 0x2
struct qla_flt_region_header {
__le32 signature;
__le32 version;
__le32 length;
__le32 checksum;
__le16 region_count;
__le16 region_size;
__le32 segment_size;
__le32 res3;
__le32 res4;
__le32 res5;
__le32 res6;
__le32 res7;
__le32 res8;
__le32 res9;
__le32 res10;
__le32 res11;
__le32 res12;
};
struct qla_flt_region_data {
__le16 region_code;
__le16 reserved;
__le32 attribute;
__le32 image_length;
__le32 mbi_offset;
__le32 version;
__le32 card_type;
__le32 chip_revision;
__le32 res4;
__le32 res5;
__le32 res6;
__le32 res7;
__le32 res8;
__le32 res9;
__le32 res10;
__le32 res11;
__le32 res12;
};
struct qla_flash_layout {
struct qla_flt_region_header flt_header;
struct qla_flt_region_data region[];
};
#define FLT_DATA_MAX_REGIONS 0xFF
/* Flash NPIV Configuration Table ********************************************/
struct qla_npiv_header {

View File

@ -580,6 +580,12 @@ extern int qla2xxx_read_remote_register(scsi_qla_host_t *, uint32_t,
extern int qla2xxx_write_remote_register(scsi_qla_host_t *, uint32_t,
uint32_t);
void qla_no_op_mb(struct scsi_qla_host *vha);
extern int qla29xx_flash_block_read(scsi_qla_host_t *vha, dma_addr_t req_dma,
uint32_t flash_addr, uint32_t flash_size,
uint16_t reg_code, uint16_t opt);
extern int qla29xx_flash_block_write(scsi_qla_host_t *vha, dma_addr_t req_dma,
uint32_t flash_addr, uint32_t flash_size,
uint16_t reg_code, uint16_t opt);
/*
* Global Function Prototypes in qla_isr.c source file.
@ -682,7 +688,14 @@ struct purex_item *qla27xx_copy_multiple_pkt(struct scsi_qla_host *vha,
void **pkt, struct rsp_que **rsp, bool is_purls, bool byte_order);
int qla_mailbox_passthru(scsi_qla_host_t *vha, uint16_t *mbx_in,
uint16_t *mbx_out);
void *qla29xx_read_optrom_data(struct scsi_qla_host *vha,
uint16_t reg_code, uint16_t opts,
void *buf, uint32_t offset,
uint32_t length);
extern int qla29xx_write_optrom_data(struct scsi_qla_host *vha,
uint16_t reg_code, uint16_t opts,
void *buf, uint32_t offset,
uint32_t length);
/*
* Global Function Prototypes in qla_dbg.c source file.
*/

View File

@ -7254,3 +7254,147 @@ int qla_mpipt_validate_fw(scsi_qla_host_t *vha, u16 img_idx, uint16_t *state)
return rval;
}
int qla29xx_flash_block_read(scsi_qla_host_t *vha, dma_addr_t req_dma,
uint32_t flash_addr, uint32_t flash_size,
uint16_t reg_code, uint16_t opt)
{
mbx_cmd_t mc;
mbx_cmd_t *mcp = &mc;
int rval = 0;
ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1067,
"Entered %s options 0x%x.\n", __func__, opt);
memset(mcp->mb, 0, sizeof(mcp->mb));
if (!is_flash_read(opt)) {
ql_log(ql_log_info, vha, 0x1068,
"%s: Invalid flash option 0x%x.\n", __func__, opt);
return -EINVAL;
}
mcp->mb[0] = MBC_RD_WR_FLASH;
/* mailbox option field :
* TIM img or Img : BIT_15 (1/0)
* Last seg img : BIT_11 (1)
* First seg img : BIT_10 (1)
* dword or block : BIT_9 (1/0)
* flash MBR update : BIT_8 (1/0)
* Secure or non-secure : BIT_7 (1/0)
* Write or Read : BIT_6 (1/0)
* Last block img : BIT_5 (1)
* First block img : BIT_4 (1)
* Request type : (BIT3 - BIT_0)
* - Normal
* - Normal, Force Sema
* - Initialize
* - Initialize, Force sema
* - Abort secured update
*/
mcp->mb[1] = opt;
mcp->mb[2] = reg_code;
mcp->mb[3] = MSW(flash_size);
mcp->mb[4] = LSW(flash_size);
mcp->mb[5] = MSW(req_dma);
mcp->mb[6] = LSW(req_dma);
mcp->mb[7] = MSW(MSD(req_dma));
mcp->mb[8] = LSW(MSD(req_dma));
mcp->mb[9] = MSW(flash_addr);
mcp->mb[10] = LSW(flash_addr);
mcp->out_mb =
MBX_10|MBX_9|MBX_8|MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0;
mcp->in_mb = MBX_3|MBX_2|MBX_1|MBX_0;
mcp->tov = MBX_TOV_SECONDS;
mcp->flags = 0;
rval = qla2x00_mailbox_command(vha, mcp);
if (rval != QLA_SUCCESS) {
ql_dbg(ql_dbg_mbx, vha, 0x103f,
"Failed=%x mb=(0x%x,0x%x,0x%x,0x%x).\n",
rval, mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3]);
} else {
ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1043,
"Done %s mb=(0x%x,0x%x,0x%x).\n", __func__,
mcp->mb[0], mcp->mb[1], mcp->mb[2]);
}
return rval;
}
int qla29xx_flash_block_write(scsi_qla_host_t *vha, dma_addr_t req_dma,
uint32_t flash_addr, uint32_t flash_size,
uint16_t reg_code, uint16_t opt)
{
mbx_cmd_t mc;
mbx_cmd_t *mcp = &mc;
int rval = 0;
ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1069,
"Entered %s options 0x%x.\n", __func__, opt);
memset(mcp->mb, 0, sizeof(mcp->mb));
if (!is_flash_write(opt)) {
ql_log(ql_log_info, vha, 0x106a,
"%s: Invalid flash option 0x%x.\n", __func__, opt);
return -EINVAL;
}
mcp->mb[0] = MBC_RD_WR_FLASH;
/* mailbox option field :
* TIM img or Img : BIT_15 (1/0)
* Last seg img : BIT_11 (1)
* First seg img : BIT_10 (1)
* dword or block : BIT_9 (1/0)
* flash MBR update : BIT_8 (1/0)
* Secure or non-secure : BIT_7 (1/0)
* Write or Read : BIT_6 (1/0)
* Last block img : BIT_5 (1)
* First block img : BIT_4 (1)
* Request type : (BIT3 - BIT_0)
* - Normal
* - Normal, Force Sema
* - Initialize
* - Initialize, Force sema
* - Abort secured update
*/
mcp->mb[1] = opt;
mcp->mb[2] = reg_code;
mcp->mb[3] = MSW(flash_size);
mcp->mb[4] = LSW(flash_size);
mcp->mb[5] = MSW(req_dma);
mcp->mb[6] = LSW(req_dma);
mcp->mb[7] = MSW(MSD(req_dma));
mcp->mb[8] = LSW(MSD(req_dma));
mcp->mb[9] = MSW(flash_addr);
mcp->mb[10] = LSW(flash_addr);
mcp->out_mb =
MBX_10|MBX_9|MBX_8|MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0;
mcp->in_mb = MBX_3|MBX_2|MBX_1|MBX_0;
mcp->tov = MBX_TOV_SECONDS;
mcp->flags = 0;
rval = qla2x00_mailbox_command(vha, mcp);
if (rval != QLA_SUCCESS) {
ql_dbg(ql_dbg_mbx, vha, 0x110a,
"Failed=%x mb=(0x%x,0x%x,0x%x,0x%x).\n",
rval, mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3]);
} else {
ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x110b,
"Done %s mb=(0x%x,0x%x,0x%x).\n", __func__,
mcp->mb[0], mcp->mb[1], mcp->mb[2]);
}
return rval;
}

View File

@ -3237,6 +3237,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
ha->flash_data_off = ~0;
ha->nvram_conf_off = ~0;
ha->nvram_data_off = ~0;
ha->flt_segment_length = QLA_SEGMENT_LENGTH;
}
ql_dbg_pci(ql_dbg_init, pdev, 0x001e,
@ -4488,6 +4489,14 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len,
goto fail_flt_buffer;
}
ha->flt_data = vzalloc(sizeof(struct qla_flash_layout) +
(sizeof(struct qla_flt_region_data) * FLT_MAX_REGIONS));
if (!ha->flt_data) {
ql_dbg_pci(ql_dbg_init, ha->pdev, 0x001a,
"Unable to allocate memory for mini FLT data.\n");
goto fail_flt;
}
/* allocate the purex dma pool */
ha->purex_dma_pool = dma_pool_create(name, &ha->pdev->dev,
ELS_MAX_PAYLOAD, 8, 0);
@ -4495,7 +4504,7 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len,
if (!ha->purex_dma_pool) {
ql_dbg_pci(ql_dbg_init, ha->pdev, 0x011b,
"Unable to allocate purex_dma_pool.\n");
goto fail_flt;
goto fail_flt_data;
}
ha->elsrej.size = sizeof(struct fc_els_ls_rjt) + 16;
@ -4528,6 +4537,9 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len,
ha->elsrej.c, ha->elsrej.cdma);
fail_elsrej:
dma_pool_destroy(ha->purex_dma_pool);
fail_flt_data:
vfree(ha->flt_data);
ha->flt_data = NULL;
fail_flt:
dma_free_coherent(&ha->pdev->dev, sizeof(struct qla_flt_header) + FLT_REGIONS_SIZE,
ha->flt, ha->flt_dma);
@ -4963,6 +4975,10 @@ qla2x00_mem_free(struct qla_hw_data *ha)
ha->flt = NULL;
ha->flt_dma = 0;
if (ha->flt_data)
vfree(ha->flt_data);
ha->flt_data = NULL;
if (ha->ms_iocb)
dma_pool_free(ha->s_dma_pool, ha->ms_iocb, ha->ms_iocb_dma);
ha->ms_iocb = NULL;

View File

@ -10,6 +10,537 @@
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
/**
* qla29xx_get_flt_layout - Retrieve the flash layout table (FLT) for QLA29xx.
* @vha: Pointer to SCSI QLogic host structure.
*
* This function reads and validates the FLT structure from the flash memory.
* It extracts region information and updates the hardware data structure.
*/
static void
qla29xx_get_flt_layout(scsi_qla_host_t *vha)
{
struct qla_hw_data *ha = vha->hw;
struct qla_flash_layout *flt_layout = ha->flt_data;
struct qla_flt_region_header *flt_header = &flt_layout->flt_header;
struct qla_flt_region_data *region = &flt_layout->region[0];
uint32_t flt_options = 0;
uint32_t flt_size;
uint32_t cnt, chksum;
uint16_t reg_cnt, i;
__le32 *wptr;
void *buf = NULL;
flt_size = sizeof(struct qla_flt_region_header);
wptr = (__force __le32 *)ha->flt_data;
buf = qla29xx_read_optrom_data(vha, FLT_REG_MINI_FLT, flt_options,
ha->flt_data, 0, flt_size);
if (!buf) {
ql_log(ql_log_warn, vha, 0x007b,
"Failed to read FLT information.\n");
goto exit_flt;
}
ql_dbg(ql_dbg_init + ql_dbg_buffer, vha, 0x0111,
"Contents of Flash Layout (0x%x):\n", flt_size);
ql_dump_buffer(ql_dbg_init + ql_dbg_buffer, vha, 0x0112,
ha->flt_data, flt_size);
/* check flt version checksum and other info */
if ((le32_to_cpu(*wptr) == 0xffffffff) ||
flt_header->version != cpu_to_le32(FLT_HDR_VERSION)) {
ql_log(ql_log_warn, vha, 0x007c,
"Unsupported FLT detected: version=0x%x length=0x%x signature=0x%x region_count=0x%x checksum=0x%x.\n",
le32_to_cpu(flt_header->version),
le32_to_cpu(flt_header->length),
le32_to_cpu(flt_header->signature),
le16_to_cpu(flt_header->region_count),
le32_to_cpu(flt_header->checksum));
goto exit_flt;
}
reg_cnt = le16_to_cpu(flt_header->region_count);
if (reg_cnt > FLT_MAX_REGIONS)
goto exit_flt;
if (le16_to_cpu(flt_header->region_size) !=
sizeof(struct qla_flt_region_data)) {
ql_log(ql_log_warn, vha, 0x0082,
"Unsupported FLT region size 0x%x (expected 0x%zx).\n",
le16_to_cpu(flt_header->region_size),
sizeof(struct qla_flt_region_data));
goto exit_flt;
}
flt_size = sizeof(struct qla_flash_layout) +
(reg_cnt * le16_to_cpu(flt_header->region_size));
if (flt_size > sizeof(struct qla_flash_layout) +
sizeof(struct qla_flt_region_data) * FLT_DATA_MAX_REGIONS) {
ql_log(ql_log_warn, vha, 0x007d,
"FLT size 0x%x exceeds buffer, region_size=0x%x.\n",
flt_size, le16_to_cpu(flt_header->region_size));
goto exit_flt;
}
buf = qla29xx_read_optrom_data(vha, FLT_REG_MINI_FLT, flt_options,
ha->flt_data, 0, flt_size);
if (!buf) {
ql_log(ql_log_warn, vha, 0x007b,
"Failed to read FLT information.\n");
goto exit_flt;
}
cnt = le32_to_cpu(flt_header->length) / sizeof(*wptr);
if (cnt > flt_size / sizeof(*wptr)) {
ql_log(ql_log_warn, vha, 0x007e,
"FLT length 0x%x exceeds read size 0x%x, rejecting.\n",
le32_to_cpu(flt_header->length), flt_size);
goto exit_flt;
}
for (chksum = 0; cnt--; wptr++)
chksum += le32_to_cpu(*wptr);
if (chksum) {
ql_log(ql_log_fatal, vha, 0x007f,
"Inconsistent FLT detected: version=0x%x length=0x%x signature=0x%x checksum=0x%x.\n",
le32_to_cpu(flt_header->version),
le32_to_cpu(flt_header->length),
le32_to_cpu(flt_header->signature),
le32_to_cpu(flt_header->checksum));
goto exit_flt;
}
ql_dbg(ql_dbg_init, vha, 0x007f,
"FLT detected: version=0x%08x length=0x%08x signature=0x%08x region_count=0x%04x region_len=0x%04x segment_len=0x%08x checksum=0x%08x.\n",
le32_to_cpu(flt_header->version),
le32_to_cpu(flt_header->length),
le32_to_cpu(flt_header->signature),
le16_to_cpu(flt_header->region_count),
le16_to_cpu(flt_header->region_size),
le32_to_cpu(flt_header->segment_size),
le32_to_cpu(flt_header->checksum));
for (i = 0; reg_cnt; i++, reg_cnt--) {
region = &flt_layout->region[i];
ql_dbg(ql_dbg_init, vha, 0x0080,
"FLT[%03x]: len=0x%08x version=0x%08x attr=0x%02x.\n",
le16_to_cpu(region->region_code),
le32_to_cpu(region->image_length),
le32_to_cpu(region->version),
le32_to_cpu(region->attribute));
if (le16_to_cpu(region->region_code) == FLT_REG_FW_DUMP_TMPLT) {
ql_dbg(ql_dbg_init, vha, 0x0080,
"%s: %d: Found FW dump template", __func__, __LINE__);
ha->fw_dump_tmplt_len = le32_to_cpu(region->image_length);
}
}
if (le32_to_cpu(flt_header->segment_size) >= sizeof(uint32_t)) {
ha->flt_segment_length = le32_to_cpu(flt_header->segment_size);
} else {
ql_log(ql_log_warn, vha, 0x0081,
"FLT segment size 0x%x too small, using default 0x%x.\n",
le32_to_cpu(flt_header->segment_size), QLA_SEGMENT_LENGTH);
ha->flt_segment_length = QLA_SEGMENT_LENGTH;
}
ha->flags.valid_flt = true;
return;
exit_flt:
ha->flt_segment_length = QLA_SEGMENT_LENGTH;
}
/**
* qla29xx_get_fdt_info - Retrieve flash descriptor table (FDT) information.
* @vha: Pointer to SCSI QLogic host structure.
*
* This function reads and validates the FDT structure from the flash memory.
* It extracts manufacturer and device-specific information and updates
* the hardware data structure.
*/
static void
qla29xx_get_fdt_info(scsi_qla_host_t *vha)
{
#define FLASH_BLK_SIZE_4K 0x1000
#define FLASH_BLK_SIZE_32K 0x8000
#define FLASH_BLK_SIZE_64K 0x10000
struct qla_hw_data *ha = vha->hw;
struct req_que *req = ha->req_q_map[0];
uint16_t cnt, chksum;
__le16 *wptr = (__force __le16 *)req->ring;
struct qla_fdt_layout *fdt = (struct qla_fdt_layout *)req->ring;
uint32_t fdt_options = 0;
void *buf = NULL;
buf = qla29xx_read_optrom_data(vha, FLT_REG_FDT, fdt_options,
fdt, 0, sizeof(*fdt));
if (!buf) {
ql_log(ql_log_warn, vha, 0x0047,
"Failed to read FLT information.\n");
return;
}
if (le16_to_cpu(*wptr) == 0xffff)
return;
if (memcmp(fdt->sig, "QLID", 4))
return;
for (cnt = 0, chksum = 0; cnt < sizeof(*fdt) >> 1; cnt++, wptr++)
chksum += le16_to_cpu(*wptr);
if (chksum) {
ql_dbg(ql_dbg_init, vha, 0x004c,
"Inconsistent FDT detected: checksum=0x%x id=%c version0x%x.\n",
chksum, fdt->sig[0], le16_to_cpu(fdt->version));
ql_dump_buffer(ql_dbg_init + ql_dbg_buffer, vha, 0x0113,
fdt, sizeof(*fdt));
return;
}
}
/**
* qla29xx_get_flash_region - Retrieve flash region information for QLA29xx adapters.
* @vha: Pointer to SCSI QLogic host structure.
* @code: Region code to identify the flash region.
* @region: Pointer to store the retrieved flash region information.
*
* This function retrieves the flash region information for QLA29xx adapters
* based on the specified region code.
*
* Returns QLA_SUCCESS on success or QLA_FUNCTION_FAILED on failure.
*/
static int
qla29xx_get_flash_region(struct scsi_qla_host *vha, uint32_t code,
struct qla_flt_region_data *region)
{
struct qla_hw_data *ha = vha->hw;
struct qla_flash_layout *flt = ha->flt_data;
struct qla_flt_region_header *flt_hdr = &flt->flt_header;
struct qla_flt_region_data *flt_reg = &flt->region[0];
uint16_t cnt;
int rval = QLA_FUNCTION_FAILED;
if (!ha->flt_data || !ha->flags.valid_flt)
return QLA_FUNCTION_FAILED;
cnt = le16_to_cpu(flt_hdr->region_count);
if (cnt > FLT_MAX_REGIONS)
return QLA_FUNCTION_FAILED;
/*
* flt_reg++ strides by sizeof(struct qla_flt_region_data). This is
* safe because valid_flt is set only after qla29xx_get_flt_layout()
* has verified the firmware region_size equals that struct size.
*/
for (; cnt; cnt--, flt_reg++) {
if (le16_to_cpu(flt_reg->region_code) == code) {
memcpy((uint8_t *)region, flt_reg,
sizeof(struct qla_flt_region_data));
rval = QLA_SUCCESS;
break;
}
}
return rval;
}
/**
* set_segment_bits - Set segment-related bits in the options field.
* @options: Pointer to the options field.
* @segment_index: Index of the current segment.
* @total: Total number of segments.
*
* This function sets the appropriate bits in the options field to indicate
* whether the current segment is the first, last, or a single segment.
*/
static void set_segment_bits(uint16_t *options, int segment_index, int total)
{
/* - Single segment complete image.
* - 1st Segment of an image.
* - Last segment of an image.
*/
if (total == 1)
*options |= (1 << 10) | (1 << 11);
else if (segment_index == 0)
*options |= (1 << 10);
else if (segment_index == total - 1)
*options |= (1 << 11);
}
/**
* set_chunk_bits - Set chunk-related bits in the options field.
* @options: Pointer to the options field.
* @count: Index of the current chunk.
* @total: Total number of chunks.
*
* This function sets the appropriate bits in the options field to indicate
* whether the current chunk is the first, last, or a single chunk.
*/
static void set_chunk_bits(uint16_t *options, int count, int total)
{
/* - Single chunk complete segment
* - 1st chunk of a segment
* - Last chunk of a segment
*/
if (total == 1)
*options |= (1 << 4) | (1 << 5);
else if (count == 0)
*options |= (1 << 4);
else if (count == total - 1)
*options |= (1 << 5);
}
/**
* qla29xx_write_optrom_data - Write data to the optrom for QLA29xx adapters.
* @vha: Pointer to SCSI QLogic host structure.
* @reg_code: Region code to write to.
* @opts: Options for the write operation.
* @buf: Buffer containing the data to write.
* @offset: Offset within the region to start writing.
* @length: Length of data to write.
*
* This function writes data to the specified optrom region for QLA29xx adapters.
*
* Returns 0 on success or a negative error code on failure.
*/
int
qla29xx_write_optrom_data(struct scsi_qla_host *vha, uint16_t reg_code,
uint16_t opts, void *buf, uint32_t offset,
uint32_t length)
{
struct qla_hw_data *ha = vha->hw;
struct qla_flt_region_data region;
dma_addr_t optrom_dma;
uint32_t faddr, left, burst;
uint32_t img_len, seg_dlen;
uint32_t region_len, region_dlen;
void *optrom;
uint8_t *pbuf;
int rval = -EINVAL;
uint16_t total_segments, segment_index = 0;
uint16_t chunk_index = 0;
memset(&region, 0, sizeof(region));
optrom = dma_alloc_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE,
&optrom_dma, GFP_KERNEL);
if (!optrom) {
ql_log(ql_log_warn, vha, 0x0090,
"Unable to allocate memory for optrom burst read (%x KB).\n",
OPTROM_BURST_SIZE / 1024);
return -ENOMEM;
}
if (ha->flags.valid_flt && length == 0) {
/* Get image length and segment length from FLT */
rval = qla29xx_get_flash_region(vha, reg_code, &region);
if (rval != QLA_SUCCESS) {
ql_log(ql_log_warn, vha, 0x0092,
"Invalid address %x - not a region start address\n",
reg_code);
goto free_buf;
}
img_len = le32_to_cpu(region.image_length);
} else {
img_len = length;
}
seg_dlen = ha->flt_segment_length >> 2;
region_len = (length > 0) ? length : img_len;
region_dlen = (region_len >> 2);
total_segments = (region_dlen + seg_dlen - 1) / seg_dlen;
faddr = offset >> 2;
left = region_dlen;
burst = OPTROM_BURST_DWORDS;
pbuf = buf;
while (region_dlen > 0) {
uint32_t segment_size, total_chunks;
uint16_t options = 0;
segment_size = (region_dlen > seg_dlen) ? seg_dlen : region_dlen;
total_chunks = (segment_size + OPTROM_BURST_DWORDS - 1) /
OPTROM_BURST_DWORDS;
burst = OPTROM_BURST_DWORDS;
if (burst > left)
burst = left;
if (burst > segment_size - chunk_index * OPTROM_BURST_DWORDS)
burst = segment_size - chunk_index * OPTROM_BURST_DWORDS;
set_segment_bits(&options, segment_index, total_segments);
set_chunk_bits(&options, chunk_index, total_chunks);
/* flash block write operations */
SET_FW_BIT(options, BIT_6);
CLEAR_FW_BIT(options, BIT_9);
check_and_set_mbc_bits(opts, options, BIT_15, BIT_15);
check_and_set_mbc_bits(opts, options, BIT_7, BIT_7);
if (segment_index == total_segments - 1 &&
chunk_index == total_chunks - 1)
check_and_set_mbc_bits(opts, options, BIT_8, BIT_8);
memcpy(optrom, pbuf, burst * 4);
/* faddr is offset relative to region code */
rval = qla29xx_flash_block_write(vha, optrom_dma,
faddr, burst, reg_code, options);
if (rval) {
ql_log(ql_log_warn, vha, 0x0095,
"Unable to burst-write optrom segment (%x/%x/%llx).\n",
rval, faddr, (unsigned long long)optrom_dma);
dma_free_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE,
optrom, optrom_dma);
goto exit_write;
}
left -= burst;
faddr += burst;
pbuf += burst * 4;
chunk_index++;
if (chunk_index >= total_chunks) {
chunk_index = 0;
segment_index++;
region_dlen -= segment_size;
}
}
free_buf:
dma_free_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE, optrom,
optrom_dma);
return rval;
exit_write:
return rval;
}
/**
* qla29xx_read_optrom_data - Read data from the optrom for QLA29xx adapters.
* @vha: Pointer to SCSI QLogic host structure.
* @reg_code: Region code to read from.
* @opts: Options for the read operation.
* @buf: Buffer to store the read data.
* @offset: Offset within the region to start reading.
* @length: Length of data to read.
*
* This function reads data from the specified optrom region for QLA29xx adapters.
*
* Returns a pointer to the buffer on success or NULL on failure.
*/
void *
qla29xx_read_optrom_data(struct scsi_qla_host *vha, uint16_t reg_code,
uint16_t opts, void *buf, uint32_t offset,
uint32_t length)
{
struct qla_hw_data *ha = vha->hw;
struct qla_flt_region_data region;
dma_addr_t optrom_dma;
uint32_t faddr, left, burst;
uint32_t img_len, seg_dlen;
uint32_t region_len, region_dlen;
void *optrom;
uint8_t *pbuf;
uint16_t total_segments, segment_index = 0;
uint16_t chunk_index = 0;
int rval;
memset(&region, 0, sizeof(region));
optrom = dma_alloc_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE,
&optrom_dma, GFP_KERNEL);
if (!optrom) {
ql_log(ql_log_warn, vha, 0x0093,
"Unable to allocate memory for optrom burst read (%x KB).\n",
OPTROM_BURST_SIZE / 1024);
return NULL;
}
if (ha->flags.valid_flt && length == 0) {
/* Get image length and segment length from FLT */
rval = qla29xx_get_flash_region(vha, reg_code, &region);
if (rval != QLA_SUCCESS) {
ql_log(ql_log_warn, vha, 0x7033,
"Invalid address %x - not a region start address\n",
reg_code);
goto free_buf;
}
img_len = le32_to_cpu(region.image_length);
} else {
img_len = length;
}
seg_dlen = ha->flt_segment_length >> 2;
region_len = (length > 0) ? length : img_len;
region_dlen = (region_len >> 2);
total_segments = (region_dlen + seg_dlen - 1) / seg_dlen;
faddr = offset >> 2;
left = region_dlen;
burst = OPTROM_BURST_DWORDS;
pbuf = buf;
ql_log(ql_log_info, vha, 0x0096,
"Reg[0x%x]: options=0x%x length=0x%x offset=0x%x segments=%u\n",
reg_code, opts, region_len, offset, total_segments);
while (region_dlen > 0) {
uint32_t segment_size, total_chunks;
uint16_t options = 0;
segment_size = (region_dlen > seg_dlen) ? seg_dlen : region_dlen;
total_chunks = (segment_size + OPTROM_BURST_DWORDS - 1) /
OPTROM_BURST_DWORDS;
burst = OPTROM_BURST_DWORDS;
if (burst > left)
burst = left;
if (burst > segment_size - chunk_index * OPTROM_BURST_DWORDS)
burst = segment_size - chunk_index * OPTROM_BURST_DWORDS;
set_segment_bits(&options, segment_index, total_segments);
set_chunk_bits(&options, chunk_index, total_chunks);
/* flash block read operations */
CLEAR_FW_BIT(options, BIT_9);
CLEAR_FW_BIT(options, BIT_6);
options |= opts;
/* faddr is offset relative to region code */
rval = qla29xx_flash_block_read(vha, optrom_dma,
faddr, burst, reg_code, options);
if (rval) {
ql_log(ql_log_warn, vha, 0x0097,
"Unable to burst-read optrom segment (%x/%x/%llx).\n",
rval, faddr, (unsigned long long)optrom_dma);
goto free_buf;
}
memcpy(pbuf, optrom, burst * 4);
left -= burst;
faddr += burst;
pbuf += burst * 4;
chunk_index++;
if (chunk_index >= total_chunks) {
chunk_index = 0;
segment_index++;
region_dlen -= segment_size;
}
}
dma_free_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE, optrom,
optrom_dma);
return buf;
free_buf:
dma_free_coherent(&ha->pdev->dev, OPTROM_BURST_SIZE, optrom,
optrom_dma);
return NULL;
}
/*
* NVRAM support routines
*/
@ -1117,10 +1648,18 @@ qla2xxx_get_flash_info(scsi_qla_host_t *vha)
uint32_t flt_addr;
struct qla_hw_data *ha = vha->hw;
if (!IS_QLA24XX_TYPE(ha) && !IS_QLA25XX(ha) &&
!IS_CNA_CAPABLE(ha) && !IS_QLA2031(ha) &&
!IS_QLA27XX(ha) && !IS_QLA28XX(ha))
return QLA_SUCCESS;
if (!IS_QLA24XX_TYPE(ha) && !IS_QLA25XX(ha) && !IS_CNA_CAPABLE(ha) &&
!IS_QLA2031(ha) && !IS_QLA27XX(ha) && !IS_QLA28XX(ha) &&
!IS_QLA29XX(ha))
goto done;
if (IS_QLA29XX(ha)) {
mutex_lock(&ha->optrom_mutex);
qla29xx_get_flt_layout(vha);
qla29xx_get_fdt_info(vha);
mutex_unlock(&ha->optrom_mutex);
goto done;
}
if (IS_QLA28XX(ha) && !qla28xx_validate_mcu_signature(vha))
ha->flags.secure_mcu = 1;
@ -1132,7 +1671,7 @@ qla2xxx_get_flash_info(scsi_qla_host_t *vha)
qla2xxx_get_flt_info(vha, flt_addr);
qla2xxx_get_fdt_info(vha);
qla2xxx_get_idc_param(vha);
done:
return QLA_SUCCESS;
}
@ -3448,6 +3987,86 @@ qla82xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
return ret;
}
/*
* Read the boot BIOS/FCODE/EFI version for 29xx adapters.
*
* 29xx reads flash through an FLT region code plus a region-relative
* offset. The firmware only honours such a read at the region base
* (offset 0) and rejects a non-zero offset with MBS_COMMAND_ERROR. The
* PCI expansion ROM header and the PCI data structure it points to both
* live within the first dwords of FLT_REG_BOOT_CODE, so read one bounded
* block at offset 0 and parse both structures from memory instead of
* issuing a second read at the PCI-data-structure offset.
*/
#define QLA29XX_BOOT_PROBE_LEN 0x100
static void
qla29xx_get_boot_version(scsi_qla_host_t *vha, void *mbuf)
{
struct qla_hw_data *ha = vha->hw;
uint8_t *bcode = mbuf;
uint32_t pcids;
uint8_t code_type;
if (!qla29xx_read_optrom_data(vha, FLT_REG_BOOT_CODE, 0, mbuf, 0,
QLA29XX_BOOT_PROBE_LEN)) {
ql_log(ql_log_info, vha, 0x018e,
"Unable to read PCI Data Structure.\n");
return;
}
/* Verify PCI expansion ROM header. */
if (memcmp(bcode, "\x55\xaa", 2)) {
ql_log(ql_log_info, vha, 0x0059,
"No matching ROM signature.\n");
return;
}
/* Locate the PCI data structure within the buffer. */
pcids = (bcode[0x19] << 8) | bcode[0x18];
if (pcids + 0x16 > QLA29XX_BOOT_PROBE_LEN)
return;
bcode += pcids;
/* Validate signature of PCI data structure. */
if (memcmp(bcode, "PCIR", 4)) {
ql_log(ql_log_info, vha, 0x005a,
"PCI data struct not found pcir_adr=%x.\n", pcids);
return;
}
code_type = bcode[0x14];
switch (code_type) {
case ROM_CODE_TYPE_BIOS:
/* Intel x86, PC-AT compatible. */
ha->bios_revision[0] = bcode[0x12];
ha->bios_revision[1] = bcode[0x13];
ql_dbg(ql_dbg_init, vha, 0x005b, "Read BIOS %d.%d.\n",
ha->bios_revision[1], ha->bios_revision[0]);
break;
case ROM_CODE_TYPE_FCODE:
/* Open Firmware standard for PCI (FCode). */
ha->fcode_revision[0] = bcode[0x12];
ha->fcode_revision[1] = bcode[0x13];
ql_dbg(ql_dbg_init, vha, 0x005c, "Read FCODE %d.%d.\n",
ha->fcode_revision[1], ha->fcode_revision[0]);
break;
case ROM_CODE_TYPE_EFI:
/* Extensible Firmware Interface (EFI). */
ha->efi_revision[0] = bcode[0x12];
ha->efi_revision[1] = bcode[0x13];
ql_dbg(ql_dbg_init, vha, 0x005d, "Read EFI %d.%d.\n",
ha->efi_revision[1], ha->efi_revision[0]);
break;
default:
ql_log(ql_log_warn, vha, 0x005e,
"Unrecognized code type %x at pcids %x.\n",
code_type, pcids);
break;
}
}
int
qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
{
@ -3472,12 +4091,31 @@ qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
memset(ha->fcode_revision, 0, sizeof(ha->fcode_revision));
memset(ha->fw_revision, 0, sizeof(ha->fw_revision));
/* ISP29xx: get FW version from FLT region metadata */
if (IS_QLA29XX(ha)) {
struct qla_flt_region_data region;
ret = qla29xx_get_flash_region(vha, FLT_REG_FW, &region);
if (ret != QLA_SUCCESS) {
ql_log(ql_log_warn, vha, 0x7033,
"Invalid region %x\n", FLT_REG_FW);
return ret;
}
ha->fw_revision[0] = (le32_to_cpu(region.version) >> 16) & 0xff;
ha->fw_revision[1] = (le32_to_cpu(region.version) >> 8) & 0xff;
ha->fw_revision[2] = le32_to_cpu(region.version) & 0xff;
/* BIOS/FCODE/EFI version from the boot-code region. */
qla29xx_get_boot_version(vha, mbuf);
return ret;
}
pcihdr = ha->flt_region_boot << 2;
if (IS_QLA27XX(ha) || IS_QLA28XX(ha)) {
qla27xx_get_active_image(vha, &active_regions);
if (active_regions.global == QLA27XX_SECONDARY_IMAGE) {
if (active_regions.global == QLA27XX_SECONDARY_IMAGE)
pcihdr = ha->flt_region_boot_sec << 2;
}
}
do {
@ -3486,7 +4124,7 @@ qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
if (ret) {
ql_log(ql_log_info, vha, 0x017d,
"Unable to read PCI EXP Rom Header(%x).\n", ret);
return QLA_FUNCTION_FAILED;
break;
}
bcode = mbuf + (pcihdr % 4);
@ -3494,7 +4132,7 @@ qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
/* No signature */
ql_log(ql_log_fatal, vha, 0x0059,
"No matching ROM signature.\n");
return QLA_FUNCTION_FAILED;
break;
}
/* Locate PCI data structure. */
@ -3504,7 +4142,7 @@ qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
if (ret) {
ql_log(ql_log_info, vha, 0x018e,
"Unable to read PCI Data Structure (%x).\n", ret);
return QLA_FUNCTION_FAILED;
break;
}
bcode = mbuf + (pcihdr % 4);
@ -3515,7 +4153,7 @@ qla24xx_get_flash_version(scsi_qla_host_t *vha, void *mbuf)
ql_log(ql_log_fatal, vha, 0x005a,
"PCI data struct not found pcir_adr=%x.\n", pcids);
ql_dump_buffer(ql_dbg_init, vha, 0x0059, dcode, 32);
return QLA_FUNCTION_FAILED;
break;
}
/* Read version */