/****************************************************************************** ** High Performance device driver for the Symbios 53C896 controller. ** ** Copyright (C) 1998-2001 Gerard Roudier ** ** This driver also supports all the Symbios 53C8XX controller family, ** except 53C810 revisions < 16, 53C825 revisions < 16 and all ** revisions of 53C815 controllers. ** ** This driver is based on the Linux port of the FreeBSD ncr driver. ** ** Copyright (C) 1994 Wolfgang Stanglmeier ** **----------------------------------------------------------------------------- ** ** 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., 675 Mass Ave, Cambridge, MA 02139, USA. ** **----------------------------------------------------------------------------- ** ** The Linux port of the FreeBSD ncr driver has been achieved in ** november 1995 by: ** ** Gerard Roudier ** ** Being given that this driver originates from the FreeBSD version, and ** in order to keep synergy on both, any suggested enhancements and corrections ** received on Linux are automatically a potential candidate for the FreeBSD ** version. ** ** The original driver has been written for 386bsd and FreeBSD by ** Wolfgang Stanglmeier ** Stefan Esser ** **----------------------------------------------------------------------------- ** ** Major contributions: ** -------------------- ** ** NVRAM detection and reading. ** Copyright (C) 1997 Richard Waltham ** ******************************************************************************* */ /* ** This file contains definitions and code that the ** sym53c8xx and ncr53c8xx drivers should share. ** The sharing will be achieved in a further version ** of the driver bundle. For now, only the ncr53c8xx ** driver includes this file. */ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define MAX(a,b) (((a) > (b)) ? (a) : (b)) /*========================================================== ** ** Hmmm... What complex some PCI-HOST bridges actually ** are, despite the fact that the PCI specifications ** are looking so smart and simple! ;-) ** **========================================================== */ #define SCSI_NCR_DYNAMIC_DMA_MAPPING /*========================================================== ** ** Miscallaneous defines. ** **========================================================== */ #define u_char unsigned char #define u_short unsigned short #define u_int unsigned int #define u_long unsigned long #ifndef bcmp #define bcmp(s, d, n) memcmp((d), (s), (n)) #endif #ifndef bzero #define bzero(d, n) memset((d), 0, (n)) #endif #ifndef offsetof #define offsetof(t, m) ((size_t) (&((t *)0)->m)) #endif /*========================================================== ** ** assert () ** **========================================================== ** ** modified copy from 386bsd:/usr/include/sys/assert.h ** **---------------------------------------------------------- */ #define assert(expression) { \ if (!(expression)) { \ (void)panic( \ "assertion \"%s\" failed: file \"%s\", line %d\n", \ #expression, \ __FILE__, __LINE__); \ } \ } /*========================================================== ** ** Debugging tags ** **========================================================== */ #define DEBUG_ALLOC (0x0001) #define DEBUG_PHASE (0x0002) #define DEBUG_QUEUE (0x0008) #define DEBUG_RESULT (0x0010) #define DEBUG_POINTER (0x0020) #define DEBUG_SCRIPT (0x0040) #define DEBUG_TINY (0x0080) #define DEBUG_TIMING (0x0100) #define DEBUG_NEGO (0x0200) #define DEBUG_TAGS (0x0400) #define DEBUG_SCATTER (0x0800) #define DEBUG_IC (0x1000) /* ** Enable/Disable debug messages. ** Can be changed at runtime too. */ #ifdef SCSI_NCR_DEBUG_INFO_SUPPORT static int ncr_debug = SCSI_NCR_DEBUG_FLAGS; #define DEBUG_FLAGS ncr_debug #else #define DEBUG_FLAGS SCSI_NCR_DEBUG_FLAGS #endif /*========================================================== ** ** A la VMS/CAM-3 queue management. ** Implemented from linux list management. ** **========================================================== */ typedef struct xpt_quehead { struct xpt_quehead *flink; /* Forward pointer */ struct xpt_quehead *blink; /* Backward pointer */ } XPT_QUEHEAD; #define xpt_que_init(ptr) do { \ (ptr)->flink = (ptr); (ptr)->blink = (ptr); \ } while (0) static inline void __xpt_que_add(struct xpt_quehead * new, struct xpt_quehead * blink, struct xpt_quehead * flink) { flink->blink = new; new->flink = flink; new->blink = blink; blink->flink = new; } static inline void __xpt_que_del(struct xpt_quehead * blink, struct xpt_quehead * flink) { flink->blink = blink; blink->flink = flink; } static inline int xpt_que_empty(struct xpt_quehead *head) { return head->flink == head; } static inline void xpt_que_splice(struct xpt_quehead *list, struct xpt_quehead *head) { struct xpt_quehead *first = list->flink; if (first != list) { struct xpt_quehead *last = list->blink; struct xpt_quehead *at = head->flink; first->blink = head; head->flink = first; last->flink = at; at->blink = last; } } #define xpt_que_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) #define xpt_insque(new, pos) __xpt_que_add(new, pos, (pos)->flink) #define xpt_remque(el) __xpt_que_del((el)->blink, (el)->flink) #define xpt_insque_head(new, head) __xpt_que_add(new, head, (head)->flink) static inline struct xpt_quehead *xpt_remque_head(struct xpt_quehead *head) { struct xpt_quehead *elem = head->flink; if (elem != head) __xpt_que_del(head, elem->flink); else elem = 0; return elem; } #define xpt_insque_tail(new, head) __xpt_que_add(new, (head)->blink, head) static inline struct xpt_quehead *xpt_remque_tail(struct xpt_quehead *head) { struct xpt_quehead *elem = head->blink; if (elem != head) __xpt_que_del(elem->blink, head); else elem = 0; return elem; } /*========================================================== ** ** SMP threading. ** ** Assuming that SMP systems are generally high end ** systems and may use several SCSI adapters, we are ** using one lock per controller instead of some global ** one. For the moment (linux-2.1.95), driver's entry ** points are called with the 'io_request_lock' lock ** held, so: ** - We are uselessly loosing a couple of micro-seconds ** to lock the controller data structure. ** - But the driver is not broken by design for SMP and ** so can be more resistant to bugs or bad changes in ** the IO sub-system code. ** - A small advantage could be that the interrupt code ** is grained as wished (e.g.: by controller). ** **========================================================== */ spinlock_t DRIVER_SMP_LOCK = SPIN_LOCK_UNLOCKED; #define NCR_LOCK_DRIVER(flags) spin_lock_irqsave(&DRIVER_SMP_LOCK, flags) #define NCR_UNLOCK_DRIVER(flags) \ spin_unlock_irqrestore(&DRIVER_SMP_LOCK, flags) #define NCR_INIT_LOCK_NCB(np) spin_lock_init(&np->smp_lock) #define NCR_LOCK_NCB(np, flags) spin_lock_irqsave(&np->smp_lock, flags) #define NCR_UNLOCK_NCB(np, flags) spin_unlock_irqrestore(&np->smp_lock, flags) #define NCR_LOCK_SCSI_DONE(host, flags) \ spin_lock_irqsave((host)->host_lock, flags) #define NCR_UNLOCK_SCSI_DONE(host, flags) \ spin_unlock_irqrestore(((host)->host_lock), flags) /*========================================================== ** ** Memory mapped IO ** ** Since linux-2.1, we must use ioremap() to map the io ** memory space and iounmap() to unmap it. This allows ** portability. Linux 1.3.X and 2.0.X allow to remap ** physical pages addresses greater than the highest ** physical memory address to kernel virtual pages with ** vremap() / vfree(). That was not portable but worked ** with i386 architecture. ** **========================================================== */ #ifdef __sparc__ #include #endif #define memcpy_to_pci(a, b, c) memcpy_toio((a), (b), (c)) /*========================================================== ** ** Insert a delay in micro-seconds and milli-seconds. ** ** Under Linux, udelay() is restricted to delay < ** 1 milli-second. In fact, it generally works for up ** to 1 second delay. Since 2.1.105, the mdelay() function ** is provided for delays in milli-seconds. ** Under 2.0 kernels, udelay() is an inline function ** that is very inaccurate on Pentium processors. ** **========================================================== */ #define UDELAY udelay #define MDELAY mdelay /*========================================================== ** ** Simple power of two buddy-like allocator. ** ** This simple code is not intended to be fast, but to ** provide power of 2 aligned memory allocations. ** Since the SCRIPTS processor only supplies 8 bit ** arithmetic, this allocator allows simple and fast ** address calculations from the SCRIPTS code. ** In addition, cache line alignment is guaranteed for ** power of 2 cache line size. ** Enhanced in linux-2.3.44 to provide a memory pool ** per pcidev to support dynamic dma mapping. (I would ** have preferred a real bus astraction, btw). ** **========================================================== */ #define __GetFreePages(flags, order) __get_free_pages(flags, order) #define MEMO_SHIFT 4 /* 16 bytes minimum memory chunk */ #if PAGE_SIZE >= 8192 #define MEMO_PAGE_ORDER 0 /* 1 PAGE maximum */ #else #define MEMO_PAGE_ORDER 1 /* 2 PAGES maximum */ #endif #define MEMO_FREE_UNUSED /* Free unused pages immediately */ #define MEMO_WARN 1 #define MEMO_GFP_FLAGS GFP_ATOMIC #define MEMO_CLUSTER_SHIFT (PAGE_SHIFT+MEMO_PAGE_ORDER) #define MEMO_CLUSTER_SIZE (1UL << MEMO_CLUSTER_SHIFT) #define MEMO_CLUSTER_MASK (MEMO_CLUSTER_SIZE-1) typedef u_long m_addr_t; /* Enough bits to bit-hack addresses */ typedef struct device *m_bush_t; /* Something that addresses DMAable */ typedef struct m_link { /* Link between free memory chunks */ struct m_link *next; } m_link_s; #ifdef SCSI_NCR_DYNAMIC_DMA_MAPPING typedef struct m_vtob { /* Virtual to Bus address translation */ struct m_vtob *next; m_addr_t vaddr; m_addr_t baddr; } m_vtob_s; #define VTOB_HASH_SHIFT 5 #define VTOB_HASH_SIZE (1UL << VTOB_HASH_SHIFT) #define VTOB_HASH_MASK (VTOB_HASH_SIZE-1) #define VTOB_HASH_CODE(m) \ ((((m_addr_t) (m)) >> MEMO_CLUSTER_SHIFT) & VTOB_HASH_MASK) #endif typedef struct m_pool { /* Memory pool of a given kind */ #ifdef SCSI_NCR_DYNAMIC_DMA_MAPPING m_bush_t bush; m_addr_t (*getp)(struct m_pool *); void (*freep)(struct m_pool *, m_addr_t); #define M_GETP() mp->getp(mp) #define M_FREEP(p) mp->freep(mp, p) #define GetPages() __GetFreePages(MEMO_GFP_FLAGS, MEMO_PAGE_ORDER) #define FreePages(p) free_pages(p, MEMO_PAGE_ORDER) int nump; m_vtob_s *(vtob[VTOB_HASH_SIZE]); struct m_pool *next; #else #define M_GETP() __GetFreePages(MEMO_GFP_FLAGS, MEMO_PAGE_ORDER) #define M_FREEP(p) free_pages(p, MEMO_PAGE_ORDER) #endif /* SCSI_NCR_DYNAMIC_DMA_MAPPING */ struct m_link h[PAGE_SHIFT-MEMO_SHIFT+MEMO_PAGE_ORDER+1]; } m_pool_s; static void *___m_alloc(m_pool_s *mp, int size) { int i = 0; int s = (1 << MEMO_SHIFT); int j; m_addr_t a; m_link_s *h = mp->h; if (size > (PAGE_SIZE << MEMO_PAGE_ORDER)) return 0; while (size > s) { s <<= 1; ++i; } j = i; while (!h[j].next) { if (s == (PAGE_SIZE << MEMO_PAGE_ORDER)) { h[j].next = (m_link_s *) M_GETP(); if (h[j].next) h[j].next->next = 0; break; } ++j; s <<= 1; } a = (m_addr_t) h[j].next; if (a) { h[j].next = h[j].next->next; while (j > i) { j -= 1; s >>= 1; h[j].next = (m_link_s *) (a+s); h[j].next->next = 0; } } #ifdef DEBUG printk("___m_alloc(%d) = %p\n", size, (void *) a); #endif return (void *) a; } static void ___m_free(m_pool_s *mp, void *ptr, int size) { int i = 0; int s = (1 << MEMO_SHIFT); m_link_s *q; m_addr_t a, b; m_link_s *h = mp->h; #ifdef DEBUG printk("___m_free(%p, %d)\n", ptr, size); #endif if (size > (PAGE_SIZE << MEMO_PAGE_ORDER)) return; while (size > s) { s <<= 1; ++i; } a = (m_addr_t) ptr; while (1) { #ifdef MEMO_FREE_UNUSED if (s == (PAGE_SIZE << MEMO_PAGE_ORDER)) { M_FREEP(a); break; } #endif b = a ^ s; q = &h[i]; while (q->next && q->next != (m_link_s *) b) { q = q->next; } if (!q->next) { ((m_link_s *) a)->next = h[i].next; h[i].next = (m_link_s *) a; break; } q->next = q->next->next; a = a & b; s <<= 1; ++i; } } static void *__m_calloc2(m_pool_s *mp, int size, char *name, int uflags) { void *p; p = ___m_alloc(mp, size); if (DEBUG_FLAGS & DEBUG_ALLOC) printk ("new %-10s[%4d] @%p.\n", name, size, p); if (p) bzero(p, size); else if (uflags & MEMO_WARN) printk (NAME53C8XX ": failed to allocate %s[%d]\n", name, size); return p; } #define __m_calloc(mp, s, n) __m_calloc2(mp, s, n, MEMO_WARN) static void __m_free(m_pool_s *mp, void *ptr, int size, char *name) { if (DEBUG_FLAGS & DEBUG_ALLOC) printk ("freeing %-10s[%4d] @%p.\n", name, size, ptr); ___m_free(mp, ptr, size); } /* * With pci bus iommu support, we use a default pool of unmapped memory * for memory we donnot need to DMA from/to and one pool per pcidev for * memory accessed by the PCI chip. `mp0' is the default not DMAable pool. */ #ifndef SCSI_NCR_DYNAMIC_DMA_MAPPING static m_pool_s mp0; #else static m_addr_t ___mp0_getp(m_pool_s *mp) { m_addr_t m = GetPages(); if (m) ++mp->nump; return m; } static void ___mp0_freep(m_pool_s *mp, m_addr_t m) { FreePages(m); --mp->nump; } static m_pool_s mp0 = {0, ___mp0_getp, ___mp0_freep}; #endif /* SCSI_NCR_DYNAMIC_DMA_MAPPING */ /* * DMAable pools. */ #ifndef SCSI_NCR_DYNAMIC_DMA_MAPPING /* Without pci bus iommu support, all the memory is assumed DMAable */ #define __m_calloc_dma(b, s, n) m_calloc(s, n) #define __m_free_dma(b, p, s, n) m_free(p, s, n) #define __vtobus(b, p) virt_to_bus(p) #else /* * With pci bus iommu support, we maintain one pool per pcidev and a * hashed reverse table for virtual to bus physical address translations. */ static m_addr_t ___dma_getp(m_pool_s *mp) { m_addr_t vp; m_vtob_s *vbp; vbp = __m_calloc(&mp0, sizeof(*vbp), "VTOB"); if (vbp) { dma_addr_t daddr; vp = (m_addr_t) dma_alloc_coherent(mp->bush, PAGE_SIZE<vaddr = vp; vbp->baddr = daddr; vbp->next = mp->vtob[hc]; mp->vtob[hc] = vbp; ++mp->nump; return vp; } } if (vbp) __m_free(&mp0, vbp, sizeof(*vbp), "VTOB"); return 0; } static void ___dma_freep(m_pool_s *mp, m_addr_t m) { m_vtob_s **vbpp, *vbp; int hc = VTOB_HASH_CODE(m); vbpp = &mp->vtob[hc]; while (*vbpp && (*vbpp)->vaddr != m) vbpp = &(*vbpp)->next; if (*vbpp) { vbp = *vbpp; *vbpp = (*vbpp)->next; dma_free_coherent(mp->bush, PAGE_SIZE<vaddr, (dma_addr_t)vbp->baddr); __m_free(&mp0, vbp, sizeof(*vbp), "VTOB"); --mp->nump; } } static inline m_pool_s *___get_dma_pool(m_bush_t bush) { m_pool_s *mp; for (mp = mp0.next; mp && mp->bush != bush; mp = mp->next); return mp; } static m_pool_s *___cre_dma_pool(m_bush_t bush) { m_pool_s *mp; mp = __m_calloc(&mp0, sizeof(*mp), "MPOOL"); if (mp) { bzero(mp, sizeof(*mp)); mp->bush = bush; mp->getp = ___dma_getp; mp->freep = ___dma_freep; mp->next = mp0.next; mp0.next = mp; } return mp; } static void ___del_dma_pool(m_pool_s *p) { struct m_pool **pp = &mp0.next; while (*pp && *pp != p) pp = &(*pp)->next; if (*pp) { *pp = (*pp)->next; __m_free(&mp0, p, sizeof(*p), "MPOOL"); } } static void *__m_calloc_dma(m_bush_t bush, int size, char *name) { u_long flags; struct m_pool *mp; void *m = 0; NCR_LOCK_DRIVER(flags); mp = ___get_dma_pool(bush); if (!mp) mp = ___cre_dma_pool(bush); if (mp) m = __m_calloc(mp, size, name); if (mp && !mp->nump) ___del_dma_pool(mp); NCR_UNLOCK_DRIVER(flags); return m; } static void __m_free_dma(m_bush_t bush, void *m, int size, char *name) { u_long flags; struct m_pool *mp; NCR_LOCK_DRIVER(flags); mp = ___get_dma_pool(bush); if (mp) __m_free(mp, m, size, name); if (mp && !mp->nump) ___del_dma_pool(mp); NCR_UNLOCK_DRIVER(flags); } static m_addr_t __vtobus(m_bush_t bush, void *m) { u_long flags; m_pool_s *mp; int hc = VTOB_HASH_CODE(m); m_vtob_s *vp = 0; m_addr_t a = ((m_addr_t) m) & ~MEMO_CLUSTER_MASK; NCR_LOCK_DRIVER(flags); mp = ___get_dma_pool(bush); if (mp) { vp = mp->vtob[hc]; while (vp && (m_addr_t) vp->vaddr != a) vp = vp->next; } NCR_UNLOCK_DRIVER(flags); return vp ? vp->baddr + (((m_addr_t) m) - a) : 0; } #endif /* SCSI_NCR_DYNAMIC_DMA_MAPPING */ #define _m_calloc_dma(np, s, n) __m_calloc_dma(np->dev, s, n) #define _m_free_dma(np, p, s, n) __m_free_dma(np->dev, p, s, n) #define m_calloc_dma(s, n) _m_calloc_dma(np, s, n) #define m_free_dma(p, s, n) _m_free_dma(np, p, s, n) #define _vtobus(np, p) __vtobus(np->dev, p) #define vtobus(p) _vtobus(np, p) /* * Deal with DMA mapping/unmapping. */ #ifndef SCSI_NCR_DYNAMIC_DMA_MAPPING /* Linux versions prior to pci bus iommu kernel interface */ #define __unmap_scsi_data(dev, cmd) do {; } while (0) #define __map_scsi_single_data(dev, cmd) (__vtobus(dev,(cmd)->request_buffer)) #define __map_scsi_sg_data(dev, cmd) ((cmd)->use_sg) #define __sync_scsi_data_for_cpu(dev, cmd) do {; } while (0) #define __sync_scsi_data_for_device(dev, cmd) do {; } while (0) #define scsi_sg_dma_address(sc) vtobus((sc)->address) #define scsi_sg_dma_len(sc) ((sc)->length) #else /* Linux version with pci bus iommu kernel interface */ /* To keep track of the dma mapping (sg/single) that has been set */ #define __data_mapped SCp.phase #define __data_mapping SCp.have_data_in static void __unmap_scsi_data(struct device *dev, Scsi_Cmnd *cmd) { enum dma_data_direction dma_dir = (enum dma_data_direction)scsi_to_pci_dma_dir(cmd->sc_data_direction); switch(cmd->__data_mapped) { case 2: dma_unmap_sg(dev, cmd->buffer, cmd->use_sg, dma_dir); break; case 1: dma_unmap_single(dev, cmd->__data_mapping, cmd->request_bufflen, dma_dir); break; } cmd->__data_mapped = 0; } static u_long __map_scsi_single_data(struct device *dev, Scsi_Cmnd *cmd) { dma_addr_t mapping; enum dma_data_direction dma_dir = (enum dma_data_direction)scsi_to_pci_dma_dir(cmd->sc_data_direction); if (cmd->request_bufflen == 0) return 0; mapping = dma_map_single(dev, cmd->request_buffer, cmd->request_bufflen, dma_dir); cmd->__data_mapped = 1; cmd->__data_mapping = mapping; return mapping; } static int __map_scsi_sg_data(struct device *dev, Scsi_Cmnd *cmd) { int use_sg; enum dma_data_direction dma_dir = (enum dma_data_direction)scsi_to_pci_dma_dir(cmd->sc_data_direction); if (cmd->use_sg == 0) return 0; use_sg = dma_map_sg(dev, cmd->buffer, cmd->use_sg, dma_dir); cmd->__data_mapped = 2; cmd->__data_mapping = use_sg; return use_sg; } static void __sync_scsi_data_for_cpu(struct device *dev, Scsi_Cmnd *cmd) { enum dma_data_direction dma_dir = (enum dma_data_direction)scsi_to_pci_dma_dir(cmd->sc_data_direction); switch(cmd->__data_mapped) { case 2: dma_sync_sg_for_cpu(dev, cmd->buffer, cmd->use_sg, dma_dir); break; case 1: dma_sync_single_for_cpu(dev, cmd->__data_mapping, cmd->request_bufflen, dma_dir); break; } } static void __sync_scsi_data_for_device(struct device *dev, Scsi_Cmnd *cmd) { enum dma_data_direction dma_dir = (enum dma_data_direction)scsi_to_pci_dma_dir(cmd->sc_data_direction); switch(cmd->__data_mapped) { case 2: dma_sync_sg_for_device(dev, cmd->buffer, cmd->use_sg, dma_dir); break; case 1: dma_sync_single_for_device(dev, cmd->__data_mapping, cmd->request_bufflen, dma_dir); break; } } #define scsi_sg_dma_address(sc) sg_dma_address(sc) #define scsi_sg_dma_len(sc) sg_dma_len(sc) #endif /* SCSI_NCR_DYNAMIC_DMA_MAPPING */ #define unmap_scsi_data(np, cmd) __unmap_scsi_data(np->dev, cmd) #define map_scsi_single_data(np, cmd) __map_scsi_single_data(np->dev, cmd) #define map_scsi_sg_data(np, cmd) __map_scsi_sg_data(np->dev, cmd) #define sync_scsi_data_for_cpu(np, cmd) __sync_scsi_data_for_cpu(np->dev, cmd) #define sync_scsi_data_for_device(np, cmd) __sync_scsi_data_for_device(np->dev, cmd) /*========================================================== ** ** SCSI data transfer direction ** ** Until some linux kernel version near 2.3.40, ** low-level scsi drivers were not told about data ** transfer direction. We check the existence of this ** feature that has been expected for a _long_ time by ** all SCSI driver developers by just testing against ** the definition of SCSI_DATA_UNKNOWN. Indeed this is ** a hack, but testing against a kernel version would ** have been a shame. ;-) ** **========================================================== */ #ifdef SCSI_DATA_UNKNOWN #define scsi_data_direction(cmd) (cmd->sc_data_direction) #else #define SCSI_DATA_UNKNOWN 0 #define SCSI_DATA_WRITE 1 #define SCSI_DATA_READ 2 #define SCSI_DATA_NONE 3 static __inline__ int scsi_data_direction(Scsi_Cmnd *cmd) { int direction; switch((int) cmd->cmnd[0]) { case 0x08: /* READ(6) 08 */ case 0x28: /* READ(10) 28 */ case 0xA8: /* READ(12) A8 */ direction = SCSI_DATA_READ; break; case 0x0A: /* WRITE(6) 0A */ case 0x2A: /* WRITE(10) 2A */ case 0xAA: /* WRITE(12) AA */ direction = SCSI_DATA_WRITE; break; default: direction = SCSI_DATA_UNKNOWN; break; } return direction; } #endif /* SCSI_DATA_UNKNOWN */ /*========================================================== ** ** Driver setup. ** ** This structure is initialized from linux config ** options. It can be overridden at boot-up by the boot ** command line. ** **========================================================== */ static struct ncr_driver_setup driver_setup = SCSI_NCR_DRIVER_SETUP; #ifdef SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT static struct ncr_driver_setup driver_safe_setup __initdata = SCSI_NCR_DRIVER_SAFE_SETUP; #endif #define initverbose (driver_setup.verbose) #define bootverbose (np->verbose) /*========================================================== ** ** NVRAM detection and reading. ** ** Currently supported: ** - 24C16 EEPROM with both Symbios and Tekram layout. ** - 93C46 EEPROM with Tekram layout. ** **========================================================== */ #ifdef SCSI_NCR_NVRAM_SUPPORT /* * 24C16 EEPROM reading. * * GPOI0 - data in/data out * GPIO1 - clock * Symbios NVRAM wiring now also used by Tekram. */ #define SET_BIT 0 #define CLR_BIT 1 #define SET_CLK 2 #define CLR_CLK 3 /* * Set/clear data/clock bit in GPIO0 */ static void __init S24C16_set_bit(ncr_slot *np, u_char write_bit, u_char *gpreg, int bit_mode) { UDELAY (5); switch (bit_mode){ case SET_BIT: *gpreg |= write_bit; break; case CLR_BIT: *gpreg &= 0xfe; break; case SET_CLK: *gpreg |= 0x02; break; case CLR_CLK: *gpreg &= 0xfd; break; } OUTB (nc_gpreg, *gpreg); UDELAY (5); } /* * Send START condition to NVRAM to wake it up. */ static void __init S24C16_start(ncr_slot *np, u_char *gpreg) { S24C16_set_bit(np, 1, gpreg, SET_BIT); S24C16_set_bit(np, 0, gpreg, SET_CLK); S24C16_set_bit(np, 0, gpreg, CLR_BIT); S24C16_set_bit(np, 0, gpreg, CLR_CLK); } /* * Send STOP condition to NVRAM - puts NVRAM to sleep... ZZzzzz!! */ static void __init S24C16_stop(ncr_slot *np, u_char *gpreg) { S24C16_set_bit(np, 0, gpreg, SET_CLK); S24C16_set_bit(np, 1, gpreg, SET_BIT); } /* * Read or write a bit to the NVRAM, * read if GPIO0 input else write if GPIO0 output */ static void __init S24C16_do_bit(ncr_slot *np, u_char *read_bit, u_char write_bit, u_char *gpreg) { S24C16_set_bit(np, write_bit, gpreg, SET_BIT); S24C16_set_bit(np, 0, gpreg, SET_CLK); if (read_bit) *read_bit = INB (nc_gpreg); S24C16_set_bit(np, 0, gpreg, CLR_CLK); S24C16_set_bit(np, 0, gpreg, CLR_BIT); } /* * Output an ACK to the NVRAM after reading, * change GPIO0 to output and when done back to an input */ static void __init S24C16_write_ack(ncr_slot *np, u_char write_bit, u_char *gpreg, u_char *gpcntl) { OUTB (nc_gpcntl, *gpcntl & 0xfe); S24C16_do_bit(np, 0, write_bit, gpreg); OUTB (nc_gpcntl, *gpcntl); } /* * Input an ACK from NVRAM after writing, * change GPIO0 to input and when done back to an output */ static void __init S24C16_read_ack(ncr_slot *np, u_char *read_bit, u_char *gpreg, u_char *gpcntl) { OUTB (nc_gpcntl, *gpcntl | 0x01); S24C16_do_bit(np, read_bit, 1, gpreg); OUTB (nc_gpcntl, *gpcntl); } /* * WRITE a byte to the NVRAM and then get an ACK to see it was accepted OK, * GPIO0 must already be set as an output */ static void __init S24C16_write_byte(ncr_slot *np, u_char *ack_data, u_char write_data, u_char *gpreg, u_char *gpcntl) { int x; for (x = 0; x < 8; x++) S24C16_do_bit(np, 0, (write_data >> (7 - x)) & 0x01, gpreg); S24C16_read_ack(np, ack_data, gpreg, gpcntl); } /* * READ a byte from the NVRAM and then send an ACK to say we have got it, * GPIO0 must already be set as an input */ static void __init S24C16_read_byte(ncr_slot *np, u_char *read_data, u_char ack_data, u_char *gpreg, u_char *gpcntl) { int x; u_char read_bit; *read_data = 0; for (x = 0; x < 8; x++) { S24C16_do_bit(np, &read_bit, 1, gpreg); *read_data |= ((read_bit & 0x01) << (7 - x)); } S24C16_write_ack(np, ack_data, gpreg, gpcntl); } /* * Read 'len' bytes starting at 'offset'. */ static int __init sym_read_S24C16_nvram (ncr_slot *np, int offset, u_char *data, int len) { u_char gpcntl, gpreg; u_char old_gpcntl, old_gpreg; u_char ack_data; int retv = 1; int x; /* save current state of GPCNTL and GPREG */ old_gpreg = INB (nc_gpreg); old_gpcntl = INB (nc_gpcntl); gpcntl = old_gpcntl & 0x1c; /* set up GPREG & GPCNTL to set GPIO0 and GPIO1 in to known state */ OUTB (nc_gpreg, old_gpreg); OUTB (nc_gpcntl, gpcntl); /* this is to set NVRAM into a known state with GPIO0/1 both low */ gpreg = old_gpreg; S24C16_set_bit(np, 0, &gpreg, CLR_CLK); S24C16_set_bit(np, 0, &gpreg, CLR_BIT); /* now set NVRAM inactive with GPIO0/1 both high */ S24C16_stop(np, &gpreg); /* activate NVRAM */ S24C16_start(np, &gpreg); /* write device code and random address MSB */ S24C16_write_byte(np, &ack_data, 0xa0 | ((offset >> 7) & 0x0e), &gpreg, &gpcntl); if (ack_data & 0x01) goto out; /* write random address LSB */ S24C16_write_byte(np, &ack_data, offset & 0xff, &gpreg, &gpcntl); if (ack_data & 0x01) goto out; /* regenerate START state to set up for reading */ S24C16_start(np, &gpreg); /* rewrite device code and address MSB with read bit set (lsb = 0x01) */ S24C16_write_byte(np, &ack_data, 0xa1 | ((offset >> 7) & 0x0e), &gpreg, &gpcntl); if (ack_data & 0x01) goto out; /* now set up GPIO0 for inputting data */ gpcntl |= 0x01; OUTB (nc_gpcntl, gpcntl); /* input all requested data - only part of total NVRAM */ for (x = 0; x < len; x++) S24C16_read_byte(np, &data[x], (x == (len-1)), &gpreg, &gpcntl); /* finally put NVRAM back in inactive mode */ gpcntl &= 0xfe; OUTB (nc_gpcntl, gpcntl); S24C16_stop(np, &gpreg); retv = 0; out: /* return GPIO0/1 to original states after having accessed NVRAM */ OUTB (nc_gpcntl, old_gpcntl); OUTB (nc_gpreg, old_gpreg); return retv; } #undef SET_BIT #undef CLR_BIT #undef SET_CLK #undef CLR_CLK /* * Try reading Symbios NVRAM. * Return 0 if OK. */ static int __init sym_read_Symbios_nvram (ncr_slot *np, Symbios_nvram *nvram) { static u_char Symbios_trailer[6] = {0xfe, 0xfe, 0, 0, 0, 0}; u_char *data = (u_char *) nvram; int len = sizeof(*nvram); u_short csum; int x; /* probe the 24c16 and read the SYMBIOS 24c16 area */ if (sym_read_S24C16_nvram (np, SYMBIOS_NVRAM_ADDRESS, data, len)) return 1; /* check valid NVRAM signature, verify byte count and checksum */ if (nvram->type != 0 || memcmp(nvram->trailer, Symbios_trailer, 6) || nvram->byte_count != len - 12) return 1; /* verify checksum */ for (x = 6, csum = 0; x < len - 6; x++) csum += data[x]; if (csum != nvram->checksum) return 1; return 0; } /* * 93C46 EEPROM reading. * * GPOI0 - data in * GPIO1 - data out * GPIO2 - clock * GPIO4 - chip select * * Used by Tekram. */ /* * Pulse clock bit in GPIO0 */ static void __init T93C46_Clk(ncr_slot *np, u_char *gpreg) { OUTB (nc_gpreg, *gpreg | 0x04); UDELAY (2); OUTB (nc_gpreg, *gpreg); } /* * Read bit from NVRAM */ static void __init T93C46_Read_Bit(ncr_slot *np, u_char *read_bit, u_char *gpreg) { UDELAY (2); T93C46_Clk(np, gpreg); *read_bit = INB (nc_gpreg); } /* * Write bit to GPIO0 */ static void __init T93C46_Write_Bit(ncr_slot *np, u_char write_bit, u_char *gpreg) { if (write_bit & 0x01) *gpreg |= 0x02; else *gpreg &= 0xfd; *gpreg |= 0x10; OUTB (nc_gpreg, *gpreg); UDELAY (2); T93C46_Clk(np, gpreg); } /* * Send STOP condition to NVRAM - puts NVRAM to sleep... ZZZzzz!! */ static void __init T93C46_Stop(ncr_slot *np, u_char *gpreg) { *gpreg &= 0xef; OUTB (nc_gpreg, *gpreg); UDELAY (2); T93C46_Clk(np, gpreg); } /* * Send read command and address to NVRAM */ static void __init T93C46_Send_Command(ncr_slot *np, u_short write_data, u_char *read_bit, u_char *gpreg) { int x; /* send 9 bits, start bit (1), command (2), address (6) */ for (x = 0; x < 9; x++) T93C46_Write_Bit(np, (u_char) (write_data >> (8 - x)), gpreg); *read_bit = INB (nc_gpreg); } /* * READ 2 bytes from the NVRAM */ static void __init T93C46_Read_Word(ncr_slot *np, u_short *nvram_data, u_char *gpreg) { int x; u_char read_bit; *nvram_data = 0; for (x = 0; x < 16; x++) { T93C46_Read_Bit(np, &read_bit, gpreg); if (read_bit & 0x01) *nvram_data |= (0x01 << (15 - x)); else *nvram_data &= ~(0x01 << (15 - x)); } } /* * Read Tekram NvRAM data. */ static int __init T93C46_Read_Data(ncr_slot *np, u_short *data,int len,u_char *gpreg) { u_char read_bit; int x; for (x = 0; x < len; x++) { /* output read command and address */ T93C46_Send_Command(np, 0x180 | x, &read_bit, gpreg); if (read_bit & 0x01) return 1; /* Bad */ T93C46_Read_Word(np, &data[x], gpreg); T93C46_Stop(np, gpreg); } return 0; } /* * Try reading 93C46 Tekram NVRAM. */ static int __init sym_read_T93C46_nvram (ncr_slot *np, Tekram_nvram *nvram) { u_char gpcntl, gpreg; u_char old_gpcntl, old_gpreg; int retv = 1; /* save current state of GPCNTL and GPREG */ old_gpreg = INB (nc_gpreg); old_gpcntl = INB (nc_gpcntl); /* set up GPREG & GPCNTL to set GPIO0/1/2/4 in to known state, 0 in, 1/2/4 out */ gpreg = old_gpreg & 0xe9; OUTB (nc_gpreg, gpreg); gpcntl = (old_gpcntl & 0xe9) | 0x09; OUTB (nc_gpcntl, gpcntl); /* input all of NVRAM, 64 words */ retv = T93C46_Read_Data(np, (u_short *) nvram, sizeof(*nvram) / sizeof(short), &gpreg); /* return GPIO0/1/2/4 to original states after having accessed NVRAM */ OUTB (nc_gpcntl, old_gpcntl); OUTB (nc_gpreg, old_gpreg); return retv; } /* * Try reading Tekram NVRAM. * Return 0 if OK. */ static int __init sym_read_Tekram_nvram (ncr_slot *np, u_short device_id, Tekram_nvram *nvram) { u_char *data = (u_char *) nvram; int len = sizeof(*nvram); u_short csum; int x; switch (device_id) { case PCI_DEVICE_ID_NCR_53C885: case PCI_DEVICE_ID_NCR_53C895: case PCI_DEVICE_ID_NCR_53C896: x = sym_read_S24C16_nvram(np, TEKRAM_24C16_NVRAM_ADDRESS, data, len); break; case PCI_DEVICE_ID_NCR_53C875: x = sym_read_S24C16_nvram(np, TEKRAM_24C16_NVRAM_ADDRESS, data, len); if (!x) break; default: x = sym_read_T93C46_nvram(np, nvram); break; } if (x) return 1; /* verify checksum */ for (x = 0, csum = 0; x < len - 1; x += 2) csum += data[x] + (data[x+1] << 8); if (csum != 0x1234) return 1; return 0; } #endif /* SCSI_NCR_NVRAM_SUPPORT */ /*=================================================================== ** ** Detect and try to read SYMBIOS and TEKRAM NVRAM. ** ** Data can be used to order booting of boards. ** ** Data is saved in ncr_device structure if NVRAM found. This ** is then used to find drive boot order for ncr_attach(). ** ** NVRAM data is passed to Scsi_Host_Template later during ** ncr_attach() for any device set up. ** **=================================================================== */ #ifdef SCSI_NCR_NVRAM_SUPPORT static void __init ncr_get_nvram(struct ncr_device *devp, ncr_nvram *nvp) { devp->nvram = nvp; if (!nvp) return; /* ** Get access to chip IO registers */ #ifdef SCSI_NCR_IOMAPPED request_region(devp->slot.io_port, 128, NAME53C8XX); devp->slot.base_io = devp->slot.io_port; #else devp->slot.reg = (struct ncr_reg *) remap_pci_mem(devp->slot.base_c, 128); if (!devp->slot.reg) return; #endif /* ** Try to read SYMBIOS nvram. ** Try to read TEKRAM nvram if Symbios nvram not found. */ if (!sym_read_Symbios_nvram(&devp->slot, &nvp->data.Symbios)) nvp->type = SCSI_NCR_SYMBIOS_NVRAM; else if (!sym_read_Tekram_nvram(&devp->slot, devp->chip.device_id, &nvp->data.Tekram)) nvp->type = SCSI_NCR_TEKRAM_NVRAM; else { nvp->type = 0; devp->nvram = 0; } /* ** Release access to chip IO registers */ #ifdef SCSI_NCR_IOMAPPED release_region(devp->slot.base_io, 128); #else unmap_pci_mem((u_long) devp->slot.reg, 128ul); #endif } /*=================================================================== ** ** Display the content of NVRAM for debugging purpose. ** **=================================================================== */ #ifdef SCSI_NCR_DEBUG_NVRAM static void __init ncr_display_Symbios_nvram(Symbios_nvram *nvram) { int i; /* display Symbios nvram host data */ printk(KERN_DEBUG NAME53C8XX ": HOST ID=%d%s%s%s%s%s\n", nvram->host_id & 0x0f, (nvram->flags & SYMBIOS_SCAM_ENABLE) ? " SCAM" :"", (nvram->flags & SYMBIOS_PARITY_ENABLE) ? " PARITY" :"", (nvram->flags & SYMBIOS_VERBOSE_MSGS) ? " VERBOSE" :"", (nvram->flags & SYMBIOS_CHS_MAPPING) ? " CHS_ALT" :"", (nvram->flags1 & SYMBIOS_SCAN_HI_LO) ? " HI_LO" :""); /* display Symbios nvram drive data */ for (i = 0 ; i < 15 ; i++) { struct Symbios_target *tn = &nvram->target[i]; printk(KERN_DEBUG NAME53C8XX "-%d:%s%s%s%s WIDTH=%d SYNC=%d TMO=%d\n", i, (tn->flags & SYMBIOS_DISCONNECT_ENABLE) ? " DISC" : "", (tn->flags & SYMBIOS_SCAN_AT_BOOT_TIME) ? " SCAN_BOOT" : "", (tn->flags & SYMBIOS_SCAN_LUNS) ? " SCAN_LUNS" : "", (tn->flags & SYMBIOS_QUEUE_TAGS_ENABLED)? " TCQ" : "", tn->bus_width, tn->sync_period / 4, tn->timeout); } } static u_char Tekram_boot_delay[7] __initdata = {3, 5, 10, 20, 30, 60, 120}; static void __init ncr_display_Tekram_nvram(Tekram_nvram *nvram) { int i, tags, boot_delay; char *rem; /* display Tekram nvram host data */ tags = 2 << nvram->max_tags_index; boot_delay = 0; if (nvram->boot_delay_index < 6) boot_delay = Tekram_boot_delay[nvram->boot_delay_index]; switch((nvram->flags & TEKRAM_REMOVABLE_FLAGS) >> 6) { default: case 0: rem = ""; break; case 1: rem = " REMOVABLE=boot device"; break; case 2: rem = " REMOVABLE=all"; break; } printk(KERN_DEBUG NAME53C8XX ": HOST ID=%d%s%s%s%s%s%s%s%s%s BOOT DELAY=%d tags=%d\n", nvram->host_id & 0x0f, (nvram->flags1 & SYMBIOS_SCAM_ENABLE) ? " SCAM" :"", (nvram->flags & TEKRAM_MORE_THAN_2_DRIVES) ? " >2DRIVES":"", (nvram->flags & TEKRAM_DRIVES_SUP_1GB) ? " >1GB" :"", (nvram->flags & TEKRAM_RESET_ON_POWER_ON) ? " RESET" :"", (nvram->flags & TEKRAM_ACTIVE_NEGATION) ? " ACT_NEG" :"", (nvram->flags & TEKRAM_IMMEDIATE_SEEK) ? " IMM_SEEK" :"", (nvram->flags & TEKRAM_SCAN_LUNS) ? " SCAN_LUNS" :"", (nvram->flags1 & TEKRAM_F2_F6_ENABLED) ? " F2_F6" :"", rem, boot_delay, tags); /* display Tekram nvram drive data */ for (i = 0; i <= 15; i++) { int sync, j; struct Tekram_target *tn = &nvram->target[i]; j = tn->sync_index & 0xf; sync = Tekram_sync[j]; printk(KERN_DEBUG NAME53C8XX "-%d:%s%s%s%s%s%s PERIOD=%d\n", i, (tn->flags & TEKRAM_PARITY_CHECK) ? " PARITY" : "", (tn->flags & TEKRAM_SYNC_NEGO) ? " SYNC" : "", (tn->flags & TEKRAM_DISCONNECT_ENABLE) ? " DISC" : "", (tn->flags & TEKRAM_START_CMD) ? " START" : "", (tn->flags & TEKRAM_TAGGED_COMMANDS) ? " TCQ" : "", (tn->flags & TEKRAM_WIDE_NEGO) ? " WIDE" : "", sync); } } #endif /* SCSI_NCR_DEBUG_NVRAM */ #endif /* SCSI_NCR_NVRAM_SUPPORT */ /*=================================================================== ** ** Utility routines that protperly return data through /proc FS. ** **=================================================================== */ #ifdef SCSI_NCR_USER_INFO_SUPPORT struct info_str { char *buffer; int length; int offset; int pos; }; static void copy_mem_info(struct info_str *info, char *data, int len) { if (info->pos + len > info->length) len = info->length - info->pos; if (info->pos + len < info->offset) { info->pos += len; return; } if (info->pos < info->offset) { data += (info->offset - info->pos); len -= (info->offset - info->pos); } if (len > 0) { memcpy(info->buffer + info->pos, data, len); info->pos += len; } } static int copy_info(struct info_str *info, char *fmt, ...) { va_list args; char buf[81]; int len; va_start(args, fmt); len = vsprintf(buf, fmt, args); va_end(args); copy_mem_info(info, buf, len); return len; } #endif /*=================================================================== ** ** Driver setup from the boot command line ** **=================================================================== */ #ifdef MODULE #define ARG_SEP ' ' #else #define ARG_SEP ',' #endif #define OPT_TAGS 1 #define OPT_MASTER_PARITY 2 #define OPT_SCSI_PARITY 3 #define OPT_DISCONNECTION 4 #define OPT_SPECIAL_FEATURES 5 #define OPT_UNUSED_1 6 #define OPT_FORCE_SYNC_NEGO 7 #define OPT_REVERSE_PROBE 8 #define OPT_DEFAULT_SYNC 9 #define OPT_VERBOSE 10 #define OPT_DEBUG 11 #define OPT_BURST_MAX 12 #define OPT_LED_PIN 13 #define OPT_MAX_WIDE 14 #define OPT_SETTLE_DELAY 15 #define OPT_DIFF_SUPPORT 16 #define OPT_IRQM 17 #define OPT_PCI_FIX_UP 18 #define OPT_BUS_CHECK 19 #define OPT_OPTIMIZE 20 #define OPT_RECOVERY 21 #define OPT_SAFE_SETUP 22 #define OPT_USE_NVRAM 23 #define OPT_EXCLUDE 24 #define OPT_HOST_ID 25 #ifdef SCSI_NCR_IARB_SUPPORT #define OPT_IARB 26 #endif static char setup_token[] __initdata = "tags:" "mpar:" "spar:" "disc:" "specf:" "ultra:" "fsn:" "revprob:" "sync:" "verb:" "debug:" "burst:" "led:" "wide:" "settle:" "diff:" "irqm:" "pcifix:" "buschk:" "optim:" "recovery:" "safe:" "nvram:" "excl:" "hostid:" #ifdef SCSI_NCR_IARB_SUPPORT "iarb:" #endif ; /* DONNOT REMOVE THIS ';' */ #ifdef MODULE #define ARG_SEP ' ' #else #define ARG_SEP ',' #endif static int __init get_setup_token(char *p) { char *cur = setup_token; char *pc; int i = 0; while (cur != NULL && (pc = strchr(cur, ':')) != NULL) { ++pc; ++i; if (!strncmp(p, cur, pc - cur)) return i; cur = pc; } return 0; } static int __init sym53c8xx__setup(char *str) { #ifdef SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT char *cur = str; char *pc, *pv; int i, val, c; int xi = 0; while (cur != NULL && (pc = strchr(cur, ':')) != NULL) { char *pe; val = 0; pv = pc; c = *++pv; if (c == 'n') val = 0; else if (c == 'y') val = 1; else val = (int) simple_strtoul(pv, &pe, 0); switch (get_setup_token(cur)) { case OPT_TAGS: driver_setup.default_tags = val; if (pe && *pe == '/') { i = 0; while (*pe && *pe != ARG_SEP && i < sizeof(driver_setup.tag_ctrl)-1) { driver_setup.tag_ctrl[i++] = *pe++; } driver_setup.tag_ctrl[i] = '\0'; } break; case OPT_MASTER_PARITY: driver_setup.master_parity = val; break; case OPT_SCSI_PARITY: driver_setup.scsi_parity = val; break; case OPT_DISCONNECTION: driver_setup.disconnection = val; break; case OPT_SPECIAL_FEATURES: driver_setup.special_features = val; break; case OPT_FORCE_SYNC_NEGO: driver_setup.force_sync_nego = val; break; case OPT_REVERSE_PROBE: driver_setup.reverse_probe = val; break; case OPT_DEFAULT_SYNC: driver_setup.default_sync = val; break; case OPT_VERBOSE: driver_setup.verbose = val; break; case OPT_DEBUG: driver_setup.debug = val; break; case OPT_BURST_MAX: driver_setup.burst_max = val; break; case OPT_LED_PIN: driver_setup.led_pin = val; break; case OPT_MAX_WIDE: driver_setup.max_wide = val? 1:0; break; case OPT_SETTLE_DELAY: driver_setup.settle_delay = val; break; case OPT_DIFF_SUPPORT: driver_setup.diff_support = val; break; case OPT_IRQM: driver_setup.irqm = val; break; case OPT_PCI_FIX_UP: driver_setup.pci_fix_up = val; break; case OPT_BUS_CHECK: driver_setup.bus_check = val; break; case OPT_OPTIMIZE: driver_setup.optimize = val; break; case OPT_RECOVERY: driver_setup.recovery = val; break; case OPT_USE_NVRAM: driver_setup.use_nvram = val; break; case OPT_SAFE_SETUP: memcpy(&driver_setup, &driver_safe_setup, sizeof(driver_setup)); break; case OPT_EXCLUDE: if (xi < SCSI_NCR_MAX_EXCLUDES) driver_setup.excludes[xi++] = val; break; case OPT_HOST_ID: driver_setup.host_id = val; break; #ifdef SCSI_NCR_IARB_SUPPORT case OPT_IARB: driver_setup.iarb = val; break; #endif default: printk("sym53c8xx_setup: unexpected boot option '%.*s' ignored\n", (int)(pc-cur+1), cur); break; } if ((cur = strchr(cur, ARG_SEP)) != NULL) ++cur; } #endif /* SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT */ return 1; } /*=================================================================== ** ** Get device queue depth from boot command line. ** **=================================================================== */ #define DEF_DEPTH (driver_setup.default_tags) #define ALL_TARGETS -2 #define NO_TARGET -1 #define ALL_LUNS -2 #define NO_LUN -1 static int device_queue_depth(int unit, int target, int lun) { int c, h, t, u, v; char *p = driver_setup.tag_ctrl; char *ep; h = -1; t = NO_TARGET; u = NO_LUN; while ((c = *p++) != 0) { v = simple_strtoul(p, &ep, 0); switch(c) { case '/': ++h; t = ALL_TARGETS; u = ALL_LUNS; break; case 't': if (t != target) t = (target == v) ? v : NO_TARGET; u = ALL_LUNS; break; case 'u': if (u != lun) u = (lun == v) ? v : NO_LUN; break; case 'q': if (h == unit && (t == ALL_TARGETS || t == target) && (u == ALL_LUNS || u == lun)) return v; break; case '-': t = ALL_TARGETS; u = ALL_LUNS; break; default: break; } p = ep; } return DEF_DEPTH; }