swh:1:snp:49cd9498d6cccc5e78252c27dcb645bcf7bf0c91
Tip revision: 3d77e6a8804abcc0504c904bd6e5cdf3a5cf8162 authored by Linus Torvalds on 31 May 2020, 23:49:15 UTC
Linux 5.7
Linux 5.7
Tip revision: 3d77e6a
esas2r_flash.c
/*
* linux/drivers/scsi/esas2r/esas2r_flash.c
* For use with ATTO ExpressSAS R6xx SAS/SATA RAID controllers
*
* Copyright (c) 2001-2013 ATTO Technology, Inc.
* (mailto:linuxdrivers@attotech.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* NO WARRANTY
* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
* solely responsible for determining the appropriateness of using and
* distributing the Program and assumes all risks associated with its
* exercise of rights under this Agreement, including but not limited to
* the risks and costs of program errors, damage to or loss of data,
* programs or equipment, and unavailability or interruption of operations.
*
* DISCLAIMER OF LIABILITY
* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
#include "esas2r.h"
/* local macro defs */
#define esas2r_nvramcalc_cksum(n) \
(esas2r_calc_byte_cksum((u8 *)(n), sizeof(struct esas2r_sas_nvram), \
SASNVR_CKSUM_SEED))
#define esas2r_nvramcalc_xor_cksum(n) \
(esas2r_calc_byte_xor_cksum((u8 *)(n), \
sizeof(struct esas2r_sas_nvram), 0))
#define ESAS2R_FS_DRVR_VER 2
static struct esas2r_sas_nvram default_sas_nvram = {
{ 'E', 'S', 'A', 'S' }, /* signature */
SASNVR_VERSION, /* version */
0, /* checksum */
31, /* max_lun_for_target */
SASNVR_PCILAT_MAX, /* pci_latency */
SASNVR1_BOOT_DRVR, /* options1 */
SASNVR2_HEARTBEAT | SASNVR2_SINGLE_BUS /* options2 */
| SASNVR2_SW_MUX_CTRL,
SASNVR_COAL_DIS, /* int_coalescing */
SASNVR_CMDTHR_NONE, /* cmd_throttle */
3, /* dev_wait_time */
1, /* dev_wait_count */
0, /* spin_up_delay */
0, /* ssp_align_rate */
{ 0x50, 0x01, 0x08, 0x60, /* sas_addr */
0x00, 0x00, 0x00, 0x00 },
{ SASNVR_SPEED_AUTO }, /* phy_speed */
{ SASNVR_MUX_DISABLED }, /* SAS multiplexing */
{ 0 }, /* phy_flags */
SASNVR_SORT_SAS_ADDR, /* sort_type */
3, /* dpm_reqcmd_lmt */
3, /* dpm_stndby_time */
0, /* dpm_active_time */
{ 0 }, /* phy_target_id */
SASNVR_VSMH_DISABLED, /* virt_ses_mode */
SASNVR_RWM_DEFAULT, /* read_write_mode */
0, /* link down timeout */
{ 0 } /* reserved */
};
static u8 cmd_to_fls_func[] = {
0xFF,
VDA_FLASH_READ,
VDA_FLASH_BEGINW,
VDA_FLASH_WRITE,
VDA_FLASH_COMMIT,
VDA_FLASH_CANCEL
};
static u8 esas2r_calc_byte_xor_cksum(u8 *addr, u32 len, u8 seed)
{
u32 cksum = seed;
u8 *p = (u8 *)&cksum;
while (len) {
if (((uintptr_t)addr & 3) == 0)
break;
cksum = cksum ^ *addr;
addr++;
len--;
}
while (len >= sizeof(u32)) {
cksum = cksum ^ *(u32 *)addr;
addr += 4;
len -= 4;
}
while (len--) {
cksum = cksum ^ *addr;
addr++;
}
return p[0] ^ p[1] ^ p[2] ^ p[3];
}
static u8 esas2r_calc_byte_cksum(void *addr, u32 len, u8 seed)
{
u8 *p = (u8 *)addr;
u8 cksum = seed;
while (len--)
cksum = cksum + p[len];
return cksum;
}
/* Interrupt callback to process FM API write requests. */
static void esas2r_fmapi_callback(struct esas2r_adapter *a,
struct esas2r_request *rq)
{
struct atto_vda_flash_req *vrq = &rq->vrq->flash;
struct esas2r_flash_context *fc =
(struct esas2r_flash_context *)rq->interrupt_cx;
if (rq->req_stat == RS_SUCCESS) {
/* Last request was successful. See what to do now. */
switch (vrq->sub_func) {
case VDA_FLASH_BEGINW:
if (fc->sgc.cur_offset == NULL)
goto commit;
vrq->sub_func = VDA_FLASH_WRITE;
rq->req_stat = RS_PENDING;
break;
case VDA_FLASH_WRITE:
commit:
vrq->sub_func = VDA_FLASH_COMMIT;
rq->req_stat = RS_PENDING;
rq->interrupt_cb = fc->interrupt_cb;
break;
default:
break;
}
}
if (rq->req_stat != RS_PENDING)
/*
* All done. call the real callback to complete the FM API
* request. We should only get here if a BEGINW or WRITE
* operation failed.
*/
(*fc->interrupt_cb)(a, rq);
}
/*
* Build a flash request based on the flash context. The request status
* is filled in on an error.
*/
static void build_flash_msg(struct esas2r_adapter *a,
struct esas2r_request *rq)
{
struct esas2r_flash_context *fc =
(struct esas2r_flash_context *)rq->interrupt_cx;
struct esas2r_sg_context *sgc = &fc->sgc;
u8 cksum = 0;
/* calculate the checksum */
if (fc->func == VDA_FLASH_BEGINW) {
if (sgc->cur_offset)
cksum = esas2r_calc_byte_xor_cksum(sgc->cur_offset,
sgc->length,
0);
rq->interrupt_cb = esas2r_fmapi_callback;
} else {
rq->interrupt_cb = fc->interrupt_cb;
}
esas2r_build_flash_req(a,
rq,
fc->func,
cksum,
fc->flsh_addr,
sgc->length);
esas2r_rq_free_sg_lists(rq, a);
/*
* remember the length we asked for. we have to keep track of
* the current amount done so we know how much to compare when
* doing the verification phase.
*/
fc->curr_len = fc->sgc.length;
if (sgc->cur_offset) {
/* setup the S/G context to build the S/G table */
esas2r_sgc_init(sgc, a, rq, &rq->vrq->flash.data.sge[0]);
if (!esas2r_build_sg_list(a, rq, sgc)) {
rq->req_stat = RS_BUSY;
return;
}
} else {
fc->sgc.length = 0;
}
/* update the flsh_addr to the next one to write to */
fc->flsh_addr += fc->curr_len;
}
/* determine the method to process the flash request */
static bool load_image(struct esas2r_adapter *a, struct esas2r_request *rq)
{
/*
* assume we have more to do. if we return with the status set to
* RS_PENDING, FM API tasks will continue.
*/
rq->req_stat = RS_PENDING;
if (test_bit(AF_DEGRADED_MODE, &a->flags))
/* not suppported for now */;
else
build_flash_msg(a, rq);
return rq->req_stat == RS_PENDING;
}
/* boot image fixer uppers called before downloading the image. */
static void fix_bios(struct esas2r_adapter *a, struct esas2r_flash_img *fi)
{
struct esas2r_component_header *ch = &fi->cmp_hdr[CH_IT_BIOS];
struct esas2r_pc_image *pi;
struct esas2r_boot_header *bh;
pi = (struct esas2r_pc_image *)((u8 *)fi + ch->image_offset);
bh =
(struct esas2r_boot_header *)((u8 *)pi +
le16_to_cpu(pi->header_offset));
bh->device_id = cpu_to_le16(a->pcid->device);
/* Recalculate the checksum in the PNP header if there */
if (pi->pnp_offset) {
u8 *pnp_header_bytes =
((u8 *)pi + le16_to_cpu(pi->pnp_offset));
/* Identifier - dword that starts at byte 10 */
*((u32 *)&pnp_header_bytes[10]) =
cpu_to_le32(MAKEDWORD(a->pcid->subsystem_vendor,
a->pcid->subsystem_device));
/* Checksum - byte 9 */
pnp_header_bytes[9] -= esas2r_calc_byte_cksum(pnp_header_bytes,
32, 0);
}
/* Recalculate the checksum needed by the PC */
pi->checksum = pi->checksum -
esas2r_calc_byte_cksum((u8 *)pi, ch->length, 0);
}
static void fix_efi(struct esas2r_adapter *a, struct esas2r_flash_img *fi)
{
struct esas2r_component_header *ch = &fi->cmp_hdr[CH_IT_EFI];
u32 len = ch->length;
u32 offset = ch->image_offset;
struct esas2r_efi_image *ei;
struct esas2r_boot_header *bh;
while (len) {
u32 thislen;
ei = (struct esas2r_efi_image *)((u8 *)fi + offset);
bh = (struct esas2r_boot_header *)((u8 *)ei +
le16_to_cpu(
ei->header_offset));
bh->device_id = cpu_to_le16(a->pcid->device);
thislen = (u32)le16_to_cpu(bh->image_length) * 512;
if (thislen > len)
break;
len -= thislen;
offset += thislen;
}
}
/* Complete a FM API request with the specified status. */
static bool complete_fmapi_req(struct esas2r_adapter *a,
struct esas2r_request *rq, u8 fi_stat)
{
struct esas2r_flash_context *fc =
(struct esas2r_flash_context *)rq->interrupt_cx;
struct esas2r_flash_img *fi = fc->fi;
fi->status = fi_stat;
fi->driver_error = rq->req_stat;
rq->interrupt_cb = NULL;
rq->req_stat = RS_SUCCESS;
if (fi_stat != FI_STAT_IMG_VER)
memset(fc->scratch, 0, FM_BUF_SZ);
esas2r_enable_heartbeat(a);
clear_bit(AF_FLASH_LOCK, &a->flags);
return false;
}
/* Process each phase of the flash download process. */
static void fw_download_proc(struct esas2r_adapter *a,
struct esas2r_request *rq)
{
struct esas2r_flash_context *fc =
(struct esas2r_flash_context *)rq->interrupt_cx;
struct esas2r_flash_img *fi = fc->fi;
struct esas2r_component_header *ch;
u32 len;
u8 *p, *q;
/* If the previous operation failed, just return. */
if (rq->req_stat != RS_SUCCESS)
goto error;
/*
* If an upload just completed and the compare length is non-zero,
* then we just read back part of the image we just wrote. verify the
* section and continue reading until the entire image is verified.
*/
if (fc->func == VDA_FLASH_READ
&& fc->cmp_len) {
ch = &fi->cmp_hdr[fc->comp_typ];
p = fc->scratch;
q = (u8 *)fi /* start of the whole gob */
+ ch->image_offset /* start of the current image */
+ ch->length /* end of the current image */
- fc->cmp_len; /* where we are now */
/*
* NOTE - curr_len is the exact count of bytes for the read
* even when the end is read and its not a full buffer
*/
for (len = fc->curr_len; len; len--)
if (*p++ != *q++)
goto error;
fc->cmp_len -= fc->curr_len; /* # left to compare */
/* Update fc and determine the length for the next upload */
if (fc->cmp_len > FM_BUF_SZ)
fc->sgc.length = FM_BUF_SZ;
else
fc->sgc.length = fc->cmp_len;
fc->sgc.cur_offset = fc->sgc_offset +
((u8 *)fc->scratch - (u8 *)fi);
}
/*
* This code uses a 'while' statement since the next component may
* have a length = zero. This can happen since some components are
* not required. At the end of this 'while' we set up the length
* for the next request and therefore sgc.length can be = 0.
*/
while (fc->sgc.length == 0) {
ch = &fi->cmp_hdr[fc->comp_typ];
switch (fc->task) {
case FMTSK_ERASE_BOOT:
/* the BIOS image is written next */
ch = &fi->cmp_hdr[CH_IT_BIOS];
if (ch->length == 0)
goto no_bios;
fc->task = FMTSK_WRTBIOS;
fc->func = VDA_FLASH_BEGINW;
fc->comp_typ = CH_IT_BIOS;
fc->flsh_addr = FLS_OFFSET_BOOT;
fc->sgc.length = ch->length;
fc->sgc.cur_offset = fc->sgc_offset +
ch->image_offset;
break;
case FMTSK_WRTBIOS:
/*
* The BIOS image has been written - read it and
* verify it
*/
fc->task = FMTSK_READBIOS;
fc->func = VDA_FLASH_READ;
fc->flsh_addr = FLS_OFFSET_BOOT;
fc->cmp_len = ch->length;
fc->sgc.length = FM_BUF_SZ;
fc->sgc.cur_offset = fc->sgc_offset
+ ((u8 *)fc->scratch -
(u8 *)fi);
break;
case FMTSK_READBIOS:
no_bios:
/*
* Mark the component header status for the image
* completed
*/
ch->status = CH_STAT_SUCCESS;
/* The MAC image is written next */
ch = &fi->cmp_hdr[CH_IT_MAC];
if (ch->length == 0)
goto no_mac;
fc->task = FMTSK_WRTMAC;
fc->func = VDA_FLASH_BEGINW;
fc->comp_typ = CH_IT_MAC;
fc->flsh_addr = FLS_OFFSET_BOOT
+ fi->cmp_hdr[CH_IT_BIOS].length;
fc->sgc.length = ch->length;
fc->sgc.cur_offset = fc->sgc_offset +
ch->image_offset;
break;
case FMTSK_WRTMAC:
/* The MAC image has been written - read and verify */
fc->task = FMTSK_READMAC;
fc->func = VDA_FLASH_READ;
fc->flsh_addr -= ch->length;
fc->cmp_len = ch->length;
fc->sgc.length = FM_BUF_SZ;
fc->sgc.cur_offset = fc->sgc_offset
+ ((u8 *)fc->scratch -
(u8 *)fi);
break;
case FMTSK_READMAC:
no_mac:
/*
* Mark the component header status for the image
* completed
*/
ch->status = CH_STAT_SUCCESS;
/* The EFI image is written next */
ch = &fi->cmp_hdr[CH_IT_EFI];
if (ch->length == 0)
goto no_efi;
fc->task = FMTSK_WRTEFI;
fc->func = VDA_FLASH_BEGINW;
fc->comp_typ = CH_IT_EFI;
fc->flsh_addr = FLS_OFFSET_BOOT
+ fi->cmp_hdr[CH_IT_BIOS].length
+ fi->cmp_hdr[CH_IT_MAC].length;
fc->sgc.length = ch->length;
fc->sgc.cur_offset = fc->sgc_offset +
ch->image_offset;
break;
case FMTSK_WRTEFI:
/* The EFI image has been written - read and verify */
fc->task = FMTSK_READEFI;
fc->func = VDA_FLASH_READ;
fc->flsh_addr -= ch->length;
fc->cmp_len = ch->length;
fc->sgc.length = FM_BUF_SZ;
fc->sgc.cur_offset = fc->sgc_offset
+ ((u8 *)fc->scratch -
(u8 *)fi);
break;
case FMTSK_READEFI:
no_efi:
/*
* Mark the component header status for the image
* completed
*/
ch->status = CH_STAT_SUCCESS;
/* The CFG image is written next */
ch = &fi->cmp_hdr[CH_IT_CFG];
if (ch->length == 0)
goto no_cfg;
fc->task = FMTSK_WRTCFG;
fc->func = VDA_FLASH_BEGINW;
fc->comp_typ = CH_IT_CFG;
fc->flsh_addr = FLS_OFFSET_CPYR - ch->length;
fc->sgc.length = ch->length;
fc->sgc.cur_offset = fc->sgc_offset +
ch->image_offset;
break;
case FMTSK_WRTCFG:
/* The CFG image has been written - read and verify */
fc->task = FMTSK_READCFG;
fc->func = VDA_FLASH_READ;
fc->flsh_addr = FLS_OFFSET_CPYR - ch->length;
fc->cmp_len = ch->length;
fc->sgc.length = FM_BUF_SZ;
fc->sgc.cur_offset = fc->sgc_offset
+ ((u8 *)fc->scratch -
(u8 *)fi);
break;
case FMTSK_READCFG:
no_cfg:
/*
* Mark the component header status for the image
* completed
*/
ch->status = CH_STAT_SUCCESS;
/*
* The download is complete. If in degraded mode,
* attempt a chip reset.
*/
if (test_bit(AF_DEGRADED_MODE, &a->flags))
esas2r_local_reset_adapter(a);
a->flash_ver = fi->cmp_hdr[CH_IT_BIOS].version;
esas2r_print_flash_rev(a);
/* Update the type of boot image on the card */
memcpy(a->image_type, fi->rel_version,
sizeof(fi->rel_version));
complete_fmapi_req(a, rq, FI_STAT_SUCCESS);
return;
}
/* If verifying, don't try reading more than what's there */
if (fc->func == VDA_FLASH_READ
&& fc->sgc.length > fc->cmp_len)
fc->sgc.length = fc->cmp_len;
}
/* Build the request to perform the next action */
if (!load_image(a, rq)) {
error:
if (fc->comp_typ < fi->num_comps) {
ch = &fi->cmp_hdr[fc->comp_typ];
ch->status = CH_STAT_FAILED;
}
complete_fmapi_req(a, rq, FI_STAT_FAILED);
}
}
/* Determine the flash image adaptyp for this adapter */
static u8 get_fi_adap_type(struct esas2r_adapter *a)
{
u8 type;
/* use the device ID to get the correct adap_typ for this HBA */
switch (a->pcid->device) {
case ATTO_DID_INTEL_IOP348:
type = FI_AT_SUN_LAKE;
break;
case ATTO_DID_MV_88RC9580:
case ATTO_DID_MV_88RC9580TS:
case ATTO_DID_MV_88RC9580TSE:
case ATTO_DID_MV_88RC9580TL:
type = FI_AT_MV_9580;
break;
default:
type = FI_AT_UNKNWN;
break;
}
return type;
}
/* Size of config + copyright + flash_ver images, 0 for failure. */
static u32 chk_cfg(u8 *cfg, u32 length, u32 *flash_ver)
{
u16 *pw = (u16 *)cfg - 1;
u32 sz = 0;
u32 len = length;
if (len == 0)
len = FM_BUF_SZ;
if (flash_ver)
*flash_ver = 0;
while (true) {
u16 type;
u16 size;
type = le16_to_cpu(*pw--);
size = le16_to_cpu(*pw--);
if (type != FBT_CPYR
&& type != FBT_SETUP
&& type != FBT_FLASH_VER)
break;
if (type == FBT_FLASH_VER
&& flash_ver)
*flash_ver = le32_to_cpu(*(u32 *)(pw - 1));
sz += size + (2 * sizeof(u16));
pw -= size / sizeof(u16);
if (sz > len - (2 * sizeof(u16)))
break;
}
/* See if we are comparing the size to the specified length */
if (length && sz != length)
return 0;
return sz;
}
/* Verify that the boot image is valid */
static u8 chk_boot(u8 *boot_img, u32 length)
{
struct esas2r_boot_image *bi = (struct esas2r_boot_image *)boot_img;
u16 hdroffset = le16_to_cpu(bi->header_offset);
struct esas2r_boot_header *bh;
if (bi->signature != le16_to_cpu(0xaa55)
|| (long)hdroffset >
(long)(65536L - sizeof(struct esas2r_boot_header))
|| (hdroffset & 3)
|| (hdroffset < sizeof(struct esas2r_boot_image))
|| ((u32)hdroffset + sizeof(struct esas2r_boot_header) > length))
return 0xff;
bh = (struct esas2r_boot_header *)((char *)bi + hdroffset);
if (bh->signature[0] != 'P'
|| bh->signature[1] != 'C'
|| bh->signature[2] != 'I'
|| bh->signature[3] != 'R'
|| le16_to_cpu(bh->struct_length) <
(u16)sizeof(struct esas2r_boot_header)
|| bh->class_code[2] != 0x01
|| bh->class_code[1] != 0x04
|| bh->class_code[0] != 0x00
|| (bh->code_type != CODE_TYPE_PC
&& bh->code_type != CODE_TYPE_OPEN
&& bh->code_type != CODE_TYPE_EFI))
return 0xff;
return bh->code_type;
}
/* The sum of all the WORDS of the image */
static u16 calc_fi_checksum(struct esas2r_flash_context *fc)
{
struct esas2r_flash_img *fi = fc->fi;
u16 cksum;
u32 len;
u16 *pw;
for (len = (fi->length - fc->fi_hdr_len) / 2,
pw = (u16 *)((u8 *)fi + fc->fi_hdr_len),
cksum = 0;
len;
len--, pw++)
cksum = cksum + le16_to_cpu(*pw);
return cksum;
}
/*
* Verify the flash image structure. The following verifications will
* be performed:
* 1) verify the fi_version is correct
* 2) verify the checksum of the entire image.
* 3) validate the adap_typ, action and length fields.
* 4) validate each component header. check the img_type and
* length fields
* 5) validate each component image. validate signatures and
* local checksums
*/
static bool verify_fi(struct esas2r_adapter *a,
struct esas2r_flash_context *fc)
{
struct esas2r_flash_img *fi = fc->fi;
u8 type;
bool imgerr;
u16 i;
u32 len;
struct esas2r_component_header *ch;
/* Verify the length - length must even since we do a word checksum */
len = fi->length;
if ((len & 1)
|| len < fc->fi_hdr_len) {
fi->status = FI_STAT_LENGTH;
return false;
}
/* Get adapter type and verify type in flash image */
type = get_fi_adap_type(a);
if ((type == FI_AT_UNKNWN) || (fi->adap_typ != type)) {
fi->status = FI_STAT_ADAPTYP;
return false;
}
/*
* Loop through each component and verify the img_type and length
* fields. Keep a running count of the sizes sooze we can verify total
* size to additive size.
*/
imgerr = false;
for (i = 0, len = 0, ch = fi->cmp_hdr;
i < fi->num_comps;
i++, ch++) {
bool cmperr = false;
/*
* Verify that the component header has the same index as the
* image type. The headers must be ordered correctly
*/
if (i != ch->img_type) {
imgerr = true;
ch->status = CH_STAT_INVALID;
continue;
}
switch (ch->img_type) {
case CH_IT_BIOS:
type = CODE_TYPE_PC;
break;
case CH_IT_MAC:
type = CODE_TYPE_OPEN;
break;
case CH_IT_EFI:
type = CODE_TYPE_EFI;
break;
}
switch (ch->img_type) {
case CH_IT_FW:
case CH_IT_NVR:
break;
case CH_IT_BIOS:
case CH_IT_MAC:
case CH_IT_EFI:
if (ch->length & 0x1ff)
cmperr = true;
/* Test if component image is present */
if (ch->length == 0)
break;
/* Image is present - verify the image */
if (chk_boot((u8 *)fi + ch->image_offset, ch->length)
!= type)
cmperr = true;
break;
case CH_IT_CFG:
/* Test if component image is present */
if (ch->length == 0) {
cmperr = true;
break;
}
/* Image is present - verify the image */
if (!chk_cfg((u8 *)fi + ch->image_offset + ch->length,
ch->length, NULL))
cmperr = true;
break;
default:
fi->status = FI_STAT_UNKNOWN;
return false;
}
if (cmperr) {
imgerr = true;
ch->status = CH_STAT_INVALID;
} else {
ch->status = CH_STAT_PENDING;
len += ch->length;
}
}
if (imgerr) {
fi->status = FI_STAT_MISSING;
return false;
}
/* Compare fi->length to the sum of ch->length fields */
if (len != fi->length - fc->fi_hdr_len) {
fi->status = FI_STAT_LENGTH;
return false;
}
/* Compute the checksum - it should come out zero */
if (fi->checksum != calc_fi_checksum(fc)) {
fi->status = FI_STAT_CHKSUM;
return false;
}
return true;
}
/* Fill in the FS IOCTL response data from a completed request. */
static void esas2r_complete_fs_ioctl(struct esas2r_adapter *a,
struct esas2r_request *rq)
{
struct esas2r_ioctl_fs *fs =
(struct esas2r_ioctl_fs *)rq->interrupt_cx;
if (rq->vrq->flash.sub_func == VDA_FLASH_COMMIT)
esas2r_enable_heartbeat(a);
fs->driver_error = rq->req_stat;
if (fs->driver_error == RS_SUCCESS)
fs->status = ATTO_STS_SUCCESS;
else
fs->status = ATTO_STS_FAILED;
}
/* Prepare an FS IOCTL request to be sent to the firmware. */
bool esas2r_process_fs_ioctl(struct esas2r_adapter *a,
struct esas2r_ioctl_fs *fs,
struct esas2r_request *rq,
struct esas2r_sg_context *sgc)
{
u8 cmdcnt = (u8)ARRAY_SIZE(cmd_to_fls_func);
struct esas2r_ioctlfs_command *fsc = &fs->command;
u8 func = 0;
u32 datalen;
fs->status = ATTO_STS_FAILED;
fs->driver_error = RS_PENDING;
if (fs->version > ESAS2R_FS_VER) {
fs->status = ATTO_STS_INV_VERSION;
return false;
}
if (fsc->command >= cmdcnt) {
fs->status = ATTO_STS_INV_FUNC;
return false;
}
func = cmd_to_fls_func[fsc->command];
if (func == 0xFF) {
fs->status = ATTO_STS_INV_FUNC;
return false;
}
if (fsc->command != ESAS2R_FS_CMD_CANCEL) {
if ((a->pcid->device != ATTO_DID_MV_88RC9580
|| fs->adap_type != ESAS2R_FS_AT_ESASRAID2)
&& (a->pcid->device != ATTO_DID_MV_88RC9580TS
|| fs->adap_type != ESAS2R_FS_AT_TSSASRAID2)
&& (a->pcid->device != ATTO_DID_MV_88RC9580TSE
|| fs->adap_type != ESAS2R_FS_AT_TSSASRAID2E)
&& (a->pcid->device != ATTO_DID_MV_88RC9580TL
|| fs->adap_type != ESAS2R_FS_AT_TLSASHBA)) {
fs->status = ATTO_STS_INV_ADAPTER;
return false;
}
if (fs->driver_ver > ESAS2R_FS_DRVR_VER) {
fs->status = ATTO_STS_INV_DRVR_VER;
return false;
}
}
if (test_bit(AF_DEGRADED_MODE, &a->flags)) {
fs->status = ATTO_STS_DEGRADED;
return false;
}
rq->interrupt_cb = esas2r_complete_fs_ioctl;
rq->interrupt_cx = fs;
datalen = le32_to_cpu(fsc->length);
esas2r_build_flash_req(a,
rq,
func,
fsc->checksum,
le32_to_cpu(fsc->flash_addr),
datalen);
if (func == VDA_FLASH_WRITE
|| func == VDA_FLASH_READ) {
if (datalen == 0) {
fs->status = ATTO_STS_INV_FUNC;
return false;
}
esas2r_sgc_init(sgc, a, rq, rq->vrq->flash.data.sge);
sgc->length = datalen;
if (!esas2r_build_sg_list(a, rq, sgc)) {
fs->status = ATTO_STS_OUT_OF_RSRC;
return false;
}
}
if (func == VDA_FLASH_COMMIT)
esas2r_disable_heartbeat(a);
esas2r_start_request(a, rq);
return true;
}
static bool esas2r_flash_access(struct esas2r_adapter *a, u32 function)
{
u32 starttime;
u32 timeout;
u32 intstat;
u32 doorbell;
/* Disable chip interrupts awhile */
if (function == DRBL_FLASH_REQ)
esas2r_disable_chip_interrupts(a);
/* Issue the request to the firmware */
esas2r_write_register_dword(a, MU_DOORBELL_IN, function);
/* Now wait for the firmware to process it */
starttime = jiffies_to_msecs(jiffies);
if (test_bit(AF_CHPRST_PENDING, &a->flags) ||
test_bit(AF_DISC_PENDING, &a->flags))
timeout = 40000;
else
timeout = 5000;
while (true) {
intstat = esas2r_read_register_dword(a, MU_INT_STATUS_OUT);
if (intstat & MU_INTSTAT_DRBL) {
/* Got a doorbell interrupt. Check for the function */
doorbell =
esas2r_read_register_dword(a, MU_DOORBELL_OUT);
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
doorbell);
if (doorbell & function)
break;
}
schedule_timeout_interruptible(msecs_to_jiffies(100));
if ((jiffies_to_msecs(jiffies) - starttime) > timeout) {
/*
* Iimeout. If we were requesting flash access,
* indicate we are done so the firmware knows we gave
* up. If this was a REQ, we also need to re-enable
* chip interrupts.
*/
if (function == DRBL_FLASH_REQ) {
esas2r_hdebug("flash access timeout");
esas2r_write_register_dword(a, MU_DOORBELL_IN,
DRBL_FLASH_DONE);
esas2r_enable_chip_interrupts(a);
} else {
esas2r_hdebug("flash release timeout");
}
return false;
}
}
/* if we're done, re-enable chip interrupts */
if (function == DRBL_FLASH_DONE)
esas2r_enable_chip_interrupts(a);
return true;
}
#define WINDOW_SIZE ((signed int)MW_DATA_WINDOW_SIZE)
bool esas2r_read_flash_block(struct esas2r_adapter *a,
void *to,
u32 from,
u32 size)
{
u8 *end = (u8 *)to;
/* Try to acquire access to the flash */
if (!esas2r_flash_access(a, DRBL_FLASH_REQ))
return false;
while (size) {
u32 len;
u32 offset;
u32 iatvr;
if (test_bit(AF2_SERIAL_FLASH, &a->flags2))
iatvr = MW_DATA_ADDR_SER_FLASH + (from & -WINDOW_SIZE);
else
iatvr = MW_DATA_ADDR_PAR_FLASH + (from & -WINDOW_SIZE);
esas2r_map_data_window(a, iatvr);
offset = from & (WINDOW_SIZE - 1);
len = size;
if (len > WINDOW_SIZE - offset)
len = WINDOW_SIZE - offset;
from += len;
size -= len;
while (len--) {
*end++ = esas2r_read_data_byte(a, offset);
offset++;
}
}
/* Release flash access */
esas2r_flash_access(a, DRBL_FLASH_DONE);
return true;
}
bool esas2r_read_flash_rev(struct esas2r_adapter *a)
{
u8 bytes[256];
u16 *pw;
u16 *pwstart;
u16 type;
u16 size;
u32 sz;
sz = sizeof(bytes);
pw = (u16 *)(bytes + sz);
pwstart = (u16 *)bytes + 2;
if (!esas2r_read_flash_block(a, bytes, FLS_OFFSET_CPYR - sz, sz))
goto invalid_rev;
while (pw >= pwstart) {
pw--;
type = le16_to_cpu(*pw);
pw--;
size = le16_to_cpu(*pw);
pw -= size / 2;
if (type == FBT_CPYR
|| type == FBT_SETUP
|| pw < pwstart)
continue;
if (type == FBT_FLASH_VER)
a->flash_ver = le32_to_cpu(*(u32 *)pw);
break;
}
invalid_rev:
return esas2r_print_flash_rev(a);
}
bool esas2r_print_flash_rev(struct esas2r_adapter *a)
{
u16 year = LOWORD(a->flash_ver);
u8 day = LOBYTE(HIWORD(a->flash_ver));
u8 month = HIBYTE(HIWORD(a->flash_ver));
if (day == 0
|| month == 0
|| day > 31
|| month > 12
|| year < 2006
|| year > 9999) {
strcpy(a->flash_rev, "not found");
a->flash_ver = 0;
return false;
}
sprintf(a->flash_rev, "%02d/%02d/%04d", month, day, year);
esas2r_hdebug("flash version: %s", a->flash_rev);
return true;
}
/*
* Find the type of boot image type that is currently in the flash.
* The chip only has a 64 KB PCI-e expansion ROM
* size so only one image can be flashed at a time.
*/
bool esas2r_read_image_type(struct esas2r_adapter *a)
{
u8 bytes[256];
struct esas2r_boot_image *bi;
struct esas2r_boot_header *bh;
u32 sz;
u32 len;
u32 offset;
/* Start at the base of the boot images and look for a valid image */
sz = sizeof(bytes);
len = FLS_LENGTH_BOOT;
offset = 0;
while (true) {
if (!esas2r_read_flash_block(a, bytes, FLS_OFFSET_BOOT +
offset,
sz))
goto invalid_rev;
bi = (struct esas2r_boot_image *)bytes;
bh = (struct esas2r_boot_header *)((u8 *)bi +
le16_to_cpu(
bi->header_offset));
if (bi->signature != cpu_to_le16(0xAA55))
goto invalid_rev;
if (bh->code_type == CODE_TYPE_PC) {
strcpy(a->image_type, "BIOS");
return true;
} else if (bh->code_type == CODE_TYPE_EFI) {
struct esas2r_efi_image *ei;
/*
* So we have an EFI image. There are several types
* so see which architecture we have.
*/
ei = (struct esas2r_efi_image *)bytes;
switch (le16_to_cpu(ei->machine_type)) {
case EFI_MACHINE_IA32:
strcpy(a->image_type, "EFI 32-bit");
return true;
case EFI_MACHINE_IA64:
strcpy(a->image_type, "EFI itanium");
return true;
case EFI_MACHINE_X64:
strcpy(a->image_type, "EFI 64-bit");
return true;
case EFI_MACHINE_EBC:
strcpy(a->image_type, "EFI EBC");
return true;
default:
goto invalid_rev;
}
} else {
u32 thislen;
/* jump to the next image */
thislen = (u32)le16_to_cpu(bh->image_length) * 512;
if (thislen == 0
|| thislen + offset > len
|| bh->indicator == INDICATOR_LAST)
break;
offset += thislen;
}
}
invalid_rev:
strcpy(a->image_type, "no boot images");
return false;
}
/*
* Read and validate current NVRAM parameters by accessing
* physical NVRAM directly. if currently stored parameters are
* invalid, use the defaults.
*/
bool esas2r_nvram_read_direct(struct esas2r_adapter *a)
{
bool result;
if (down_interruptible(&a->nvram_semaphore))
return false;
if (!esas2r_read_flash_block(a, a->nvram, FLS_OFFSET_NVR,
sizeof(struct esas2r_sas_nvram))) {
esas2r_hdebug("NVRAM read failed, using defaults");
up(&a->nvram_semaphore);
return false;
}
result = esas2r_nvram_validate(a);
up(&a->nvram_semaphore);
return result;
}
/* Interrupt callback to process NVRAM completions. */
static void esas2r_nvram_callback(struct esas2r_adapter *a,
struct esas2r_request *rq)
{
struct atto_vda_flash_req *vrq = &rq->vrq->flash;
if (rq->req_stat == RS_SUCCESS) {
/* last request was successful. see what to do now. */
switch (vrq->sub_func) {
case VDA_FLASH_BEGINW:
vrq->sub_func = VDA_FLASH_WRITE;
rq->req_stat = RS_PENDING;
break;
case VDA_FLASH_WRITE:
vrq->sub_func = VDA_FLASH_COMMIT;
rq->req_stat = RS_PENDING;
break;
case VDA_FLASH_READ:
esas2r_nvram_validate(a);
break;
case VDA_FLASH_COMMIT:
default:
break;
}
}
if (rq->req_stat != RS_PENDING) {
/* update the NVRAM state */
if (rq->req_stat == RS_SUCCESS)
set_bit(AF_NVR_VALID, &a->flags);
else
clear_bit(AF_NVR_VALID, &a->flags);
esas2r_enable_heartbeat(a);
up(&a->nvram_semaphore);
}
}
/*
* Write the contents of nvram to the adapter's physical NVRAM.
* The cached copy of the NVRAM is also updated.
*/
bool esas2r_nvram_write(struct esas2r_adapter *a, struct esas2r_request *rq,
struct esas2r_sas_nvram *nvram)
{
struct esas2r_sas_nvram *n = nvram;
u8 sas_address_bytes[8];
u32 *sas_address_dwords = (u32 *)&sas_address_bytes[0];
struct atto_vda_flash_req *vrq = &rq->vrq->flash;
if (test_bit(AF_DEGRADED_MODE, &a->flags))
return false;
if (down_interruptible(&a->nvram_semaphore))
return false;
if (n == NULL)
n = a->nvram;
/* check the validity of the settings */
if (n->version > SASNVR_VERSION) {
up(&a->nvram_semaphore);
return false;
}
memcpy(&sas_address_bytes[0], n->sas_addr, 8);
if (sas_address_bytes[0] != 0x50
|| sas_address_bytes[1] != 0x01
|| sas_address_bytes[2] != 0x08
|| (sas_address_bytes[3] & 0xF0) != 0x60
|| ((sas_address_bytes[3] & 0x0F) | sas_address_dwords[1]) == 0) {
up(&a->nvram_semaphore);
return false;
}
if (n->spin_up_delay > SASNVR_SPINUP_MAX)
n->spin_up_delay = SASNVR_SPINUP_MAX;
n->version = SASNVR_VERSION;
n->checksum = n->checksum - esas2r_nvramcalc_cksum(n);
memcpy(a->nvram, n, sizeof(struct esas2r_sas_nvram));
/* write the NVRAM */
n = a->nvram;
esas2r_disable_heartbeat(a);
esas2r_build_flash_req(a,
rq,
VDA_FLASH_BEGINW,
esas2r_nvramcalc_xor_cksum(n),
FLS_OFFSET_NVR,
sizeof(struct esas2r_sas_nvram));
if (test_bit(AF_LEGACY_SGE_MODE, &a->flags)) {
vrq->data.sge[0].length =
cpu_to_le32(SGE_LAST |
sizeof(struct esas2r_sas_nvram));
vrq->data.sge[0].address = cpu_to_le64(
a->uncached_phys + (u64)((u8 *)n - a->uncached));
} else {
vrq->data.prde[0].ctl_len =
cpu_to_le32(sizeof(struct esas2r_sas_nvram));
vrq->data.prde[0].address = cpu_to_le64(
a->uncached_phys
+ (u64)((u8 *)n - a->uncached));
}
rq->interrupt_cb = esas2r_nvram_callback;
esas2r_start_request(a, rq);
return true;
}
/* Validate the cached NVRAM. if the NVRAM is invalid, load the defaults. */
bool esas2r_nvram_validate(struct esas2r_adapter *a)
{
struct esas2r_sas_nvram *n = a->nvram;
bool rslt = false;
if (n->signature[0] != 'E'
|| n->signature[1] != 'S'
|| n->signature[2] != 'A'
|| n->signature[3] != 'S') {
esas2r_hdebug("invalid NVRAM signature");
} else if (esas2r_nvramcalc_cksum(n)) {
esas2r_hdebug("invalid NVRAM checksum");
} else if (n->version > SASNVR_VERSION) {
esas2r_hdebug("invalid NVRAM version");
} else {
set_bit(AF_NVR_VALID, &a->flags);
rslt = true;
}
if (rslt == false) {
esas2r_hdebug("using defaults");
esas2r_nvram_set_defaults(a);
}
return rslt;
}
/*
* Set the cached NVRAM to defaults. note that this function sets the default
* NVRAM when it has been determined that the physical NVRAM is invalid.
* In this case, the SAS address is fabricated.
*/
void esas2r_nvram_set_defaults(struct esas2r_adapter *a)
{
struct esas2r_sas_nvram *n = a->nvram;
u32 time = jiffies_to_msecs(jiffies);
clear_bit(AF_NVR_VALID, &a->flags);
*n = default_sas_nvram;
n->sas_addr[3] |= 0x0F;
n->sas_addr[4] = HIBYTE(LOWORD(time));
n->sas_addr[5] = LOBYTE(LOWORD(time));
n->sas_addr[6] = a->pcid->bus->number;
n->sas_addr[7] = a->pcid->devfn;
}
void esas2r_nvram_get_defaults(struct esas2r_adapter *a,
struct esas2r_sas_nvram *nvram)
{
u8 sas_addr[8];
/*
* in case we are copying the defaults into the adapter, copy the SAS
* address out first.
*/
memcpy(&sas_addr[0], a->nvram->sas_addr, 8);
*nvram = default_sas_nvram;
memcpy(&nvram->sas_addr[0], &sas_addr[0], 8);
}
bool esas2r_fm_api(struct esas2r_adapter *a, struct esas2r_flash_img *fi,
struct esas2r_request *rq, struct esas2r_sg_context *sgc)
{
struct esas2r_flash_context *fc = &a->flash_context;
u8 j;
struct esas2r_component_header *ch;
if (test_and_set_bit(AF_FLASH_LOCK, &a->flags)) {
/* flag was already set */
fi->status = FI_STAT_BUSY;
return false;
}
memcpy(&fc->sgc, sgc, sizeof(struct esas2r_sg_context));
sgc = &fc->sgc;
fc->fi = fi;
fc->sgc_offset = sgc->cur_offset;
rq->req_stat = RS_SUCCESS;
rq->interrupt_cx = fc;
switch (fi->fi_version) {
case FI_VERSION_1:
fc->scratch = ((struct esas2r_flash_img *)fi)->scratch_buf;
fc->num_comps = FI_NUM_COMPS_V1;
fc->fi_hdr_len = sizeof(struct esas2r_flash_img);
break;
default:
return complete_fmapi_req(a, rq, FI_STAT_IMG_VER);
}
if (test_bit(AF_DEGRADED_MODE, &a->flags))
return complete_fmapi_req(a, rq, FI_STAT_DEGRADED);
switch (fi->action) {
case FI_ACT_DOWN: /* Download the components */
/* Verify the format of the flash image */
if (!verify_fi(a, fc))
return complete_fmapi_req(a, rq, fi->status);
/* Adjust the BIOS fields that are dependent on the HBA */
ch = &fi->cmp_hdr[CH_IT_BIOS];
if (ch->length)
fix_bios(a, fi);
/* Adjust the EFI fields that are dependent on the HBA */
ch = &fi->cmp_hdr[CH_IT_EFI];
if (ch->length)
fix_efi(a, fi);
/*
* Since the image was just modified, compute the checksum on
* the modified image. First update the CRC for the composite
* expansion ROM image.
*/
fi->checksum = calc_fi_checksum(fc);
/* Disable the heartbeat */
esas2r_disable_heartbeat(a);
/* Now start up the download sequence */
fc->task = FMTSK_ERASE_BOOT;
fc->func = VDA_FLASH_BEGINW;
fc->comp_typ = CH_IT_CFG;
fc->flsh_addr = FLS_OFFSET_BOOT;
fc->sgc.length = FLS_LENGTH_BOOT;
fc->sgc.cur_offset = NULL;
/* Setup the callback address */
fc->interrupt_cb = fw_download_proc;
break;
case FI_ACT_UPSZ: /* Get upload sizes */
fi->adap_typ = get_fi_adap_type(a);
fi->flags = 0;
fi->num_comps = fc->num_comps;
fi->length = fc->fi_hdr_len;
/* Report the type of boot image in the rel_version string */
memcpy(fi->rel_version, a->image_type,
sizeof(fi->rel_version));
/* Build the component headers */
for (j = 0, ch = fi->cmp_hdr;
j < fi->num_comps;
j++, ch++) {
ch->img_type = j;
ch->status = CH_STAT_PENDING;
ch->length = 0;
ch->version = 0xffffffff;
ch->image_offset = 0;
ch->pad[0] = 0;
ch->pad[1] = 0;
}
if (a->flash_ver != 0) {
fi->cmp_hdr[CH_IT_BIOS].version =
fi->cmp_hdr[CH_IT_MAC].version =
fi->cmp_hdr[CH_IT_EFI].version =
fi->cmp_hdr[CH_IT_CFG].version
= a->flash_ver;
fi->cmp_hdr[CH_IT_BIOS].status =
fi->cmp_hdr[CH_IT_MAC].status =
fi->cmp_hdr[CH_IT_EFI].status =
fi->cmp_hdr[CH_IT_CFG].status =
CH_STAT_SUCCESS;
return complete_fmapi_req(a, rq, FI_STAT_SUCCESS);
}
/* fall through */
case FI_ACT_UP: /* Upload the components */
default:
return complete_fmapi_req(a, rq, FI_STAT_INVALID);
}
/*
* If we make it here, fc has been setup to do the first task. Call
* load_image to format the request, start it, and get out. The
* interrupt code will call the callback when the first message is
* complete.
*/
if (!load_image(a, rq))
return complete_fmapi_req(a, rq, FI_STAT_FAILED);
esas2r_start_request(a, rq);
return true;
}