| /* |
| * 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 |
| */ |
| |
| #include "sym_glue.h" |
| |
| /* |
| * 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 |
| #include "sym_fw1.h" |
| 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) |
| }; |
| 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 |
| #include "sym_fw2.h" |
| 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) |
| 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 Scsi_Host *shost) |
| { |
| struct sym_hcb *np = sym_get_hcb(shost); |
| 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 Scsi_Host *shost) |
| { |
| struct sym_data *sym_data = shost_priv(shost); |
| struct pci_dev *pdev = sym_data->pdev; |
| struct sym_hcb *np = sym_data->ncb; |
| 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 (!use_dac(np)) { |
| 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 (!(pdev->device == PCI_DEVICE_ID_LSI_53C1010_66 && |
| pdev->revision < 0x1 && |
| np->pciclk_khz < 60000)) { |
| scripta0->datao_phase[0] = cpu_to_scr(SCR_NO_OP); |
| scripta0->datao_phase[1] = cpu_to_scr(0); |
| } |
| if (!(pdev->device == PCI_DEVICE_ID_LSI_53C1010_33 /* && |
| pdev->revision < 0xff */)) { |
| 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); |
| } |
| }; |
| } |