diff options
Diffstat (limited to 'drivers/fc4')
-rw-r--r-- | drivers/fc4/Config.in | 17 | ||||
-rw-r--r-- | drivers/fc4/Makefile | 39 | ||||
-rw-r--r-- | drivers/fc4/fc.c | 688 | ||||
-rw-r--r-- | drivers/fc4/fc.h | 222 | ||||
-rw-r--r-- | drivers/fc4/fc_syms.c | 28 | ||||
-rw-r--r-- | drivers/fc4/fcp.h | 94 | ||||
-rw-r--r-- | drivers/fc4/fcp_scsi.h | 140 | ||||
-rw-r--r-- | drivers/fc4/soc.c | 710 | ||||
-rw-r--r-- | drivers/fc4/soc.h | 275 |
9 files changed, 2213 insertions, 0 deletions
diff --git a/drivers/fc4/Config.in b/drivers/fc4/Config.in new file mode 100644 index 000000000..52597561e --- /dev/null +++ b/drivers/fc4/Config.in @@ -0,0 +1,17 @@ +# +# FC4 device configuration +# +mainmenu_option next_comment +comment 'Fibre Channel support' + +tristate 'Fibre Channel and FC4 SCSI support' CONFIG_FC4 m +if [ ! "$CONFIG_FC4" = "n" ]; then + comment 'FC4 drivers' + tristate 'Sun SOC/Sbus' CONFIG_FC4_SOC m + comment 'FC4 targets' + dep_tristate 'SparcSTORAGE Array 100 and 200 series' CONFIG_SCSI_PLUTO "$CONFIG_SCSI" +else + define_bool CONFIG_FC4_SOC n + define_bool CONFIG_SCSI_PLUTO n +fi +endmenu diff --git a/drivers/fc4/Makefile b/drivers/fc4/Makefile new file mode 100644 index 000000000..3ecfe0f83 --- /dev/null +++ b/drivers/fc4/Makefile @@ -0,0 +1,39 @@ +# File: drivers/fc4/Makefile +# +# Makefile for the Linux Fibre Channel device drivers. +# + +L_TARGET := fc4.a +M_OBJS := +MOD_LIST_NAME := FC4_MODULES + +include ../../.config + +ifeq ($(CONFIG_FC4),y) + FC4 = fc.o + ifeq ($(CONFIG_MODULES),y) + O_TARGET := fc_n_syms.o + O_OBJS := fc.o + OX_OBJS := fc_syms.o + FC4 := $(O_TARGET) + endif + L_OBJS += $(FC4) +else + ifeq ($(CONFIG_FC4),m) + MIX_OBJS += fc_syms.o + M_OBJS += fc4.o + endif +endif + +ifeq ($(CONFIG_FC4_SOC),y) +L_OBJS += soc.o +else + ifeq ($(CONFIG_FC4_SOC),m) + M_OBJS += soc.o + endif +endif + +include $(TOPDIR)/Rules.make + +fc4.o: $(MIX_OBJS) fc.o + $(LD) $(LD_RFLAG) -r -o $@ $(MIX_OBJS) fc.o diff --git a/drivers/fc4/fc.c b/drivers/fc4/fc.c new file mode 100644 index 000000000..04a18c0ff --- /dev/null +++ b/drivers/fc4/fc.c @@ -0,0 +1,688 @@ +/* fc.c: Generic Fibre Channel and FC4 SCSI driver. + * + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1997 Jiri Hanika (geo@ff.cuni.cz) + * + * Sources: + * Fibre Channel Physical & Signaling Interface (FC-PH), dpANS, 1994 + * dpANS Fibre Channel Protocol for SCSI (X3.269-199X), Rev. 012, 1995 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/blk.h> + +#include <asm/pgtable.h> +#include <asm/irq.h> +#include <asm/semaphore.h> +#include "fcp_scsi.h" +#include "../scsi/hosts.h" + +/* #define FCDEBUG */ + +#define fc_printk printk ("%s: ", fc->name); printk + +#ifdef FCDEBUG +#define FCD(x) fc_printk x; +#define FCND(x) printk ("FC: "); printk x; +#else +#define FCD(x) +#define FCND(x) +#endif + +#ifdef __sparc__ +static inline void *fc_dma_alloc(long size, char *name, dma_handle *dma) +{ + return (void *) sparc_dvma_malloc (size, "FCP SCSI cmd & rsp queues", dma); +} + +static inline dma_handle fc_sync_dma_entry(void *buf, int len, fc_channel *fc) +{ + return mmu_get_scsi_one (buf, len, fc->dev->my_bus); +} + +static inline void fc_sync_dma_exit(void *buf, long size, fc_channel *fc) +{ + mmu_release_scsi_one ((u32)(long)buf, size, fc->dev->my_bus); +} + +static inline void fc_sync_dma_entry_sg(struct scatterlist *list, int count, fc_channel *fc) +{ + mmu_get_scsi_sgl((struct mmu_sglist *)list, count - 1, fc->dev->my_bus); +} + +static inline void fc_sync_dma_exit_sg(struct scatterlist *list, int count, fc_channel *fc) +{ + mmu_release_scsi_sgl ((struct mmu_sglist *)list, count - 1, fc->dev->my_bus); +} +#else +#error Port this +#endif + +#define FCP_CMND(SCpnt) ((fcp_cmnd *)&(SCpnt->SCp)) +#define FC_SCMND(SCpnt) ((fc_channel *)(SCpnt->host->hostdata[0])) +#define SC_FCMND(fcmnd) ((Scsi_Cmnd *)((long)fcmnd - (long)&(((Scsi_Cmnd *)0)->SCp))) + +static void fcp_scsi_insert_queue (fc_channel *fc, int no, fcp_cmnd *fcmd) +{ + if (!fc->scsi_que[no]) { + fc->scsi_que[no] = fcmd; + fcmd->next = fcmd; + fcmd->prev = fcmd; + } else { + fc->scsi_que[no]->prev->next = fcmd; + fcmd->prev = fc->scsi_que[no]->prev; + fc->scsi_que[no]->prev = fcmd; + fcmd->next = fc->scsi_que[no]; + } +} + +static void fcp_scsi_remove_queue (fc_channel *fc, int no, fcp_cmnd *fcmd) +{ + if (fcmd == fcmd->next) { + fc->scsi_que[no] = NULL; + return; + } + if (fcmd == fc->scsi_que[no]) + fc->scsi_que[no] = fcmd->next; + fcmd->prev->next = fcmd->next; + fcmd->next->prev = fcmd->prev; +} + +fc_channel *fc_channels = NULL; + +#define LSMAGIC 0x2a3b4d2a +typedef struct { + /* Must be first */ + struct semaphore sem; + int magic; + int count; + logi *logi; + fcp_cmnd *fcmds; + atomic_t todo; + struct timer_list timer; + int grace[1]; +} ls; + +#define LSOMAGIC 0x2a3c4e3c +typedef struct { + /* Must be first */ + struct semaphore sem; + int magic; + int count; + fcp_cmnd *fcmds; + atomic_t todo; + struct timer_list timer; +} lso; + +static void fcp_login_timeout(unsigned long data) +{ + ls *l = (ls *)data; + FCND(("Login timeout\n")) + up(&l->sem); +} + +static void fcp_login_done(fc_channel *fc, int i, int status) +{ + fcp_cmnd *fcmd; + logi *plogi; + fc_hdr *fch; + ls *l = (ls *)fc->ls; + + FCD(("Login done %d %d\n", i, status)) + if (i < l->count) { + if (fc->state == FC_STATE_FPORT_OK) { + FCD(("Additional FPORT_OK received with status %d\n", status)) + return; + } + switch (status) { + case FC_STATUS_OK: /* Oh, we found a fabric */ + case FC_STATUS_P_RJT: /* Oh, we haven't found any */ + fc->state = FC_STATE_FPORT_OK; + fcmd = l->fcmds + i; + plogi = l->logi + 3 * i; + fc_sync_dma_exit (plogi, 3 * sizeof(logi), fc); + plogi->code = LS_PLOGI; + memcpy (&plogi->nport_wwn, &fc->wwn_nport, sizeof(fc_wwn)); + memcpy (&plogi->node_wwn, &fc->wwn_node, sizeof(fc_wwn)); + memcpy (&plogi->common, fc->common_svc, sizeof(common_svc_parm)); + memcpy (&plogi->class1, fc->class_svcs, 3*sizeof(svc_parm)); + fch = &fcmd->fch; + fcmd->token += l->count; + fcmd->rsp += sizeof(logi); + FILL_FCHDR_RCTL_DID(fch, R_CTL_ELS_REQ, fc->did); + FILL_FCHDR_SID(fch, fc->sid); +#ifdef FCDEBUG + { + int i; + unsigned *x = (unsigned *)plogi; + printk ("logi: "); + for (i = 0; i < 21; i++) + printk ("%08x ", x[i]); + printk ("\n"); + } +#endif + fc_sync_dma_entry (plogi, 3 * sizeof(logi), fc); + if (fc->hw_enque (fc, fcmd)) + printk ("FC: Cannot enque PLOGI packet on %s\n", fc->name); + break; + case FC_STATUS_ERR_OFFLINE: + fc->state = FC_STATE_MAYBEOFFLINE; + FCD (("FC is offline %d\n", l->grace[i])) + break; + default: + printk ("FLOGI failed for %s with status %d\n", fc->name, status); + /* Do some sort of error recovery here */ + break; + } + } else { + i -= l->count; + if (fc->state != FC_STATE_FPORT_OK) { + FCD(("Unexpected N-PORT rsp received")) + return; + } + switch (status) { + case FC_STATUS_OK: + plogi = l->logi + 3 * i; + fc_sync_dma_exit (plogi, 3 * sizeof(logi), fc); + if (!fc->wwn_dest.lo && !fc->wwn_dest.hi) { + memcpy (&fc->wwn_dest, &plogi[1].node_wwn, sizeof(fc_wwn)); + FCD(("Dest WWN %08x%08x\n", *(u32 *)&fc->wwn_dest, fc->wwn_dest.lo)) + } else if (fc->wwn_dest.lo != plogi[1].node_wwn.lo || + fc->wwn_dest.hi != plogi[1].node_wwn.hi) { + printk ("%s: mismatch in wwns. Got %08x%08x, expected %08x%08x\n", + fc->name, + *(u32 *)&plogi[1].node_wwn, plogi[1].node_wwn.lo, + *(u32 *)&fc->wwn_dest, fc->wwn_dest.lo); + } + fc->state = FC_STATE_ONLINE; + printk ("%s: ONLINE\n", fc->name); + if (atomic_dec_and_test (&l->todo)) + up(&l->sem); + break; + case FC_STATUS_ERR_OFFLINE: + fc->state = FC_STATE_OFFLINE; + fc_sync_dma_exit (l->logi + 3 * i, 3 * sizeof(logi), fc); + printk ("%s: FC is offline\n", fc->name); + if (atomic_dec_and_test (&l->todo)) + up(&l->sem); + break; + default: + printk ("PLOGI failed for %s with status %d\n", fc->name, status); + /* Do some sort of error recovery here */ + break; + } + } +} + +void fcp_register(fc_channel *fc, u8 type, int unregister) +{ + int size, i; + + if (type == TYPE_SCSI_FCP) { + if (!unregister) { + fc->scsi_cmd_pool = + (fcp_cmd *) fc_dma_alloc (fc->can_queue * (sizeof (fcp_cmd) + fc->rsp_size), + "FCP SCSI cmd & rsp queues", &fc->dma_scsi_cmd); + fc->scsi_rsp_pool = (char *)(fc->scsi_cmd_pool + fc->can_queue); + fc->dma_scsi_rsp = fc->dma_scsi_cmd + fc->can_queue * sizeof (fcp_cmd); + fc->scsi_bitmap_end = ((fc->can_queue + 63) & ~63); + size = fc->scsi_bitmap_end / 8; + fc->scsi_bitmap = kmalloc (size, GFP_KERNEL); + memset (fc->scsi_bitmap, 0, size); + for (i = fc->can_queue; i < fc->scsi_bitmap_end; i++) + set_bit (i, fc->scsi_bitmap); + fc->scsi_free = fc->can_queue; + fc->token_tab = (fcp_cmnd **)kmalloc(fc->can_queue * sizeof(fcp_cmnd*), GFP_KERNEL); + } else { + fc->scsi_name[0] = 0; + kfree (fc->scsi_bitmap); + kfree (fc->token_tab); + } + } else + printk ("FC: %segistering unknown type %02x\n", unregister ? "Unr" : "R", type); +} + +static void fcp_scsi_done(Scsi_Cmnd *SCpnt); + +static inline void fcp_scsi_receive(fc_channel *fc, int token, int status, fc_hdr *fch) +{ + fcp_cmnd *fcmd; + fcp_rsp *rsp; + int host_status; + Scsi_Cmnd *SCpnt; + int sense_len; + int rsp_status; + + fcmd = fc->token_tab[token]; + if (!fcmd) return; + rsp = (fcp_rsp *) (fc->scsi_rsp_pool + fc->rsp_size * token); + SCpnt = SC_FCMND(fcmd); + + if (SCpnt->done != fcp_scsi_done) + return; + + rsp_status = rsp->fcp_status; + switch (status) { + case FC_STATUS_OK: + host_status=DID_OK; + + if (rsp_status & FCP_STATUS_RESID) { +#ifdef FCDEBUG + FCD(("Resid %d\n", rsp->fcp_resid)) + { + fcp_cmd *cmd = fc->scsi_cmd_pool + token; + int i; + + printk ("Command "); + for (i = 0; i < sizeof(fcp_cmd); i+=4) + printk ("%08x ", *(u32 *)(((char *)cmd)+i)); + printk ("\nResponse "); + for (i = 0; i < fc->rsp_size; i+=4) + printk ("%08x ", *(u32 *)(((char *)rsp)+i)); + printk ("\n"); + } +#endif + } + + if (rsp_status & FCP_STATUS_SENSE_LEN) { + sense_len = rsp->fcp_sense_len; + if (sense_len > sizeof(SCpnt->sense_buffer)) sense_len = sizeof(SCpnt->sense_buffer); + memcpy(SCpnt->sense_buffer, ((char *)(rsp+1)), sense_len); + } + + if (fcmd->data) { + if (SCpnt->use_sg) + fc_sync_dma_exit_sg((struct scatterlist *)SCpnt->buffer, SCpnt->use_sg, fc); + else + fc_sync_dma_exit(SCpnt->request_buffer, SCpnt->request_bufflen, fc); + } + break; + default: + host_status=DID_ERROR; + FCD(("Wrong FC status %d for token %d\n", status, token)) + break; + } + + if (status_byte(rsp_status) == QUEUE_FULL) { + printk ("%s: (%d,%d) Received rsp_status 0x%x\n", fc->name, SCpnt->channel, SCpnt->target, rsp_status); + } + + SCpnt->result = (host_status << 16) | (rsp_status & 0xff); + SCpnt->done = fcmd->done; + fcmd->done=NULL; + clear_bit(token, fc->scsi_bitmap); + fc->scsi_free++; + SCpnt->scsi_done(SCpnt); +} + +void fcp_receive_solicited(fc_channel *fc, int proto, int token, int status, fc_hdr *fch) +{ + FCD(("Receive solicited %d %d %d\n", proto, token, status)) + switch (proto) { + case TYPE_SCSI_FCP: + fcp_scsi_receive(fc, token, status, fch); break; + case TYPE_EXTENDED_LS: + if (fc->ls && ((ls *)(fc->ls))->magic == LSMAGIC) { + ls *l = (ls *)fc->ls; + int i = (token >= l->count) ? token - l->count : token; + + /* Make us sure */ + if ((unsigned)i < l->count && l->fcmds[i].fc == fc) { + fcp_login_done(fc, token, status); + break; + } + } + break; + case PROTO_OFFLINE: + if (fc->ls && ((lso *)(fc->ls))->magic == LSOMAGIC) { + lso *l = (lso *)fc->ls; + + if ((unsigned)token < l->count && l->fcmds[token].fc == fc) { + /* Wow, OFFLINE response arrived :) */ + FCD(("OFFLINE Response arrived\n")) + fc->state = FC_STATE_OFFLINE; + if (atomic_dec_and_test (&l->todo)) + up(&l->sem); + } + } + break; + + default: + break; + } +} + +void fcp_state_change(fc_channel *fc, int state) +{ + FCD(("state_change %d %d\n", state, fc->state)) + if (state == FC_STATE_ONLINE && fc->state == FC_STATE_MAYBEOFFLINE) + fc->state = FC_STATE_UNINITED; + else if (state == FC_STATE_ONLINE) + printk (KERN_WARNING "%s: state change to ONLINE\n", fc->name); + else + printk (KERN_ERR "%s: state change to OFFLINE\n", fc->name); +} + +int fcp_initialize(fc_channel *fcchain, int count) +{ + fc_channel *fc; + fcp_cmnd *fcmd; + int i, retry, ret; + ls *l; + + FCND(("fcp_inititialize %08lx\n", (long)fcp_init)) + FCND(("fc_channels %08lx\n", (long)fc_channels)) + FCND((" SID %d DID %d\n", fcchain->sid, fcchain->did)) + l = kmalloc(sizeof (ls) + count * sizeof(int), GFP_KERNEL); + if (!l) { + printk ("FC: Cannot allocate memory for initialization\n"); + return -ENOMEM; + } + memset (l, 0, sizeof(ls) + count * sizeof(int)); + l->magic = LSMAGIC; + l->count = count; + FCND(("FCP Init for %d channels\n", count)) + l->sem = MUTEX_LOCKED; + l->timer.function = fcp_login_timeout; + l->timer.data = (unsigned long)l; + atomic_set (&l->todo, count); + l->logi = kmalloc (count * 3 * sizeof(logi), GFP_DMA); + l->fcmds = kmalloc (count * sizeof(fcp_cmnd), GFP_KERNEL); + if (!l->logi || !l->fcmds) { + if (l->logi) kfree (l->logi); + if (l->fcmds) kfree (l->fcmds); + kfree (l); + printk ("FC: Cannot allocate DMA memory for initialization\n"); + return -ENOMEM; + } + memset (l->logi, 0, count * 3 * sizeof(logi)); + memset (l->fcmds, 0, count * sizeof(fcp_cmnd)); + FCND(("Initializing FLOGI packets\n")) + for (fc = fcchain, i = 0; fc && i < count; fc = fc->next, i++) { + fc_hdr *fch; + + FCD(("SID %d DID %d\n", fc->sid, fc->did)) + fc->state = FC_STATE_UNINITED; + fcmd = l->fcmds + i; + fc->login = fcmd; + fc->ls = (void *)l; + fch = &fcmd->fch; + FILL_FCHDR_RCTL_DID(fch, R_CTL_ELS_REQ, FS_FABRIC_F_PORT); + FILL_FCHDR_SID(fch, 0); + FILL_FCHDR_TYPE_FCTL(fch, TYPE_EXTENDED_LS, F_CTL_FIRST_SEQ | F_CTL_SEQ_INITIATIVE); + FILL_FCHDR_SEQ_DF_SEQ(fch, 0, 0, 0); + FILL_FCHDR_OXRX(fch, 0xffff, 0xffff); + fch->param = 0; + l->logi [3 * i].code = LS_FLOGI; + fcmd->cmd = fc_sync_dma_entry (l->logi + 3 * i, 3 * sizeof(logi), fc); + fcmd->rsp = fcmd->cmd + sizeof(logi); + fcmd->cmdlen = sizeof(logi); + fcmd->rsplen = sizeof(logi); + fcmd->data = (dma_handle)NULL; + fcmd->class = FC_CLASS_SIMPLE; + fcmd->proto = TYPE_EXTENDED_LS; + fcmd->token = i; + fcmd->fc = fc; + } + for (retry = 0; retry < 8; retry++) { + FCND(("Sending FLOGI/PLOGI packets\n")) + for (fc = fcchain, i = 0; fc && i < count; fc = fc->next, i++) { + if (fc->state == FC_STATE_ONLINE || fc->state == FC_STATE_OFFLINE) + continue; + disable_irq(fc->irq); + if (fc->state == FC_STATE_MAYBEOFFLINE) { + if (!l->grace[i]) { + l->grace[i]++; + FCD(("Grace\n")) + } else { + fc->state = FC_STATE_OFFLINE; + enable_irq(fc->irq); + fc_sync_dma_exit (l->logi + 3 * i, 3 * sizeof(logi), fc); + if (atomic_dec_and_test (&l->todo)) + goto all_done; + } + } + ret = fc->hw_enque (fc, fc->login); + enable_irq(fc->irq); + if (ret) printk ("FC: Cannot enque FLOGI packet on %s\n", fc->name); + } + + l->timer.expires = jiffies + 5 * HZ; + add_timer(&l->timer); + + down(&l->sem); + if (!atomic_read(&l->todo)) { + FCND(("All channels answered in time\n")) + break; /* All fc channels have answered us */ + } + } +all_done: + for (fc = fcchain, i = 0; fc && i < count; fc = fc->next, i += 3) { + switch (fc->state) { + case FC_STATE_ONLINE: break; + case FC_STATE_OFFLINE: break; + default: fc_sync_dma_exit (l->logi + i, 3 * sizeof(logi), fc); + break; + } + fc->ls = NULL; + } + del_timer(&l->timer); + kfree (l->logi); + kfree (l->fcmds); + kfree (l); + return 0; +} + +int fcp_forceoffline(fc_channel *fcchain, int count) +{ + fc_channel *fc; + fcp_cmnd *fcmd; + int i, ret; + lso l; + + memset (&l, 0, sizeof(lso)); + l.count = count; + l.magic = LSOMAGIC; + FCND(("FCP Force Offline for %d channels\n", count)) + l.sem = MUTEX_LOCKED; + l.timer.function = fcp_login_timeout; + l.timer.data = (unsigned long)&l; + atomic_set (&l.todo, count); + l.fcmds = kmalloc (count * sizeof(fcp_cmnd), GFP_KERNEL); + if (!l.fcmds) { + kfree (l.fcmds); + printk ("FC: Cannot allocate memory for forcing offline\n"); + return -ENOMEM; + } + memset (l.fcmds, 0, count * sizeof(fcp_cmnd)); + FCND(("Initializing OFFLINE packets\n")) + for (fc = fcchain, i = 0; fc && i < count; fc = fc->next, i++) { + fc->state = FC_STATE_UNINITED; + fcmd = l.fcmds + i; + fc->login = fcmd; + fc->ls = (void *)&l; + fcmd->class = FC_CLASS_OFFLINE; + fcmd->proto = PROTO_OFFLINE; + fcmd->token = i; + fcmd->fc = fc; + disable_irq(fc->irq); + ret = fc->hw_enque (fc, fc->login); + enable_irq(fc->irq); + if (ret) printk ("FC: Cannot enque OFFLINE packet on %s\n", fc->name); + } + + l.timer.expires = jiffies + 5 * HZ; + add_timer(&l.timer); + down(&l.sem); + del_timer(&l.timer); + + kfree (l.fcmds); + return 0; +} + +int fcp_init(fc_channel *fcchain) +{ + fc_channel *fc; + int count=0; + + for (fc = fcchain; fc; fc = fc->next) { + fc->fcp_register = fcp_register; + fc->fcp_state_change = fcp_state_change; + count++; + } + + fcp_initialize (fcchain, count); + + if (!fc_channels) + fc_channels = fcchain; + else { + for (fc = fc_channels; fc->next; fc = fc->next); + fc->next = fcchain; + } + return 0; +} + +static void fcp_scsi_done (Scsi_Cmnd *SCpnt) +{ + if (FCP_CMND(SCpnt)->done) + FCP_CMND(SCpnt)->done(SCpnt); +} + +static int fcp_scsi_queue_it(fc_channel *fc, Scsi_Cmnd *SCpnt, fcp_cmnd *fcmd, int prepare) +{ + if (prepare) { + long i; + fcp_cmd *cmd; + u32 fcp_cntl; + + i = find_first_zero_bit (fc->scsi_bitmap, fc->scsi_bitmap_end); + set_bit (i, fc->scsi_bitmap); + fcmd->token = i; + cmd = fc->scsi_cmd_pool + i; + FCD(("Chose token %ld %08lx\n", i, (long)cmd)) + if (fc->encode_addr (SCpnt, cmd->fcp_addr)) { + /* Invalid channel/id/lun and couldn't map it into fcp_addr */ + clear_bit (i, fc->scsi_bitmap); + SCpnt->result = (DID_BAD_TARGET << 16); + SCpnt->scsi_done(SCpnt); + return 0; + } + fc->scsi_free--; + fc->token_tab[fcmd->token] = fcmd; + + if (SCpnt->device->tagged_supported) { + if (jiffies - fc->ages[SCpnt->channel * fc->targets + SCpnt->target] > (5 * 60 * HZ)) { + fc->ages[SCpnt->channel * fc->targets + SCpnt->target] = jiffies; + fcp_cntl = FCP_CNTL_QTYPE_ORDERED; + } else + fcp_cntl = FCP_CNTL_QTYPE_SIMPLE; + } else + fcp_cntl = FCP_CNTL_QTYPE_UNTAGGED; + if (!SCpnt->request_bufflen && !SCpnt->use_sg) { + cmd->fcp_cntl = fcp_cntl; + fcmd->data = (dma_handle)NULL; + } else { + switch (SCpnt->cmnd[0]) { + case WRITE_6: + case WRITE_10: + case WRITE_12: + cmd->fcp_cntl = (FCP_CNTL_WRITE | fcp_cntl); break; + default: + cmd->fcp_cntl = (FCP_CNTL_READ | fcp_cntl); break; + } + if (!SCpnt->use_sg) { + cmd->fcp_data_len = SCpnt->request_bufflen; + fcmd->data = fc_sync_dma_entry ((char *)SCpnt->request_buffer, + SCpnt->request_bufflen, fc); + } else { + struct scatterlist *sg = (struct scatterlist *)SCpnt->buffer; + + FCD(("XXX: Use_sg %d %d\n", SCpnt->use_sg, sg->length)) + if (SCpnt->use_sg > 1) printk ("%s: SG for use_sg > 1 not handled yet\n", fc->name); + fc_sync_dma_entry_sg (sg, SCpnt->use_sg, fc); + fcmd->data = sg->dvma_address; + cmd->fcp_data_len = sg->length; + } + } + memcpy (cmd->fcp_cdb, SCpnt->cmnd, SCpnt->cmd_len); + memset (cmd->fcp_cdb+SCpnt->cmd_len, 0, sizeof(cmd->fcp_cdb)-SCpnt->cmd_len); + FCD(("XXX: %04x.%04x.%04x.%04x - %08x%08x%08x\n", cmd->fcp_addr[0], cmd->fcp_addr[1], cmd->fcp_addr[2], cmd->fcp_addr[3], *(u32 *)SCpnt->cmnd, *(u32 *)(SCpnt->cmnd+4), *(u32 *)(SCpnt->cmnd+8))) + } + FCD(("Trying to enque %08x\n", (int)fcmd)) + if (!fc->scsi_que[1]) { + if (!fc->hw_enque (fc, fcmd)) { + FCD(("hw_enque succeeded for %08x\n", (int)fcmd)) + return 0; + } + } + FCD(("Putting into que1 %08x\n", (int)fcmd)) + fcp_scsi_insert_queue (fc, 1, fcmd); + return 0; +} + +int fcp_scsi_queuecommand(Scsi_Cmnd *SCpnt, void (* done)(Scsi_Cmnd *)) +{ + fcp_cmnd *fcmd = FCP_CMND(SCpnt); + fc_channel *fc = FC_SCMND(SCpnt); + + FCD(("Entering SCSI queuecommand %08x\n", (int)fcmd)) + if (SCpnt->done != fcp_scsi_done) { + fcmd->done = SCpnt->done; + SCpnt->done = fcp_scsi_done; + SCpnt->scsi_done = done; + fcmd->proto = TYPE_SCSI_FCP; + if (!fc->scsi_free) { + FCD(("Putting into que0\n")) + fcp_scsi_insert_queue (fc, 0, fcmd); + return 1; + } + return fcp_scsi_queue_it(fc, SCpnt, fcmd, 1); + } + return fcp_scsi_queue_it(fc, SCpnt, fcmd, 0); +} + +void fcp_queue_empty(fc_channel *fc) +{ + fcp_cmnd *fcmd; + FCD(("Queue empty\n")) + while ((fcmd = fc->scsi_que[1])) { + /* The hw tell us we can try again queue some packet */ + if (fc->hw_enque (fc, fcmd)) + return; + fcp_scsi_remove_queue (fc, 1, fcmd); + } +} + +int fcp_scsi_abort(Scsi_Cmnd *SCpnt) +{ + printk ("FC: AIEEE, abort!\n"); + return 0; +} + +int fcp_scsi_reset(Scsi_Cmnd *SCpnt, unsigned int reset_flags) +{ + printk ("FC: AIEEE, reset!\n"); + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{ +} +#endif diff --git a/drivers/fc4/fc.h b/drivers/fc4/fc.h new file mode 100644 index 000000000..d1c991802 --- /dev/null +++ b/drivers/fc4/fc.h @@ -0,0 +1,222 @@ +/* fc.h: Definitions for Fibre Channel Physical and Signaling Interface. + * + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * + * Sources: + * Fibre Channel Physical & Signaling Interface (FC-PH), dpANS, 1994 + * dpANS Fibre Channel Protocol for SCSI (X3.269-199X), Rev. 012, 1995 + */ + +#ifndef __FC_H +#define __FC_H + +/* World Wide Name */ +#define NAAID_IEEE 1 +#define NAAID_IEEE_EXT 2 +#define NAAID_LOCAL 3 +#define NAAID_IP 4 +#define NAAID_CCITT 12 +#define NAAID_CCITT_GRP 14 + +typedef struct { + u32 naaid:4; + u32 nportid:12; + u32 hi:16; + u32 lo; +} fc_wwn; + +/* Frame header for FC-PH frames */ + +/* r_ctl field */ +#define R_CTL_DEVICE_DATA 0x00 /* FC4 Device_Data frame */ +#define R_CTL_EXTENDED_SVC 0x20 /* Extended Link_Data frame */ +#define R_CTL_FC4_SVC 0x30 /* FC4 Link_Data frame */ +#define R_CTL_VIDEO 0x40 /* Video_Data frame */ +#define R_CTL_BASIC_SVC 0x80 /* Basic Link_Data frame */ +#define R_CTL_LINK_CTL 0xc0 /* Link_Control frame */ +/* FC4 Device_Data frames */ +#define R_CTL_UNCATEGORIZED 0x00 +#define R_CTL_SOLICITED_DATA 0x01 +#define R_CTL_UNSOL_CONTROL 0x02 +#define R_CTL_SOLICITED_CONTROL 0x03 +#define R_CTL_UNSOL_DATA 0x04 +#define R_CTL_XFER_RDY 0x05 +#define R_CTL_COMMAND 0x06 +#define R_CTL_STATUS 0x07 +/* Basic Link_Data frames */ +#define R_CTL_LS_NOP 0x80 +#define R_CTL_LS_ABTS 0x81 +#define R_CTL_LS_RMC 0x82 +#define R_CTL_LS_BA_ACC 0x84 +#define R_CTL_LS_BA_RJT 0x85 +/* Extended Link_Data frames */ +#define R_CTL_ELS_REQ 0x22 +#define R_CTL_ELS_RSP 0x23 +/* Link_Control frames */ +#define R_CTL_ACK_1 0xc0 +#define R_CTL_ACK_N 0xc1 +#define R_CTL_P_RJT 0xc2 +#define R_CTL_F_RJT 0xc3 +#define R_CTL_P_BSY 0xc4 +#define R_CTL_F_BSY_DF 0xc5 +#define R_CTL_F_BSY_LC 0xc6 +#define R_CTL_LCR 0xc7 + +/* type field */ +#define TYPE_BASIC_LS 0x00 +#define TYPE_EXTENDED_LS 0x01 +#define TYPE_IS8802 0x04 +#define TYPE_IS8802_SNAP 0x05 +#define TYPE_SCSI_FCP 0x08 +#define TYPE_SCSI_GPP 0x09 +#define TYPE_HIPP_FP 0x0a +#define TYPE_IPI3_MASTER 0x11 +#define TYPE_IPI3_SLAVE 0x12 +#define TYPE_IPI3_PEER 0x13 + +/* f_ctl field */ +#define F_CTL_FILL_BYTES 0x000003 +#define F_CTL_XCHG_REASSEMBLE 0x000004 +#define F_CTL_RO_PRESENT 0x000008 +#define F_CTL_ABORT_SEQ 0x000030 +#define F_CTL_CONTINUE_SEQ 0x0000c0 +#define F_CTL_INVALIDATE_XID 0x004000 +#define F_CTL_XID_REASSIGNED 0x008000 +#define F_CTL_SEQ_INITIATIVE 0x010000 +#define F_CTL_CHAINED_SEQ 0x020000 +#define F_CTL_END_CONNECT 0x040000 +#define F_CTL_END_SEQ 0x080000 +#define F_CTL_LAST_SEQ 0x100000 +#define F_CTL_FIRST_SEQ 0x200000 +#define F_CTL_SEQ_CONTEXT 0x400000 +#define F_CTL_XCHG_CONTEXT 0x800000 + +typedef struct { + u32 r_ctl:8, did:24; + u32 xxx1:8, sid:24; + u32 type:8, f_ctl:24; + u32 seq_id:8, df_ctl:8, seq_cnt:16; + u16 ox_id, rx_id; + u32 param; +} fc_hdr; +/* The following are ugly macros to make setup of this structure faster */ +#define FILL_FCHDR_RCTL_DID(fch, r_ctl, did) *(u32 *)(fch) = ((r_ctl) << 24) | (did); +#define FILL_FCHDR_SID(fch, sid) *((u32 *)(fch)+1) = (sid); +#define FILL_FCHDR_TYPE_FCTL(fch, type, f_ctl) *((u32 *)(fch)+2) = ((type) << 24) | (f_ctl); +#define FILL_FCHDR_SEQ_DF_SEQ(fch, seq_id, df_ctl, seq_cnt) *((u32 *)(fch)+3) = ((seq_id) << 24) | ((df_ctl) << 16) | (seq_cnt); +#define FILL_FCHDR_OXRX(fch, ox_id, rx_id) *((u32 *)(fch)+4) = ((ox_id) << 16) | (rx_id); + +/* Well known addresses */ +#define FS_GENERAL_MULTICAST 0xfffff7 +#define FS_WELL_KNOWN_MULTICAST 0xfffff8 +#define FS_HUNT_GROUP 0xfffff9 +#define FS_MANAGEMENT_SERVER 0xfffffa +#define FS_TIME_SERVER 0xfffffb +#define FS_NAME_SERVER 0xfffffc +#define FS_FABRIC_CONTROLLER 0xfffffd +#define FS_FABRIC_F_PORT 0xfffffe +#define FS_BROADCAST 0xffffff + +/* Reject frames */ +/* The param field should be cast to this structure */ +typedef struct { + u8 action; + u8 reason; + u8 xxx; + u8 vendor_unique; +} rjt_param; + +/* Reject action codes */ +#define RJT_RETRY 0x01 +#define RJT_NONRETRY 0x02 + +/* Reject reason codes */ +#define RJT_INVALID_DID 0x01 +#define RJT_INVALID_SID 0x02 +#define RJT_NPORT_NOT_AVAIL_TEMP 0x03 +#define RJT_NPORT_NOT_AVAIL_PERM 0x04 +#define RJT_CLASS_NOT_SUPPORTED 0x05 +#define RJT_DELIMITER_ERROR 0x06 +#define RJT_TYPE_NOT_SUPPORTED 0x07 +#define RJT_INVALID_LINK_CONTROL 0x08 +#define RJT_INVALID_R_CTL 0x09 +#define RJT_INVALID_F_CTL 0x0a +#define RJT_INVALID_OX_ID 0x0b +#define RJT_INVALID_RX_ID 0x0c +#define RJT_INVALID_SEQ_ID 0x0d +#define RJT_INVALID_DF_CTL 0x0e +#define RJT_INVALID_SEQ_CNT 0x0f +#define RJT_INVALID_PARAMETER 0x10 +#define RJT_EXCHANGE_ERROR 0x11 +#define RJT_PROTOCOL_ERROR 0x12 +#define RJT_INCORRECT_LENGTH 0x13 +#define RJT_UNEXPECTED_ACK 0x14 +#define RJT_UNEXPECTED_LINK_RESP 0x15 +#define RJT_LOGIN_REQUIRED 0x16 +#define RJT_EXCESSIVE_SEQUENCES 0x17 +#define RJT_CANT_ESTABLISH_EXCHANGE 0x18 +#define RJT_SECURITY_NOT_SUPPORTED 0x19 +#define RJT_FABRIC_NA 0x1a +#define RJT_VENDOR_UNIQUE 0xff + + +#define SP_F_PORT_LOGIN 0x10 + +/* Extended SVC commands */ +#define LS_RJT 0x01000000 +#define LS_ACC 0x02000000 +#define LS_PLOGI 0x03000000 +#define LS_FLOGI 0x04000000 +#define LS_LOGO 0x05000000 +#define LS_ABTX 0x06000000 +#define LS_RCS 0x07000000 +#define LS_RES 0x08000000 +#define LS_RSS 0x09000000 +#define LS_RSI 0x0a000000 +#define LS_ESTS 0x0b000000 +#define LS_ESTC 0x0c000000 +#define LS_ADVC 0x0d000000 +#define LS_RTV 0x0e000000 +#define LS_RLS 0x0f000000 +#define LS_ECHO 0x10000000 +#define LS_TEST 0x11000000 +#define LS_RRQ 0x12000000 +#define LS_IDENT 0x20000000 +#define LS_DISPLAY 0x21000000 + +typedef struct { + u8 fcph_hi, fcph_lo; + u16 buf2buf_credit; + u8 common_features; + u8 xxx1; + u16 buf2buf_size; + u8 xxx2; + u8 total_concurrent; + u16 off_by_info; + u32 e_d_tov; +} common_svc_parm; + +typedef struct { + u16 serv_opts; + u16 initiator_ctl; + u16 rcpt_ctl; + u16 recv_size; + u8 xxx1; + u8 concurrent_seqs; + u16 end2end_credit; + u16 open_seqs_per_xchg; + u16 xxx2; +} svc_parm; + +/* Login */ +typedef struct { + u32 code; + common_svc_parm common; + fc_wwn nport_wwn; + fc_wwn node_wwn; + svc_parm class1; + svc_parm class2; + svc_parm class3; +} logi; + +#endif /* !(__FC_H) */ diff --git a/drivers/fc4/fc_syms.c b/drivers/fc4/fc_syms.c new file mode 100644 index 000000000..f896a9b0f --- /dev/null +++ b/drivers/fc4/fc_syms.c @@ -0,0 +1,28 @@ +/* + * We should not even be trying to compile this if we are not doing + * a module. + */ +#define __NO_VERSION__ +#include <linux/config.h> +#include <linux/module.h> + +#ifdef CONFIG_MODULES + +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#include "fcp_scsi.h" + +EXPORT_SYMBOL(fcp_init); +EXPORT_SYMBOL(fcp_queue_empty); +EXPORT_SYMBOL(fcp_receive_solicited); +EXPORT_SYMBOL(fc_channels); + +/* SCSI stuff */ +EXPORT_SYMBOL(fcp_scsi_queuecommand); +EXPORT_SYMBOL(fcp_scsi_abort); +EXPORT_SYMBOL(fcp_scsi_reset); + +#endif /* CONFIG_MODULES */ diff --git a/drivers/fc4/fcp.h b/drivers/fc4/fcp.h new file mode 100644 index 000000000..6aa34a7a4 --- /dev/null +++ b/drivers/fc4/fcp.h @@ -0,0 +1,94 @@ +/* fcp.h: Definitions for Fibre Channel Protocol. + * + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * + */ + +#ifndef __FCP_H +#define __FCP_H + +/* FCP addressing is hierarchical with up to 4 layers, MS first. + Exact meaning of the addresses is up to the vendor */ + +/* fcp_cntl field */ +#define FCP_CNTL_WRITE 0x00000001 /* Initiator write */ +#define FCP_CNTL_READ 0x00000002 /* Initiator read */ +#define FCP_CNTL_ABORT_TSK 0x00000200 /* Abort task set */ +#define FCP_CNTL_CLR_TASK 0x00000400 /* Clear task set */ +#define FCP_CNTL_RESET 0x00002000 /* Reset */ +#define FCP_CNTL_CLR_ACA 0x00004000 /* Clear ACA */ +#define FCP_CNTL_KILL_TASK 0x00008000 /* Terminate task */ +#define FCP_CNTL_QTYPE_MASK 0x00070000 /* Tagged queueing type */ +#define FCP_CNTL_QTYPE_SIMPLE 0x00000000 +#define FCP_CNTL_QTYPE_HEAD_OF_Q 0x00010000 +#define FCP_CNTL_QTYPE_ORDERED 0x00020000 +#define FCP_CNTL_QTYPE_ACA_Q_TAG 0x00040000 +#define FCP_CNTL_QTYPE_UNTAGGED 0x00050000 + +typedef struct { + u16 fcp_addr[4]; + u32 fcp_cntl; + u8 fcp_cdb[16]; + u32 fcp_data_len; +} fcp_cmd; + +/* fcp_status field */ +#define FCP_STATUS_MASK 0x000000ff /* scsi status of command */ +#define FCP_STATUS_RSP_LEN 0x00000100 /* response_len != 0 */ +#define FCP_STATUS_SENSE_LEN 0x00000200 /* sense_len != 0 */ +#define FCP_STATUS_RESID 0x00000400 /* resid != 0 */ + +typedef struct { + u32 xxx[2]; + u32 fcp_status; + u32 fcp_resid; + u32 fcp_sense_len; + u32 fcp_response_len; + /* u8 fcp_sense[fcp_sense_len]; */ + /* u8 fcp_response[fcp_response_len]; */ +} fcp_rsp; + +/* fcp errors */ + +/* rsp_info_type field */ +#define FCP_RSP_SCSI_BUS_ERR 0x01 +#define FCP_RSP_SCSI_PORT_ERR 0x02 +#define FCP_RSP_CARD_ERR 0x03 + +/* isp_status field */ +#define FCP_RSP_CMD_COMPLETE 0x0000 +#define FCP_RSP_CMD_INCOMPLETE 0x0001 +#define FCP_RSP_CMD_DMA_ERR 0x0002 +#define FCP_RSP_CMD_TRAN_ERR 0x0003 +#define FCP_RSP_CMD_RESET 0x0004 +#define FCP_RSP_CMD_ABORTED 0x0005 +#define FCP_RSP_CMD_TIMEOUT 0x0006 +#define FCP_RSP_CMD_OVERRUN 0x0007 + +/* isp_state_flags field */ +#define FCP_RSP_ST_GOT_BUS 0x0100 +#define FCP_RSP_ST_GOT_TARGET 0x0200 +#define FCP_RSP_ST_SENT_CMD 0x0400 +#define FCP_RSP_ST_XFRD_DATA 0x0800 +#define FCP_RSP_ST_GOT_STATUS 0x1000 +#define FCP_RSP_ST_GOT_SENSE 0x2000 + +/* isp_stat_flags field */ +#define FCP_RSP_STAT_DISC 0x0001 +#define FCP_RSP_STAT_SYNC 0x0002 +#define FCP_RSP_STAT_PERR 0x0004 +#define FCP_RSP_STAT_BUS_RESET 0x0008 +#define FCP_RSP_STAT_DEV_RESET 0x0010 +#define FCP_RSP_STAT_ABORTED 0x0020 +#define FCP_RSP_STAT_TIMEOUT 0x0040 +#define FCP_RSP_STAT_NEGOTIATE 0x0080 + +typedef struct { + u8 rsp_info_type; + u8 xxx; + u16 isp_status; + u16 isp_state_flags; + u16 isp_stat_flags; +} fcp_scsi_err; + +#endif /* !(__FCP_H) */ diff --git a/drivers/fc4/fcp_scsi.h b/drivers/fc4/fcp_scsi.h new file mode 100644 index 000000000..6f8cc0257 --- /dev/null +++ b/drivers/fc4/fcp_scsi.h @@ -0,0 +1,140 @@ +/* fcp_scsi.h: Generic SCSI on top of FC4 - interface defines. + * + * Copyright (C) 1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#ifndef _FCP_SCSI_H +#define _FCP_SCSI_H + +#include <linux/types.h> +#include <linux/blk.h> +#include "../scsi/scsi.h" + +#include "fc.h" +#include "fcp.h" + +#ifdef __sparc__ + +#include <asm/sbus.h> +typedef u32 dma_handle; + +#else +#error Need to port FC layer to your architecture +#endif + +#define FC_CLASS_OUTBOUND 0x01 +#define FC_CLASS_INBOUND 0x02 +#define FC_CLASS_SIMPLE 0x03 +#define FC_CLASS_IO_WRITE 0x04 +#define FC_CLASS_IO_READ 0x05 +#define FC_CLASS_UNSOLICITED 0x06 +#define FC_CLASS_OFFLINE 0x08 + +#define PROTO_OFFLINE 0x02 + +struct _fc_channel; + +typedef struct fcp_cmnd { + struct fcp_cmnd *next; + struct fcp_cmnd *prev; + void (*done)(Scsi_Cmnd *); + int proto; + int token; + /* FCP SCSI stuff */ + dma_handle data; + /* From now on this cannot be touched for proto == TYPE_SCSI_FCP */ + fc_hdr fch; + dma_handle cmd; + dma_handle rsp; + int cmdlen; + int rsplen; + int class; + int datalen; + /* This is just used as a verification during login */ + struct _fc_channel *fc; +} fcp_cmnd; + +typedef struct _fc_channel { + struct _fc_channel *next; + int irq; + int state; + int sid; + int did; + char name[16]; + void (*fcp_register)(struct _fc_channel *, u8, int); + void (*fcp_state_change)(struct _fc_channel *, int); + void (*reset)(struct _fc_channel *); + int (*hw_enque)(struct _fc_channel *, fcp_cmnd *); + fc_wwn wwn_node; + fc_wwn wwn_nport; + fc_wwn wwn_dest; + common_svc_parm *common_svc; + svc_parm *class_svcs; +#ifdef __sparc__ + struct linux_sbus_device *dev; +#endif + /* FCP SCSI stuff */ + int can_queue; + int rsp_size; + fcp_cmd *scsi_cmd_pool; + char *scsi_rsp_pool; + dma_handle dma_scsi_cmd, dma_scsi_rsp; + long *scsi_bitmap; + long scsi_bitmap_end; + int scsi_free; + int (*encode_addr)(Scsi_Cmnd *cmnd, u16 *addr); + fcp_cmnd *scsi_que[2]; + char scsi_name[4]; + fcp_cmnd **token_tab; + int channels; + int targets; + long *ages; + /* LOGIN stuff */ + fcp_cmnd *login; + void *ls; +} fc_channel; + +extern fc_channel *fc_channels; + +#define FC_STATE_UNINITED 0 +#define FC_STATE_ONLINE 1 +#define FC_STATE_OFFLINE 2 +#define FC_STATE_RESETING 3 +#define FC_STATE_FPORT_OK 4 +#define FC_STATE_MAYBEOFFLINE 5 + +#define FC_STATUS_OK 0 +#define FC_STATUS_P_RJT 2 +#define FC_STATUS_F_RJT 3 +#define FC_STATUS_P_BSY 4 +#define FC_STATUS_F_BSY 5 +#define FC_STATUS_ERR_OFFLINE 0x11 +#define FC_STATUS_TIMEOUT 0x12 +#define FC_STATUS_ERR_OVERRUN 0x13 +#define FC_STATUS_UNKNOWN_CQ_TYPE 0x20 +#define FC_STATUS_BAD_SEG_CNT 0x21 +#define FC_STATUS_MAX_XCHG_EXCEEDED 0x22 +#define FC_STATUS_BAD_XID 0x23 +#define FC_STATUS_XCHG_BUSY 0x24 +#define FC_STATUS_BAD_POOL_ID 0x25 +#define FC_STATUS_INSUFFICIENT_CQES 0x26 +#define FC_STATUS_ALLOC_FAIL 0x27 +#define FC_STATUS_BAD_SID 0x28 +#define FC_STATUS_NO_SEQ_INIT 0x29 + +void fcp_queue_empty(fc_channel *); +int fcp_init(fc_channel *); +void fcp_receive_solicited(fc_channel *, int, int, int, fc_hdr *); + +#define for_each_fc_channel(fc) \ + for (fc = fc_channels; fc; fc = fc->next) + +#define for_each_online_fc_channel(fc) \ + for_each_fc_channel(fc) \ + if (fc->state == FC_STATE_ONLINE) + +int fcp_scsi_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *)); +int fcp_scsi_abort(Scsi_Cmnd *); +int fcp_scsi_reset(Scsi_Cmnd *, unsigned int); + +#endif /* !(_FCP_SCSI_H) */ diff --git a/drivers/fc4/soc.c b/drivers/fc4/soc.c new file mode 100644 index 000000000..ee3f513e1 --- /dev/null +++ b/drivers/fc4/soc.c @@ -0,0 +1,710 @@ +/* soc.c: Sparc SUNW,soc (Serial Optical Channel) Fibre Channel Sbus adapter support. + * + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1997 Jiri Hanika (geo@ff.cuni.cz) + * + * Sources: + * Fibre Channel Physical & Signaling Interface (FC-PH), dpANS, 1994 + * dpANS Fibre Channel Protocol for SCSI (X3.269-199X), Rev. 012, 1995 + */ + +static char *version = + "soc.c:v1.0 24/Nov/97 Jakub Jelinek (jj@sunsite.mff.cuni.cz), Jiri Hanika (geo@ff.cuni.cz)\n"; + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/init.h> +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/errno.h> +#include <asm/byteorder.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/auxio.h> +#include <asm/system.h> +#include <asm/pgtable.h> +#include <asm/irq.h> + +/* #define SOCDEBUG */ +/* #define HAVE_SOC_UCODE */ + +#include "fcp_scsi.h" +#include "soc.h" +#ifdef HAVE_SOC_UCODE +#include "soc_asm.c" +#endif + +#define soc_printk printk ("soc%d: ", s->soc_no); printk + +#ifdef SOCDEBUG +#define SOD(x) soc_printk x; +#else +#define SOD(x) +#endif + +#define for_each_soc(s) for (s = socs; s; s = s->next) +struct soc *socs = NULL; + +static inline void soc_disable(struct soc *s) +{ + s->regs->imask = 0; s->regs->cmd = SOC_CMD_SOFT_RESET; +} + +static inline void soc_enable(struct soc *s) +{ + SOD(("enable %08x\n", s->cfg)) + s->regs->sae = 0; s->regs->cfg = s->cfg; + s->regs->cmd = SOC_CMD_RSP_QALL; + SOC_SETIMASK(s, SOC_IMASK_RSP_QALL | SOC_IMASK_SAE); +} + +static void soc_reset(fc_channel *fc) +{ + soc_port *port = (soc_port *)fc; + struct soc *s = port->s; + + /* FIXME */ + soc_disable(s); + s->req[0].seqno = 1; + s->req[1].seqno = 1; + s->rsp[0].seqno = 1; + s->rsp[1].seqno = 1; + s->req[0].in = 0; + s->req[1].in = 0; + s->rsp[0].in = 0; + s->rsp[1].in = 0; + s->req[0].out = 0; + s->req[1].out = 0; + s->rsp[0].out = 0; + s->rsp[1].out = 0; + + /* FIXME */ + soc_enable(s); +} + +static void inline soc_solicited (struct soc *s) +{ + fc_hdr fchdr; + soc_rsp *hwrsp; + soc_cq *sw_cq; + int token; + int status; + fc_channel *fc; + + sw_cq = &s->rsp[SOC_SOLICITED_RSP_Q]; + + if (sw_cq->pool == NULL) + sw_cq->pool = + (soc_req *)(s->xram + (xram_get_32low ((xram_p)&sw_cq->hw_cq->address) / sizeof(u16))); + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + SOD (("soc_solicited, %d packets arrived\n", (sw_cq->in - sw_cq->out) & sw_cq->last)) + for (;;) { + hwrsp = (soc_rsp *)sw_cq->pool + sw_cq->out; + token = xram_get_32low ((xram_p)&hwrsp->shdr.token); + status = xram_get_32low ((xram_p)&hwrsp->status); + fc = (fc_channel *)(&s->port[(token >> 11) & 1]); + + if (status == SOC_OK) + fcp_receive_solicited(fc, token >> 12, token & ((1 << 11) - 1), FC_STATUS_OK, NULL); + else { + xram_copy_from(&fchdr, (xram_p)&hwrsp->fchdr, sizeof(fchdr)); + /* We have intentionally defined FC_STATUS_* constants to match SOC_* constants, otherwise + we'd have to translate status */ + fcp_receive_solicited(fc, token >> 12, token & ((1 << 11) - 1), status, &fchdr); + } + + if (++sw_cq->out > sw_cq->last) { + sw_cq->seqno++; + sw_cq->out = 0; + } + + if (sw_cq->out == sw_cq->in) { + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + if (sw_cq->out == sw_cq->in) { + /* Tell the hardware about it */ + s->regs->cmd = (sw_cq->out << 24) | (SOC_CMD_RSP_QALL & ~(SOC_CMD_RSP_Q0 << SOC_SOLICITED_RSP_Q)); + /* Read it, so that we're sure it has been updated */ + s->regs->cmd; + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + if (sw_cq->out == sw_cq->in) + break; + } + } + } +} + +static void inline soc_request (struct soc *s, u32 cmd) +{ + SOC_SETIMASK(s, s->imask & ~(cmd & SOC_CMD_REQ_QALL)); + + SOD(("Queues available %08x OUT %X %X\n", cmd, xram_get_8((xram_p)&s->req[0].hw_cq->out), xram_get_8((xram_p)&s->req[0].hw_cq->out))) + if (s->port[s->curr_port].fc.state != FC_STATE_OFFLINE) { + fcp_queue_empty ((fc_channel *)&(s->port[s->curr_port])); + if (((s->req[1].in + 1) & s->req[1].last) != (s->req[1].out)) + fcp_queue_empty ((fc_channel *)&(s->port[1 - s->curr_port])); + } else + fcp_queue_empty ((fc_channel *)&(s->port[1 - s->curr_port])); + if (s->port[1 - s->curr_port].fc.state != FC_STATE_OFFLINE) + s->curr_port ^= 1; +} + +static void inline soc_unsolicited (struct soc *s) +{ + soc_rsp *hwrsp, *hwrspc; + soc_cq *sw_cq; + int count; + int status; + int flags; + fc_channel *fc; + + sw_cq = &s->rsp[SOC_UNSOLICITED_RSP_Q]; + if (sw_cq->pool == NULL) + sw_cq->pool = + (soc_req *)(s->xram + (xram_get_32low ((xram_p)&sw_cq->hw_cq->address) / sizeof(u16))); + + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + SOD (("soc_unsolicited, %d packets arrived\n", (sw_cq->in - sw_cq->out) & sw_cq->last)) + while (sw_cq->in != sw_cq->out) { + /* ...real work per entry here... */ + hwrsp = (soc_rsp *)sw_cq->pool + sw_cq->out; + hwrspc = NULL; + flags = hwrsp->shdr.flags; + count = xram_get_8 ((xram_p)&hwrsp->count); + fc = (fc_channel *)&s->port[flags & SOC_PORT_B]; + SOD(("FC %08lx fcp_state_change %08lx\n", (long)fc, (long)fc->fcp_state_change)) + + if (count != 1) { + /* Ugh, continuation entries */ + u8 in; + + if (count != 2) { + printk("%s: Too many continuations entries %d\n", fc->name, count); + goto update_out; + } + + in = sw_cq->in; + if (in < sw_cq->out) in += sw_cq->last + 1; + if (in < sw_cq->out + 2) { + /* Ask the hardware about it if they haven't arrived yet */ + s->regs->cmd = (sw_cq->out << 24) | (SOC_CMD_RSP_QALL & ~(SOC_CMD_RSP_Q0 << SOC_UNSOLICITED_RSP_Q)); + /* Read it, so that we're sure it has been updated */ + s->regs->cmd; + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + in = sw_cq->in; + if (in < sw_cq->out) in += sw_cq->last + 1; + if (in < sw_cq->out + 2) /* Nothing came, let us wait */ + return; + } + if (sw_cq->out == sw_cq->last) + hwrspc = (soc_rsp *)sw_cq->pool; + else + hwrspc = hwrsp + 1; + } + + switch (flags & ~SOC_PORT_B) { + case SOC_STATUS: + status = xram_get_32low ((xram_p)&hwrsp->status); + switch (status) { + case SOC_ONLINE: + fc->fcp_state_change(fc, FC_STATE_ONLINE); + break; + case SOC_OFFLINE: + fc->fcp_state_change(fc, FC_STATE_OFFLINE); + break; + default: + printk ("%s: Unknown STATUS no %d\n", fc->name, status); + break; + } + break; + case (SOC_UNSOLICITED|SOC_FC_HDR): + { + int r_ctl = xram_get_8 ((xram_p)&hwrsp->fchdr); + unsigned len; + char buf[64]; + + if ((r_ctl & 0xf0) == R_CTL_EXTENDED_SVC) { + len = xram_get_32 ((xram_p)&hwrsp->shdr.bytecnt); + if (len < 4 || !hwrspc) + printk ("%s: Invalid R_CTL %02x continuation entries\n", fc->name, r_ctl); + else { + if (len > 60) len = 60; + xram_copy_from (buf, (xram_p)hwrspc, (len + 3) & ~3); + if (*(u32 *)buf == LS_DISPLAY) { + int i; + + for (i = 4; i < len; i++) + if (buf[i] == '\n') buf[i] = ' '; + buf[len] = 0; + printk ("%s message: %s\n", fc->name, buf + 4); + } else + printk ("%s: Unknown LS_CMD %02x\n", fc->name, buf[0]); + } + } else + printk ("%s: Unsolicited R_CTL %02x not handled\n", fc->name, r_ctl); + } + break; + default: + printk ("%s: Unexpected flags %08x\n", fc->name, flags); + break; + } +update_out: + if (++sw_cq->out > sw_cq->last) { + sw_cq->seqno++; + sw_cq->out = 0; + } + + if (hwrspc) { + if (++sw_cq->out > sw_cq->last) { + sw_cq->seqno++; + sw_cq->out = 0; + } + } + + if (sw_cq->out == sw_cq->in) { + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + if (sw_cq->out == sw_cq->in) { + /* Tell the hardware about it */ + s->regs->cmd = (sw_cq->out << 24) | (SOC_CMD_RSP_QALL & ~(SOC_CMD_RSP_Q0 << SOC_UNSOLICITED_RSP_Q)); + /* Read it, so that we're sure it has been updated */ + s->regs->cmd; + sw_cq->in = xram_get_8 ((xram_p)&sw_cq->hw_cq->in); + } + } + } +} + +static void soc_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 cmd; + register struct soc *s = (struct soc *)dev_id; + + cmd = s->regs->cmd; + for (; (cmd = SOC_INTR (s, cmd)); cmd = s->regs->cmd) { + if (cmd & SOC_CMD_RSP_Q1) soc_unsolicited (s); + if (cmd & SOC_CMD_RSP_Q0) soc_solicited (s); + if (cmd & SOC_CMD_REQ_QALL) soc_request (s, cmd); + } +} + +#define TOKEN(proto, port, token) (((proto)<<12)|(token)|(port)) + +static int soc_hw_enque (fc_channel *fc, fcp_cmnd *fcmd) +{ + soc_port *port = (soc_port *)fc; + struct soc *s = port->s; + int qno; + soc_cq *sw_cq; + int cq_next_in; + soc_req *request; + fc_hdr *fch; + int i; + + if (fcmd->proto == TYPE_SCSI_FCP) + qno = 1; + else + qno = 0; + SOD(("Putting a FCP packet type %d into hw queue %d\n", fcmd->proto, qno)) + if (s->imask & (SOC_IMASK_REQ_Q0 << qno)) + return -EIO; + sw_cq = s->req + qno; + cq_next_in = (sw_cq->in + 1) & sw_cq->last; + + if (cq_next_in == sw_cq->out + && cq_next_in == (sw_cq->out = xram_get_8((xram_p)&sw_cq->hw_cq->out))) { + SOD(("%d IN %d OUT %d LAST %d\n", qno, sw_cq->in, sw_cq->out, sw_cq->last)) + SOC_SETIMASK(s, s->imask | (SOC_IMASK_REQ_Q0 << qno)); + return -EBUSY; // If queue full, just say NO + } + + request = sw_cq->pool + sw_cq->in; + fch = &request->fchdr; + + switch (fcmd->proto) { + case TYPE_SCSI_FCP: + request->shdr.token = TOKEN(TYPE_SCSI_FCP, port->mask, fcmd->token); + request->data[0].base = fc->dma_scsi_cmd + fcmd->token * sizeof(fcp_cmd); + request->data[0].count = sizeof(fcp_cmd); + request->data[1].base = fc->dma_scsi_rsp + fcmd->token * fc->rsp_size; + request->data[1].count = fc->rsp_size; + if (fcmd->data) { + request->shdr.segcnt = 3; + i = fc->scsi_cmd_pool[fcmd->token].fcp_data_len; + request->shdr.bytecnt = i; + request->data[2].base = fcmd->data; + request->data[2].count = i; + request->type = (fc->scsi_cmd_pool[fcmd->token].fcp_cntl & FCP_CNTL_WRITE) ? + SOC_CQTYPE_IO_WRITE : SOC_CQTYPE_IO_READ; + } else { + request->shdr.segcnt = 2; + request->shdr.bytecnt = 0; + request->data[2].base = 0; + request->data[2].count = 0; + request->type = SOC_CQTYPE_SIMPLE; + } + FILL_FCHDR_RCTL_DID(fch, R_CTL_COMMAND, fc->did); + FILL_FCHDR_SID(fch, fc->sid); + FILL_FCHDR_TYPE_FCTL(fch, TYPE_SCSI_FCP, F_CTL_FIRST_SEQ | F_CTL_SEQ_INITIATIVE); + FILL_FCHDR_SEQ_DF_SEQ(fch, 0, 0, 0); + FILL_FCHDR_OXRX(fch, 0xffff, 0xffff); + fch->param = 0; + request->shdr.flags = port->flags; + request->shdr.class = 2; + break; + + case PROTO_OFFLINE: + memset (request, 0, sizeof(*request)); + request->shdr.token = TOKEN(PROTO_OFFLINE, port->mask, fcmd->token); + request->type = SOC_CQTYPE_OFFLINE; + FILL_FCHDR_RCTL_DID(fch, R_CTL_COMMAND, fc->did); + FILL_FCHDR_SID(fch, fc->sid); + FILL_FCHDR_TYPE_FCTL(fch, TYPE_SCSI_FCP, F_CTL_FIRST_SEQ | F_CTL_SEQ_INITIATIVE); + FILL_FCHDR_SEQ_DF_SEQ(fch, 0, 0, 0); + FILL_FCHDR_OXRX(fch, 0xffff, 0xffff); + request->shdr.flags = port->flags; + break; + + default: + request->shdr.token = TOKEN(fcmd->proto, port->mask, fcmd->token); + request->shdr.class = 2; + request->shdr.flags = port->flags; + memcpy (fch, &fcmd->fch, sizeof(fc_hdr)); + request->data[0].count = fcmd->cmdlen; + request->data[1].count = fcmd->rsplen; + request->type = fcmd->class; + switch (fcmd->class) { + case FC_CLASS_OUTBOUND: + request->data[0].base = fcmd->cmd; + request->data[0].count = fcmd->cmdlen; + request->type = SOC_CQTYPE_OUTBOUND; + request->shdr.bytecnt = fcmd->cmdlen; + request->shdr.segcnt = 1; + break; + case FC_CLASS_INBOUND: + request->data[0].base = fcmd->rsp; + request->data[0].count = fcmd->rsplen; + request->type = SOC_CQTYPE_INBOUND; + request->shdr.bytecnt = 0; + request->shdr.segcnt = 1; + break; + case FC_CLASS_SIMPLE: + request->data[0].base = fcmd->cmd; + request->data[1].base = fcmd->rsp; + request->data[0].count = fcmd->cmdlen; + request->data[1].count = fcmd->rsplen; + request->type = SOC_CQTYPE_SIMPLE; + request->shdr.bytecnt = fcmd->cmdlen; + request->shdr.segcnt = 2; + break; + case FC_CLASS_IO_READ: + case FC_CLASS_IO_WRITE: + request->data[0].base = fcmd->cmd; + request->data[1].base = fcmd->rsp; + request->data[0].count = fcmd->cmdlen; + request->data[1].count = fcmd->rsplen; + request->type = (fcmd->class == FC_CLASS_IO_READ) ? SOC_CQTYPE_IO_READ : SOC_CQTYPE_IO_WRITE; + if (fcmd->data) { + request->data[2].base = fcmd->data; + request->data[2].count = fcmd->datalen; + request->shdr.bytecnt = fcmd->datalen; + request->shdr.segcnt = 3; + } else { + request->shdr.bytecnt = 0; + request->shdr.segcnt = 2; + } + break; + } + break; + } + + request->count = 1; + request->flags = 0; + request->seqno = sw_cq->seqno; + + /* And now tell the SOC about it */ + + if (++sw_cq->in > sw_cq->last) { + sw_cq->in = 0; + sw_cq->seqno++; + } + + SOD(("Putting %08x into cmd\n", SOC_CMD_RSP_QALL | (sw_cq->in << 24) | (SOC_CMD_REQ_Q0 << qno))) + + s->regs->cmd = SOC_CMD_RSP_QALL | (sw_cq->in << 24) | (SOC_CMD_REQ_Q0 << qno); + /* Read so that command is completed */ + s->regs->cmd; + + return 0; +} + +static inline void soc_download_fw(struct soc *s) +{ +#ifdef HAVE_SOC_UCODE + xram_copy_to (s->xram, soc_ucode, sizeof(soc_ucode)); + xram_bzero (s->xram + (sizeof(soc_ucode)/sizeof(u16)), 32768 - sizeof(soc_ucode)); +#endif +} + +/* Check for what the best SBUS burst we can use happens + * to be on this machine. + */ +static inline void soc_init_bursts(struct soc *s, struct linux_sbus_device *sdev) +{ + int bsizes, bsizes_more; + + bsizes = (prom_getintdefault(sdev->prom_node,"burst-sizes",0xff) & 0xff); + bsizes_more = (prom_getintdefault(sdev->my_bus->prom_node, "burst-sizes", 0xff) & 0xff); + bsizes &= bsizes_more; + if ((bsizes & 0x7f) == 0x7f) + s->cfg = SOC_CFG_BURST_64; + else if ((bsizes & 0x3f) == 0x3f) + s->cfg = SOC_CFG_BURST_32; + else if ((bsizes & 0x1f) == 0x1f) + s->cfg = SOC_CFG_BURST_16; + else + s->cfg = SOC_CFG_BURST_4; +} + +static inline void soc_init(struct linux_sbus_device *sdev, int no) +{ +#ifdef __sparc_v9__ + struct devid_cookie dcookie; +#endif + unsigned char tmp[60]; + int propl; + struct soc *s; + static unsigned version_printed = 0; + soc_hw_cq cq[8]; + int size, i; + int irq; + + s = kmalloc (sizeof (struct soc), GFP_KERNEL); + if (!s) return; + memset (s, 0, sizeof(struct soc)); + s->soc_no = no; + + SOD(("socs %08lx soc_intr %08lx soc_hw_enque %08x\n", (long)socs, (long)soc_intr, (long)soc_hw_enque)) + if (version_printed++ == 0) + printk (version); + + s->next = socs; + socs = s; + s->port[0].fc.dev = sdev; + s->port[1].fc.dev = sdev; + s->port[0].s = s; + s->port[1].s = s; + + s->port[0].fc.next = &s->port[1].fc; + + /* World Wide Name of SOC */ + propl = prom_getproperty (sdev->prom_node, "soc-wwn", tmp, sizeof(tmp)); + if (propl != sizeof (fc_wwn)) { + s->wwn.naaid = NAAID_IEEE; + s->wwn.lo = 0x12345678; + } else + memcpy (&s->wwn, tmp, sizeof (fc_wwn)); + + propl = prom_getproperty (sdev->prom_node, "port-wwns", tmp, sizeof(tmp)); + if (propl != 2 * sizeof (fc_wwn)) { + s->port[0].fc.wwn_nport.naaid = NAAID_IEEE_EXT; + s->port[0].fc.wwn_nport.hi = s->wwn.hi; + s->port[0].fc.wwn_nport.lo = s->wwn.lo; + s->port[1].fc.wwn_nport.naaid = NAAID_IEEE_EXT; + s->port[1].fc.wwn_nport.nportid = 1; + s->port[1].fc.wwn_nport.hi = s->wwn.hi; + s->port[1].fc.wwn_nport.lo = s->wwn.lo; + } else { + memcpy (&s->port[0].fc.wwn_nport, tmp, sizeof (fc_wwn)); + memcpy (&s->port[1].fc.wwn_nport, tmp + sizeof (fc_wwn), sizeof (fc_wwn)); + } + memcpy (&s->port[0].fc.wwn_node, &s->wwn, sizeof (fc_wwn)); + memcpy (&s->port[1].fc.wwn_node, &s->wwn, sizeof (fc_wwn)); + SOD(("Got wwns %08x%08x ports %08x%08x and %08x%08x\n", + *(u32 *)&s->port[0].fc.wwn_nport, s->port[0].fc.wwn_nport.lo, + *(u32 *)&s->port[0].fc.wwn_nport, s->port[0].fc.wwn_nport.lo, + *(u32 *)&s->port[1].fc.wwn_nport, s->port[1].fc.wwn_nport.lo)) + + s->port[0].fc.sid = 1; + s->port[1].fc.sid = 17; + s->port[0].fc.did = 2; + s->port[1].fc.did = 18; + + s->port[0].fc.reset = soc_reset; + s->port[1].fc.reset = soc_reset; + + /* Setup the reg property for this device. */ + prom_apply_sbus_ranges(sdev->my_bus, sdev->reg_addrs, sdev->num_registers, sdev); + + if (sdev->num_registers == 1) { + /* Probably SunFire onboard SOC */ + s->xram = (xram_p) + sparc_alloc_io (sdev->reg_addrs [0].phys_addr, 0, + sdev->reg_addrs [0].reg_size, "soc_xram", + sdev->reg_addrs [0].which_io, 0); + s->regs = (struct soc_regs *)((char *)s->xram + 0x10000); + } else { + /* Probably SOC sbus card */ + s->xram = (xram_p) + sparc_alloc_io (sdev->reg_addrs [1].phys_addr, 0, + sdev->reg_addrs [1].reg_size, "soc_xram", + sdev->reg_addrs [1].which_io, 0); + s->regs = (struct soc_regs *) + sparc_alloc_io (sdev->reg_addrs [2].phys_addr, 0, + sdev->reg_addrs [2].reg_size, "soc_regs", + sdev->reg_addrs [2].which_io, 0); + } + + soc_init_bursts(s, sdev); + + SOD(("Disabling SOC\n")) + + soc_disable (s); + + irq = sdev->irqs[0].pri; + +#ifndef __sparc_v9__ + if (request_irq (irq, soc_intr, SA_SHIRQ, "SOC", (void *)s)) { + soc_printk ("Cannot order irq %d to go\n", irq); + return; + } +#else + dcookie.real_dev_id = s; + dcookie.imap = dcookie.iclr = 0; + dcookie.pil = -1; + dcookie.bus_cookie = sdev->my_bus; + if (request_irq (irq, soc_intr, (SA_SHIRQ | SA_SBUS | SA_DCOOKIE), "SOC", &dcookie)) { + soc_printk ("Cannot order irq %d to go\n", irq); + return; + } + irq = dcookie.ret_ino; +#endif + + SOD(("SOC uses IRQ%d\n", irq)) + + s->port[0].fc.irq = irq; + s->port[1].fc.irq = irq; + + sprintf (s->port[0].fc.name, "soc%d port A", no); + sprintf (s->port[1].fc.name, "soc%d port B", no); + s->port[0].flags = SOC_FC_HDR | SOC_PORT_A; + s->port[1].flags = SOC_FC_HDR | SOC_PORT_B; + s->port[1].mask = (1 << 11); + + s->port[0].fc.hw_enque = soc_hw_enque; + s->port[1].fc.hw_enque = soc_hw_enque; + + soc_download_fw (s); + + SOD(("Downloaded firmware\n")) + + /* Now setup xram circular queues */ + memset (cq, 0, sizeof(cq)); + + size = (SOC_CQ_REQ0_SIZE + SOC_CQ_REQ1_SIZE) * sizeof(soc_req); + s->req[0].pool = (soc_req *) sparc_dvma_malloc (size, "SOC request queues", &cq[0].address); + s->req[1].pool = s->req[0].pool + SOC_CQ_REQ0_SIZE; + + s->req[0].hw_cq = (soc_hw_cq *)(s->xram + SOC_CQ_REQ_OFFSET); + s->req[1].hw_cq = (soc_hw_cq *)(s->xram + SOC_CQ_REQ_OFFSET + sizeof(soc_hw_cq) / sizeof(u16)); + s->rsp[0].hw_cq = (soc_hw_cq *)(s->xram + SOC_CQ_RSP_OFFSET); + s->rsp[1].hw_cq = (soc_hw_cq *)(s->xram + SOC_CQ_RSP_OFFSET + sizeof(soc_hw_cq) / sizeof(u16)); + + cq[1].address = cq[0].address + (SOC_CQ_REQ0_SIZE * sizeof(soc_req)); + cq[4].address = 1; + cq[5].address = 1; + cq[0].last = SOC_CQ_REQ0_SIZE - 1; + cq[1].last = SOC_CQ_REQ1_SIZE - 1; + cq[4].last = SOC_CQ_RSP0_SIZE - 1; + cq[5].last = SOC_CQ_RSP1_SIZE - 1; + for (i = 0; i < 8; i++) + cq[i].seqno = 1; + + s->req[0].last = SOC_CQ_REQ0_SIZE - 1; + s->req[1].last = SOC_CQ_REQ1_SIZE - 1; + s->rsp[0].last = SOC_CQ_RSP0_SIZE - 1; + s->rsp[1].last = SOC_CQ_RSP1_SIZE - 1; + + s->req[0].seqno = 1; + s->req[1].seqno = 1; + s->rsp[0].seqno = 1; + s->rsp[1].seqno = 1; + + xram_copy_to (s->xram + SOC_CQ_REQ_OFFSET, cq, sizeof(cq)); + + /* Make our sw copy of SOC service parameters */ + xram_copy_from (s->serv_params, s->xram + 0x140, sizeof (s->serv_params)); + + s->port[0].fc.common_svc = (common_svc_parm *)s->serv_params; + s->port[0].fc.class_svcs = (svc_parm *)(s->serv_params + 0x20); + s->port[1].fc.common_svc = (common_svc_parm *)&s->serv_params; + s->port[1].fc.class_svcs = (svc_parm *)(s->serv_params + 0x20); + + soc_enable (s); + + SOD(("Enabled SOC\n")) +} + +#ifndef MODULE +__initfunc(int soc_probe(void)) +#else +int init_module(void) +#endif +{ + struct linux_sbus *bus; + struct linux_sbus_device *sdev = 0; + struct soc *s; + int cards = 0; + + for_each_sbus(bus) { + for_each_sbusdev(sdev, bus) { + if(!strcmp(sdev->prom_name, "SUNW,soc")) { + soc_init(sdev, cards); + cards++; + } + } + } + if (!cards) return -EIO; + + for_each_soc(s) + if (s->next) + s->port[1].fc.next = &s->next->port[0].fc; + fcp_init (&socs->port[0].fc); + return 0; +} + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE +void cleanup_module(void) +{ + struct soc *s; + int irq; + struct linux_sbus_device *sdev; + + for_each_soc(s) { + /* FIXME: Tell FC layer we say good bye */ + irq = s->port[0].fc.irq; + disable_irq (irq); + free_irq (irq, s); + sdev = s->port[0].fc.dev; + if (sdev->num_registers == 1) + sparc_free_io ((char *)s->xram, sdev->reg_addrs [0].reg_size); + else { + sparc_free_io ((char *)s->xram, sdev->reg_addrs [1].reg_size); + sparc_free_io ((char *)s->regs, sdev->reg_addrs [2].reg_size); + } + /* FIXME: sparc_dvma_free() ??? */ + } +} +#endif diff --git a/drivers/fc4/soc.h b/drivers/fc4/soc.h new file mode 100644 index 000000000..92435d3af --- /dev/null +++ b/drivers/fc4/soc.h @@ -0,0 +1,275 @@ +/* soc.h: Definitions for Sparc SUNW,soc Fibre Channel Sbus driver. + * + * Copyright (C) 1996,1997 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + */ + +#ifndef __SOC_H +#define __SOC_H + +#include "fc.h" +#include "fcp.h" +#include "fcp_scsi.h" + +/* Hardware structures and constants first {{{ */ + +struct soc_regs { + volatile u32 cfg; /* Config Register */ + volatile u32 sae; /* Slave Access Error Register */ + volatile u32 cmd; /* Command & Status Register */ + volatile u32 imask; /* Interrupt Mask Register */ +}; + +/* Config Register */ +#define SOC_CFG_EXT_RAM_BANK_MASK 0x07000000 +#define SOC_CFG_EEPROM_BANK_MASK 0x00030000 +#define SOC_CFG_BURST64_MASK 0x00000700 +#define SOC_CFG_SBUS_PARITY_TEST 0x00000020 +#define SOC_CFG_SBUS_PARITY_CHECK 0x00000010 +#define SOC_CFG_SBUS_ENHANCED 0x00000008 +#define SOC_CFG_BURST_MASK 0x00000007 +/* Bursts */ +#define SOC_CFG_BURST_4 0x00000000 +#define SOC_CFG_BURST_16 0x00000004 +#define SOC_CFG_BURST_32 0x00000005 +#define SOC_CFG_BURST_64 0x00000006 + +/* Slave Access Error Register */ +#define SOC_SAE_ALIGNMENT 0x00000004 +#define SOC_SAE_UNSUPPORTED 0x00000002 +#define SOC_SAE_PARITY 0x00000001 + +/* Command & Status Register */ +#define SOC_CMD_RSP_QALL 0x000f0000 +#define SOC_CMD_RSP_Q0 0x00010000 +#define SOC_CMD_RSP_Q1 0x00020000 +#define SOC_CMD_RSP_Q2 0x00040000 +#define SOC_CMD_RSP_Q3 0x00080000 +#define SOC_CMD_REQ_QALL 0x00000f00 +#define SOC_CMD_REQ_Q0 0x00000100 +#define SOC_CMD_REQ_Q1 0x00000200 +#define SOC_CMD_REQ_Q2 0x00000400 +#define SOC_CMD_REQ_Q3 0x00000800 +#define SOC_CMD_SAE 0x00000080 +#define SOC_CMD_INTR_PENDING 0x00000008 +#define SOC_CMD_NON_QUEUED 0x00000004 +#define SOC_CMD_IDLE 0x00000002 +#define SOC_CMD_SOFT_RESET 0x00000001 + +/* Interrupt Mask Register */ +#define SOC_IMASK_RSP_QALL 0x000f0000 +#define SOC_IMASK_RSP_Q0 0x00010000 +#define SOC_IMASK_RSP_Q1 0x00020000 +#define SOC_IMASK_RSP_Q2 0x00040000 +#define SOC_IMASK_RSP_Q3 0x00080000 +#define SOC_IMASK_REQ_QALL 0x00000f00 +#define SOC_IMASK_REQ_Q0 0x00000100 +#define SOC_IMASK_REQ_Q1 0x00000200 +#define SOC_IMASK_REQ_Q2 0x00000400 +#define SOC_IMASK_REQ_Q3 0x00000800 +#define SOC_IMASK_SAE 0x00000080 +#define SOC_IMASK_NON_QUEUED 0x00000004 + +#define SOC_INTR(s, cmd) \ + (((cmd & SOC_CMD_RSP_QALL) | ((~cmd) & SOC_CMD_REQ_QALL)) \ + & s->imask) + +#define SOC_SETIMASK(s, i) \ + (s)->imask = (i); (s)->regs->imask = (i) + +/* XRAM + * + * This is a 64KB register area. It accepts only halfword access. + * That's why here are the following inline functions... + */ + +typedef u16 *xram_p; + +/* Get 32bit number from XRAM */ +static inline u32 xram_get_32 (xram_p x) +{ + return (((u32)*x) << 16) | (x[1]); +} + +/* Like the above, but when we don't care about the high 16 bits */ +static inline u32 xram_get_32low (xram_p x) +{ + return (u32)x[1]; +} + +static inline u8 xram_get_8 (xram_p x) +{ + if (((long)x) & 1) { + x = (xram_p)((long)x - 1); + return (u8)*x; + } else + return (u8)(*x >> 8); +} + +static inline void xram_copy_from (void *p, xram_p x, int len) +{ + for (len >>= 2; len > 0; len--, x += 2) { + *((u32 *)p)++ = (((u32)(*x)) << 16) | (x[1]); + } +} + +static inline void xram_copy_to (xram_p x, void *p, int len) +{ + register u32 tmp; + for (len >>= 2; len > 0; len--, x += 2) { + tmp = *((u32 *)p)++; + *x = tmp >> 16; + x[1] = tmp; + } +} + +static inline void xram_bzero (xram_p x, int len) +{ + for (len >>= 1; len > 0; len--) *x++ = 0; +} + +/* Circular Queue */ + +/* These two are in sizeof(u16) units */ +#define SOC_CQ_REQ_OFFSET 0x100 +#define SOC_CQ_RSP_OFFSET 0x110 + +typedef struct { + u32 address; + u8 in; + u8 out; + u8 last; + u8 seqno; +} soc_hw_cq; + +#define SOC_PORT_A 0x0000 /* From/To Port A */ +#define SOC_PORT_B 0x0001 /* From/To Port A */ +#define SOC_FC_HDR 0x0002 /* Contains FC Header */ +#define SOC_NORSP 0x0004 /* Don't generate response nor interrupt */ +#define SOC_NOINT 0x0008 /* Generate response but not interrupt */ +#define SOC_XFERRDY 0x0010 /* Generate XFERRDY */ +#define SOC_IGNOREPARAM 0x0020 /* Ignore PARAM field in the FC header */ +#define SOC_COMPLETE 0x0040 /* Command completed */ +#define SOC_UNSOLICITED 0x0080 /* For request this is the packet to establish unsolicited pools, */ + /* for rsp this is unsolicited packet */ +#define SOC_STATUS 0x0100 /* State change (on/off line) */ + +typedef struct { + u32 token; + u16 flags; + u8 class; + u8 segcnt; + u32 bytecnt; +} soc_hdr; + +typedef struct { + u32 base; + u32 count; +} soc_data; + +#define SOC_CQTYPE_OUTBOUND 0x01 +#define SOC_CQTYPE_INBOUND 0x02 +#define SOC_CQTYPE_SIMPLE 0x03 +#define SOC_CQTYPE_IO_WRITE 0x04 +#define SOC_CQTYPE_IO_READ 0x05 +#define SOC_CQTYPE_UNSOLICITED 0x06 +#define SOC_CQTYPE_DIAG 0x07 +#define SOC_CQTYPE_OFFLINE 0x08 +#define SOC_CQTYPE_RESPONSE 0x10 +#define SOC_CQTYPE_INLINE 0x20 + +#define SOC_CQFLAGS_CONT 0x01 +#define SOC_CQFLAGS_FULL 0x02 +#define SOC_CQFLAGS_BADHDR 0x04 +#define SOC_CQFLAGS_BADPKT 0x08 + +typedef struct { + soc_hdr shdr; + soc_data data[3]; + fc_hdr fchdr; + u8 count; + u8 type; + u8 flags; + u8 seqno; +} soc_req; + +#define SOC_OK 0 +#define SOC_P_RJT 2 +#define SOC_F_RJT 3 +#define SOC_P_BSY 4 +#define SOC_F_BSY 5 +#define SOC_ONLINE 0x10 +#define SOC_OFFLINE 0x11 +#define SOC_TIMEOUT 0x12 +#define SOC_OVERRUN 0x13 +#define SOC_UNKOWN_CQ_TYPE 0x20 +#define SOC_BAD_SEG_CNT 0x21 +#define SOC_MAX_XCHG_EXCEEDED 0x22 +#define SOC_BAD_XID 0x23 +#define SOC_XCHG_BUSY 0x24 +#define SOC_BAD_POOL_ID 0x25 +#define SOC_INSUFFICIENT_CQES 0x26 +#define SOC_ALLOC_FAIL 0x27 +#define SOC_BAD_SID 0x28 +#define SOC_NO_SEG_INIT 0x29 + +typedef struct { + soc_hdr shdr; + u32 status; + soc_data data; + u8 xxx1[12]; + fc_hdr fchdr; + u8 count; + u8 type; + u8 flags; + u8 seqno; +} soc_rsp; + +/* }}} */ + +/* Now our software structures and constants we use to drive the beast {{{ */ + +#define SOC_CQ_REQ0_SIZE 4 +#define SOC_CQ_REQ1_SIZE 64 +#define SOC_CQ_RSP0_SIZE 8 +#define SOC_CQ_RSP1_SIZE 4 + +#define SOC_SOLICITED_RSP_Q 0 +#define SOC_UNSOLICITED_RSP_Q 1 + +struct soc; + +typedef struct { + /* This must come first */ + fc_channel fc; + struct soc *s; + u16 flags; + u16 mask; +} soc_port; + +typedef struct { + soc_hw_cq *hw_cq; /* Related XRAM cq */ + soc_req *pool; + u8 in; + u8 out; + u8 last; + u8 seqno; +} soc_cq; + +struct soc { + soc_port port[2]; /* Every SOC has one or two FC ports */ + soc_cq req[2]; /* Request CQs */ + soc_cq rsp[2]; /* Response CQs */ + int soc_no; + struct soc_regs *regs; + xram_p xram; + fc_wwn wwn; + u32 imask; /* Our copy of regs->imask */ + u32 cfg; /* Our copy of regs->cfg */ + char serv_params[80]; + struct soc *next; + int curr_port; /* Which port will have priority to fcp_queue_empty */ +}; + +/* }}} */ + +#endif /* !(__SOC_H) */ |