569 lines
13 KiB
C
569 lines
13 KiB
C
/*
|
|
* Device driver for the SYMBIOS/LSILOGIC 53C8XX and 53C1010 family
|
|
* of PCI-SCSI IO processors.
|
|
*
|
|
* Copyright (C) 1999-2001 Gerard Roudier <groudier@free.fr>
|
|
*
|
|
* This driver is derived from the Linux sym53c8xx driver.
|
|
* Copyright (C) 1998-2000 Gerard Roudier
|
|
*
|
|
* The sym53c8xx driver is derived from the ncr53c8xx driver that had been
|
|
* a port of the FreeBSD ncr driver to Linux-1.2.13.
|
|
*
|
|
* The original ncr driver has been written for 386bsd and FreeBSD by
|
|
* Wolfgang Stanglmeier <wolf@cologne.de>
|
|
* Stefan Esser <se@mi.Uni-Koeln.de>
|
|
* Copyright (C) 1994 Wolfgang Stanglmeier
|
|
*
|
|
* Other major contributions:
|
|
*
|
|
* NVRAM detection and reading.
|
|
* Copyright (C) 1997 Richard Waltham <dormouse@farsrobt.demon.co.uk>
|
|
*
|
|
*-----------------------------------------------------------------------------
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (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.
|
|
*
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <dev/sym/sym_glue.h>
|
|
#else
|
|
#include "sym_glue.h"
|
|
#endif
|
|
|
|
/*
|
|
* Macros used for all firmwares.
|
|
*/
|
|
#define SYM_GEN_A(s, label) ((short) offsetof(s, label)),
|
|
#define SYM_GEN_B(s, label) ((short) offsetof(s, label)),
|
|
#define SYM_GEN_Z(s, label) ((short) offsetof(s, label)),
|
|
#define PADDR_A(label) SYM_GEN_PADDR_A(struct SYM_FWA_SCR, label)
|
|
#define PADDR_B(label) SYM_GEN_PADDR_B(struct SYM_FWB_SCR, label)
|
|
|
|
|
|
#if SYM_CONF_GENERIC_SUPPORT
|
|
/*
|
|
* Allocate firmware #1 script area.
|
|
*/
|
|
#define SYM_FWA_SCR sym_fw1a_scr
|
|
#define SYM_FWB_SCR sym_fw1b_scr
|
|
#define SYM_FWZ_SCR sym_fw1z_scr
|
|
#ifdef __FreeBSD__
|
|
#include <dev/sym/sym_fw1.h>
|
|
#else
|
|
#include "sym_fw1.h"
|
|
#endif
|
|
static struct sym_fwa_ofs sym_fw1a_ofs = {
|
|
SYM_GEN_FW_A(struct SYM_FWA_SCR)
|
|
};
|
|
static struct sym_fwb_ofs sym_fw1b_ofs = {
|
|
SYM_GEN_FW_B(struct SYM_FWB_SCR)
|
|
#ifdef SYM_OPT_HANDLE_DIR_UNKNOWN
|
|
SYM_GEN_B(struct SYM_FWB_SCR, data_io)
|
|
#endif
|
|
};
|
|
static struct sym_fwz_ofs sym_fw1z_ofs = {
|
|
SYM_GEN_FW_Z(struct SYM_FWZ_SCR)
|
|
};
|
|
#undef SYM_FWA_SCR
|
|
#undef SYM_FWB_SCR
|
|
#undef SYM_FWZ_SCR
|
|
#endif /* SYM_CONF_GENERIC_SUPPORT */
|
|
|
|
/*
|
|
* Allocate firmware #2 script area.
|
|
*/
|
|
#define SYM_FWA_SCR sym_fw2a_scr
|
|
#define SYM_FWB_SCR sym_fw2b_scr
|
|
#define SYM_FWZ_SCR sym_fw2z_scr
|
|
#ifdef __FreeBSD__
|
|
#include <dev/sym/sym_fw2.h>
|
|
#else
|
|
#include "sym_fw2.h"
|
|
#endif
|
|
static struct sym_fwa_ofs sym_fw2a_ofs = {
|
|
SYM_GEN_FW_A(struct SYM_FWA_SCR)
|
|
};
|
|
static struct sym_fwb_ofs sym_fw2b_ofs = {
|
|
SYM_GEN_FW_B(struct SYM_FWB_SCR)
|
|
#ifdef SYM_OPT_HANDLE_DIR_UNKNOWN
|
|
SYM_GEN_B(struct SYM_FWB_SCR, data_io)
|
|
#endif
|
|
SYM_GEN_B(struct SYM_FWB_SCR, start64)
|
|
SYM_GEN_B(struct SYM_FWB_SCR, pm_handle)
|
|
};
|
|
static struct sym_fwz_ofs sym_fw2z_ofs = {
|
|
SYM_GEN_FW_Z(struct SYM_FWZ_SCR)
|
|
};
|
|
#undef SYM_FWA_SCR
|
|
#undef SYM_FWB_SCR
|
|
#undef SYM_FWZ_SCR
|
|
|
|
#undef SYM_GEN_A
|
|
#undef SYM_GEN_B
|
|
#undef SYM_GEN_Z
|
|
#undef PADDR_A
|
|
#undef PADDR_B
|
|
|
|
#if SYM_CONF_GENERIC_SUPPORT
|
|
/*
|
|
* Patch routine for firmware #1.
|
|
*/
|
|
static void
|
|
sym_fw1_patch(struct sym_hcb *np)
|
|
{
|
|
struct sym_fw1a_scr *scripta0;
|
|
struct sym_fw1b_scr *scriptb0;
|
|
|
|
scripta0 = (struct sym_fw1a_scr *) np->scripta0;
|
|
scriptb0 = (struct sym_fw1b_scr *) np->scriptb0;
|
|
|
|
/*
|
|
* Remove LED support if not needed.
|
|
*/
|
|
if (!(np->features & FE_LED0)) {
|
|
scripta0->idle[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->reselected[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->start[0] = cpu_to_scr(SCR_NO_OP);
|
|
}
|
|
|
|
#ifdef SYM_CONF_IARB_SUPPORT
|
|
/*
|
|
* If user does not want to use IMMEDIATE ARBITRATION
|
|
* when we are reselected while attempting to arbitrate,
|
|
* patch the SCRIPTS accordingly with a SCRIPT NO_OP.
|
|
*/
|
|
if (!SYM_CONF_SET_IARB_ON_ARB_LOST)
|
|
scripta0->ungetjob[0] = cpu_to_scr(SCR_NO_OP);
|
|
#endif
|
|
/*
|
|
* Patch some data in SCRIPTS.
|
|
* - start and done queue initial bus address.
|
|
* - target bus address table bus address.
|
|
*/
|
|
scriptb0->startpos[0] = cpu_to_scr(np->squeue_ba);
|
|
scriptb0->done_pos[0] = cpu_to_scr(np->dqueue_ba);
|
|
scriptb0->targtbl[0] = cpu_to_scr(np->targtbl_ba);
|
|
}
|
|
#endif /* SYM_CONF_GENERIC_SUPPORT */
|
|
|
|
/*
|
|
* Patch routine for firmware #2.
|
|
*/
|
|
static void
|
|
sym_fw2_patch(struct sym_hcb *np)
|
|
{
|
|
struct sym_fw2a_scr *scripta0;
|
|
struct sym_fw2b_scr *scriptb0;
|
|
|
|
scripta0 = (struct sym_fw2a_scr *) np->scripta0;
|
|
scriptb0 = (struct sym_fw2b_scr *) np->scriptb0;
|
|
|
|
/*
|
|
* Remove LED support if not needed.
|
|
*/
|
|
if (!(np->features & FE_LED0)) {
|
|
scripta0->idle[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->reselected[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->start[0] = cpu_to_scr(SCR_NO_OP);
|
|
}
|
|
|
|
#if SYM_CONF_DMA_ADDRESSING_MODE == 2
|
|
/*
|
|
* Remove useless 64 bit DMA specific SCRIPTS,
|
|
* when this feature is not available.
|
|
*/
|
|
if (!np->use_dac) {
|
|
scripta0->is_dmap_dirty[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->is_dmap_dirty[1] = 0;
|
|
scripta0->is_dmap_dirty[2] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->is_dmap_dirty[3] = 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SYM_CONF_IARB_SUPPORT
|
|
/*
|
|
* If user does not want to use IMMEDIATE ARBITRATION
|
|
* when we are reselected while attempting to arbitrate,
|
|
* patch the SCRIPTS accordingly with a SCRIPT NO_OP.
|
|
*/
|
|
if (!SYM_CONF_SET_IARB_ON_ARB_LOST)
|
|
scripta0->ungetjob[0] = cpu_to_scr(SCR_NO_OP);
|
|
#endif
|
|
/*
|
|
* Patch some variable in SCRIPTS.
|
|
* - start and done queue initial bus address.
|
|
* - target bus address table bus address.
|
|
*/
|
|
scriptb0->startpos[0] = cpu_to_scr(np->squeue_ba);
|
|
scriptb0->done_pos[0] = cpu_to_scr(np->dqueue_ba);
|
|
scriptb0->targtbl[0] = cpu_to_scr(np->targtbl_ba);
|
|
|
|
/*
|
|
* Remove the load of SCNTL4 on reselection if not a C10.
|
|
*/
|
|
if (!(np->features & FE_C10)) {
|
|
scripta0->resel_scntl4[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->resel_scntl4[1] = cpu_to_scr(0);
|
|
}
|
|
|
|
/*
|
|
* Remove a couple of work-arounds specific to C1010 if
|
|
* they are not desirable. See `sym_fw2.h' for more details.
|
|
*/
|
|
if (!(np->device_id == PCI_DEVICE_ID_LSI_53C1010_66 &&
|
|
np->revision_id < 0x1 &&
|
|
np->pciclk_khz < 60000)) {
|
|
scripta0->datao_phase[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->datao_phase[1] = cpu_to_scr(0);
|
|
}
|
|
if (!(np->device_id == PCI_DEVICE_ID_LSI_53C1010_33 &&
|
|
/* np->revision_id < 0xff */ 1)) {
|
|
scripta0->sel_done[0] = cpu_to_scr(SCR_NO_OP);
|
|
scripta0->sel_done[1] = cpu_to_scr(0);
|
|
}
|
|
|
|
/*
|
|
* Patch some other variables in SCRIPTS.
|
|
* These ones are loaded by the SCRIPTS processor.
|
|
*/
|
|
scriptb0->pm0_data_addr[0] =
|
|
cpu_to_scr(np->scripta_ba +
|
|
offsetof(struct sym_fw2a_scr, pm0_data));
|
|
scriptb0->pm1_data_addr[0] =
|
|
cpu_to_scr(np->scripta_ba +
|
|
offsetof(struct sym_fw2a_scr, pm1_data));
|
|
}
|
|
|
|
/*
|
|
* Fill the data area in scripts.
|
|
* To be done for all firmwares.
|
|
*/
|
|
static void
|
|
sym_fw_fill_data (u32 *in, u32 *out)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SYM_CONF_MAX_SG; i++) {
|
|
*in++ = SCR_CHMOV_TBL ^ SCR_DATA_IN;
|
|
*in++ = offsetof (struct sym_dsb, data[i]);
|
|
*out++ = SCR_CHMOV_TBL ^ SCR_DATA_OUT;
|
|
*out++ = offsetof (struct sym_dsb, data[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup useful script bus addresses.
|
|
* To be done for all firmwares.
|
|
*/
|
|
static void
|
|
sym_fw_setup_bus_addresses(struct sym_hcb *np, struct sym_fw *fw)
|
|
{
|
|
u32 *pa;
|
|
u_short *po;
|
|
int i;
|
|
|
|
/*
|
|
* Build the bus address table for script A
|
|
* from the script A offset table.
|
|
*/
|
|
po = (u_short *) fw->a_ofs;
|
|
pa = (u32 *) &np->fwa_bas;
|
|
for (i = 0 ; i < sizeof(np->fwa_bas)/sizeof(u32) ; i++)
|
|
pa[i] = np->scripta_ba + po[i];
|
|
|
|
/*
|
|
* Same for script B.
|
|
*/
|
|
po = (u_short *) fw->b_ofs;
|
|
pa = (u32 *) &np->fwb_bas;
|
|
for (i = 0 ; i < sizeof(np->fwb_bas)/sizeof(u32) ; i++)
|
|
pa[i] = np->scriptb_ba + po[i];
|
|
|
|
/*
|
|
* Same for script Z.
|
|
*/
|
|
po = (u_short *) fw->z_ofs;
|
|
pa = (u32 *) &np->fwz_bas;
|
|
for (i = 0 ; i < sizeof(np->fwz_bas)/sizeof(u32) ; i++)
|
|
pa[i] = np->scriptz_ba + po[i];
|
|
}
|
|
|
|
#if SYM_CONF_GENERIC_SUPPORT
|
|
/*
|
|
* Setup routine for firmware #1.
|
|
*/
|
|
static void
|
|
sym_fw1_setup(struct sym_hcb *np, struct sym_fw *fw)
|
|
{
|
|
struct sym_fw1a_scr *scripta0;
|
|
struct sym_fw1b_scr *scriptb0;
|
|
|
|
scripta0 = (struct sym_fw1a_scr *) np->scripta0;
|
|
scriptb0 = (struct sym_fw1b_scr *) np->scriptb0;
|
|
|
|
/*
|
|
* Fill variable parts in scripts.
|
|
*/
|
|
sym_fw_fill_data(scripta0->data_in, scripta0->data_out);
|
|
|
|
/*
|
|
* Setup bus addresses used from the C code..
|
|
*/
|
|
sym_fw_setup_bus_addresses(np, fw);
|
|
}
|
|
#endif /* SYM_CONF_GENERIC_SUPPORT */
|
|
|
|
/*
|
|
* Setup routine for firmware #2.
|
|
*/
|
|
static void
|
|
sym_fw2_setup(struct sym_hcb *np, struct sym_fw *fw)
|
|
{
|
|
struct sym_fw2a_scr *scripta0;
|
|
struct sym_fw2b_scr *scriptb0;
|
|
|
|
scripta0 = (struct sym_fw2a_scr *) np->scripta0;
|
|
scriptb0 = (struct sym_fw2b_scr *) np->scriptb0;
|
|
|
|
/*
|
|
* Fill variable parts in scripts.
|
|
*/
|
|
sym_fw_fill_data(scripta0->data_in, scripta0->data_out);
|
|
|
|
/*
|
|
* Setup bus addresses used from the C code..
|
|
*/
|
|
sym_fw_setup_bus_addresses(np, fw);
|
|
}
|
|
|
|
/*
|
|
* Allocate firmware descriptors.
|
|
*/
|
|
#if SYM_CONF_GENERIC_SUPPORT
|
|
static struct sym_fw sym_fw1 = SYM_FW_ENTRY(sym_fw1, "NCR-generic");
|
|
#endif /* SYM_CONF_GENERIC_SUPPORT */
|
|
static struct sym_fw sym_fw2 = SYM_FW_ENTRY(sym_fw2, "LOAD/STORE-based");
|
|
|
|
/*
|
|
* Find the most appropriate firmware for a chip.
|
|
*/
|
|
struct sym_fw *
|
|
sym_find_firmware(struct sym_chip *chip)
|
|
{
|
|
if (chip->features & FE_LDSTR)
|
|
return &sym_fw2;
|
|
#if SYM_CONF_GENERIC_SUPPORT
|
|
else if (!(chip->features & (FE_PFEN|FE_NOPM|FE_DAC)))
|
|
return &sym_fw1;
|
|
#endif
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Bind a script to physical addresses.
|
|
*/
|
|
void sym_fw_bind_script(struct sym_hcb *np, u32 *start, int len)
|
|
{
|
|
u32 opcode, new, old, tmp1, tmp2;
|
|
u32 *end, *cur;
|
|
int relocs;
|
|
|
|
cur = start;
|
|
end = start + len/4;
|
|
|
|
while (cur < end) {
|
|
|
|
opcode = *cur;
|
|
|
|
/*
|
|
* If we forget to change the length
|
|
* in scripts, a field will be
|
|
* padded with 0. This is an illegal
|
|
* command.
|
|
*/
|
|
if (opcode == 0) {
|
|
printf ("%s: ERROR0 IN SCRIPT at %d.\n",
|
|
sym_name(np), (int) (cur-start));
|
|
++cur;
|
|
continue;
|
|
};
|
|
|
|
/*
|
|
* We use the bogus value 0xf00ff00f ;-)
|
|
* to reserve data area in SCRIPTS.
|
|
*/
|
|
if (opcode == SCR_DATA_ZERO) {
|
|
*cur++ = 0;
|
|
continue;
|
|
}
|
|
|
|
if (DEBUG_FLAGS & DEBUG_SCRIPT)
|
|
printf ("%d: <%x>\n", (int) (cur-start),
|
|
(unsigned)opcode);
|
|
|
|
/*
|
|
* We don't have to decode ALL commands
|
|
*/
|
|
switch (opcode >> 28) {
|
|
case 0xf:
|
|
/*
|
|
* LOAD / STORE DSA relative, don't relocate.
|
|
*/
|
|
relocs = 0;
|
|
break;
|
|
case 0xe:
|
|
/*
|
|
* LOAD / STORE absolute.
|
|
*/
|
|
relocs = 1;
|
|
break;
|
|
case 0xc:
|
|
/*
|
|
* COPY has TWO arguments.
|
|
*/
|
|
relocs = 2;
|
|
tmp1 = cur[1];
|
|
tmp2 = cur[2];
|
|
if ((tmp1 ^ tmp2) & 3) {
|
|
printf ("%s: ERROR1 IN SCRIPT at %d.\n",
|
|
sym_name(np), (int) (cur-start));
|
|
}
|
|
/*
|
|
* If PREFETCH feature not enabled, remove
|
|
* the NO FLUSH bit if present.
|
|
*/
|
|
if ((opcode & SCR_NO_FLUSH) &&
|
|
!(np->features & FE_PFEN)) {
|
|
opcode = (opcode & ~SCR_NO_FLUSH);
|
|
}
|
|
break;
|
|
case 0x0:
|
|
/*
|
|
* MOVE/CHMOV (absolute address)
|
|
*/
|
|
if (!(np->features & FE_WIDE))
|
|
opcode = (opcode | OPC_MOVE);
|
|
relocs = 1;
|
|
break;
|
|
case 0x1:
|
|
/*
|
|
* MOVE/CHMOV (table indirect)
|
|
*/
|
|
if (!(np->features & FE_WIDE))
|
|
opcode = (opcode | OPC_MOVE);
|
|
relocs = 0;
|
|
break;
|
|
#ifdef SYM_CONF_TARGET_ROLE_SUPPORT
|
|
case 0x2:
|
|
/*
|
|
* MOVE/CHMOV in target role (absolute address)
|
|
*/
|
|
opcode &= ~0x20000000;
|
|
if (!(np->features & FE_WIDE))
|
|
opcode = (opcode & ~OPC_TCHMOVE);
|
|
relocs = 1;
|
|
break;
|
|
case 0x3:
|
|
/*
|
|
* MOVE/CHMOV in target role (table indirect)
|
|
*/
|
|
opcode &= ~0x20000000;
|
|
if (!(np->features & FE_WIDE))
|
|
opcode = (opcode & ~OPC_TCHMOVE);
|
|
relocs = 0;
|
|
break;
|
|
#endif
|
|
case 0x8:
|
|
/*
|
|
* JUMP / CALL
|
|
* don't relocate if relative :-)
|
|
*/
|
|
if (opcode & 0x00800000)
|
|
relocs = 0;
|
|
else if ((opcode & 0xf8400000) == 0x80400000)/*JUMP64*/
|
|
relocs = 2;
|
|
else
|
|
relocs = 1;
|
|
break;
|
|
case 0x4:
|
|
case 0x5:
|
|
case 0x6:
|
|
case 0x7:
|
|
relocs = 1;
|
|
break;
|
|
default:
|
|
relocs = 0;
|
|
break;
|
|
};
|
|
|
|
/*
|
|
* Scriptify:) the opcode.
|
|
*/
|
|
*cur++ = cpu_to_scr(opcode);
|
|
|
|
/*
|
|
* If no relocation, assume 1 argument
|
|
* and just scriptize:) it.
|
|
*/
|
|
if (!relocs) {
|
|
*cur = cpu_to_scr(*cur);
|
|
++cur;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Otherwise performs all needed relocations.
|
|
*/
|
|
while (relocs--) {
|
|
old = *cur;
|
|
|
|
switch (old & RELOC_MASK) {
|
|
case RELOC_REGISTER:
|
|
new = (old & ~RELOC_MASK) + np->mmio_ba;
|
|
break;
|
|
case RELOC_LABEL_A:
|
|
new = (old & ~RELOC_MASK) + np->scripta_ba;
|
|
break;
|
|
case RELOC_LABEL_B:
|
|
new = (old & ~RELOC_MASK) + np->scriptb_ba;
|
|
break;
|
|
case RELOC_SOFTC:
|
|
new = (old & ~RELOC_MASK) + np->hcb_ba;
|
|
break;
|
|
case 0:
|
|
/*
|
|
* Don't relocate a 0 address.
|
|
* They are mostly used for patched or
|
|
* script self-modified areas.
|
|
*/
|
|
if (old == 0) {
|
|
new = old;
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
new = 0;
|
|
panic("sym_fw_bind_script: "
|
|
"weird relocation %x\n", old);
|
|
break;
|
|
}
|
|
|
|
*cur++ = cpu_to_scr(new);
|
|
}
|
|
};
|
|
}
|