Index: head/sys/dev/aac/aac.c =================================================================== --- head/sys/dev/aac/aac.c (revision 280346) +++ head/sys/dev/aac/aac.c (revision 280347) @@ -1,3791 +1,3791 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2001 Scott Long * Copyright (c) 2000 BSDi * Copyright (c) 2001 Adaptec, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for the Adaptec 'FSA' family of PCI/SCSI RAID adapters. */ #define AAC_DRIVERNAME "aac" #include "opt_aac.h" /* #include */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void aac_startup(void *arg); static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f); static void aac_get_bus_info(struct aac_softc *sc); static void aac_daemon(void *arg); /* Command Processing */ static void aac_timeout(struct aac_softc *sc); static void aac_complete(void *context, int pending); static int aac_bio_command(struct aac_softc *sc, struct aac_command **cmp); static void aac_bio_complete(struct aac_command *cm); static int aac_wait_command(struct aac_command *cm); static void aac_command_thread(struct aac_softc *sc); /* Command Buffer Management */ static void aac_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error); static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_alloc_commands(struct aac_softc *sc); static void aac_free_commands(struct aac_softc *sc); static void aac_unmap_command(struct aac_command *cm); /* Hardware Interface */ static int aac_alloc(struct aac_softc *sc); static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_check_firmware(struct aac_softc *sc); static int aac_init(struct aac_softc *sc); static int aac_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp); static int aac_setup_intr(struct aac_softc *sc); static int aac_enqueue_fib(struct aac_softc *sc, int queue, struct aac_command *cm); static int aac_dequeue_fib(struct aac_softc *sc, int queue, u_int32_t *fib_size, struct aac_fib **fib_addr); static int aac_enqueue_response(struct aac_softc *sc, int queue, struct aac_fib *fib); /* StrongARM interface */ static int aac_sa_get_fwstatus(struct aac_softc *sc); static void aac_sa_qnotify(struct aac_softc *sc, int qbit); static int aac_sa_get_istatus(struct aac_softc *sc); static void aac_sa_clear_istatus(struct aac_softc *sc, int mask); static void aac_sa_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_sa_get_mailbox(struct aac_softc *sc, int mb); static void aac_sa_set_interrupts(struct aac_softc *sc, int enable); const struct aac_interface aac_sa_interface = { aac_sa_get_fwstatus, aac_sa_qnotify, aac_sa_get_istatus, aac_sa_clear_istatus, aac_sa_set_mailbox, aac_sa_get_mailbox, aac_sa_set_interrupts, NULL, NULL, NULL }; /* i960Rx interface */ static int aac_rx_get_fwstatus(struct aac_softc *sc); static void aac_rx_qnotify(struct aac_softc *sc, int qbit); static int aac_rx_get_istatus(struct aac_softc *sc); static void aac_rx_clear_istatus(struct aac_softc *sc, int mask); static void aac_rx_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_rx_get_mailbox(struct aac_softc *sc, int mb); static void aac_rx_set_interrupts(struct aac_softc *sc, int enable); static int aac_rx_send_command(struct aac_softc *sc, struct aac_command *cm); static int aac_rx_get_outb_queue(struct aac_softc *sc); static void aac_rx_set_outb_queue(struct aac_softc *sc, int index); const struct aac_interface aac_rx_interface = { aac_rx_get_fwstatus, aac_rx_qnotify, aac_rx_get_istatus, aac_rx_clear_istatus, aac_rx_set_mailbox, aac_rx_get_mailbox, aac_rx_set_interrupts, aac_rx_send_command, aac_rx_get_outb_queue, aac_rx_set_outb_queue }; /* Rocket/MIPS interface */ static int aac_rkt_get_fwstatus(struct aac_softc *sc); static void aac_rkt_qnotify(struct aac_softc *sc, int qbit); static int aac_rkt_get_istatus(struct aac_softc *sc); static void aac_rkt_clear_istatus(struct aac_softc *sc, int mask); static void aac_rkt_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_rkt_get_mailbox(struct aac_softc *sc, int mb); static void aac_rkt_set_interrupts(struct aac_softc *sc, int enable); static int aac_rkt_send_command(struct aac_softc *sc, struct aac_command *cm); static int aac_rkt_get_outb_queue(struct aac_softc *sc); static void aac_rkt_set_outb_queue(struct aac_softc *sc, int index); const struct aac_interface aac_rkt_interface = { aac_rkt_get_fwstatus, aac_rkt_qnotify, aac_rkt_get_istatus, aac_rkt_clear_istatus, aac_rkt_set_mailbox, aac_rkt_get_mailbox, aac_rkt_set_interrupts, aac_rkt_send_command, aac_rkt_get_outb_queue, aac_rkt_set_outb_queue }; /* Debugging and Diagnostics */ static void aac_describe_controller(struct aac_softc *sc); static const char *aac_describe_code(const struct aac_code_lookup *table, u_int32_t code); /* Management Interface */ static d_open_t aac_open; static d_ioctl_t aac_ioctl; static d_poll_t aac_poll; static void aac_cdevpriv_dtor(void *arg); static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib); static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg); static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib); static int aac_rev_check(struct aac_softc *sc, caddr_t udata); static int aac_open_aif(struct aac_softc *sc, caddr_t arg); static int aac_close_aif(struct aac_softc *sc, caddr_t arg); static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg); static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr); static int aac_query_disk(struct aac_softc *sc, caddr_t uptr); static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr); static int aac_supported_features(struct aac_softc *sc, caddr_t uptr); static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg); static struct aac_mntinforesp * aac_get_container_info(struct aac_softc *sc, struct aac_fib *fib, int cid); static struct cdevsw aac_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = aac_open, .d_ioctl = aac_ioctl, .d_poll = aac_poll, .d_name = "aac", }; static MALLOC_DEFINE(M_AACBUF, "aacbuf", "Buffers for the AAC driver"); /* sysctl node */ SYSCTL_NODE(_hw, OID_AUTO, aac, CTLFLAG_RD, 0, "AAC driver parameters"); /* * Device Interface */ /* * Initialize the controller and softc */ int aac_attach(struct aac_softc *sc) { int error, unit; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Initialize per-controller queues. */ aac_initq_free(sc); aac_initq_ready(sc); aac_initq_busy(sc); aac_initq_bio(sc); /* * Initialize command-completion task. */ TASK_INIT(&sc->aac_task_complete, 0, aac_complete, sc); /* mark controller as suspended until we get ourselves organised */ sc->aac_state |= AAC_STATE_SUSPEND; /* * Check that the firmware on the card is supported. */ if ((error = aac_check_firmware(sc)) != 0) return(error); /* * Initialize locks */ mtx_init(&sc->aac_aifq_lock, "AAC AIF lock", NULL, MTX_DEF); mtx_init(&sc->aac_io_lock, "AAC I/O lock", NULL, MTX_DEF); mtx_init(&sc->aac_container_lock, "AAC container lock", NULL, MTX_DEF); TAILQ_INIT(&sc->aac_container_tqh); TAILQ_INIT(&sc->aac_ev_cmfree); /* Initialize the clock daemon callout. */ callout_init_mtx(&sc->aac_daemontime, &sc->aac_io_lock, 0); /* * Initialize the adapter. */ if ((error = aac_alloc(sc)) != 0) return(error); if ((error = aac_init(sc)) != 0) return(error); /* * Allocate and connect our interrupt. */ if ((error = aac_setup_intr(sc)) != 0) return(error); /* * Print a little information about the controller. */ aac_describe_controller(sc); /* * Add sysctls. */ SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->aac_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->aac_dev)), OID_AUTO, "firmware_build", CTLFLAG_RD, &sc->aac_revision.buildNumber, 0, "firmware build number"); /* * Register to probe our containers later. */ sc->aac_ich.ich_func = aac_startup; sc->aac_ich.ich_arg = sc; if (config_intrhook_establish(&sc->aac_ich) != 0) { device_printf(sc->aac_dev, "can't establish configuration hook\n"); return(ENXIO); } /* * Make the control device. */ unit = device_get_unit(sc->aac_dev); sc->aac_dev_t = make_dev(&aac_cdevsw, unit, UID_ROOT, GID_OPERATOR, 0640, "aac%d", unit); (void)make_dev_alias(sc->aac_dev_t, "afa%d", unit); (void)make_dev_alias(sc->aac_dev_t, "hpn%d", unit); sc->aac_dev_t->si_drv1 = sc; /* Create the AIF thread */ if (kproc_create((void(*)(void *))aac_command_thread, sc, &sc->aifthread, 0, 0, "aac%daif", unit)) panic("Could not create AIF thread"); /* Register the shutdown method to only be called post-dump */ if ((sc->eh = EVENTHANDLER_REGISTER(shutdown_final, aac_shutdown, sc->aac_dev, SHUTDOWN_PRI_DEFAULT)) == NULL) device_printf(sc->aac_dev, "shutdown event registration failed\n"); /* Register with CAM for the non-DASD devices */ if ((sc->flags & AAC_FLAGS_ENABLE_CAM) != 0) { TAILQ_INIT(&sc->aac_sim_tqh); aac_get_bus_info(sc); } mtx_lock(&sc->aac_io_lock); callout_reset(&sc->aac_daemontime, 60 * hz, aac_daemon, sc); mtx_unlock(&sc->aac_io_lock); return(0); } static void aac_daemon(void *arg) { struct timeval tv; struct aac_softc *sc; struct aac_fib *fib; sc = arg; mtx_assert(&sc->aac_io_lock, MA_OWNED); if (callout_pending(&sc->aac_daemontime) || callout_active(&sc->aac_daemontime) == 0) return; getmicrotime(&tv); aac_alloc_sync_fib(sc, &fib); *(uint32_t *)fib->data = tv.tv_sec; aac_sync_fib(sc, SendHostTime, 0, fib, sizeof(uint32_t)); aac_release_sync_fib(sc); callout_schedule(&sc->aac_daemontime, 30 * 60 * hz); } void aac_add_event(struct aac_softc *sc, struct aac_event *event) { switch (event->ev_type & AAC_EVENT_MASK) { case AAC_EVENT_CMFREE: TAILQ_INSERT_TAIL(&sc->aac_ev_cmfree, event, ev_links); break; default: device_printf(sc->aac_dev, "aac_add event: unknown event %d\n", event->ev_type); break; } } /* * Request information of container #cid */ static struct aac_mntinforesp * aac_get_container_info(struct aac_softc *sc, struct aac_fib *fib, int cid) { struct aac_mntinfo *mi; mi = (struct aac_mntinfo *)&fib->data[0]; /* use 64-bit LBA if enabled */ mi->Command = (sc->flags & AAC_FLAGS_LBA_64BIT) ? VM_NameServe64 : VM_NameServe; mi->MntType = FT_FILESYS; mi->MntCount = cid; if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_mntinfo))) { device_printf(sc->aac_dev, "Error probing container %d\n", cid); return (NULL); } return ((struct aac_mntinforesp *)&fib->data[0]); } /* * Probe for containers, create disks. */ static void aac_startup(void *arg) { struct aac_softc *sc; struct aac_fib *fib; struct aac_mntinforesp *mir; int count = 0, i = 0; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* disconnect ourselves from the intrhook chain */ config_intrhook_disestablish(&sc->aac_ich); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); /* loop over possible containers */ do { if ((mir = aac_get_container_info(sc, fib, i)) == NULL) continue; if (i == 0) count = mir->MntRespCount; aac_add_container(sc, mir, 0); i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); /* poke the bus to actually attach the child devices */ if (bus_generic_attach(sc->aac_dev)) device_printf(sc->aac_dev, "bus_generic_attach failed\n"); /* mark the controller up */ sc->aac_state &= ~AAC_STATE_SUSPEND; /* enable interrupts now */ AAC_UNMASK_INTERRUPTS(sc); } /* * Create a device to represent a new container */ static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f) { struct aac_container *co; device_t child; /* * Check container volume type for validity. Note that many of * the possible types may never show up. */ if ((mir->Status == ST_OK) && (mir->MntTable[0].VolType != CT_NONE)) { co = (struct aac_container *)malloc(sizeof *co, M_AACBUF, M_NOWAIT | M_ZERO); if (co == NULL) panic("Out of memory?!"); fwprintf(sc, HBA_FLAGS_DBG_INIT_B, "id %x name '%.16s' size %u type %d", mir->MntTable[0].ObjectId, mir->MntTable[0].FileSystemName, mir->MntTable[0].Capacity, mir->MntTable[0].VolType); if ((child = device_add_child(sc->aac_dev, "aacd", -1)) == NULL) device_printf(sc->aac_dev, "device_add_child failed\n"); else device_set_ivars(child, co); device_set_desc(child, aac_describe_code(aac_container_types, mir->MntTable[0].VolType)); co->co_disk = child; co->co_found = f; bcopy(&mir->MntTable[0], &co->co_mntobj, sizeof(struct aac_mntobj)); mtx_lock(&sc->aac_container_lock); TAILQ_INSERT_TAIL(&sc->aac_container_tqh, co, co_link); mtx_unlock(&sc->aac_container_lock); } } /* * Allocate resources associated with (sc) */ static int aac_alloc(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Create DMA tag for mapping buffers into controller-addressable space. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_SG_64BIT) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, /* maxsize */ + sc->aac_max_sectors << 9, /* maxsize */ sc->aac_sg_tablesize, /* nsegments */ - MAXBSIZE, /* maxsegsize */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->aac_io_lock, /* lockfuncarg */ &sc->aac_buffer_dmat)) { device_printf(sc->aac_dev, "can't allocate buffer DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for mapping FIBs into controller-addressable space.. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->aac_max_fibs_alloc * sc->aac_max_fib_size, /* maxsize */ 1, /* nsegments */ sc->aac_max_fibs_alloc * sc->aac_max_fib_size, /* maxsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_fib_dmat)) { device_printf(sc->aac_dev, "can't allocate FIB DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for the common structure and allocate it. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ 8192 + sizeof(struct aac_common), /* maxsize */ 1, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_common_dmat)) { device_printf(sc->aac_dev, "can't allocate common structure DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->aac_common_dmat, (void **)&sc->aac_common, BUS_DMA_NOWAIT, &sc->aac_common_dmamap)) { device_printf(sc->aac_dev, "can't allocate common structure\n"); return (ENOMEM); } /* * Work around a bug in the 2120 and 2200 that cannot DMA commands * below address 8192 in physical memory. * XXX If the padding is not needed, can it be put to use instead * of ignored? */ (void)bus_dmamap_load(sc->aac_common_dmat, sc->aac_common_dmamap, sc->aac_common, 8192 + sizeof(*sc->aac_common), aac_common_map, sc, 0); if (sc->aac_common_busaddr < 8192) { sc->aac_common = (struct aac_common *) ((uint8_t *)sc->aac_common + 8192); sc->aac_common_busaddr += 8192; } bzero(sc->aac_common, sizeof(*sc->aac_common)); /* Allocate some FIBs and associated command structs */ TAILQ_INIT(&sc->aac_fibmap_tqh); sc->aac_commands = malloc(sc->aac_max_fibs * sizeof(struct aac_command), M_AACBUF, M_WAITOK|M_ZERO); while (sc->total_fibs < sc->aac_max_fibs) { if (aac_alloc_commands(sc) != 0) break; } if (sc->total_fibs == 0) return (ENOMEM); return (0); } /* * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ void aac_free(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* remove the control device */ if (sc->aac_dev_t != NULL) destroy_dev(sc->aac_dev_t); /* throw away any FIB buffers, discard the FIB DMA tag */ aac_free_commands(sc); if (sc->aac_fib_dmat) bus_dma_tag_destroy(sc->aac_fib_dmat); free(sc->aac_commands, M_AACBUF); /* destroy the common area */ if (sc->aac_common) { bus_dmamap_unload(sc->aac_common_dmat, sc->aac_common_dmamap); bus_dmamem_free(sc->aac_common_dmat, sc->aac_common, sc->aac_common_dmamap); } if (sc->aac_common_dmat) bus_dma_tag_destroy(sc->aac_common_dmat); /* disconnect the interrupt handler */ if (sc->aac_intr) bus_teardown_intr(sc->aac_dev, sc->aac_irq, sc->aac_intr); if (sc->aac_irq != NULL) { bus_release_resource(sc->aac_dev, SYS_RES_IRQ, rman_get_rid(sc->aac_irq), sc->aac_irq); pci_release_msi(sc->aac_dev); } /* destroy data-transfer DMA tag */ if (sc->aac_buffer_dmat) bus_dma_tag_destroy(sc->aac_buffer_dmat); /* destroy the parent DMA tag */ if (sc->aac_parent_dmat) bus_dma_tag_destroy(sc->aac_parent_dmat); /* release the register window mapping */ if (sc->aac_regs_res0 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rman_get_rid(sc->aac_regs_res0), sc->aac_regs_res0); if (sc->aac_hwif == AAC_HWIF_NARK && sc->aac_regs_res1 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rman_get_rid(sc->aac_regs_res1), sc->aac_regs_res1); } /* * Disconnect from the controller completely, in preparation for unload. */ int aac_detach(device_t dev) { struct aac_softc *sc; struct aac_container *co; struct aac_sim *sim; int error; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); callout_drain(&sc->aac_daemontime); mtx_lock(&sc->aac_io_lock); while (sc->aifflags & AAC_AIFFLAGS_RUNNING) { sc->aifflags |= AAC_AIFFLAGS_EXIT; wakeup(sc->aifthread); msleep(sc->aac_dev, &sc->aac_io_lock, PUSER, "aacdch", 0); } mtx_unlock(&sc->aac_io_lock); KASSERT((sc->aifflags & AAC_AIFFLAGS_RUNNING) == 0, ("%s: invalid detach state", __func__)); /* Remove the child containers */ while ((co = TAILQ_FIRST(&sc->aac_container_tqh)) != NULL) { error = device_delete_child(dev, co->co_disk); if (error) return (error); TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); free(co, M_AACBUF); } /* Remove the CAM SIMs */ while ((sim = TAILQ_FIRST(&sc->aac_sim_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_sim_tqh, sim, sim_link); error = device_delete_child(dev, sim->sim_dev); if (error) return (error); free(sim, M_AACBUF); } if ((error = aac_shutdown(dev))) return(error); EVENTHANDLER_DEREGISTER(shutdown_final, sc->eh); aac_free(sc); mtx_destroy(&sc->aac_aifq_lock); mtx_destroy(&sc->aac_io_lock); mtx_destroy(&sc->aac_container_lock); return(0); } /* * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach or system shutdown. * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ int aac_shutdown(device_t dev) { struct aac_softc *sc; struct aac_fib *fib; struct aac_close_command *cc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; /* * Send a Container shutdown followed by a HostShutdown FIB to the * controller to convince it that we don't want to talk to it anymore. * We've been closed and all I/O completed already */ device_printf(sc->aac_dev, "shutting down controller..."); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); cc = (struct aac_close_command *)&fib->data[0]; bzero(cc, sizeof(struct aac_close_command)); cc->Command = VM_CloseAll; cc->ContainerId = 0xffffffff; if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_close_command))) printf("FAILED.\n"); else printf("done\n"); #if 0 else { fib->data[0] = 0; /* * XXX Issuing this command to the controller makes it shut down * but also keeps it from coming back up without a reset of the * PCI bus. This is not desirable if you are just unloading the * driver module with the intent to reload it later. */ if (aac_sync_fib(sc, FsaHostShutdown, AAC_FIBSTATE_SHUTDOWN, fib, 1)) { printf("FAILED.\n"); } else { printf("done.\n"); } } #endif AAC_MASK_INTERRUPTS(sc); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return(0); } /* * Bring the controller to a quiescent state, ready for system suspend. */ int aac_suspend(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; AAC_MASK_INTERRUPTS(sc); return(0); } /* * Bring the controller back to a state ready for operation. */ int aac_resume(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state &= ~AAC_STATE_SUSPEND; AAC_UNMASK_INTERRUPTS(sc); return(0); } /* * Interrupt handler for NEW_COMM interface. */ void aac_new_intr(void *arg) { struct aac_softc *sc; u_int32_t index, fast; struct aac_command *cm; struct aac_fib *fib; int i; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); while (1) { index = AAC_GET_OUTB_QUEUE(sc); if (index == 0xffffffff) index = AAC_GET_OUTB_QUEUE(sc); if (index == 0xffffffff) break; if (index & 2) { if (index == 0xfffffffe) { /* XXX This means that the controller wants * more work. Ignore it for now. */ continue; } /* AIF */ fib = (struct aac_fib *)malloc(sizeof *fib, M_AACBUF, M_NOWAIT | M_ZERO); if (fib == NULL) { /* If we're really this short on memory, * hopefully breaking out of the handler will * allow something to get freed. This * actually sucks a whole lot. */ break; } index &= ~2; for (i = 0; i < sizeof(struct aac_fib)/4; ++i) ((u_int32_t *)fib)[i] = AAC_MEM1_GETREG4(sc, index + i*4); aac_handle_aif(sc, fib); free(fib, M_AACBUF); /* * AIF memory is owned by the adapter, so let it * know that we are done with it. */ AAC_SET_OUTB_QUEUE(sc, index); AAC_CLEAR_ISTATUS(sc, AAC_DB_RESPONSE_READY); } else { fast = index & 1; cm = sc->aac_commands + (index >> 2); fib = cm->cm_fib; if (fast) { fib->Header.XferState |= AAC_FIBSTATE_DONEADAP; *((u_int32_t *)(fib->data)) = AAC_ERROR_NORMAL; } aac_remove_busy(cm); aac_unmap_command(cm); cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this * command */ wakeup(cm); } sc->flags &= ~AAC_QUEUE_FRZN; } } /* see if we can start some more I/O */ if ((sc->flags & AAC_QUEUE_FRZN) == 0) aac_startio(sc); mtx_unlock(&sc->aac_io_lock); } /* * Interrupt filter for !NEW_COMM interface. */ int aac_filter(void *arg) { struct aac_softc *sc; u_int16_t reason; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Read the status register directly. This is faster than taking the * driver lock and reading the queues directly. It also saves having * to turn parts of the driver lock into a spin mutex, which would be * ugly. */ reason = AAC_GET_ISTATUS(sc); AAC_CLEAR_ISTATUS(sc, reason); /* handle completion processing */ if (reason & AAC_DB_RESPONSE_READY) taskqueue_enqueue_fast(taskqueue_fast, &sc->aac_task_complete); /* controller wants to talk to us */ if (reason & (AAC_DB_PRINTF | AAC_DB_COMMAND_READY)) { /* * XXX Make sure that we don't get fooled by strange messages * that start with a NULL. */ if ((reason & AAC_DB_PRINTF) && (sc->aac_common->ac_printf[0] == 0)) sc->aac_common->ac_printf[0] = 32; /* * This might miss doing the actual wakeup. However, the * msleep that this is waking up has a timeout, so it will * wake up eventually. AIFs and printfs are low enough * priority that they can handle hanging out for a few seconds * if needed. */ wakeup(sc->aifthread); } return (FILTER_HANDLED); } /* * Command Processing */ /* * Start as much queued I/O as possible on the controller */ void aac_startio(struct aac_softc *sc) { struct aac_command *cm; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); for (;;) { /* * This flag might be set if the card is out of resources. * Checking it here prevents an infinite loop of deferrals. */ if (sc->flags & AAC_QUEUE_FRZN) break; /* * Try to get a command that's been put off for lack of * resources */ cm = aac_dequeue_ready(sc); /* * Try to build a command off the bio queue (ignore error * return) */ if (cm == NULL) aac_bio_command(sc, &cm); /* nothing to do? */ if (cm == NULL) break; /* don't map more than once */ if (cm->cm_flags & AAC_CMD_MAPPED) panic("aac: command %p already mapped", cm); /* * Set up the command to go to the controller. If there are no * data buffers associated with the command then it can bypass * busdma. */ if (cm->cm_datalen != 0) { if (cm->cm_flags & AAC_REQ_BIO) error = bus_dmamap_load_bio( sc->aac_buffer_dmat, cm->cm_datamap, (struct bio *)cm->cm_private, aac_map_command_sg, cm, 0); else error = bus_dmamap_load(sc->aac_buffer_dmat, cm->cm_datamap, cm->cm_data, cm->cm_datalen, aac_map_command_sg, cm, 0); if (error == EINPROGRESS) { fwprintf(sc, HBA_FLAGS_DBG_COMM_B, "freezing queue\n"); sc->flags |= AAC_QUEUE_FRZN; } else if (error != 0) panic("aac_startio: unexpected error %d from " "busdma", error); } else aac_map_command_sg(cm, NULL, 0, 0); } } /* * Handle notification of one or more FIBs coming from the controller. */ static void aac_command_thread(struct aac_softc *sc) { struct aac_fib *fib; u_int32_t fib_size; int size, retval; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); sc->aifflags = AAC_AIFFLAGS_RUNNING; while ((sc->aifflags & AAC_AIFFLAGS_EXIT) == 0) { retval = 0; if ((sc->aifflags & AAC_AIFFLAGS_PENDING) == 0) retval = msleep(sc->aifthread, &sc->aac_io_lock, PRIBIO, "aifthd", AAC_PERIODIC_INTERVAL * hz); /* * First see if any FIBs need to be allocated. This needs * to be called without the driver lock because contigmalloc * can sleep. */ if ((sc->aifflags & AAC_AIFFLAGS_ALLOCFIBS) != 0) { mtx_unlock(&sc->aac_io_lock); aac_alloc_commands(sc); mtx_lock(&sc->aac_io_lock); sc->aifflags &= ~AAC_AIFFLAGS_ALLOCFIBS; aac_startio(sc); } /* * While we're here, check to see if any commands are stuck. * This is pretty low-priority, so it's ok if it doesn't * always fire. */ if (retval == EWOULDBLOCK) aac_timeout(sc); /* Check the hardware printf message buffer */ if (sc->aac_common->ac_printf[0] != 0) aac_print_printf(sc); /* Also check to see if the adapter has a command for us. */ if (sc->flags & AAC_FLAGS_NEW_COMM) continue; for (;;) { if (aac_dequeue_fib(sc, AAC_HOST_NORM_CMD_QUEUE, &fib_size, &fib)) break; AAC_PRINT_FIB(sc, fib); switch (fib->Header.Command) { case AifRequest: aac_handle_aif(sc, fib); break; default: device_printf(sc->aac_dev, "unknown command " "from controller\n"); break; } if ((fib->Header.XferState == 0) || (fib->Header.StructType != AAC_FIBTYPE_TFIB)) { break; } /* Return the AIF to the controller. */ if (fib->Header.XferState & AAC_FIBSTATE_FROMADAP) { fib->Header.XferState |= AAC_FIBSTATE_DONEHOST; *(AAC_FSAStatus*)fib->data = ST_OK; /* XXX Compute the Size field? */ size = fib->Header.Size; if (size > sizeof(struct aac_fib)) { size = sizeof(struct aac_fib); fib->Header.Size = size; } /* * Since we did not generate this command, it * cannot go through the normal * enqueue->startio chain. */ aac_enqueue_response(sc, AAC_ADAP_NORM_RESP_QUEUE, fib); } } } sc->aifflags &= ~AAC_AIFFLAGS_RUNNING; mtx_unlock(&sc->aac_io_lock); wakeup(sc->aac_dev); kproc_exit(0); } /* * Process completed commands. */ static void aac_complete(void *context, int pending) { struct aac_softc *sc; struct aac_command *cm; struct aac_fib *fib; u_int32_t fib_size; sc = (struct aac_softc *)context; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); /* pull completed commands off the queue */ for (;;) { /* look for completed FIBs on our queue */ if (aac_dequeue_fib(sc, AAC_HOST_NORM_RESP_QUEUE, &fib_size, &fib)) break; /* nothing to do */ /* get the command, unmap and hand off for processing */ cm = sc->aac_commands + fib->Header.SenderData; if (cm == NULL) { AAC_PRINT_FIB(sc, fib); break; } if ((cm->cm_flags & AAC_CMD_TIMEDOUT) != 0) device_printf(sc->aac_dev, "COMMAND %p COMPLETED AFTER %d SECONDS\n", cm, (int)(time_uptime-cm->cm_timestamp)); aac_remove_busy(cm); aac_unmap_command(cm); cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this command */ wakeup(cm); } } /* see if we can start some more I/O */ sc->flags &= ~AAC_QUEUE_FRZN; aac_startio(sc); mtx_unlock(&sc->aac_io_lock); } /* * Handle a bio submitted from a disk device. */ void aac_submit_bio(struct bio *bp) { struct aac_disk *ad; struct aac_softc *sc; ad = (struct aac_disk *)bp->bio_disk->d_drv1; sc = ad->ad_controller; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* queue the BIO and try to get some work done */ aac_enqueue_bio(sc, bp); aac_startio(sc); } /* * Get a bio and build a command to go with it. */ static int aac_bio_command(struct aac_softc *sc, struct aac_command **cmp) { struct aac_command *cm; struct aac_fib *fib; struct aac_disk *ad; struct bio *bp; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* get the resources we will need */ cm = NULL; bp = NULL; if (aac_alloc_command(sc, &cm)) /* get a command */ goto fail; if ((bp = aac_dequeue_bio(sc)) == NULL) goto fail; /* fill out the command */ cm->cm_datalen = bp->bio_bcount; cm->cm_complete = aac_bio_complete; cm->cm_flags = AAC_REQ_BIO; cm->cm_private = bp; cm->cm_timestamp = time_uptime; /* build the FIB */ fib = cm->cm_fib; fib->Header.Size = sizeof(struct aac_fib_header); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; /* build the read/write request */ ad = (struct aac_disk *)bp->bio_disk->d_drv1; if (sc->flags & AAC_FLAGS_RAW_IO) { struct aac_raw_io *raw; raw = (struct aac_raw_io *)&fib->data[0]; fib->Header.Command = RawIo; raw->BlockNumber = (u_int64_t)bp->bio_pblkno; raw->ByteCount = bp->bio_bcount; raw->ContainerId = ad->ad_container->co_mntobj.ObjectId; raw->BpTotal = 0; raw->BpComplete = 0; fib->Header.Size += sizeof(struct aac_raw_io); cm->cm_sgtable = (struct aac_sg_table *)&raw->SgMapRaw; if (bp->bio_cmd == BIO_READ) { raw->Flags = 1; cm->cm_flags |= AAC_CMD_DATAIN; } else { raw->Flags = 0; cm->cm_flags |= AAC_CMD_DATAOUT; } } else if ((sc->flags & AAC_FLAGS_SG_64BIT) == 0) { fib->Header.Command = ContainerCommand; if (bp->bio_cmd == BIO_READ) { struct aac_blockread *br; br = (struct aac_blockread *)&fib->data[0]; br->Command = VM_CtBlockRead; br->ContainerId = ad->ad_container->co_mntobj.ObjectId; br->BlockNumber = bp->bio_pblkno; br->ByteCount = bp->bio_bcount; fib->Header.Size += sizeof(struct aac_blockread); cm->cm_sgtable = &br->SgMap; cm->cm_flags |= AAC_CMD_DATAIN; } else { struct aac_blockwrite *bw; bw = (struct aac_blockwrite *)&fib->data[0]; bw->Command = VM_CtBlockWrite; bw->ContainerId = ad->ad_container->co_mntobj.ObjectId; bw->BlockNumber = bp->bio_pblkno; bw->ByteCount = bp->bio_bcount; bw->Stable = CUNSTABLE; fib->Header.Size += sizeof(struct aac_blockwrite); cm->cm_flags |= AAC_CMD_DATAOUT; cm->cm_sgtable = &bw->SgMap; } } else { fib->Header.Command = ContainerCommand64; if (bp->bio_cmd == BIO_READ) { struct aac_blockread64 *br; br = (struct aac_blockread64 *)&fib->data[0]; br->Command = VM_CtHostRead64; br->ContainerId = ad->ad_container->co_mntobj.ObjectId; br->SectorCount = bp->bio_bcount / AAC_BLOCK_SIZE; br->BlockNumber = bp->bio_pblkno; br->Pad = 0; br->Flags = 0; fib->Header.Size += sizeof(struct aac_blockread64); cm->cm_flags |= AAC_CMD_DATAIN; cm->cm_sgtable = (struct aac_sg_table *)&br->SgMap64; } else { struct aac_blockwrite64 *bw; bw = (struct aac_blockwrite64 *)&fib->data[0]; bw->Command = VM_CtHostWrite64; bw->ContainerId = ad->ad_container->co_mntobj.ObjectId; bw->SectorCount = bp->bio_bcount / AAC_BLOCK_SIZE; bw->BlockNumber = bp->bio_pblkno; bw->Pad = 0; bw->Flags = 0; fib->Header.Size += sizeof(struct aac_blockwrite64); cm->cm_flags |= AAC_CMD_DATAOUT; cm->cm_sgtable = (struct aac_sg_table *)&bw->SgMap64; } } *cmp = cm; return(0); fail: if (bp != NULL) aac_enqueue_bio(sc, bp); if (cm != NULL) aac_release_command(cm); return(ENOMEM); } /* * Handle a bio-instigated command that has been completed. */ static void aac_bio_complete(struct aac_command *cm) { struct aac_blockread_response *brr; struct aac_blockwrite_response *bwr; struct bio *bp; AAC_FSAStatus status; /* fetch relevant status and then release the command */ bp = (struct bio *)cm->cm_private; if (bp->bio_cmd == BIO_READ) { brr = (struct aac_blockread_response *)&cm->cm_fib->data[0]; status = brr->Status; } else { bwr = (struct aac_blockwrite_response *)&cm->cm_fib->data[0]; status = bwr->Status; } aac_release_command(cm); /* fix up the bio based on status */ if (status == ST_OK) { bp->bio_resid = 0; } else { bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; } aac_biodone(bp); } /* * Submit a command to the controller, return when it completes. * XXX This is very dangerous! If the card has gone out to lunch, we could * be stuck here forever. At the same time, signals are not caught * because there is a risk that a signal could wakeup the sleep before * the card has a chance to complete the command. Since there is no way * to cancel a command that is in progress, we can't protect against the * card completing a command late and spamming the command and data * memory. So, we are held hostage until the command completes. */ static int aac_wait_command(struct aac_command *cm) { struct aac_softc *sc; int error; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* Put the command on the ready queue and get things going */ aac_enqueue_ready(cm); aac_startio(sc); error = msleep(cm, &sc->aac_io_lock, PRIBIO, "aacwait", 0); return(error); } /* *Command Buffer Management */ /* * Allocate a command. */ int aac_alloc_command(struct aac_softc *sc, struct aac_command **cmp) { struct aac_command *cm; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((cm = aac_dequeue_free(sc)) == NULL) { if (sc->total_fibs < sc->aac_max_fibs) { mtx_lock(&sc->aac_io_lock); sc->aifflags |= AAC_AIFFLAGS_ALLOCFIBS; mtx_unlock(&sc->aac_io_lock); wakeup(sc->aifthread); } return (EBUSY); } *cmp = cm; return(0); } /* * Release a command back to the freelist. */ void aac_release_command(struct aac_command *cm) { struct aac_event *event; struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* (re)initialize the command/FIB */ cm->cm_datalen = 0; cm->cm_sgtable = NULL; cm->cm_flags = 0; cm->cm_complete = NULL; cm->cm_private = NULL; cm->cm_queue = AAC_ADAP_NORM_CMD_QUEUE; cm->cm_fib->Header.XferState = AAC_FIBSTATE_EMPTY; cm->cm_fib->Header.StructType = AAC_FIBTYPE_TFIB; cm->cm_fib->Header.Flags = 0; cm->cm_fib->Header.SenderSize = cm->cm_sc->aac_max_fib_size; /* * These are duplicated in aac_start to cover the case where an * intermediate stage may have destroyed them. They're left * initialized here for debugging purposes only. */ cm->cm_fib->Header.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; cm->cm_fib->Header.SenderData = 0; aac_enqueue_free(cm); if ((event = TAILQ_FIRST(&sc->aac_ev_cmfree)) != NULL) { TAILQ_REMOVE(&sc->aac_ev_cmfree, event, ev_links); event->ev_callback(sc, event, event->ev_arg); } } /* * Map helper for command/FIB allocation. */ static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { uint64_t *fibphys; fibphys = (uint64_t *)arg; *fibphys = segs[0].ds_addr; } /* * Allocate and initialize commands/FIBs for this adapter. */ static int aac_alloc_commands(struct aac_softc *sc) { struct aac_command *cm; struct aac_fibmap *fm; uint64_t fibphys; int i, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (sc->total_fibs + sc->aac_max_fibs_alloc > sc->aac_max_fibs) return (ENOMEM); fm = malloc(sizeof(struct aac_fibmap), M_AACBUF, M_NOWAIT|M_ZERO); if (fm == NULL) return (ENOMEM); /* allocate the FIBs in DMAable memory and load them */ if (bus_dmamem_alloc(sc->aac_fib_dmat, (void **)&fm->aac_fibs, BUS_DMA_NOWAIT, &fm->aac_fibmap)) { device_printf(sc->aac_dev, "Not enough contiguous memory available.\n"); free(fm, M_AACBUF); return (ENOMEM); } /* Ignore errors since this doesn't bounce */ (void)bus_dmamap_load(sc->aac_fib_dmat, fm->aac_fibmap, fm->aac_fibs, sc->aac_max_fibs_alloc * sc->aac_max_fib_size, aac_map_command_helper, &fibphys, 0); /* initialize constant fields in the command structure */ bzero(fm->aac_fibs, sc->aac_max_fibs_alloc * sc->aac_max_fib_size); for (i = 0; i < sc->aac_max_fibs_alloc; i++) { cm = sc->aac_commands + sc->total_fibs; fm->aac_commands = cm; cm->cm_sc = sc; cm->cm_fib = (struct aac_fib *) ((u_int8_t *)fm->aac_fibs + i*sc->aac_max_fib_size); cm->cm_fibphys = fibphys + i*sc->aac_max_fib_size; cm->cm_index = sc->total_fibs; if ((error = bus_dmamap_create(sc->aac_buffer_dmat, 0, &cm->cm_datamap)) != 0) break; mtx_lock(&sc->aac_io_lock); aac_release_command(cm); sc->total_fibs++; mtx_unlock(&sc->aac_io_lock); } if (i > 0) { mtx_lock(&sc->aac_io_lock); TAILQ_INSERT_TAIL(&sc->aac_fibmap_tqh, fm, fm_link); fwprintf(sc, HBA_FLAGS_DBG_COMM_B, "total_fibs= %d\n", sc->total_fibs); mtx_unlock(&sc->aac_io_lock); return (0); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACBUF); return (ENOMEM); } /* * Free FIBs owned by this adapter. */ static void aac_free_commands(struct aac_softc *sc) { struct aac_fibmap *fm; struct aac_command *cm; int i; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); while ((fm = TAILQ_FIRST(&sc->aac_fibmap_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_fibmap_tqh, fm, fm_link); /* * We check against total_fibs to handle partially * allocated blocks. */ for (i = 0; i < sc->aac_max_fibs_alloc && sc->total_fibs--; i++) { cm = fm->aac_commands + i; bus_dmamap_destroy(sc->aac_buffer_dmat, cm->cm_datamap); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACBUF); } } /* * Command-mapping helper function - populate this command's s/g table. */ static void aac_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; struct aac_command *cm; struct aac_fib *fib; int i; cm = (struct aac_command *)arg; sc = cm->cm_sc; fib = cm->cm_fib; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* copy into the FIB */ if (cm->cm_sgtable != NULL) { if (fib->Header.Command == RawIo) { struct aac_sg_tableraw *sg; sg = (struct aac_sg_tableraw *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntryRaw[i].SgAddress = segs[i].ds_addr; sg->SgEntryRaw[i].SgByteCount = segs[i].ds_len; sg->SgEntryRaw[i].Next = 0; sg->SgEntryRaw[i].Prev = 0; sg->SgEntryRaw[i].Flags = 0; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entryraw); } else if ((cm->cm_sc->flags & AAC_FLAGS_SG_64BIT) == 0) { struct aac_sg_table *sg; sg = cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry[i].SgAddress = segs[i].ds_addr; sg->SgEntry[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry); } else { struct aac_sg_table64 *sg; sg = (struct aac_sg_table64 *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry64[i].SgAddress = segs[i].ds_addr; sg->SgEntry64[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry64); } } /* Fix up the address values in the FIB. Use the command array index * instead of a pointer since these fields are only 32 bits. Shift * the SenderFibAddress over to make room for the fast response bit * and for the AIF bit */ cm->cm_fib->Header.SenderFibAddress = (cm->cm_index << 2); cm->cm_fib->Header.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; /* save a pointer to the command for speedy reverse-lookup */ cm->cm_fib->Header.SenderData = cm->cm_index; if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREWRITE); cm->cm_flags |= AAC_CMD_MAPPED; if (sc->flags & AAC_FLAGS_NEW_COMM) { int count = 10000000L; while (AAC_SEND_COMMAND(sc, cm) != 0) { if (--count == 0) { aac_unmap_command(cm); sc->flags |= AAC_QUEUE_FRZN; aac_requeue_ready(cm); } DELAY(5); /* wait 5 usec. */ } } else { /* Put the FIB on the outbound queue */ if (aac_enqueue_fib(sc, cm->cm_queue, cm) == EBUSY) { aac_unmap_command(cm); sc->flags |= AAC_QUEUE_FRZN; aac_requeue_ready(cm); } } } /* * Unmap a command from controller-visible space. */ static void aac_unmap_command(struct aac_command *cm) { struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (!(cm->cm_flags & AAC_CMD_MAPPED)) return; if (cm->cm_datalen != 0) { if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->aac_buffer_dmat, cm->cm_datamap); } cm->cm_flags &= ~AAC_CMD_MAPPED; } /* * Hardware Interface */ /* * Initialize the adapter. */ static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_common_busaddr = segs[0].ds_addr; } static int aac_check_firmware(struct aac_softc *sc) { u_int32_t code, major, minor, options = 0, atu_size = 0; int rid, status; time_t then; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Wait for the adapter to come ready. */ then = time_uptime; do { code = AAC_GET_FWSTATUS(sc); if (code & AAC_SELF_TEST_FAILED) { device_printf(sc->aac_dev, "FATAL: selftest failed\n"); return(ENXIO); } if (code & AAC_KERNEL_PANIC) { device_printf(sc->aac_dev, "FATAL: controller kernel panic"); return(ENXIO); } if (time_uptime > (then + AAC_BOOT_TIMEOUT)) { device_printf(sc->aac_dev, "FATAL: controller not coming ready, " "status %x\n", code); return(ENXIO); } } while (!(code & AAC_UP_AND_RUNNING)); /* * Retrieve the firmware version numbers. Dell PERC2/QC cards with * firmware version 1.x are not compatible with this driver. */ if (sc->flags & AAC_FLAGS_PERC2QC) { if (aac_sync_command(sc, AAC_MONKER_GETKERNVER, 0, 0, 0, 0, NULL)) { device_printf(sc->aac_dev, "Error reading firmware version\n"); return (EIO); } /* These numbers are stored as ASCII! */ major = (AAC_GET_MAILBOX(sc, 1) & 0xff) - 0x30; minor = (AAC_GET_MAILBOX(sc, 2) & 0xff) - 0x30; if (major == 1) { device_printf(sc->aac_dev, "Firmware version %d.%d is not supported.\n", major, minor); return (EINVAL); } } /* * Retrieve the capabilities/supported options word so we know what * work-arounds to enable. Some firmware revs don't support this * command. */ if (aac_sync_command(sc, AAC_MONKER_GETINFO, 0, 0, 0, 0, &status)) { if (status != AAC_SRB_STS_INVALID_REQUEST) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); return (EIO); } } else { options = AAC_GET_MAILBOX(sc, 1); atu_size = AAC_GET_MAILBOX(sc, 2); sc->supported_options = options; if ((options & AAC_SUPPORTED_4GB_WINDOW) != 0 && (sc->flags & AAC_FLAGS_NO4GB) == 0) sc->flags |= AAC_FLAGS_4GB_WINDOW; if (options & AAC_SUPPORTED_NONDASD) sc->flags |= AAC_FLAGS_ENABLE_CAM; if ((options & AAC_SUPPORTED_SGMAP_HOST64) != 0 && (sizeof(bus_addr_t) > 4)) { device_printf(sc->aac_dev, "Enabling 64-bit address support\n"); sc->flags |= AAC_FLAGS_SG_64BIT; } if ((options & AAC_SUPPORTED_NEW_COMM) && sc->aac_if->aif_send_command) sc->flags |= AAC_FLAGS_NEW_COMM; if (options & AAC_SUPPORTED_64BIT_ARRAYSIZE) sc->flags |= AAC_FLAGS_ARRAY_64BIT; } /* Check for broken hardware that does a lower number of commands */ sc->aac_max_fibs = (sc->flags & AAC_FLAGS_256FIBS ? 256:512); /* Remap mem. resource, if required */ if ((sc->flags & AAC_FLAGS_NEW_COMM) && atu_size > rman_get_size(sc->aac_regs_res1)) { rid = rman_get_rid(sc->aac_regs_res1); bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, rid, sc->aac_regs_res1); sc->aac_regs_res1 = bus_alloc_resource(sc->aac_dev, SYS_RES_MEMORY, &rid, 0ul, ~0ul, atu_size, RF_ACTIVE); if (sc->aac_regs_res1 == NULL) { sc->aac_regs_res1 = bus_alloc_resource_any( sc->aac_dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->aac_regs_res1 == NULL) { device_printf(sc->aac_dev, "couldn't allocate register window\n"); return (ENXIO); } sc->flags &= ~AAC_FLAGS_NEW_COMM; } sc->aac_btag1 = rman_get_bustag(sc->aac_regs_res1); sc->aac_bhandle1 = rman_get_bushandle(sc->aac_regs_res1); if (sc->aac_hwif == AAC_HWIF_NARK) { sc->aac_regs_res0 = sc->aac_regs_res1; sc->aac_btag0 = sc->aac_btag1; sc->aac_bhandle0 = sc->aac_bhandle1; } } /* Read preferred settings */ sc->aac_max_fib_size = sizeof(struct aac_fib); sc->aac_max_sectors = 128; /* 64KB */ if (sc->flags & AAC_FLAGS_SG_64BIT) sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite64)) / sizeof(struct aac_sg_entry64); else sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite)) / sizeof(struct aac_sg_entry); if (!aac_sync_command(sc, AAC_MONKER_GETCOMMPREF, 0, 0, 0, 0, NULL)) { options = AAC_GET_MAILBOX(sc, 1); sc->aac_max_fib_size = (options & 0xFFFF); sc->aac_max_sectors = (options >> 16) << 1; options = AAC_GET_MAILBOX(sc, 2); sc->aac_sg_tablesize = (options >> 16); options = AAC_GET_MAILBOX(sc, 3); sc->aac_max_fibs = (options & 0xFFFF); } if (sc->aac_max_fib_size > PAGE_SIZE) sc->aac_max_fib_size = PAGE_SIZE; sc->aac_max_fibs_alloc = PAGE_SIZE / sc->aac_max_fib_size; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { sc->flags |= AAC_FLAGS_RAW_IO; device_printf(sc->aac_dev, "Enable Raw I/O\n"); } if ((sc->flags & AAC_FLAGS_RAW_IO) && (sc->flags & AAC_FLAGS_ARRAY_64BIT)) { sc->flags |= AAC_FLAGS_LBA_64BIT; device_printf(sc->aac_dev, "Enable 64-bit array\n"); } return (0); } static int aac_init(struct aac_softc *sc) { struct aac_adapter_init *ip; u_int32_t qoffset; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Fill in the init structure. This tells the adapter about the * physical location of various important shared data structures. */ ip = &sc->aac_common->ac_init; ip->InitStructRevision = AAC_INIT_STRUCT_REVISION; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { ip->InitStructRevision = AAC_INIT_STRUCT_REVISION_4; sc->flags |= AAC_FLAGS_RAW_IO; } ip->MiniPortRevision = AAC_INIT_STRUCT_MINIPORT_REVISION; ip->AdapterFibsPhysicalAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_fibs); ip->AdapterFibsVirtualAddress = 0; ip->AdapterFibsSize = AAC_ADAPTER_FIBS * sizeof(struct aac_fib); ip->AdapterFibAlign = sizeof(struct aac_fib); ip->PrintfBufferAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_printf); ip->PrintfBufferSize = AAC_PRINTF_BUFSIZE; /* * The adapter assumes that pages are 4K in size, except on some * broken firmware versions that do the page->byte conversion twice, * therefore 'assuming' that this value is in 16MB units (2^24). * Round up since the granularity is so high. */ ip->HostPhysMemPages = ctob(physmem) / AAC_PAGE_SIZE; if (sc->flags & AAC_FLAGS_BROKEN_MEMMAP) { ip->HostPhysMemPages = (ip->HostPhysMemPages + AAC_PAGE_SIZE) / AAC_PAGE_SIZE; } ip->HostElapsedSeconds = time_uptime; /* reset later if invalid */ ip->InitFlags = 0; if (sc->flags & AAC_FLAGS_NEW_COMM) { ip->InitFlags |= AAC_INITFLAGS_NEW_COMM_SUPPORTED; device_printf(sc->aac_dev, "New comm. interface enabled\n"); } ip->MaxIoCommands = sc->aac_max_fibs; ip->MaxIoSize = sc->aac_max_sectors << 9; ip->MaxFibSize = sc->aac_max_fib_size; /* * Initialize FIB queues. Note that it appears that the layout of the * indexes and the segmentation of the entries may be mandated by the * adapter, which is only told about the base of the queue index fields. * * The initial values of the indices are assumed to inform the adapter * of the sizes of the respective queues, and theoretically it could * work out the entire layout of the queue structures from this. We * take the easy route and just lay this area out like everyone else * does. * * The Linux driver uses a much more complex scheme whereby several * header records are kept for each queue. We use a couple of generic * list manipulation functions which 'know' the size of each list by * virtue of a table. */ qoffset = offsetof(struct aac_common, ac_qbuf) + AAC_QUEUE_ALIGN; qoffset &= ~(AAC_QUEUE_ALIGN - 1); sc->aac_queues = (struct aac_queue_table *)((uintptr_t)sc->aac_common + qoffset); ip->CommHeaderAddress = sc->aac_common_busaddr + qoffset; sc->aac_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_HOST_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_HOST_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_HOST_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_HOST_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_ADAP_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_ADAP_NORM_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_PRODUCER_INDEX] = AAC_ADAP_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_CMD_QUEUE][AAC_CONSUMER_INDEX] = AAC_ADAP_HIGH_CMD_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_HOST_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_HOST_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_HOST_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_HOST_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_HOST_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_ADAP_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_NORM_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_ADAP_NORM_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_PRODUCER_INDEX]= AAC_ADAP_HIGH_RESP_ENTRIES; sc->aac_queues->qt_qindex[AAC_ADAP_HIGH_RESP_QUEUE][AAC_CONSUMER_INDEX]= AAC_ADAP_HIGH_RESP_ENTRIES; sc->aac_qentries[AAC_HOST_NORM_CMD_QUEUE] = &sc->aac_queues->qt_HostNormCmdQueue[0]; sc->aac_qentries[AAC_HOST_HIGH_CMD_QUEUE] = &sc->aac_queues->qt_HostHighCmdQueue[0]; sc->aac_qentries[AAC_ADAP_NORM_CMD_QUEUE] = &sc->aac_queues->qt_AdapNormCmdQueue[0]; sc->aac_qentries[AAC_ADAP_HIGH_CMD_QUEUE] = &sc->aac_queues->qt_AdapHighCmdQueue[0]; sc->aac_qentries[AAC_HOST_NORM_RESP_QUEUE] = &sc->aac_queues->qt_HostNormRespQueue[0]; sc->aac_qentries[AAC_HOST_HIGH_RESP_QUEUE] = &sc->aac_queues->qt_HostHighRespQueue[0]; sc->aac_qentries[AAC_ADAP_NORM_RESP_QUEUE] = &sc->aac_queues->qt_AdapNormRespQueue[0]; sc->aac_qentries[AAC_ADAP_HIGH_RESP_QUEUE] = &sc->aac_queues->qt_AdapHighRespQueue[0]; /* * Do controller-type-specific initialisation */ switch (sc->aac_hwif) { case AAC_HWIF_I960RX: AAC_MEM0_SETREG4(sc, AAC_RX_ODBR, ~0); break; case AAC_HWIF_RKT: AAC_MEM0_SETREG4(sc, AAC_RKT_ODBR, ~0); break; default: break; } /* * Give the init structure to the controller. */ if (aac_sync_command(sc, AAC_MONKER_INITSTRUCT, sc->aac_common_busaddr + offsetof(struct aac_common, ac_init), 0, 0, 0, NULL)) { device_printf(sc->aac_dev, "error establishing init structure\n"); error = EIO; goto out; } error = 0; out: return(error); } static int aac_setup_intr(struct aac_softc *sc) { if (sc->flags & AAC_FLAGS_NEW_COMM) { if (bus_setup_intr(sc->aac_dev, sc->aac_irq, INTR_MPSAFE|INTR_TYPE_BIO, NULL, aac_new_intr, sc, &sc->aac_intr)) { device_printf(sc->aac_dev, "can't set up interrupt\n"); return (EINVAL); } } else { if (bus_setup_intr(sc->aac_dev, sc->aac_irq, INTR_TYPE_BIO, aac_filter, NULL, sc, &sc->aac_intr)) { device_printf(sc->aac_dev, "can't set up interrupt filter\n"); return (EINVAL); } } return (0); } /* * Send a synchronous command to the controller and wait for a result. * Indicate if the controller completed the command with an error status. */ static int aac_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp) { time_t then; u_int32_t status; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* populate the mailbox */ AAC_SET_MAILBOX(sc, command, arg0, arg1, arg2, arg3); /* ensure the sync command doorbell flag is cleared */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* then set it to signal the adapter */ AAC_QNOTIFY(sc, AAC_DB_SYNC_COMMAND); /* spin waiting for the command to complete */ then = time_uptime; do { if (time_uptime > (then + AAC_IMMEDIATE_TIMEOUT)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "timed out"); return(EIO); } } while (!(AAC_GET_ISTATUS(sc) & AAC_DB_SYNC_COMMAND)); /* clear the completion flag */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* get the command status */ status = AAC_GET_MAILBOX(sc, 0); if (sp != NULL) *sp = status; if (status != AAC_SRB_STS_SUCCESS) return (-1); return(0); } int aac_sync_fib(struct aac_softc *sc, u_int32_t command, u_int32_t xferstate, struct aac_fib *fib, u_int16_t datasize) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); if (datasize > AAC_FIB_DATASIZE) return(EINVAL); /* * Set up the sync FIB */ fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY; fib->Header.XferState |= xferstate; fib->Header.Command = command; fib->Header.StructType = AAC_FIBTYPE_TFIB; fib->Header.Size = sizeof(struct aac_fib_header) + datasize; fib->Header.SenderSize = sizeof(struct aac_fib); fib->Header.SenderFibAddress = 0; /* Not needed */ fib->Header.ReceiverFibAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_sync_fib); /* * Give the FIB to the controller, wait for a response. */ if (aac_sync_command(sc, AAC_MONKER_SYNCFIB, fib->Header.ReceiverFibAddress, 0, 0, 0, NULL)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "IO error"); return(EIO); } return (0); } /* * Adapter-space FIB queue manipulation * * Note that the queue implementation here is a little funky; neither the PI or * CI will ever be zero. This behaviour is a controller feature. */ static const struct { int size; int notify; } aac_qinfo[] = { {AAC_HOST_NORM_CMD_ENTRIES, AAC_DB_COMMAND_NOT_FULL}, {AAC_HOST_HIGH_CMD_ENTRIES, 0}, {AAC_ADAP_NORM_CMD_ENTRIES, AAC_DB_COMMAND_READY}, {AAC_ADAP_HIGH_CMD_ENTRIES, 0}, {AAC_HOST_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_NOT_FULL}, {AAC_HOST_HIGH_RESP_ENTRIES, 0}, {AAC_ADAP_NORM_RESP_ENTRIES, AAC_DB_RESPONSE_READY}, {AAC_ADAP_HIGH_RESP_ENTRIES, 0} }; /* * Atomically insert an entry into the nominated queue, returns 0 on success or * EBUSY if the queue is full. * * Note: it would be more efficient to defer notifying the controller in * the case where we may be inserting several entries in rapid succession, * but implementing this usefully may be difficult (it would involve a * separate queue/notify interface). */ static int aac_enqueue_fib(struct aac_softc *sc, int queue, struct aac_command *cm) { u_int32_t pi, ci; int error; u_int32_t fib_size; u_int32_t fib_addr; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); fib_size = cm->cm_fib->Header.Size; fib_addr = cm->cm_fib->Header.ReceiverFibAddress; /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* wrap the queue? */ if (pi >= aac_qinfo[queue].size) pi = 0; /* check for queue full */ if ((pi + 1) == ci) { error = EBUSY; goto out; } /* * To avoid a race with its completion interrupt, place this command on * the busy queue prior to advertising it to the controller. */ aac_enqueue_busy(cm); /* populate queue entry */ (sc->aac_qentries[queue] + pi)->aq_fib_size = fib_size; (sc->aac_qentries[queue] + pi)->aq_fib_addr = fib_addr; /* update producer index */ sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX] = pi + 1; /* notify the adapter if we know how */ if (aac_qinfo[queue].notify != 0) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Atomically remove one entry from the nominated queue, returns 0 on * success or ENOENT if the queue is empty. */ static int aac_dequeue_fib(struct aac_softc *sc, int queue, u_int32_t *fib_size, struct aac_fib **fib_addr) { u_int32_t pi, ci; u_int32_t fib_index; int error; int notify; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* check for queue empty */ if (ci == pi) { error = ENOENT; goto out; } /* wrap the pi so the following test works */ if (pi >= aac_qinfo[queue].size) pi = 0; notify = 0; if (ci == pi + 1) notify++; /* wrap the queue? */ if (ci >= aac_qinfo[queue].size) ci = 0; /* fetch the entry */ *fib_size = (sc->aac_qentries[queue] + ci)->aq_fib_size; switch (queue) { case AAC_HOST_NORM_CMD_QUEUE: case AAC_HOST_HIGH_CMD_QUEUE: /* * The aq_fib_addr is only 32 bits wide so it can't be counted * on to hold an address. For AIF's, the adapter assumes * that it's giving us an address into the array of AIF fibs. * Therefore, we have to convert it to an index. */ fib_index = (sc->aac_qentries[queue] + ci)->aq_fib_addr / sizeof(struct aac_fib); *fib_addr = &sc->aac_common->ac_fibs[fib_index]; break; case AAC_HOST_NORM_RESP_QUEUE: case AAC_HOST_HIGH_RESP_QUEUE: { struct aac_command *cm; /* * As above, an index is used instead of an actual address. * Gotta shift the index to account for the fast response * bit. No other correction is needed since this value was * originally provided by the driver via the SenderFibAddress * field. */ fib_index = (sc->aac_qentries[queue] + ci)->aq_fib_addr; cm = sc->aac_commands + (fib_index >> 2); *fib_addr = cm->cm_fib; /* * Is this a fast response? If it is, update the fib fields in * local memory since the whole fib isn't DMA'd back up. */ if (fib_index & 0x01) { (*fib_addr)->Header.XferState |= AAC_FIBSTATE_DONEADAP; *((u_int32_t*)((*fib_addr)->data)) = AAC_ERROR_NORMAL; } break; } default: panic("Invalid queue in aac_dequeue_fib()"); break; } /* update consumer index */ sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX] = ci + 1; /* if we have made the queue un-full, notify the adapter */ if (notify && (aac_qinfo[queue].notify != 0)) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Put our response to an Adapter Initialed Fib on the response queue */ static int aac_enqueue_response(struct aac_softc *sc, int queue, struct aac_fib *fib) { u_int32_t pi, ci; int error; u_int32_t fib_size; u_int32_t fib_addr; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* Tell the adapter where the FIB is */ fib_size = fib->Header.Size; fib_addr = fib->Header.SenderFibAddress; fib->Header.ReceiverFibAddress = fib_addr; /* get the producer/consumer indices */ pi = sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX]; ci = sc->aac_queues->qt_qindex[queue][AAC_CONSUMER_INDEX]; /* wrap the queue? */ if (pi >= aac_qinfo[queue].size) pi = 0; /* check for queue full */ if ((pi + 1) == ci) { error = EBUSY; goto out; } /* populate queue entry */ (sc->aac_qentries[queue] + pi)->aq_fib_size = fib_size; (sc->aac_qentries[queue] + pi)->aq_fib_addr = fib_addr; /* update producer index */ sc->aac_queues->qt_qindex[queue][AAC_PRODUCER_INDEX] = pi + 1; /* notify the adapter if we know how */ if (aac_qinfo[queue].notify != 0) AAC_QNOTIFY(sc, aac_qinfo[queue].notify); error = 0; out: return(error); } /* * Check for commands that have been outstanding for a suspiciously long time, * and complain about them. */ static void aac_timeout(struct aac_softc *sc) { struct aac_command *cm; time_t deadline; int timedout, code; /* * Traverse the busy command list, bitch about late commands once * only. */ timedout = 0; deadline = time_uptime - AAC_CMD_TIMEOUT; TAILQ_FOREACH(cm, &sc->aac_busy, cm_link) { if ((cm->cm_timestamp < deadline) && !(cm->cm_flags & AAC_CMD_TIMEDOUT)) { cm->cm_flags |= AAC_CMD_TIMEDOUT; device_printf(sc->aac_dev, "COMMAND %p (TYPE %d) TIMEOUT AFTER %d SECONDS\n", cm, cm->cm_fib->Header.Command, (int)(time_uptime-cm->cm_timestamp)); AAC_PRINT_FIB(sc, cm->cm_fib); timedout++; } } if (timedout) { code = AAC_GET_FWSTATUS(sc); if (code != AAC_UP_AND_RUNNING) { device_printf(sc->aac_dev, "WARNING! Controller is no " "longer running! code= 0x%x\n", code); } } } /* * Interface Function Vectors */ /* * Read the current firmware status word. */ static int aac_sa_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_SA_FWSTATUS)); } static int aac_rx_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, sc->flags & AAC_FLAGS_NEW_COMM ? AAC_RX_OMR0 : AAC_RX_FWSTATUS)); } static int aac_rkt_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, sc->flags & AAC_FLAGS_NEW_COMM ? AAC_RKT_OMR0 : AAC_RKT_FWSTATUS)); } /* * Notify the controller of a change in a given queue */ static void aac_sa_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG2(sc, AAC_SA_DOORBELL1_SET, qbit); } static void aac_rx_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_IDBR, qbit); } static void aac_rkt_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_IDBR, qbit); } /* * Get the interrupt reason bits */ static int aac_sa_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG2(sc, AAC_SA_DOORBELL0)); } static int aac_rx_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RX_ODBR)); } static int aac_rkt_get_istatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RKT_ODBR)); } /* * Clear some interrupt reason bits */ static void aac_sa_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG2(sc, AAC_SA_DOORBELL0_CLEAR, mask); } static void aac_rx_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_ODBR, mask); } static void aac_rkt_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_ODBR, mask); } /* * Populate the mailbox and set the command word */ static void aac_sa_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_SA_MAILBOX + 16, arg3); } static void aac_rx_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_RX_MAILBOX + 16, arg3); } static void aac_rkt_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX, command); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 4, arg0); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 8, arg1); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 12, arg2); AAC_MEM1_SETREG4(sc, AAC_RKT_MAILBOX + 16, arg3); } /* * Fetch the immediate command status word */ static int aac_sa_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_SA_MAILBOX + (mb * 4))); } static int aac_rx_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_RX_MAILBOX + (mb * 4))); } static int aac_rkt_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM1_GETREG4(sc, AAC_RKT_MAILBOX + (mb * 4))); } /* * Set/clear interrupt masks */ static void aac_sa_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { AAC_MEM0_SETREG2((sc), AAC_SA_MASK0_CLEAR, AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG2((sc), AAC_SA_MASK0_SET, ~0); } } static void aac_rx_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { if (sc->flags & AAC_FLAGS_NEW_COMM) AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~AAC_DB_INT_NEW_COMM); else AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG4(sc, AAC_RX_OIMR, ~0); } } static void aac_rkt_set_interrupts(struct aac_softc *sc, int enable) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "%sable interrupts", enable ? "en" : "dis"); if (enable) { if (sc->flags & AAC_FLAGS_NEW_COMM) AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~AAC_DB_INT_NEW_COMM); else AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~AAC_DB_INTERRUPTS); } else { AAC_MEM0_SETREG4(sc, AAC_RKT_OIMR, ~0); } } /* * New comm. interface: Send command functions */ static int aac_rx_send_command(struct aac_softc *sc, struct aac_command *cm) { u_int32_t index, device; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "send command (new comm.)"); index = AAC_MEM0_GETREG4(sc, AAC_RX_IQUE); if (index == 0xffffffffL) index = AAC_MEM0_GETREG4(sc, AAC_RX_IQUE); if (index == 0xffffffffL) return index; aac_enqueue_busy(cm); device = index; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys & 0xffffffffUL)); device += 4; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys >> 32)); device += 4; AAC_MEM1_SETREG4(sc, device, cm->cm_fib->Header.Size); AAC_MEM0_SETREG4(sc, AAC_RX_IQUE, index); return 0; } static int aac_rkt_send_command(struct aac_softc *sc, struct aac_command *cm) { u_int32_t index, device; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "send command (new comm.)"); index = AAC_MEM0_GETREG4(sc, AAC_RKT_IQUE); if (index == 0xffffffffL) index = AAC_MEM0_GETREG4(sc, AAC_RKT_IQUE); if (index == 0xffffffffL) return index; aac_enqueue_busy(cm); device = index; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys & 0xffffffffUL)); device += 4; AAC_MEM1_SETREG4(sc, device, (u_int32_t)(cm->cm_fibphys >> 32)); device += 4; AAC_MEM1_SETREG4(sc, device, cm->cm_fib->Header.Size); AAC_MEM0_SETREG4(sc, AAC_RKT_IQUE, index); return 0; } /* * New comm. interface: get, set outbound queue index */ static int aac_rx_get_outb_queue(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RX_OQUE)); } static int aac_rkt_get_outb_queue(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_RKT_OQUE)); } static void aac_rx_set_outb_queue(struct aac_softc *sc, int index) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RX_OQUE, index); } static void aac_rkt_set_outb_queue(struct aac_softc *sc, int index) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_RKT_OQUE, index); } /* * Debugging and Diagnostics */ /* * Print some information about the controller. */ static void aac_describe_controller(struct aac_softc *sc) { struct aac_fib *fib; struct aac_adapter_info *info; char *adapter_type = "Adaptec RAID controller"; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); fib->data[0] = 0; if (aac_sync_fib(sc, RequestAdapterInfo, 0, fib, 1)) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } /* save the kernel revision structure for later use */ info = (struct aac_adapter_info *)&fib->data[0]; sc->aac_revision = info->KernelRevision; if (bootverbose) { device_printf(sc->aac_dev, "%s %dMHz, %dMB memory " "(%dMB cache, %dMB execution), %s\n", aac_describe_code(aac_cpu_variant, info->CpuVariant), info->ClockSpeed, info->TotalMem / (1024 * 1024), info->BufferMem / (1024 * 1024), info->ExecutionMem / (1024 * 1024), aac_describe_code(aac_battery_platform, info->batteryPlatform)); device_printf(sc->aac_dev, "Kernel %d.%d-%d, Build %d, S/N %6X\n", info->KernelRevision.external.comp.major, info->KernelRevision.external.comp.minor, info->KernelRevision.external.comp.dash, info->KernelRevision.buildNumber, (u_int32_t)(info->SerialNumber & 0xffffff)); device_printf(sc->aac_dev, "Supported Options=%b\n", sc->supported_options, "\20" "\1SNAPSHOT" "\2CLUSTERS" "\3WCACHE" "\4DATA64" "\5HOSTTIME" "\6RAID50" "\7WINDOW4GB" "\10SCSIUPGD" "\11SOFTERR" "\12NORECOND" "\13SGMAP64" "\14ALARM" "\15NONDASD" "\16SCSIMGT" "\17RAIDSCSI" "\21ADPTINFO" "\22NEWCOMM" "\23ARRAY64BIT" "\24HEATSENSOR"); } if (sc->supported_options & AAC_SUPPORTED_SUPPLEMENT_ADAPTER_INFO) { fib->data[0] = 0; if (aac_sync_fib(sc, RequestSupplementAdapterInfo, 0, fib, 1)) device_printf(sc->aac_dev, "RequestSupplementAdapterInfo failed\n"); else adapter_type = ((struct aac_supplement_adapter_info *) &fib->data[0])->AdapterTypeText; } device_printf(sc->aac_dev, "%s, aac driver %d.%d.%d-%d\n", adapter_type, AAC_DRIVER_MAJOR_VERSION, AAC_DRIVER_MINOR_VERSION, AAC_DRIVER_BUGFIX_LEVEL, AAC_DRIVER_BUILD); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); } /* * Look up a text description of a numeric error code and return a pointer to * same. */ static const char * aac_describe_code(const struct aac_code_lookup *table, u_int32_t code) { int i; for (i = 0; table[i].string != NULL; i++) if (table[i].code == code) return(table[i].string); return(table[i + 1].string); } /* * Management Interface */ static int aac_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct aac_softc *sc; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); device_busy(sc->aac_dev); devfs_set_cdevpriv(sc, aac_cdevpriv_dtor); return 0; } static int aac_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { union aac_statrequest *as; struct aac_softc *sc; int error = 0; as = (union aac_statrequest *)arg; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); switch (cmd) { case AACIO_STATS: switch (as->as_item) { case AACQ_FREE: case AACQ_BIO: case AACQ_READY: case AACQ_BUSY: bcopy(&sc->aac_qstat[as->as_item], &as->as_qstat, sizeof(struct aac_qstat)); break; default: error = ENOENT; break; } break; case FSACTL_SENDFIB: case FSACTL_SEND_LARGE_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_SENDFIB: case FSACTL_LNX_SEND_LARGE_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SENDFIB"); error = aac_ioctl_sendfib(sc, arg); break; case FSACTL_SEND_RAW_SRB: arg = *(caddr_t*)arg; case FSACTL_LNX_SEND_RAW_SRB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SEND_RAW_SRB"); error = aac_ioctl_send_raw_srb(sc, arg); break; case FSACTL_AIF_THREAD: case FSACTL_LNX_AIF_THREAD: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_AIF_THREAD"); error = EINVAL; break; case FSACTL_OPEN_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_OPEN_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_OPEN_GET_ADAPTER_FIB"); error = aac_open_aif(sc, arg); break; case FSACTL_GET_NEXT_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_NEXT_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_NEXT_ADAPTER_FIB"); error = aac_getnext_aif(sc, arg); break; case FSACTL_CLOSE_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_CLOSE_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_CLOSE_GET_ADAPTER_FIB"); error = aac_close_aif(sc, arg); break; case FSACTL_MINIPORT_REV_CHECK: arg = *(caddr_t*)arg; case FSACTL_LNX_MINIPORT_REV_CHECK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_MINIPORT_REV_CHECK"); error = aac_rev_check(sc, arg); break; case FSACTL_QUERY_DISK: arg = *(caddr_t*)arg; case FSACTL_LNX_QUERY_DISK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_QUERY_DISK"); error = aac_query_disk(sc, arg); break; case FSACTL_DELETE_DISK: case FSACTL_LNX_DELETE_DISK: /* * We don't trust the underland to tell us when to delete a * container, rather we rely on an AIF coming from the * controller */ error = 0; break; case FSACTL_GET_PCI_INFO: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_PCI_INFO: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_PCI_INFO"); error = aac_get_pci_info(sc, arg); break; case FSACTL_GET_FEATURES: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_FEATURES: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_FEATURES"); error = aac_supported_features(sc, arg); break; default: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "unsupported cmd 0x%lx\n", cmd); error = EINVAL; break; } return(error); } static int aac_poll(struct cdev *dev, int poll_events, struct thread *td) { struct aac_softc *sc; struct aac_fib_context *ctx; int revents; sc = dev->si_drv1; revents = 0; mtx_lock(&sc->aac_aifq_lock); if ((poll_events & (POLLRDNORM | POLLIN)) != 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->ctx_idx != sc->aifq_idx || ctx->ctx_wrap) { revents |= poll_events & (POLLIN | POLLRDNORM); break; } } } mtx_unlock(&sc->aac_aifq_lock); if (revents == 0) { if (poll_events & (POLLIN | POLLRDNORM)) selrecord(td, &sc->rcv_select); } return (revents); } static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg) { switch (event->ev_type) { case AAC_EVENT_CMFREE: mtx_assert(&sc->aac_io_lock, MA_OWNED); if (aac_alloc_command(sc, (struct aac_command **)arg)) { aac_add_event(sc, event); return; } free(event, M_AACBUF); wakeup(arg); break; default: break; } } /* * Send a FIB supplied from userspace */ static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib) { struct aac_command *cm; int size, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; /* * Get a command */ mtx_lock(&sc->aac_io_lock); if (aac_alloc_command(sc, &cm)) { struct aac_event *event; event = malloc(sizeof(struct aac_event), M_AACBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aac_add_event(sc, event); msleep(&cm, &sc->aac_io_lock, 0, "sendfib", 0); } mtx_unlock(&sc->aac_io_lock); /* * Fetch the FIB header, then re-copy to get data as well. */ if ((error = copyin(ufib, cm->cm_fib, sizeof(struct aac_fib_header))) != 0) goto out; size = cm->cm_fib->Header.Size + sizeof(struct aac_fib_header); if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "incoming FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } if ((error = copyin(ufib, cm->cm_fib, size)) != 0) goto out; cm->cm_fib->Header.Size = size; cm->cm_timestamp = time_uptime; /* * Pass the FIB to the controller, wait for it to complete. */ mtx_lock(&sc->aac_io_lock); error = aac_wait_command(cm); mtx_unlock(&sc->aac_io_lock); if (error != 0) { device_printf(sc->aac_dev, "aac_wait_command return %d\n", error); goto out; } /* * Copy the FIB and data back out to the caller. */ size = cm->cm_fib->Header.Size; if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "outbound FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } error = copyout(cm->cm_fib, ufib, size); out: if (cm != NULL) { mtx_lock(&sc->aac_io_lock); aac_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * Send a passthrough FIB supplied from userspace */ static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg) { struct aac_command *cm; struct aac_event *event; struct aac_fib *fib; struct aac_srb *srbcmd, *user_srb; struct aac_sg_entry *sge; struct aac_sg_entry64 *sge64; void *srb_sg_address, *ureply; uint32_t fibsize, srb_sg_bytecount; int error, transfer_data; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; transfer_data = 0; fibsize = 0; user_srb = (struct aac_srb *)arg; mtx_lock(&sc->aac_io_lock); if (aac_alloc_command(sc, &cm)) { event = malloc(sizeof(struct aac_event), M_AACBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aac_add_event(sc, event); msleep(cm, &sc->aac_io_lock, 0, "aacraw", 0); } mtx_unlock(&sc->aac_io_lock); cm->cm_data = NULL; fib = cm->cm_fib; srbcmd = (struct aac_srb *)fib->data; error = copyin(&user_srb->data_len, &fibsize, sizeof(uint32_t)); if (error != 0) goto out; if (fibsize > (sc->aac_max_fib_size - sizeof(struct aac_fib_header))) { error = EINVAL; goto out; } error = copyin(user_srb, srbcmd, fibsize); if (error != 0) goto out; srbcmd->function = 0; srbcmd->retry_limit = 0; if (srbcmd->sg_map.SgCount > 1) { error = EINVAL; goto out; } /* Retrieve correct SG entries. */ if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry))) { sge = srbcmd->sg_map.SgEntry; sge64 = NULL; srb_sg_bytecount = sge->SgByteCount; srb_sg_address = (void *)(uintptr_t)sge->SgAddress; } #ifdef __amd64__ else if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry64))) { sge = NULL; sge64 = (struct aac_sg_entry64 *)srbcmd->sg_map.SgEntry; srb_sg_bytecount = sge64->SgByteCount; srb_sg_address = (void *)sge64->SgAddress; if (sge64->SgAddress > 0xffffffffull && (sc->flags & AAC_FLAGS_SG_64BIT) == 0) { error = EINVAL; goto out; } } #endif else { error = EINVAL; goto out; } ureply = (char *)arg + fibsize; srbcmd->data_len = srb_sg_bytecount; if (srbcmd->sg_map.SgCount == 1) transfer_data = 1; cm->cm_sgtable = (struct aac_sg_table *)&srbcmd->sg_map; if (transfer_data) { cm->cm_datalen = srb_sg_bytecount; cm->cm_data = malloc(cm->cm_datalen, M_AACBUF, M_NOWAIT); if (cm->cm_data == NULL) { error = ENOMEM; goto out; } if (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN) cm->cm_flags |= AAC_CMD_DATAIN; if (srbcmd->flags & AAC_SRB_FLAGS_DATA_OUT) { cm->cm_flags |= AAC_CMD_DATAOUT; error = copyin(srb_sg_address, cm->cm_data, cm->cm_datalen); if (error != 0) goto out; } } fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(struct aac_srb); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; fib->Header.Command = (sc->flags & AAC_FLAGS_SG_64BIT) != 0 ? ScsiPortCommandU64 : ScsiPortCommand; mtx_lock(&sc->aac_io_lock); aac_wait_command(cm); mtx_unlock(&sc->aac_io_lock); if (transfer_data && (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN) != 0) { error = copyout(cm->cm_data, srb_sg_address, cm->cm_datalen); if (error != 0) goto out; } error = copyout(fib->data, ureply, sizeof(struct aac_srb_response)); out: if (cm != NULL) { if (cm->cm_data != NULL) free(cm->cm_data, M_AACBUF); mtx_lock(&sc->aac_io_lock); aac_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * cdevpriv interface private destructor. */ static void aac_cdevpriv_dtor(void *arg) { struct aac_softc *sc; sc = arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&Giant); device_unbusy(sc->aac_dev); mtx_unlock(&Giant); } /* * Handle an AIF sent to us by the controller; queue it for later reference. * If the queue fills up, then drop the older entries. */ static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib) { struct aac_aif_command *aif; struct aac_container *co, *co_next; struct aac_fib_context *ctx; struct aac_mntinforesp *mir; int next, current, found; int count = 0, added = 0, i = 0; uint32_t channel; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); aif = (struct aac_aif_command*)&fib->data[0]; aac_print_aif(sc, aif); /* Is it an event that we should care about? */ switch (aif->command) { case AifCmdEventNotify: switch (aif->data.EN.type) { case AifEnAddContainer: case AifEnDeleteContainer: /* * A container was added or deleted, but the message * doesn't tell us anything else! Re-enumerate the * containers and sort things out. */ aac_alloc_sync_fib(sc, &fib); do { /* * Ask the controller for its containers one at * a time. * XXX What if the controller's list changes * midway through this enumaration? * XXX This should be done async. */ if ((mir = aac_get_container_info(sc, fib, i)) == NULL) continue; if (i == 0) count = mir->MntRespCount; /* * Check the container against our list. * co->co_found was already set to 0 in a * previous run. */ if ((mir->Status == ST_OK) && (mir->MntTable[0].VolType != CT_NONE)) { found = 0; TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == mir->MntTable[0].ObjectId) { co->co_found = 1; found = 1; break; } } /* * If the container matched, continue * in the list. */ if (found) { i++; continue; } /* * This is a new container. Do all the * appropriate things to set it up. */ aac_add_container(sc, mir, 1); added = 1; } i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); /* * Go through our list of containers and see which ones * were not marked 'found'. Since the controller didn't * list them they must have been deleted. Do the * appropriate steps to destroy the device. Also reset * the co->co_found field. */ co = TAILQ_FIRST(&sc->aac_container_tqh); while (co != NULL) { if (co->co_found == 0) { mtx_unlock(&sc->aac_io_lock); mtx_lock(&Giant); device_delete_child(sc->aac_dev, co->co_disk); mtx_unlock(&Giant); mtx_lock(&sc->aac_io_lock); co_next = TAILQ_NEXT(co, co_link); mtx_lock(&sc->aac_container_lock); TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); mtx_unlock(&sc->aac_container_lock); free(co, M_AACBUF); co = co_next; } else { co->co_found = 0; co = TAILQ_NEXT(co, co_link); } } /* Attach the newly created containers */ if (added) { mtx_unlock(&sc->aac_io_lock); mtx_lock(&Giant); bus_generic_attach(sc->aac_dev); mtx_unlock(&Giant); mtx_lock(&sc->aac_io_lock); } break; case AifEnEnclosureManagement: switch (aif->data.EN.data.EEE.eventType) { case AIF_EM_DRIVE_INSERTION: case AIF_EM_DRIVE_REMOVAL: channel = aif->data.EN.data.EEE.unitID; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, (channel >> 24) & 0xF, (channel & 0xFFFF)); break; } break; case AifEnAddJBOD: case AifEnDeleteJBOD: channel = aif->data.EN.data.ECE.container; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, (channel >> 24) & 0xF, AAC_CAM_TARGET_WILDCARD); break; default: break; } default: break; } /* Copy the AIF data to the AIF queue for ioctl retrieval */ mtx_lock(&sc->aac_aifq_lock); current = sc->aifq_idx; next = (current + 1) % AAC_AIFQ_LENGTH; if (next == 0) sc->aifq_filled = 1; bcopy(fib, &sc->aac_aifq[current], sizeof(struct aac_fib)); /* modify AIF contexts */ if (sc->aifq_filled) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (next == ctx->ctx_idx) ctx->ctx_wrap = 1; else if (current == ctx->ctx_idx && ctx->ctx_wrap) ctx->ctx_idx = next; } } sc->aifq_idx = next; /* On the off chance that someone is sleeping for an aif... */ if (sc->aac_state & AAC_STATE_AIF_SLEEPER) wakeup(sc->aac_aifq); /* Wakeup any poll()ers */ selwakeuppri(&sc->rcv_select, PRIBIO); mtx_unlock(&sc->aac_aifq_lock); } /* * Return the Revision of the driver to userspace and check to see if the * userspace app is possibly compatible. This is extremely bogus since * our driver doesn't follow Adaptec's versioning system. Cheat by just * returning what the card reported. */ static int aac_rev_check(struct aac_softc *sc, caddr_t udata) { struct aac_rev_check rev_check; struct aac_rev_check_resp rev_check_resp; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Copyin the revision struct from userspace */ if ((error = copyin(udata, (caddr_t)&rev_check, sizeof(struct aac_rev_check))) != 0) { return error; } fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "Userland revision= %d\n", rev_check.callingRevision.buildNumber); /* * Doctor up the response struct. */ rev_check_resp.possiblyCompatible = 1; rev_check_resp.adapterSWRevision.external.comp.major = AAC_DRIVER_MAJOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.minor = AAC_DRIVER_MINOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.type = AAC_DRIVER_TYPE; rev_check_resp.adapterSWRevision.external.comp.dash = AAC_DRIVER_BUGFIX_LEVEL; rev_check_resp.adapterSWRevision.buildNumber = AAC_DRIVER_BUILD; return(copyout((caddr_t)&rev_check_resp, udata, sizeof(struct aac_rev_check_resp))); } /* * Pass the fib context to the caller */ static int aac_open_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *fibctx, *ctx; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); fibctx = malloc(sizeof(struct aac_fib_context), M_AACBUF, M_NOWAIT|M_ZERO); if (fibctx == NULL) return (ENOMEM); mtx_lock(&sc->aac_aifq_lock); /* all elements are already 0, add to queue */ if (sc->fibctx == NULL) sc->fibctx = fibctx; else { for (ctx = sc->fibctx; ctx->next; ctx = ctx->next) ; ctx->next = fibctx; fibctx->prev = ctx; } /* evaluate unique value */ fibctx->unique = (*(u_int32_t *)&fibctx & 0xffffffff); ctx = sc->fibctx; while (ctx != fibctx) { if (ctx->unique == fibctx->unique) { fibctx->unique++; ctx = sc->fibctx; } else { ctx = ctx->next; } } mtx_unlock(&sc->aac_aifq_lock); error = copyout(&fibctx->unique, (void *)arg, sizeof(u_int32_t)); if (error) aac_close_aif(sc, (caddr_t)ctx); return error; } /* * Close the caller's fib context */ static int aac_close_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *ctx; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_aifq_lock); for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->unique == *(uint32_t *)&arg) { if (ctx == sc->fibctx) sc->fibctx = NULL; else { ctx->prev->next = ctx->next; if (ctx->next) ctx->next->prev = ctx->prev; } break; } } mtx_unlock(&sc->aac_aifq_lock); if (ctx) free(ctx, M_AACBUF); return 0; } /* * Pass the caller the next AIF in their queue */ static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg) { struct get_adapter_fib_ioctl agf; struct aac_fib_context *ctx; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((error = copyin(arg, &agf, sizeof(agf))) == 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (agf.AdapterFibContext == ctx->unique) break; } if (!ctx) return (EFAULT); error = aac_return_aif(sc, ctx, agf.AifFib); if (error == EAGAIN && agf.Wait) { fwprintf(sc, HBA_FLAGS_DBG_AIF_B, "aac_getnext_aif(): waiting for AIF"); sc->aac_state |= AAC_STATE_AIF_SLEEPER; while (error == EAGAIN) { error = tsleep(sc->aac_aifq, PRIBIO | PCATCH, "aacaif", 0); if (error == 0) error = aac_return_aif(sc, ctx, agf.AifFib); } sc->aac_state &= ~AAC_STATE_AIF_SLEEPER; } } return(error); } /* * Hand the next AIF off the top of the queue out to userspace. */ static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr) { int current, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_aifq_lock); current = ctx->ctx_idx; if (current == sc->aifq_idx && !ctx->ctx_wrap) { /* empty */ mtx_unlock(&sc->aac_aifq_lock); return (EAGAIN); } error = copyout(&sc->aac_aifq[current], (void *)uptr, sizeof(struct aac_fib)); if (error) device_printf(sc->aac_dev, "aac_return_aif: copyout returned %d\n", error); else { ctx->ctx_wrap = 0; ctx->ctx_idx = (current + 1) % AAC_AIFQ_LENGTH; } mtx_unlock(&sc->aac_aifq_lock); return(error); } static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr) { struct aac_pci_info { u_int32_t bus; u_int32_t slot; } pciinf; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); pciinf.bus = pci_get_bus(sc->aac_dev); pciinf.slot = pci_get_slot(sc->aac_dev); error = copyout((caddr_t)&pciinf, uptr, sizeof(struct aac_pci_info)); return (error); } static int aac_supported_features(struct aac_softc *sc, caddr_t uptr) { struct aac_features f; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((error = copyin(uptr, &f, sizeof (f))) != 0) return (error); /* * When the management driver receives FSACTL_GET_FEATURES ioctl with * ALL zero in the featuresState, the driver will return the current * state of all the supported features, the data field will not be * valid. * When the management driver receives FSACTL_GET_FEATURES ioctl with * a specific bit set in the featuresState, the driver will return the * current state of this specific feature and whatever data that are * associated with the feature in the data field or perform whatever * action needed indicates in the data field. */ if (f.feat.fValue == 0) { f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: In the future, add other features state here as well */ } else { if (f.feat.fBits.largeLBA) f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: Add other features state and data in the future */ } error = copyout(&f, uptr, sizeof (f)); return (error); } /* * Give the userland some information about the container. The AAC arch * expects the driver to be a SCSI passthrough type driver, so it expects * the containers to have b:t:l numbers. Fake it. */ static int aac_query_disk(struct aac_softc *sc, caddr_t uptr) { struct aac_query_disk query_disk; struct aac_container *co; struct aac_disk *disk; int error, id; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); disk = NULL; error = copyin(uptr, (caddr_t)&query_disk, sizeof(struct aac_query_disk)); if (error) return (error); id = query_disk.ContainerNumber; if (id == -1) return (EINVAL); mtx_lock(&sc->aac_container_lock); TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == id) break; } if (co == NULL) { query_disk.Valid = 0; query_disk.Locked = 0; query_disk.Deleted = 1; /* XXX is this right? */ } else { disk = device_get_softc(co->co_disk); query_disk.Valid = 1; query_disk.Locked = (disk->ad_flags & AAC_DISK_OPEN) ? 1 : 0; query_disk.Deleted = 0; query_disk.Bus = device_get_unit(sc->aac_dev); query_disk.Target = disk->unit; query_disk.Lun = 0; query_disk.UnMapped = 0; sprintf(&query_disk.diskDeviceName[0], "%s%d", disk->ad_disk->d_name, disk->ad_disk->d_unit); } mtx_unlock(&sc->aac_container_lock); error = copyout((caddr_t)&query_disk, uptr, sizeof(struct aac_query_disk)); return (error); } static void aac_get_bus_info(struct aac_softc *sc) { struct aac_fib *fib; struct aac_ctcfg *c_cmd; struct aac_ctcfg_resp *c_resp; struct aac_vmioctl *vmi; struct aac_vmi_businf_resp *vmi_resp; struct aac_getbusinf businfo; struct aac_sim *caminf; device_t child; int i, found, error; mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); c_cmd = (struct aac_ctcfg *)&fib->data[0]; bzero(c_cmd, sizeof(struct aac_ctcfg)); c_cmd->Command = VM_ContainerConfig; c_cmd->cmd = CT_GET_SCSI_METHOD; c_cmd->param = 0; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_ctcfg)); if (error) { device_printf(sc->aac_dev, "Error %d sending " "VM_ContainerConfig command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } c_resp = (struct aac_ctcfg_resp *)&fib->data[0]; if (c_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_ContainerConfig returned 0x%x\n", c_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } sc->scsi_method_id = c_resp->param; vmi = (struct aac_vmioctl *)&fib->data[0]; bzero(vmi, sizeof(struct aac_vmioctl)); vmi->Command = VM_Ioctl; vmi->ObjType = FT_DRIVE; vmi->MethId = sc->scsi_method_id; vmi->ObjId = 0; vmi->IoctlCmd = GetBusInfo; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_vmi_businf_resp)); if (error) { device_printf(sc->aac_dev, "Error %d sending VMIoctl command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } vmi_resp = (struct aac_vmi_businf_resp *)&fib->data[0]; if (vmi_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_Ioctl returned %d\n", vmi_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } bcopy(&vmi_resp->BusInf, &businfo, sizeof(struct aac_getbusinf)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); found = 0; for (i = 0; i < businfo.BusCount; i++) { if (businfo.BusValid[i] != AAC_BUS_VALID) continue; caminf = (struct aac_sim *)malloc( sizeof(struct aac_sim), M_AACBUF, M_NOWAIT | M_ZERO); if (caminf == NULL) { device_printf(sc->aac_dev, "No memory to add passthrough bus %d\n", i); break; }; child = device_add_child(sc->aac_dev, "aacp", -1); if (child == NULL) { device_printf(sc->aac_dev, "device_add_child failed for passthrough bus %d\n", i); free(caminf, M_AACBUF); break; } caminf->TargetsPerBus = businfo.TargetsPerBus; caminf->BusNumber = i; caminf->InitiatorBusId = businfo.InitiatorBusId[i]; caminf->aac_sc = sc; caminf->sim_dev = child; device_set_ivars(child, caminf); device_set_desc(child, "SCSI Passthrough Bus"); TAILQ_INSERT_TAIL(&sc->aac_sim_tqh, caminf, sim_link); found = 1; } if (found) bus_generic_attach(sc->aac_dev); } Index: head/sys/dev/aacraid/aacraid.c =================================================================== --- head/sys/dev/aacraid/aacraid.c (revision 280346) +++ head/sys/dev/aacraid/aacraid.c (revision 280347) @@ -1,3853 +1,3853 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2001 Scott Long * Copyright (c) 2000 BSDi * Copyright (c) 2001-2010 Adaptec, Inc. * Copyright (c) 2010-2012 PMC-Sierra, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for the Adaptec by PMC Series 6,7,8,... families of RAID controllers */ #define AAC_DRIVERNAME "aacraid" #include "opt_aacraid.h" /* #include */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILTER_HANDLED #define FILTER_HANDLED 0x02 #endif static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f, u_int32_t uid); static void aac_get_bus_info(struct aac_softc *sc); static void aac_container_bus(struct aac_softc *sc); static void aac_daemon(void *arg); static int aac_convert_sgraw2(struct aac_softc *sc, struct aac_raw_io2 *raw, int pages, int nseg, int nseg_new); /* Command Processing */ static void aac_timeout(struct aac_softc *sc); static void aac_command_thread(struct aac_softc *sc); static int aac_sync_fib(struct aac_softc *sc, u_int32_t command, u_int32_t xferstate, struct aac_fib *fib, u_int16_t datasize); /* Command Buffer Management */ static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_alloc_commands(struct aac_softc *sc); static void aac_free_commands(struct aac_softc *sc); static void aac_unmap_command(struct aac_command *cm); /* Hardware Interface */ static int aac_alloc(struct aac_softc *sc); static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int aac_check_firmware(struct aac_softc *sc); static void aac_define_int_mode(struct aac_softc *sc); static int aac_init(struct aac_softc *sc); static int aac_find_pci_capability(struct aac_softc *sc, int cap); static int aac_setup_intr(struct aac_softc *sc); static int aac_check_config(struct aac_softc *sc); /* PMC SRC interface */ static int aac_src_get_fwstatus(struct aac_softc *sc); static void aac_src_qnotify(struct aac_softc *sc, int qbit); static int aac_src_get_istatus(struct aac_softc *sc); static void aac_src_clear_istatus(struct aac_softc *sc, int mask); static void aac_src_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_src_get_mailbox(struct aac_softc *sc, int mb); static void aac_src_access_devreg(struct aac_softc *sc, int mode); static int aac_src_send_command(struct aac_softc *sc, struct aac_command *cm); static int aac_src_get_outb_queue(struct aac_softc *sc); static void aac_src_set_outb_queue(struct aac_softc *sc, int index); struct aac_interface aacraid_src_interface = { aac_src_get_fwstatus, aac_src_qnotify, aac_src_get_istatus, aac_src_clear_istatus, aac_src_set_mailbox, aac_src_get_mailbox, aac_src_access_devreg, aac_src_send_command, aac_src_get_outb_queue, aac_src_set_outb_queue }; /* PMC SRCv interface */ static void aac_srcv_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int aac_srcv_get_mailbox(struct aac_softc *sc, int mb); struct aac_interface aacraid_srcv_interface = { aac_src_get_fwstatus, aac_src_qnotify, aac_src_get_istatus, aac_src_clear_istatus, aac_srcv_set_mailbox, aac_srcv_get_mailbox, aac_src_access_devreg, aac_src_send_command, aac_src_get_outb_queue, aac_src_set_outb_queue }; /* Debugging and Diagnostics */ static struct aac_code_lookup aac_cpu_variant[] = { {"i960JX", CPUI960_JX}, {"i960CX", CPUI960_CX}, {"i960HX", CPUI960_HX}, {"i960RX", CPUI960_RX}, {"i960 80303", CPUI960_80303}, {"StrongARM SA110", CPUARM_SA110}, {"PPC603e", CPUPPC_603e}, {"XScale 80321", CPU_XSCALE_80321}, {"MIPS 4KC", CPU_MIPS_4KC}, {"MIPS 5KC", CPU_MIPS_5KC}, {"Unknown StrongARM", CPUARM_xxx}, {"Unknown PowerPC", CPUPPC_xxx}, {NULL, 0}, {"Unknown processor", 0} }; static struct aac_code_lookup aac_battery_platform[] = { {"required battery present", PLATFORM_BAT_REQ_PRESENT}, {"REQUIRED BATTERY NOT PRESENT", PLATFORM_BAT_REQ_NOTPRESENT}, {"optional battery present", PLATFORM_BAT_OPT_PRESENT}, {"optional battery not installed", PLATFORM_BAT_OPT_NOTPRESENT}, {"no battery support", PLATFORM_BAT_NOT_SUPPORTED}, {NULL, 0}, {"unknown battery platform", 0} }; static void aac_describe_controller(struct aac_softc *sc); static char *aac_describe_code(struct aac_code_lookup *table, u_int32_t code); /* Management Interface */ static d_open_t aac_open; static d_ioctl_t aac_ioctl; static d_poll_t aac_poll; #if __FreeBSD_version >= 702000 static void aac_cdevpriv_dtor(void *arg); #else static d_close_t aac_close; #endif static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib); static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg); static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib); static void aac_request_aif(struct aac_softc *sc); static int aac_rev_check(struct aac_softc *sc, caddr_t udata); static int aac_open_aif(struct aac_softc *sc, caddr_t arg); static int aac_close_aif(struct aac_softc *sc, caddr_t arg); static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg); static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr); static int aac_query_disk(struct aac_softc *sc, caddr_t uptr); static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr); static int aac_supported_features(struct aac_softc *sc, caddr_t uptr); static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg); static int aac_reset_adapter(struct aac_softc *sc); static int aac_get_container_info(struct aac_softc *sc, struct aac_fib *fib, int cid, struct aac_mntinforesp *mir, u_int32_t *uid); static u_int32_t aac_check_adapter_health(struct aac_softc *sc, u_int8_t *bled); static struct cdevsw aacraid_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = aac_open, #if __FreeBSD_version < 702000 .d_close = aac_close, #endif .d_ioctl = aac_ioctl, .d_poll = aac_poll, .d_name = "aacraid", }; MALLOC_DEFINE(M_AACRAIDBUF, "aacraid_buf", "Buffers for the AACRAID driver"); /* sysctl node */ SYSCTL_NODE(_hw, OID_AUTO, aacraid, CTLFLAG_RD, 0, "AACRAID driver parameters"); /* * Device Interface */ /* * Initialize the controller and softc */ int aacraid_attach(struct aac_softc *sc) { int error, unit; struct aac_fib *fib; struct aac_mntinforesp mir; int count = 0, i = 0; u_int32_t uid; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->hint_flags = device_get_flags(sc->aac_dev); /* * Initialize per-controller queues. */ aac_initq_free(sc); aac_initq_ready(sc); aac_initq_busy(sc); /* mark controller as suspended until we get ourselves organised */ sc->aac_state |= AAC_STATE_SUSPEND; /* * Check that the firmware on the card is supported. */ sc->msi_enabled = FALSE; if ((error = aac_check_firmware(sc)) != 0) return(error); /* * Initialize locks */ mtx_init(&sc->aac_io_lock, "AACRAID I/O lock", NULL, MTX_DEF); TAILQ_INIT(&sc->aac_container_tqh); TAILQ_INIT(&sc->aac_ev_cmfree); #if __FreeBSD_version >= 800000 /* Initialize the clock daemon callout. */ callout_init_mtx(&sc->aac_daemontime, &sc->aac_io_lock, 0); #endif /* * Initialize the adapter. */ if ((error = aac_alloc(sc)) != 0) return(error); if (!(sc->flags & AAC_FLAGS_SYNC_MODE)) { aac_define_int_mode(sc); if ((error = aac_init(sc)) != 0) return(error); } /* * Allocate and connect our interrupt. */ if ((error = aac_setup_intr(sc)) != 0) return(error); /* * Print a little information about the controller. */ aac_describe_controller(sc); /* * Make the control device. */ unit = device_get_unit(sc->aac_dev); sc->aac_dev_t = make_dev(&aacraid_cdevsw, unit, UID_ROOT, GID_OPERATOR, 0640, "aacraid%d", unit); sc->aac_dev_t->si_drv1 = sc; /* Create the AIF thread */ if (aac_kthread_create((void(*)(void *))aac_command_thread, sc, &sc->aifthread, 0, 0, "aacraid%daif", unit)) panic("Could not create AIF thread"); /* Register the shutdown method to only be called post-dump */ if ((sc->eh = EVENTHANDLER_REGISTER(shutdown_final, aacraid_shutdown, sc->aac_dev, SHUTDOWN_PRI_DEFAULT)) == NULL) device_printf(sc->aac_dev, "shutdown event registration failed\n"); /* Find containers */ mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); /* loop over possible containers */ do { if ((aac_get_container_info(sc, fib, i, &mir, &uid)) != 0) continue; if (i == 0) count = mir.MntRespCount; aac_add_container(sc, &mir, 0, uid); i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); /* Register with CAM for the containers */ TAILQ_INIT(&sc->aac_sim_tqh); aac_container_bus(sc); /* Register with CAM for the non-DASD devices */ if ((sc->flags & AAC_FLAGS_ENABLE_CAM) != 0) aac_get_bus_info(sc); /* poke the bus to actually attach the child devices */ bus_generic_attach(sc->aac_dev); /* mark the controller up */ sc->aac_state &= ~AAC_STATE_SUSPEND; /* enable interrupts now */ AAC_ACCESS_DEVREG(sc, AAC_ENABLE_INTERRUPT); #if __FreeBSD_version >= 800000 mtx_lock(&sc->aac_io_lock); callout_reset(&sc->aac_daemontime, 60 * hz, aac_daemon, sc); mtx_unlock(&sc->aac_io_lock); #else { struct timeval tv; tv.tv_sec = 60; tv.tv_usec = 0; sc->timeout_id = timeout(aac_daemon, (void *)sc, tvtohz(&tv)); } #endif return(0); } static void aac_daemon(void *arg) { struct aac_softc *sc; struct timeval tv; struct aac_command *cm; struct aac_fib *fib; sc = arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); #if __FreeBSD_version >= 800000 mtx_assert(&sc->aac_io_lock, MA_OWNED); if (callout_pending(&sc->aac_daemontime) || callout_active(&sc->aac_daemontime) == 0) return; #else mtx_lock(&sc->aac_io_lock); #endif getmicrotime(&tv); if (!aacraid_alloc_command(sc, &cm)) { fib = cm->cm_fib; cm->cm_timestamp = time_uptime; cm->cm_datalen = 0; cm->cm_flags |= AAC_CMD_WAIT; fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(u_int32_t); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; fib->Header.Command = SendHostTime; *(uint32_t *)fib->data = tv.tv_sec; aacraid_map_command_sg(cm, NULL, 0, 0); aacraid_release_command(cm); } #if __FreeBSD_version >= 800000 callout_schedule(&sc->aac_daemontime, 30 * 60 * hz); #else mtx_unlock(&sc->aac_io_lock); tv.tv_sec = 30 * 60; tv.tv_usec = 0; sc->timeout_id = timeout(aac_daemon, (void *)sc, tvtohz(&tv)); #endif } void aacraid_add_event(struct aac_softc *sc, struct aac_event *event) { switch (event->ev_type & AAC_EVENT_MASK) { case AAC_EVENT_CMFREE: TAILQ_INSERT_TAIL(&sc->aac_ev_cmfree, event, ev_links); break; default: device_printf(sc->aac_dev, "aac_add event: unknown event %d\n", event->ev_type); break; } return; } /* * Request information of container #cid */ static int aac_get_container_info(struct aac_softc *sc, struct aac_fib *sync_fib, int cid, struct aac_mntinforesp *mir, u_int32_t *uid) { struct aac_command *cm; struct aac_fib *fib; struct aac_mntinfo *mi; struct aac_cnt_config *ccfg; int rval; if (sync_fib == NULL) { if (aacraid_alloc_command(sc, &cm)) { device_printf(sc->aac_dev, "Warning, no free command available\n"); return (-1); } fib = cm->cm_fib; } else { fib = sync_fib; } mi = (struct aac_mntinfo *)&fib->data[0]; /* 4KB support?, 64-bit LBA? */ if (sc->aac_support_opt2 & AAC_SUPPORTED_VARIABLE_BLOCK_SIZE) mi->Command = VM_NameServeAllBlk; else if (sc->flags & AAC_FLAGS_LBA_64BIT) mi->Command = VM_NameServe64; else mi->Command = VM_NameServe; mi->MntType = FT_FILESYS; mi->MntCount = cid; if (sync_fib) { if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_mntinfo))) { device_printf(sc->aac_dev, "Error probing container %d\n", cid); return (-1); } } else { cm->cm_timestamp = time_uptime; cm->cm_datalen = 0; fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(struct aac_mntinfo); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; fib->Header.Command = ContainerCommand; if (aacraid_wait_command(cm) != 0) { device_printf(sc->aac_dev, "Error probing container %d\n", cid); aacraid_release_command(cm); return (-1); } } bcopy(&fib->data[0], mir, sizeof(struct aac_mntinforesp)); /* UID */ *uid = cid; if (mir->MntTable[0].VolType != CT_NONE && !(mir->MntTable[0].ContentState & AAC_FSCS_HIDDEN)) { if (!(sc->aac_support_opt2 & AAC_SUPPORTED_VARIABLE_BLOCK_SIZE)) { mir->MntTable[0].ObjExtension.BlockDevice.BlockSize = 0x200; mir->MntTable[0].ObjExtension.BlockDevice.bdLgclPhysMap = 0; } ccfg = (struct aac_cnt_config *)&fib->data[0]; bzero(ccfg, sizeof (*ccfg) - CT_PACKET_SIZE); ccfg->Command = VM_ContainerConfig; ccfg->CTCommand.command = CT_CID_TO_32BITS_UID; ccfg->CTCommand.param[0] = cid; if (sync_fib) { rval = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_cnt_config)); if (rval == 0 && ccfg->Command == ST_OK && ccfg->CTCommand.param[0] == CT_OK && mir->MntTable[0].VolType != CT_PASSTHRU) *uid = ccfg->CTCommand.param[1]; } else { fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(struct aac_cnt_config); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC | AAC_FIBSTATE_FAST_RESPONSE; fib->Header.Command = ContainerCommand; rval = aacraid_wait_command(cm); if (rval == 0 && ccfg->Command == ST_OK && ccfg->CTCommand.param[0] == CT_OK && mir->MntTable[0].VolType != CT_PASSTHRU) *uid = ccfg->CTCommand.param[1]; aacraid_release_command(cm); } } return (0); } /* * Create a device to represent a new container */ static void aac_add_container(struct aac_softc *sc, struct aac_mntinforesp *mir, int f, u_int32_t uid) { struct aac_container *co; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Check container volume type for validity. Note that many of * the possible types may never show up. */ if ((mir->Status == ST_OK) && (mir->MntTable[0].VolType != CT_NONE)) { co = (struct aac_container *)malloc(sizeof *co, M_AACRAIDBUF, M_NOWAIT | M_ZERO); if (co == NULL) { panic("Out of memory?!"); } co->co_found = f; bcopy(&mir->MntTable[0], &co->co_mntobj, sizeof(struct aac_mntobj)); co->co_uid = uid; TAILQ_INSERT_TAIL(&sc->aac_container_tqh, co, co_link); } } /* * Allocate resources associated with (sc) */ static int aac_alloc(struct aac_softc *sc) { bus_size_t maxsize; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Create DMA tag for mapping buffers into controller-addressable space. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_SG_64BIT) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, /* maxsize */ + sc->aac_max_sectors << 9, /* maxsize */ sc->aac_sg_tablesize, /* nsegments */ - MAXBSIZE, /* maxsegsize */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->aac_io_lock, /* lockfuncarg */ &sc->aac_buffer_dmat)) { device_printf(sc->aac_dev, "can't allocate buffer DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for mapping FIBs into controller-addressable space.. */ if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE1) maxsize = sc->aac_max_fibs_alloc * (sc->aac_max_fib_size + sizeof(struct aac_fib_xporthdr) + 31); else maxsize = sc->aac_max_fibs_alloc * (sc->aac_max_fib_size + 31); if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ maxsize, /* maxsize */ 1, /* nsegments */ maxsize, /* maxsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_fib_dmat)) { device_printf(sc->aac_dev, "can't allocate FIB DMA tag\n"); return (ENOMEM); } /* * Create DMA tag for the common structure and allocate it. */ maxsize = sizeof(struct aac_common); maxsize += sc->aac_max_fibs * sizeof(u_int32_t); if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_4GB_WINDOW) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ maxsize, /* maxsize */ 1, /* nsegments */ maxsize, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &sc->aac_common_dmat)) { device_printf(sc->aac_dev, "can't allocate common structure DMA tag\n"); return (ENOMEM); } if (bus_dmamem_alloc(sc->aac_common_dmat, (void **)&sc->aac_common, BUS_DMA_NOWAIT, &sc->aac_common_dmamap)) { device_printf(sc->aac_dev, "can't allocate common structure\n"); return (ENOMEM); } (void)bus_dmamap_load(sc->aac_common_dmat, sc->aac_common_dmamap, sc->aac_common, maxsize, aac_common_map, sc, 0); bzero(sc->aac_common, maxsize); /* Allocate some FIBs and associated command structs */ TAILQ_INIT(&sc->aac_fibmap_tqh); sc->aac_commands = malloc(sc->aac_max_fibs * sizeof(struct aac_command), M_AACRAIDBUF, M_WAITOK|M_ZERO); mtx_lock(&sc->aac_io_lock); while (sc->total_fibs < sc->aac_max_fibs) { if (aac_alloc_commands(sc) != 0) break; } mtx_unlock(&sc->aac_io_lock); if (sc->total_fibs == 0) return (ENOMEM); return (0); } /* * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ void aacraid_free(struct aac_softc *sc) { int i; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* remove the control device */ if (sc->aac_dev_t != NULL) destroy_dev(sc->aac_dev_t); /* throw away any FIB buffers, discard the FIB DMA tag */ aac_free_commands(sc); if (sc->aac_fib_dmat) bus_dma_tag_destroy(sc->aac_fib_dmat); free(sc->aac_commands, M_AACRAIDBUF); /* destroy the common area */ if (sc->aac_common) { bus_dmamap_unload(sc->aac_common_dmat, sc->aac_common_dmamap); bus_dmamem_free(sc->aac_common_dmat, sc->aac_common, sc->aac_common_dmamap); } if (sc->aac_common_dmat) bus_dma_tag_destroy(sc->aac_common_dmat); /* disconnect the interrupt handler */ for (i = 0; i < AAC_MAX_MSIX; ++i) { if (sc->aac_intr[i]) bus_teardown_intr(sc->aac_dev, sc->aac_irq[i], sc->aac_intr[i]); if (sc->aac_irq[i]) bus_release_resource(sc->aac_dev, SYS_RES_IRQ, sc->aac_irq_rid[i], sc->aac_irq[i]); else break; } if (sc->msi_enabled) pci_release_msi(sc->aac_dev); /* destroy data-transfer DMA tag */ if (sc->aac_buffer_dmat) bus_dma_tag_destroy(sc->aac_buffer_dmat); /* destroy the parent DMA tag */ if (sc->aac_parent_dmat) bus_dma_tag_destroy(sc->aac_parent_dmat); /* release the register window mapping */ if (sc->aac_regs_res0 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, sc->aac_regs_rid0, sc->aac_regs_res0); if (sc->aac_regs_res1 != NULL) bus_release_resource(sc->aac_dev, SYS_RES_MEMORY, sc->aac_regs_rid1, sc->aac_regs_res1); } /* * Disconnect from the controller completely, in preparation for unload. */ int aacraid_detach(device_t dev) { struct aac_softc *sc; struct aac_container *co; struct aac_sim *sim; int error; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); #if __FreeBSD_version >= 800000 callout_drain(&sc->aac_daemontime); #else untimeout(aac_daemon, (void *)sc, sc->timeout_id); #endif /* Remove the child containers */ while ((co = TAILQ_FIRST(&sc->aac_container_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); free(co, M_AACRAIDBUF); } /* Remove the CAM SIMs */ while ((sim = TAILQ_FIRST(&sc->aac_sim_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_sim_tqh, sim, sim_link); error = device_delete_child(dev, sim->sim_dev); if (error) return (error); free(sim, M_AACRAIDBUF); } if (sc->aifflags & AAC_AIFFLAGS_RUNNING) { sc->aifflags |= AAC_AIFFLAGS_EXIT; wakeup(sc->aifthread); tsleep(sc->aac_dev, PUSER | PCATCH, "aac_dch", 30 * hz); } if (sc->aifflags & AAC_AIFFLAGS_RUNNING) panic("Cannot shutdown AIF thread"); if ((error = aacraid_shutdown(dev))) return(error); EVENTHANDLER_DEREGISTER(shutdown_final, sc->eh); aacraid_free(sc); mtx_destroy(&sc->aac_io_lock); return(0); } /* * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach or system shutdown. * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ int aacraid_shutdown(device_t dev) { struct aac_softc *sc; struct aac_fib *fib; struct aac_close_command *cc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; /* * Send a Container shutdown followed by a HostShutdown FIB to the * controller to convince it that we don't want to talk to it anymore. * We've been closed and all I/O completed already */ device_printf(sc->aac_dev, "shutting down controller..."); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); cc = (struct aac_close_command *)&fib->data[0]; bzero(cc, sizeof(struct aac_close_command)); cc->Command = VM_CloseAll; cc->ContainerId = 0xfffffffe; if (aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_close_command))) printf("FAILED.\n"); else printf("done\n"); AAC_ACCESS_DEVREG(sc, AAC_DISABLE_INTERRUPT); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return(0); } /* * Bring the controller to a quiescent state, ready for system suspend. */ int aacraid_suspend(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state |= AAC_STATE_SUSPEND; AAC_ACCESS_DEVREG(sc, AAC_DISABLE_INTERRUPT); return(0); } /* * Bring the controller back to a state ready for operation. */ int aacraid_resume(device_t dev) { struct aac_softc *sc; sc = device_get_softc(dev); fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_state &= ~AAC_STATE_SUSPEND; AAC_ACCESS_DEVREG(sc, AAC_ENABLE_INTERRUPT); return(0); } /* * Interrupt handler for NEW_COMM_TYPE1, NEW_COMM_TYPE2, NEW_COMM_TYPE34 interface. */ void aacraid_new_intr_type1(void *arg) { struct aac_msix_ctx *ctx; struct aac_softc *sc; int vector_no; struct aac_command *cm; struct aac_fib *fib; u_int32_t bellbits, bellbits_shifted, index, handle; int isFastResponse, isAif, noMoreAif, mode; ctx = (struct aac_msix_ctx *)arg; sc = ctx->sc; vector_no = ctx->vector_no; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); if (sc->msi_enabled) { mode = AAC_INT_MODE_MSI; if (vector_no == 0) { bellbits = AAC_MEM0_GETREG4(sc, AAC_SRC_ODBR_MSI); if (bellbits & 0x40000) mode |= AAC_INT_MODE_AIF; else if (bellbits & 0x1000) mode |= AAC_INT_MODE_SYNC; } } else { mode = AAC_INT_MODE_INTX; bellbits = AAC_MEM0_GETREG4(sc, AAC_SRC_ODBR_R); if (bellbits & AAC_DB_RESPONSE_SENT_NS) { bellbits = AAC_DB_RESPONSE_SENT_NS; AAC_MEM0_SETREG4(sc, AAC_SRC_ODBR_C, bellbits); } else { bellbits_shifted = (bellbits >> AAC_SRC_ODR_SHIFT); AAC_MEM0_SETREG4(sc, AAC_SRC_ODBR_C, bellbits); if (bellbits_shifted & AAC_DB_AIF_PENDING) mode |= AAC_INT_MODE_AIF; else if (bellbits_shifted & AAC_DB_SYNC_COMMAND) mode |= AAC_INT_MODE_SYNC; } /* ODR readback, Prep #238630 */ AAC_MEM0_GETREG4(sc, AAC_SRC_ODBR_R); } if (mode & AAC_INT_MODE_SYNC) { if (sc->aac_sync_cm) { cm = sc->aac_sync_cm; cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this command */ wakeup(cm); } sc->flags &= ~AAC_QUEUE_FRZN; sc->aac_sync_cm = NULL; } mode = 0; } if (mode & AAC_INT_MODE_AIF) { if (mode & AAC_INT_MODE_INTX) { aac_request_aif(sc); mode = 0; } } if (mode) { /* handle async. status */ index = sc->aac_host_rrq_idx[vector_no]; for (;;) { isFastResponse = isAif = noMoreAif = 0; /* remove toggle bit (31) */ handle = (sc->aac_common->ac_host_rrq[index] & 0x7fffffff); /* check fast response bit (30) */ if (handle & 0x40000000) isFastResponse = 1; /* check AIF bit (23) */ else if (handle & 0x00800000) isAif = TRUE; handle &= 0x0000ffff; if (handle == 0) break; cm = sc->aac_commands + (handle - 1); fib = cm->cm_fib; sc->aac_rrq_outstanding[vector_no]--; if (isAif) { noMoreAif = (fib->Header.XferState & AAC_FIBSTATE_NOMOREAIF) ? 1:0; if (!noMoreAif) aac_handle_aif(sc, fib); aac_remove_busy(cm); aacraid_release_command(cm); } else { if (isFastResponse) { fib->Header.XferState |= AAC_FIBSTATE_DONEADAP; *((u_int32_t *)(fib->data)) = ST_OK; cm->cm_flags |= AAC_CMD_FASTRESP; } aac_remove_busy(cm); aac_unmap_command(cm); cm->cm_flags |= AAC_CMD_COMPLETED; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this command */ wakeup(cm); } sc->flags &= ~AAC_QUEUE_FRZN; } sc->aac_common->ac_host_rrq[index++] = 0; if (index == (vector_no + 1) * sc->aac_vector_cap) index = vector_no * sc->aac_vector_cap; sc->aac_host_rrq_idx[vector_no] = index; if ((isAif && !noMoreAif) || sc->aif_pending) aac_request_aif(sc); } } if (mode & AAC_INT_MODE_AIF) { aac_request_aif(sc); AAC_ACCESS_DEVREG(sc, AAC_CLEAR_AIF_BIT); mode = 0; } /* see if we can start some more I/O */ if ((sc->flags & AAC_QUEUE_FRZN) == 0) aacraid_startio(sc); mtx_unlock(&sc->aac_io_lock); } /* * Handle notification of one or more FIBs coming from the controller. */ static void aac_command_thread(struct aac_softc *sc) { int retval; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); sc->aifflags = AAC_AIFFLAGS_RUNNING; while ((sc->aifflags & AAC_AIFFLAGS_EXIT) == 0) { retval = 0; if ((sc->aifflags & AAC_AIFFLAGS_PENDING) == 0) retval = msleep(sc->aifthread, &sc->aac_io_lock, PRIBIO, "aacraid_aifthd", AAC_PERIODIC_INTERVAL * hz); /* * First see if any FIBs need to be allocated. This needs * to be called without the driver lock because contigmalloc * will grab Giant, and would result in an LOR. */ if ((sc->aifflags & AAC_AIFFLAGS_ALLOCFIBS) != 0) { aac_alloc_commands(sc); sc->aifflags &= ~AAC_AIFFLAGS_ALLOCFIBS; aacraid_startio(sc); } /* * While we're here, check to see if any commands are stuck. * This is pretty low-priority, so it's ok if it doesn't * always fire. */ if (retval == EWOULDBLOCK) aac_timeout(sc); /* Check the hardware printf message buffer */ if (sc->aac_common->ac_printf[0] != 0) aac_print_printf(sc); } sc->aifflags &= ~AAC_AIFFLAGS_RUNNING; mtx_unlock(&sc->aac_io_lock); wakeup(sc->aac_dev); aac_kthread_exit(0); } /* * Submit a command to the controller, return when it completes. * XXX This is very dangerous! If the card has gone out to lunch, we could * be stuck here forever. At the same time, signals are not caught * because there is a risk that a signal could wakeup the sleep before * the card has a chance to complete the command. Since there is no way * to cancel a command that is in progress, we can't protect against the * card completing a command late and spamming the command and data * memory. So, we are held hostage until the command completes. */ int aacraid_wait_command(struct aac_command *cm) { struct aac_softc *sc; int error; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); /* Put the command on the ready queue and get things going */ aac_enqueue_ready(cm); aacraid_startio(sc); error = msleep(cm, &sc->aac_io_lock, PRIBIO, "aacraid_wait", 0); return(error); } /* *Command Buffer Management */ /* * Allocate a command. */ int aacraid_alloc_command(struct aac_softc *sc, struct aac_command **cmp) { struct aac_command *cm; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((cm = aac_dequeue_free(sc)) == NULL) { if (sc->total_fibs < sc->aac_max_fibs) { sc->aifflags |= AAC_AIFFLAGS_ALLOCFIBS; wakeup(sc->aifthread); } return (EBUSY); } *cmp = cm; return(0); } /* * Release a command back to the freelist. */ void aacraid_release_command(struct aac_command *cm) { struct aac_event *event; struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); /* (re)initialize the command/FIB */ cm->cm_sgtable = NULL; cm->cm_flags = 0; cm->cm_complete = NULL; cm->cm_ccb = NULL; cm->cm_passthr_dmat = 0; cm->cm_fib->Header.XferState = AAC_FIBSTATE_EMPTY; cm->cm_fib->Header.StructType = AAC_FIBTYPE_TFIB; cm->cm_fib->Header.Unused = 0; cm->cm_fib->Header.SenderSize = cm->cm_sc->aac_max_fib_size; /* * These are duplicated in aac_start to cover the case where an * intermediate stage may have destroyed them. They're left * initialized here for debugging purposes only. */ cm->cm_fib->Header.u.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; cm->cm_fib->Header.Handle = 0; aac_enqueue_free(cm); /* * Dequeue all events so that there's no risk of events getting * stranded. */ while ((event = TAILQ_FIRST(&sc->aac_ev_cmfree)) != NULL) { TAILQ_REMOVE(&sc->aac_ev_cmfree, event, ev_links); event->ev_callback(sc, event, event->ev_arg); } } /* * Map helper for command/FIB allocation. */ static void aac_map_command_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { uint64_t *fibphys; fibphys = (uint64_t *)arg; *fibphys = segs[0].ds_addr; } /* * Allocate and initialize commands/FIBs for this adapter. */ static int aac_alloc_commands(struct aac_softc *sc) { struct aac_command *cm; struct aac_fibmap *fm; uint64_t fibphys; int i, error; u_int32_t maxsize; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); if (sc->total_fibs + sc->aac_max_fibs_alloc > sc->aac_max_fibs) return (ENOMEM); fm = malloc(sizeof(struct aac_fibmap), M_AACRAIDBUF, M_NOWAIT|M_ZERO); if (fm == NULL) return (ENOMEM); mtx_unlock(&sc->aac_io_lock); /* allocate the FIBs in DMAable memory and load them */ if (bus_dmamem_alloc(sc->aac_fib_dmat, (void **)&fm->aac_fibs, BUS_DMA_NOWAIT, &fm->aac_fibmap)) { device_printf(sc->aac_dev, "Not enough contiguous memory available.\n"); free(fm, M_AACRAIDBUF); mtx_lock(&sc->aac_io_lock); return (ENOMEM); } maxsize = sc->aac_max_fib_size + 31; if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE1) maxsize += sizeof(struct aac_fib_xporthdr); /* Ignore errors since this doesn't bounce */ (void)bus_dmamap_load(sc->aac_fib_dmat, fm->aac_fibmap, fm->aac_fibs, sc->aac_max_fibs_alloc * maxsize, aac_map_command_helper, &fibphys, 0); mtx_lock(&sc->aac_io_lock); /* initialize constant fields in the command structure */ bzero(fm->aac_fibs, sc->aac_max_fibs_alloc * maxsize); for (i = 0; i < sc->aac_max_fibs_alloc; i++) { cm = sc->aac_commands + sc->total_fibs; fm->aac_commands = cm; cm->cm_sc = sc; cm->cm_fib = (struct aac_fib *) ((u_int8_t *)fm->aac_fibs + i * maxsize); cm->cm_fibphys = fibphys + i * maxsize; if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE1) { u_int64_t fibphys_aligned; fibphys_aligned = (cm->cm_fibphys + sizeof(struct aac_fib_xporthdr) + 31) & ~31; cm->cm_fib = (struct aac_fib *) ((u_int8_t *)cm->cm_fib + (fibphys_aligned - cm->cm_fibphys)); cm->cm_fibphys = fibphys_aligned; } else { u_int64_t fibphys_aligned; fibphys_aligned = (cm->cm_fibphys + 31) & ~31; cm->cm_fib = (struct aac_fib *) ((u_int8_t *)cm->cm_fib + (fibphys_aligned - cm->cm_fibphys)); cm->cm_fibphys = fibphys_aligned; } cm->cm_index = sc->total_fibs; if ((error = bus_dmamap_create(sc->aac_buffer_dmat, 0, &cm->cm_datamap)) != 0) break; if (sc->aac_max_fibs <= 1 || sc->aac_max_fibs - sc->total_fibs > 1) aacraid_release_command(cm); sc->total_fibs++; } if (i > 0) { TAILQ_INSERT_TAIL(&sc->aac_fibmap_tqh, fm, fm_link); fwprintf(sc, HBA_FLAGS_DBG_COMM_B, "total_fibs= %d\n", sc->total_fibs); return (0); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACRAIDBUF); return (ENOMEM); } /* * Free FIBs owned by this adapter. */ static void aac_free_commands(struct aac_softc *sc) { struct aac_fibmap *fm; struct aac_command *cm; int i; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); while ((fm = TAILQ_FIRST(&sc->aac_fibmap_tqh)) != NULL) { TAILQ_REMOVE(&sc->aac_fibmap_tqh, fm, fm_link); /* * We check against total_fibs to handle partially * allocated blocks. */ for (i = 0; i < sc->aac_max_fibs_alloc && sc->total_fibs--; i++) { cm = fm->aac_commands + i; bus_dmamap_destroy(sc->aac_buffer_dmat, cm->cm_datamap); } bus_dmamap_unload(sc->aac_fib_dmat, fm->aac_fibmap); bus_dmamem_free(sc->aac_fib_dmat, fm->aac_fibs, fm->aac_fibmap); free(fm, M_AACRAIDBUF); } } /* * Command-mapping helper function - populate this command's s/g table. */ void aacraid_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; struct aac_command *cm; struct aac_fib *fib; int i; cm = (struct aac_command *)arg; sc = cm->cm_sc; fib = cm->cm_fib; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "nseg %d", nseg); mtx_assert(&sc->aac_io_lock, MA_OWNED); /* copy into the FIB */ if (cm->cm_sgtable != NULL) { if (fib->Header.Command == RawIo2) { struct aac_raw_io2 *raw; struct aac_sge_ieee1212 *sg; u_int32_t min_size = PAGE_SIZE, cur_size; int conformable = TRUE; raw = (struct aac_raw_io2 *)&fib->data[0]; sg = (struct aac_sge_ieee1212 *)cm->cm_sgtable; raw->sgeCnt = nseg; for (i = 0; i < nseg; i++) { cur_size = segs[i].ds_len; sg[i].addrHigh = 0; *(bus_addr_t *)&sg[i].addrLow = segs[i].ds_addr; sg[i].length = cur_size; sg[i].flags = 0; if (i == 0) { raw->sgeFirstSize = cur_size; } else if (i == 1) { raw->sgeNominalSize = cur_size; min_size = cur_size; } else if ((i+1) < nseg && cur_size != raw->sgeNominalSize) { conformable = FALSE; if (cur_size < min_size) min_size = cur_size; } } /* not conformable: evaluate required sg elements */ if (!conformable) { int j, err_found, nseg_new = nseg; for (i = min_size / PAGE_SIZE; i >= 1; --i) { err_found = FALSE; nseg_new = 2; for (j = 1; j < nseg - 1; ++j) { if (sg[j].length % (i*PAGE_SIZE)) { err_found = TRUE; break; } nseg_new += (sg[j].length / (i*PAGE_SIZE)); } if (!err_found) break; } if (i>0 && nseg_new<=sc->aac_sg_tablesize && !(sc->hint_flags & 4)) nseg = aac_convert_sgraw2(sc, raw, i, nseg, nseg_new); } else { raw->flags |= RIO2_SGL_CONFORMANT; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg * sizeof(struct aac_sge_ieee1212); } else if (fib->Header.Command == RawIo) { struct aac_sg_tableraw *sg; sg = (struct aac_sg_tableraw *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntryRaw[i].SgAddress = segs[i].ds_addr; sg->SgEntryRaw[i].SgByteCount = segs[i].ds_len; sg->SgEntryRaw[i].Next = 0; sg->SgEntryRaw[i].Prev = 0; sg->SgEntryRaw[i].Flags = 0; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entryraw); } else if ((cm->cm_sc->flags & AAC_FLAGS_SG_64BIT) == 0) { struct aac_sg_table *sg; sg = cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry[i].SgAddress = segs[i].ds_addr; sg->SgEntry[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry); } else { struct aac_sg_table64 *sg; sg = (struct aac_sg_table64 *)cm->cm_sgtable; sg->SgCount = nseg; for (i = 0; i < nseg; i++) { sg->SgEntry64[i].SgAddress = segs[i].ds_addr; sg->SgEntry64[i].SgByteCount = segs[i].ds_len; } /* update the FIB size for the s/g count */ fib->Header.Size += nseg*sizeof(struct aac_sg_entry64); } } /* Fix up the address values in the FIB. Use the command array index * instead of a pointer since these fields are only 32 bits. Shift * the SenderFibAddress over to make room for the fast response bit * and for the AIF bit */ cm->cm_fib->Header.SenderFibAddress = (cm->cm_index << 2); cm->cm_fib->Header.u.ReceiverFibAddress = (u_int32_t)cm->cm_fibphys; /* save a pointer to the command for speedy reverse-lookup */ cm->cm_fib->Header.Handle += cm->cm_index + 1; if (cm->cm_passthr_dmat == 0) { if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_PREWRITE); } cm->cm_flags |= AAC_CMD_MAPPED; if (sc->flags & AAC_FLAGS_SYNC_MODE) { u_int32_t wait = 0; aacraid_sync_command(sc, AAC_MONKER_SYNCFIB, cm->cm_fibphys, 0, 0, 0, &wait, NULL); } else if (cm->cm_flags & AAC_CMD_WAIT) { aacraid_sync_command(sc, AAC_MONKER_SYNCFIB, cm->cm_fibphys, 0, 0, 0, NULL, NULL); } else { int count = 10000000L; while (AAC_SEND_COMMAND(sc, cm) != 0) { if (--count == 0) { aac_unmap_command(cm); sc->flags |= AAC_QUEUE_FRZN; aac_requeue_ready(cm); } DELAY(5); /* wait 5 usec. */ } } } static int aac_convert_sgraw2(struct aac_softc *sc, struct aac_raw_io2 *raw, int pages, int nseg, int nseg_new) { struct aac_sge_ieee1212 *sge; int i, j, pos; u_int32_t addr_low; sge = malloc(nseg_new * sizeof(struct aac_sge_ieee1212), M_AACRAIDBUF, M_NOWAIT|M_ZERO); if (sge == NULL) return nseg; for (i = 1, pos = 1; i < nseg - 1; ++i) { for (j = 0; j < raw->sge[i].length / (pages*PAGE_SIZE); ++j) { addr_low = raw->sge[i].addrLow + j * pages * PAGE_SIZE; sge[pos].addrLow = addr_low; sge[pos].addrHigh = raw->sge[i].addrHigh; if (addr_low < raw->sge[i].addrLow) sge[pos].addrHigh++; sge[pos].length = pages * PAGE_SIZE; sge[pos].flags = 0; pos++; } } sge[pos] = raw->sge[nseg-1]; for (i = 1; i < nseg_new; ++i) raw->sge[i] = sge[i]; free(sge, M_AACRAIDBUF); raw->sgeCnt = nseg_new; raw->flags |= RIO2_SGL_CONFORMANT; raw->sgeNominalSize = pages * PAGE_SIZE; return nseg_new; } /* * Unmap a command from controller-visible space. */ static void aac_unmap_command(struct aac_command *cm) { struct aac_softc *sc; sc = cm->cm_sc; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (!(cm->cm_flags & AAC_CMD_MAPPED)) return; if (cm->cm_datalen != 0 && cm->cm_passthr_dmat == 0) { if (cm->cm_flags & AAC_CMD_DATAIN) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTREAD); if (cm->cm_flags & AAC_CMD_DATAOUT) bus_dmamap_sync(sc->aac_buffer_dmat, cm->cm_datamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->aac_buffer_dmat, cm->cm_datamap); } cm->cm_flags &= ~AAC_CMD_MAPPED; } /* * Hardware Interface */ /* * Initialize the adapter. */ static void aac_common_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aac_softc *sc; sc = (struct aac_softc *)arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); sc->aac_common_busaddr = segs[0].ds_addr; } static int aac_check_firmware(struct aac_softc *sc) { u_int32_t code, major, minor, maxsize; u_int32_t options = 0, atu_size = 0, status, waitCount; time_t then; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* check if flash update is running */ if (AAC_GET_FWSTATUS(sc) & AAC_FLASH_UPD_PENDING) { then = time_uptime; do { code = AAC_GET_FWSTATUS(sc); if (time_uptime > (then + AAC_FWUPD_TIMEOUT)) { device_printf(sc->aac_dev, "FATAL: controller not coming ready, " "status %x\n", code); return(ENXIO); } } while (!(code & AAC_FLASH_UPD_SUCCESS) && !(code & AAC_FLASH_UPD_FAILED)); /* * Delay 10 seconds. Because right now FW is doing a soft reset, * do not read scratch pad register at this time */ waitCount = 10 * 10000; while (waitCount) { DELAY(100); /* delay 100 microseconds */ waitCount--; } } /* * Wait for the adapter to come ready. */ then = time_uptime; do { code = AAC_GET_FWSTATUS(sc); if (time_uptime > (then + AAC_BOOT_TIMEOUT)) { device_printf(sc->aac_dev, "FATAL: controller not coming ready, " "status %x\n", code); return(ENXIO); } } while (!(code & AAC_UP_AND_RUNNING) || code == 0xffffffff); /* * Retrieve the firmware version numbers. Dell PERC2/QC cards with * firmware version 1.x are not compatible with this driver. */ if (sc->flags & AAC_FLAGS_PERC2QC) { if (aacraid_sync_command(sc, AAC_MONKER_GETKERNVER, 0, 0, 0, 0, NULL, NULL)) { device_printf(sc->aac_dev, "Error reading firmware version\n"); return (EIO); } /* These numbers are stored as ASCII! */ major = (AAC_GET_MAILBOX(sc, 1) & 0xff) - 0x30; minor = (AAC_GET_MAILBOX(sc, 2) & 0xff) - 0x30; if (major == 1) { device_printf(sc->aac_dev, "Firmware version %d.%d is not supported.\n", major, minor); return (EINVAL); } } /* * Retrieve the capabilities/supported options word so we know what * work-arounds to enable. Some firmware revs don't support this * command. */ if (aacraid_sync_command(sc, AAC_MONKER_GETINFO, 0, 0, 0, 0, &status, NULL)) { if (status != AAC_SRB_STS_INVALID_REQUEST) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); return (EIO); } } else { options = AAC_GET_MAILBOX(sc, 1); atu_size = AAC_GET_MAILBOX(sc, 2); sc->supported_options = options; if ((options & AAC_SUPPORTED_4GB_WINDOW) != 0 && (sc->flags & AAC_FLAGS_NO4GB) == 0) sc->flags |= AAC_FLAGS_4GB_WINDOW; if (options & AAC_SUPPORTED_NONDASD) sc->flags |= AAC_FLAGS_ENABLE_CAM; if ((options & AAC_SUPPORTED_SGMAP_HOST64) != 0 && (sizeof(bus_addr_t) > 4) && (sc->hint_flags & 0x1)) { device_printf(sc->aac_dev, "Enabling 64-bit address support\n"); sc->flags |= AAC_FLAGS_SG_64BIT; } if (sc->aac_if.aif_send_command) { if ((options & AAC_SUPPORTED_NEW_COMM_TYPE3) || (options & AAC_SUPPORTED_NEW_COMM_TYPE4)) sc->flags |= AAC_FLAGS_NEW_COMM | AAC_FLAGS_NEW_COMM_TYPE34; else if (options & AAC_SUPPORTED_NEW_COMM_TYPE1) sc->flags |= AAC_FLAGS_NEW_COMM | AAC_FLAGS_NEW_COMM_TYPE1; else if (options & AAC_SUPPORTED_NEW_COMM_TYPE2) sc->flags |= AAC_FLAGS_NEW_COMM | AAC_FLAGS_NEW_COMM_TYPE2; } if (options & AAC_SUPPORTED_64BIT_ARRAYSIZE) sc->flags |= AAC_FLAGS_ARRAY_64BIT; } if (!(sc->flags & AAC_FLAGS_NEW_COMM)) { device_printf(sc->aac_dev, "Communication interface not supported!\n"); return (ENXIO); } if (sc->hint_flags & 2) { device_printf(sc->aac_dev, "Sync. mode enforced by driver parameter. This will cause a significant performance decrease!\n"); sc->flags |= AAC_FLAGS_SYNC_MODE; } else if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE34) { device_printf(sc->aac_dev, "Async. mode not supported by current driver, sync. mode enforced.\nPlease update driver to get full performance.\n"); sc->flags |= AAC_FLAGS_SYNC_MODE; } /* Check for broken hardware that does a lower number of commands */ sc->aac_max_fibs = (sc->flags & AAC_FLAGS_256FIBS ? 256:512); /* Remap mem. resource, if required */ if (atu_size > rman_get_size(sc->aac_regs_res0)) { bus_release_resource( sc->aac_dev, SYS_RES_MEMORY, sc->aac_regs_rid0, sc->aac_regs_res0); sc->aac_regs_res0 = bus_alloc_resource( sc->aac_dev, SYS_RES_MEMORY, &sc->aac_regs_rid0, 0ul, ~0ul, atu_size, RF_ACTIVE); if (sc->aac_regs_res0 == NULL) { sc->aac_regs_res0 = bus_alloc_resource_any( sc->aac_dev, SYS_RES_MEMORY, &sc->aac_regs_rid0, RF_ACTIVE); if (sc->aac_regs_res0 == NULL) { device_printf(sc->aac_dev, "couldn't allocate register window\n"); return (ENXIO); } } sc->aac_btag0 = rman_get_bustag(sc->aac_regs_res0); sc->aac_bhandle0 = rman_get_bushandle(sc->aac_regs_res0); } /* Read preferred settings */ sc->aac_max_fib_size = sizeof(struct aac_fib); sc->aac_max_sectors = 128; /* 64KB */ sc->aac_max_aif = 1; if (sc->flags & AAC_FLAGS_SG_64BIT) sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite64)) / sizeof(struct aac_sg_entry64); else sc->aac_sg_tablesize = (AAC_FIB_DATASIZE - sizeof(struct aac_blockwrite)) / sizeof(struct aac_sg_entry); if (!aacraid_sync_command(sc, AAC_MONKER_GETCOMMPREF, 0, 0, 0, 0, NULL, NULL)) { options = AAC_GET_MAILBOX(sc, 1); sc->aac_max_fib_size = (options & 0xFFFF); sc->aac_max_sectors = (options >> 16) << 1; options = AAC_GET_MAILBOX(sc, 2); sc->aac_sg_tablesize = (options >> 16); options = AAC_GET_MAILBOX(sc, 3); sc->aac_max_fibs = ((options >> 16) & 0xFFFF); if (sc->aac_max_fibs == 0 || sc->aac_hwif != AAC_HWIF_SRCV) sc->aac_max_fibs = (options & 0xFFFF); options = AAC_GET_MAILBOX(sc, 4); sc->aac_max_aif = (options & 0xFFFF); options = AAC_GET_MAILBOX(sc, 5); sc->aac_max_msix =(sc->flags & AAC_FLAGS_NEW_COMM_TYPE2) ? options : 0; } maxsize = sc->aac_max_fib_size + 31; if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE1) maxsize += sizeof(struct aac_fib_xporthdr); if (maxsize > PAGE_SIZE) { sc->aac_max_fib_size -= (maxsize - PAGE_SIZE); maxsize = PAGE_SIZE; } sc->aac_max_fibs_alloc = PAGE_SIZE / maxsize; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { sc->flags |= AAC_FLAGS_RAW_IO; device_printf(sc->aac_dev, "Enable Raw I/O\n"); } if ((sc->flags & AAC_FLAGS_RAW_IO) && (sc->flags & AAC_FLAGS_ARRAY_64BIT)) { sc->flags |= AAC_FLAGS_LBA_64BIT; device_printf(sc->aac_dev, "Enable 64-bit array\n"); } #ifdef AACRAID_DEBUG aacraid_get_fw_debug_buffer(sc); #endif return (0); } static int aac_init(struct aac_softc *sc) { struct aac_adapter_init *ip; int i, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* reset rrq index */ sc->aac_fibs_pushed_no = 0; for (i = 0; i < sc->aac_max_msix; i++) sc->aac_host_rrq_idx[i] = i * sc->aac_vector_cap; /* * Fill in the init structure. This tells the adapter about the * physical location of various important shared data structures. */ ip = &sc->aac_common->ac_init; ip->InitStructRevision = AAC_INIT_STRUCT_REVISION; if (sc->aac_max_fib_size > sizeof(struct aac_fib)) { ip->InitStructRevision = AAC_INIT_STRUCT_REVISION_4; sc->flags |= AAC_FLAGS_RAW_IO; } ip->NoOfMSIXVectors = sc->aac_max_msix; ip->AdapterFibsPhysicalAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_fibs); ip->AdapterFibsVirtualAddress = 0; ip->AdapterFibsSize = AAC_ADAPTER_FIBS * sizeof(struct aac_fib); ip->AdapterFibAlign = sizeof(struct aac_fib); ip->PrintfBufferAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_printf); ip->PrintfBufferSize = AAC_PRINTF_BUFSIZE; /* * The adapter assumes that pages are 4K in size, except on some * broken firmware versions that do the page->byte conversion twice, * therefore 'assuming' that this value is in 16MB units (2^24). * Round up since the granularity is so high. */ ip->HostPhysMemPages = ctob(physmem) / AAC_PAGE_SIZE; if (sc->flags & AAC_FLAGS_BROKEN_MEMMAP) { ip->HostPhysMemPages = (ip->HostPhysMemPages + AAC_PAGE_SIZE) / AAC_PAGE_SIZE; } ip->HostElapsedSeconds = time_uptime; /* reset later if invalid */ ip->InitFlags = AAC_INITFLAGS_NEW_COMM_SUPPORTED; if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE1) { ip->InitStructRevision = AAC_INIT_STRUCT_REVISION_6; ip->InitFlags |= (AAC_INITFLAGS_NEW_COMM_TYPE1_SUPPORTED | AAC_INITFLAGS_FAST_JBOD_SUPPORTED); device_printf(sc->aac_dev, "New comm. interface type1 enabled\n"); } else if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE2) { ip->InitStructRevision = AAC_INIT_STRUCT_REVISION_7; ip->InitFlags |= (AAC_INITFLAGS_NEW_COMM_TYPE2_SUPPORTED | AAC_INITFLAGS_FAST_JBOD_SUPPORTED); device_printf(sc->aac_dev, "New comm. interface type2 enabled\n"); } ip->MaxNumAif = sc->aac_max_aif; ip->HostRRQ_AddrLow = sc->aac_common_busaddr + offsetof(struct aac_common, ac_host_rrq); /* always 32-bit address */ ip->HostRRQ_AddrHigh = 0; if (sc->aac_support_opt2 & AAC_SUPPORTED_POWER_MANAGEMENT) { ip->InitFlags |= AAC_INITFLAGS_DRIVER_SUPPORTS_PM; ip->InitFlags |= AAC_INITFLAGS_DRIVER_USES_UTC_TIME; device_printf(sc->aac_dev, "Power Management enabled\n"); } ip->MaxIoCommands = sc->aac_max_fibs; ip->MaxIoSize = sc->aac_max_sectors << 9; ip->MaxFibSize = sc->aac_max_fib_size; /* * Do controller-type-specific initialisation */ AAC_MEM0_SETREG4(sc, AAC_SRC_ODBR_C, ~0); /* * Give the init structure to the controller. */ if (aacraid_sync_command(sc, AAC_MONKER_INITSTRUCT, sc->aac_common_busaddr + offsetof(struct aac_common, ac_init), 0, 0, 0, NULL, NULL)) { device_printf(sc->aac_dev, "error establishing init structure\n"); error = EIO; goto out; } /* * Check configuration issues */ if ((error = aac_check_config(sc)) != 0) goto out; error = 0; out: return(error); } static void aac_define_int_mode(struct aac_softc *sc) { device_t dev; int cap, msi_count, error = 0; uint32_t val; dev = sc->aac_dev; /* max. vectors from AAC_MONKER_GETCOMMPREF */ if (sc->aac_max_msix == 0) { sc->aac_max_msix = 1; sc->aac_vector_cap = sc->aac_max_fibs; return; } /* OS capability */ msi_count = pci_msix_count(dev); if (msi_count > AAC_MAX_MSIX) msi_count = AAC_MAX_MSIX; if (msi_count > sc->aac_max_msix) msi_count = sc->aac_max_msix; if (msi_count == 0 || (error = pci_alloc_msix(dev, &msi_count)) != 0) { device_printf(dev, "alloc msix failed - msi_count=%d, err=%d; " "will try MSI\n", msi_count, error); pci_release_msi(dev); } else { sc->msi_enabled = TRUE; device_printf(dev, "using MSI-X interrupts (%u vectors)\n", msi_count); } if (!sc->msi_enabled) { msi_count = 1; if ((error = pci_alloc_msi(dev, &msi_count)) != 0) { device_printf(dev, "alloc msi failed - err=%d; " "will use INTx\n", error); pci_release_msi(dev); } else { sc->msi_enabled = TRUE; device_printf(dev, "using MSI interrupts\n"); } } if (sc->msi_enabled) { /* now read controller capability from PCI config. space */ cap = aac_find_pci_capability(sc, PCIY_MSIX); val = (cap != 0 ? pci_read_config(dev, cap + 2, 2) : 0); if (!(val & AAC_PCI_MSI_ENABLE)) { pci_release_msi(dev); sc->msi_enabled = FALSE; } } if (!sc->msi_enabled) { device_printf(dev, "using legacy interrupts\n"); sc->aac_max_msix = 1; } else { AAC_ACCESS_DEVREG(sc, AAC_ENABLE_MSIX); if (sc->aac_max_msix > msi_count) sc->aac_max_msix = msi_count; } sc->aac_vector_cap = sc->aac_max_fibs / sc->aac_max_msix; fwprintf(sc, HBA_FLAGS_DBG_DEBUG_B, "msi_enabled %d vector_cap %d max_fibs %d max_msix %d", sc->msi_enabled,sc->aac_vector_cap, sc->aac_max_fibs, sc->aac_max_msix); } static int aac_find_pci_capability(struct aac_softc *sc, int cap) { device_t dev; uint32_t status; uint8_t ptr; dev = sc->aac_dev; status = pci_read_config(dev, PCIR_STATUS, 2); if (!(status & PCIM_STATUS_CAPPRESENT)) return (0); status = pci_read_config(dev, PCIR_HDRTYPE, 1); switch (status & PCIM_HDRTYPE) { case 0: case 1: ptr = PCIR_CAP_PTR; break; case 2: ptr = PCIR_CAP_PTR_2; break; default: return (0); break; } ptr = pci_read_config(dev, ptr, 1); while (ptr != 0) { int next, val; next = pci_read_config(dev, ptr + PCICAP_NEXTPTR, 1); val = pci_read_config(dev, ptr + PCICAP_ID, 1); if (val == cap) return (ptr); ptr = next; } return (0); } static int aac_setup_intr(struct aac_softc *sc) { int i, msi_count, rid; struct resource *res; void *tag; msi_count = sc->aac_max_msix; rid = (sc->msi_enabled ? 1:0); for (i = 0; i < msi_count; i++, rid++) { if ((res = bus_alloc_resource_any(sc->aac_dev,SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { device_printf(sc->aac_dev,"can't allocate interrupt\n"); return (EINVAL); } sc->aac_irq_rid[i] = rid; sc->aac_irq[i] = res; if (aac_bus_setup_intr(sc->aac_dev, res, INTR_MPSAFE | INTR_TYPE_BIO, NULL, aacraid_new_intr_type1, &sc->aac_msix[i], &tag)) { device_printf(sc->aac_dev, "can't set up interrupt\n"); return (EINVAL); } sc->aac_msix[i].vector_no = i; sc->aac_msix[i].sc = sc; sc->aac_intr[i] = tag; } return (0); } static int aac_check_config(struct aac_softc *sc) { struct aac_fib *fib; struct aac_cnt_config *ccfg; struct aac_cf_status_hdr *cf_shdr; int rval; mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); ccfg = (struct aac_cnt_config *)&fib->data[0]; bzero(ccfg, sizeof (*ccfg) - CT_PACKET_SIZE); ccfg->Command = VM_ContainerConfig; ccfg->CTCommand.command = CT_GET_CONFIG_STATUS; ccfg->CTCommand.param[CNT_SIZE] = sizeof(struct aac_cf_status_hdr); rval = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof (struct aac_cnt_config)); cf_shdr = (struct aac_cf_status_hdr *)ccfg->CTCommand.data; if (rval == 0 && ccfg->Command == ST_OK && ccfg->CTCommand.param[0] == CT_OK) { if (cf_shdr->action <= CFACT_PAUSE) { bzero(ccfg, sizeof (*ccfg) - CT_PACKET_SIZE); ccfg->Command = VM_ContainerConfig; ccfg->CTCommand.command = CT_COMMIT_CONFIG; rval = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof (struct aac_cnt_config)); if (rval == 0 && ccfg->Command == ST_OK && ccfg->CTCommand.param[0] == CT_OK) { /* successful completion */ rval = 0; } else { /* auto commit aborted due to error(s) */ rval = -2; } } else { /* auto commit aborted due to adapter indicating config. issues too dangerous to auto commit */ rval = -3; } } else { /* error */ rval = -1; } aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return(rval); } /* * Send a synchronous command to the controller and wait for a result. * Indicate if the controller completed the command with an error status. */ int aacraid_sync_command(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3, u_int32_t *sp, u_int32_t *r1) { time_t then; u_int32_t status; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* populate the mailbox */ AAC_SET_MAILBOX(sc, command, arg0, arg1, arg2, arg3); /* ensure the sync command doorbell flag is cleared */ if (!sc->msi_enabled) AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* then set it to signal the adapter */ AAC_QNOTIFY(sc, AAC_DB_SYNC_COMMAND); if ((command != AAC_MONKER_SYNCFIB) || (sp == NULL) || (*sp != 0)) { /* spin waiting for the command to complete */ then = time_uptime; do { if (time_uptime > (then + AAC_SYNC_TIMEOUT)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "timed out"); return(EIO); } } while (!(AAC_GET_ISTATUS(sc) & AAC_DB_SYNC_COMMAND)); /* clear the completion flag */ AAC_CLEAR_ISTATUS(sc, AAC_DB_SYNC_COMMAND); /* get the command status */ status = AAC_GET_MAILBOX(sc, 0); if (sp != NULL) *sp = status; /* return parameter */ if (r1 != NULL) *r1 = AAC_GET_MAILBOX(sc, 1); if (status != AAC_SRB_STS_SUCCESS) return (-1); } return(0); } static int aac_sync_fib(struct aac_softc *sc, u_int32_t command, u_int32_t xferstate, struct aac_fib *fib, u_int16_t datasize) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); if (datasize > AAC_FIB_DATASIZE) return(EINVAL); /* * Set up the sync FIB */ fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY; fib->Header.XferState |= xferstate; fib->Header.Command = command; fib->Header.StructType = AAC_FIBTYPE_TFIB; fib->Header.Size = sizeof(struct aac_fib_header) + datasize; fib->Header.SenderSize = sizeof(struct aac_fib); fib->Header.SenderFibAddress = 0; /* Not needed */ fib->Header.u.ReceiverFibAddress = sc->aac_common_busaddr + offsetof(struct aac_common, ac_sync_fib); /* * Give the FIB to the controller, wait for a response. */ if (aacraid_sync_command(sc, AAC_MONKER_SYNCFIB, fib->Header.u.ReceiverFibAddress, 0, 0, 0, NULL, NULL)) { fwprintf(sc, HBA_FLAGS_DBG_ERROR_B, "IO error"); return(EIO); } return (0); } /* * Check for commands that have been outstanding for a suspiciously long time, * and complain about them. */ static void aac_timeout(struct aac_softc *sc) { struct aac_command *cm; time_t deadline; int timedout; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Traverse the busy command list, bitch about late commands once * only. */ timedout = 0; deadline = time_uptime - AAC_CMD_TIMEOUT; TAILQ_FOREACH(cm, &sc->aac_busy, cm_link) { if (cm->cm_timestamp < deadline) { device_printf(sc->aac_dev, "COMMAND %p TIMEOUT AFTER %d SECONDS\n", cm, (int)(time_uptime-cm->cm_timestamp)); AAC_PRINT_FIB(sc, cm->cm_fib); timedout++; } } if (timedout) aac_reset_adapter(sc); aacraid_print_queues(sc); } /* * Interface Function Vectors */ /* * Read the current firmware status word. */ static int aac_src_get_fwstatus(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_SRC_OMR)); } /* * Notify the controller of a change in a given queue */ static void aac_src_qnotify(struct aac_softc *sc, int qbit) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, qbit << AAC_SRC_IDR_SHIFT); } /* * Get the interrupt reason bits */ static int aac_src_get_istatus(struct aac_softc *sc) { int val; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (sc->msi_enabled) { val = AAC_MEM0_GETREG4(sc, AAC_SRC_ODBR_MSI); if (val & AAC_MSI_SYNC_STATUS) val = AAC_DB_SYNC_COMMAND; else val = 0; } else { val = AAC_MEM0_GETREG4(sc, AAC_SRC_ODBR_R) >> AAC_SRC_ODR_SHIFT; } return(val); } /* * Clear some interrupt reason bits */ static void aac_src_clear_istatus(struct aac_softc *sc, int mask) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (sc->msi_enabled) { if (mask == AAC_DB_SYNC_COMMAND) AAC_ACCESS_DEVREG(sc, AAC_CLEAR_SYNC_BIT); } else { AAC_MEM0_SETREG4(sc, AAC_SRC_ODBR_C, mask << AAC_SRC_ODR_SHIFT); } } /* * Populate the mailbox and set the command word */ static void aac_src_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_SRC_MAILBOX, command); AAC_MEM0_SETREG4(sc, AAC_SRC_MAILBOX + 4, arg0); AAC_MEM0_SETREG4(sc, AAC_SRC_MAILBOX + 8, arg1); AAC_MEM0_SETREG4(sc, AAC_SRC_MAILBOX + 12, arg2); AAC_MEM0_SETREG4(sc, AAC_SRC_MAILBOX + 16, arg3); } static void aac_srcv_set_mailbox(struct aac_softc *sc, u_int32_t command, u_int32_t arg0, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); AAC_MEM0_SETREG4(sc, AAC_SRCV_MAILBOX, command); AAC_MEM0_SETREG4(sc, AAC_SRCV_MAILBOX + 4, arg0); AAC_MEM0_SETREG4(sc, AAC_SRCV_MAILBOX + 8, arg1); AAC_MEM0_SETREG4(sc, AAC_SRCV_MAILBOX + 12, arg2); AAC_MEM0_SETREG4(sc, AAC_SRCV_MAILBOX + 16, arg3); } /* * Fetch the immediate command status word */ static int aac_src_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_SRC_MAILBOX + (mb * 4))); } static int aac_srcv_get_mailbox(struct aac_softc *sc, int mb) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(AAC_MEM0_GETREG4(sc, AAC_SRCV_MAILBOX + (mb * 4))); } /* * Set/clear interrupt masks */ static void aac_src_access_devreg(struct aac_softc *sc, int mode) { u_int32_t val; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); switch (mode) { case AAC_ENABLE_INTERRUPT: AAC_MEM0_SETREG4(sc, AAC_SRC_OIMR, (sc->msi_enabled ? AAC_INT_ENABLE_TYPE1_MSIX : AAC_INT_ENABLE_TYPE1_INTX)); break; case AAC_DISABLE_INTERRUPT: AAC_MEM0_SETREG4(sc, AAC_SRC_OIMR, AAC_INT_DISABLE_ALL); break; case AAC_ENABLE_MSIX: /* set bit 6 */ val = AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); val |= 0x40; AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, val); AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); /* unmask int. */ val = PMC_ALL_INTERRUPT_BITS; AAC_MEM0_SETREG4(sc, AAC_SRC_IOAR, val); val = AAC_MEM0_GETREG4(sc, AAC_SRC_OIMR); AAC_MEM0_SETREG4(sc, AAC_SRC_OIMR, val & (~(PMC_GLOBAL_INT_BIT2 | PMC_GLOBAL_INT_BIT0))); break; case AAC_DISABLE_MSIX: /* reset bit 6 */ val = AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); val &= ~0x40; AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, val); AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); break; case AAC_CLEAR_AIF_BIT: /* set bit 5 */ val = AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); val |= 0x20; AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, val); AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); break; case AAC_CLEAR_SYNC_BIT: /* set bit 4 */ val = AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); val |= 0x10; AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, val); AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); break; case AAC_ENABLE_INTX: /* set bit 7 */ val = AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); val |= 0x80; AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, val); AAC_MEM0_GETREG4(sc, AAC_SRC_IDBR); /* unmask int. */ val = PMC_ALL_INTERRUPT_BITS; AAC_MEM0_SETREG4(sc, AAC_SRC_IOAR, val); val = AAC_MEM0_GETREG4(sc, AAC_SRC_OIMR); AAC_MEM0_SETREG4(sc, AAC_SRC_OIMR, val & (~(PMC_GLOBAL_INT_BIT2))); break; default: break; } } /* * New comm. interface: Send command functions */ static int aac_src_send_command(struct aac_softc *sc, struct aac_command *cm) { struct aac_fib_xporthdr *pFibX; u_int32_t fibsize, high_addr; u_int64_t address; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, "send command (new comm. type1)"); if (sc->msi_enabled && cm->cm_fib->Header.Command != AifRequest && sc->aac_max_msix > 1) { u_int16_t vector_no, first_choice = 0xffff; vector_no = sc->aac_fibs_pushed_no % sc->aac_max_msix; do { vector_no += 1; if (vector_no == sc->aac_max_msix) vector_no = 1; if (sc->aac_rrq_outstanding[vector_no] < sc->aac_vector_cap) break; if (0xffff == first_choice) first_choice = vector_no; else if (vector_no == first_choice) break; } while (1); if (vector_no == first_choice) vector_no = 0; sc->aac_rrq_outstanding[vector_no]++; if (sc->aac_fibs_pushed_no == 0xffffffff) sc->aac_fibs_pushed_no = 0; else sc->aac_fibs_pushed_no++; cm->cm_fib->Header.Handle += (vector_no << 16); } if (sc->flags & AAC_FLAGS_NEW_COMM_TYPE2) { /* Calculate the amount to the fibsize bits */ fibsize = (cm->cm_fib->Header.Size + 127) / 128 - 1; /* Fill new FIB header */ address = cm->cm_fibphys; high_addr = (u_int32_t)(address >> 32); if (high_addr == 0L) { cm->cm_fib->Header.StructType = AAC_FIBTYPE_TFIB2; cm->cm_fib->Header.u.TimeStamp = 0L; } else { cm->cm_fib->Header.StructType = AAC_FIBTYPE_TFIB2_64; cm->cm_fib->Header.u.SenderFibAddressHigh = high_addr; } cm->cm_fib->Header.SenderFibAddress = (u_int32_t)address; } else { /* Calculate the amount to the fibsize bits */ fibsize = (sizeof(struct aac_fib_xporthdr) + cm->cm_fib->Header.Size + 127) / 128 - 1; /* Fill XPORT header */ pFibX = (struct aac_fib_xporthdr *) ((unsigned char *)cm->cm_fib - sizeof(struct aac_fib_xporthdr)); pFibX->Handle = cm->cm_fib->Header.Handle; pFibX->HostAddress = cm->cm_fibphys; pFibX->Size = cm->cm_fib->Header.Size; address = cm->cm_fibphys - sizeof(struct aac_fib_xporthdr); high_addr = (u_int32_t)(address >> 32); } if (fibsize > 31) fibsize = 31; aac_enqueue_busy(cm); if (high_addr) { AAC_MEM0_SETREG4(sc, AAC_SRC_IQUE64_H, high_addr); AAC_MEM0_SETREG4(sc, AAC_SRC_IQUE64_L, (u_int32_t)address + fibsize); } else { AAC_MEM0_SETREG4(sc, AAC_SRC_IQUE32, (u_int32_t)address + fibsize); } return 0; } /* * New comm. interface: get, set outbound queue index */ static int aac_src_get_outb_queue(struct aac_softc *sc) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return(-1); } static void aac_src_set_outb_queue(struct aac_softc *sc, int index) { fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); } /* * Debugging and Diagnostics */ /* * Print some information about the controller. */ static void aac_describe_controller(struct aac_softc *sc) { struct aac_fib *fib; struct aac_adapter_info *info; char *adapter_type = "Adaptec RAID controller"; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); if (sc->supported_options & AAC_SUPPORTED_SUPPLEMENT_ADAPTER_INFO) { fib->data[0] = 0; if (aac_sync_fib(sc, RequestSupplementAdapterInfo, 0, fib, 1)) device_printf(sc->aac_dev, "RequestSupplementAdapterInfo failed\n"); else { struct aac_supplement_adapter_info *supp_info; supp_info = ((struct aac_supplement_adapter_info *)&fib->data[0]); adapter_type = (char *)supp_info->AdapterTypeText; sc->aac_feature_bits = supp_info->FeatureBits; sc->aac_support_opt2 = supp_info->SupportedOptions2; } } device_printf(sc->aac_dev, "%s, aacraid driver %d.%d.%d-%d\n", adapter_type, AAC_DRIVER_MAJOR_VERSION, AAC_DRIVER_MINOR_VERSION, AAC_DRIVER_BUGFIX_LEVEL, AAC_DRIVER_BUILD); fib->data[0] = 0; if (aac_sync_fib(sc, RequestAdapterInfo, 0, fib, 1)) { device_printf(sc->aac_dev, "RequestAdapterInfo failed\n"); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } /* save the kernel revision structure for later use */ info = (struct aac_adapter_info *)&fib->data[0]; sc->aac_revision = info->KernelRevision; if (bootverbose) { device_printf(sc->aac_dev, "%s %dMHz, %dMB memory " "(%dMB cache, %dMB execution), %s\n", aac_describe_code(aac_cpu_variant, info->CpuVariant), info->ClockSpeed, info->TotalMem / (1024 * 1024), info->BufferMem / (1024 * 1024), info->ExecutionMem / (1024 * 1024), aac_describe_code(aac_battery_platform, info->batteryPlatform)); device_printf(sc->aac_dev, "Kernel %d.%d-%d, Build %d, S/N %6X\n", info->KernelRevision.external.comp.major, info->KernelRevision.external.comp.minor, info->KernelRevision.external.comp.dash, info->KernelRevision.buildNumber, (u_int32_t)(info->SerialNumber & 0xffffff)); device_printf(sc->aac_dev, "Supported Options=%b\n", sc->supported_options, "\20" "\1SNAPSHOT" "\2CLUSTERS" "\3WCACHE" "\4DATA64" "\5HOSTTIME" "\6RAID50" "\7WINDOW4GB" "\10SCSIUPGD" "\11SOFTERR" "\12NORECOND" "\13SGMAP64" "\14ALARM" "\15NONDASD" "\16SCSIMGT" "\17RAIDSCSI" "\21ADPTINFO" "\22NEWCOMM" "\23ARRAY64BIT" "\24HEATSENSOR"); } aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); } /* * Look up a text description of a numeric error code and return a pointer to * same. */ static char * aac_describe_code(struct aac_code_lookup *table, u_int32_t code) { int i; for (i = 0; table[i].string != NULL; i++) if (table[i].code == code) return(table[i].string); return(table[i + 1].string); } /* * Management Interface */ static int aac_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct aac_softc *sc; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); #if __FreeBSD_version >= 702000 device_busy(sc->aac_dev); devfs_set_cdevpriv(sc, aac_cdevpriv_dtor); #endif return 0; } static int aac_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td) { union aac_statrequest *as; struct aac_softc *sc; int error = 0; as = (union aac_statrequest *)arg; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); switch (cmd) { case AACIO_STATS: switch (as->as_item) { case AACQ_FREE: case AACQ_READY: case AACQ_BUSY: bcopy(&sc->aac_qstat[as->as_item], &as->as_qstat, sizeof(struct aac_qstat)); break; default: error = ENOENT; break; } break; case FSACTL_SENDFIB: case FSACTL_SEND_LARGE_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_SENDFIB: case FSACTL_LNX_SEND_LARGE_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SENDFIB"); error = aac_ioctl_sendfib(sc, arg); break; case FSACTL_SEND_RAW_SRB: arg = *(caddr_t*)arg; case FSACTL_LNX_SEND_RAW_SRB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_SEND_RAW_SRB"); error = aac_ioctl_send_raw_srb(sc, arg); break; case FSACTL_AIF_THREAD: case FSACTL_LNX_AIF_THREAD: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_AIF_THREAD"); error = EINVAL; break; case FSACTL_OPEN_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_OPEN_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_OPEN_GET_ADAPTER_FIB"); error = aac_open_aif(sc, arg); break; case FSACTL_GET_NEXT_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_NEXT_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_NEXT_ADAPTER_FIB"); error = aac_getnext_aif(sc, arg); break; case FSACTL_CLOSE_GET_ADAPTER_FIB: arg = *(caddr_t*)arg; case FSACTL_LNX_CLOSE_GET_ADAPTER_FIB: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_CLOSE_GET_ADAPTER_FIB"); error = aac_close_aif(sc, arg); break; case FSACTL_MINIPORT_REV_CHECK: arg = *(caddr_t*)arg; case FSACTL_LNX_MINIPORT_REV_CHECK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_MINIPORT_REV_CHECK"); error = aac_rev_check(sc, arg); break; case FSACTL_QUERY_DISK: arg = *(caddr_t*)arg; case FSACTL_LNX_QUERY_DISK: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_QUERY_DISK"); error = aac_query_disk(sc, arg); break; case FSACTL_DELETE_DISK: case FSACTL_LNX_DELETE_DISK: /* * We don't trust the underland to tell us when to delete a * container, rather we rely on an AIF coming from the * controller */ error = 0; break; case FSACTL_GET_PCI_INFO: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_PCI_INFO: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_PCI_INFO"); error = aac_get_pci_info(sc, arg); break; case FSACTL_GET_FEATURES: arg = *(caddr_t*)arg; case FSACTL_LNX_GET_FEATURES: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "FSACTL_GET_FEATURES"); error = aac_supported_features(sc, arg); break; default: fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "unsupported cmd 0x%lx\n", cmd); error = EINVAL; break; } return(error); } static int aac_poll(struct cdev *dev, int poll_events, struct thread *td) { struct aac_softc *sc; struct aac_fib_context *ctx; int revents; sc = dev->si_drv1; revents = 0; mtx_lock(&sc->aac_io_lock); if ((poll_events & (POLLRDNORM | POLLIN)) != 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->ctx_idx != sc->aifq_idx || ctx->ctx_wrap) { revents |= poll_events & (POLLIN | POLLRDNORM); break; } } } mtx_unlock(&sc->aac_io_lock); if (revents == 0) { if (poll_events & (POLLIN | POLLRDNORM)) selrecord(td, &sc->rcv_select); } return (revents); } static void aac_ioctl_event(struct aac_softc *sc, struct aac_event *event, void *arg) { switch (event->ev_type) { case AAC_EVENT_CMFREE: mtx_assert(&sc->aac_io_lock, MA_OWNED); if (aacraid_alloc_command(sc, (struct aac_command **)arg)) { aacraid_add_event(sc, event); return; } free(event, M_AACRAIDBUF); wakeup(arg); break; default: break; } } /* * Send a FIB supplied from userspace */ static int aac_ioctl_sendfib(struct aac_softc *sc, caddr_t ufib) { struct aac_command *cm; int size, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; /* * Get a command */ mtx_lock(&sc->aac_io_lock); if (aacraid_alloc_command(sc, &cm)) { struct aac_event *event; event = malloc(sizeof(struct aac_event), M_AACRAIDBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aacraid_add_event(sc, event); msleep(cm, &sc->aac_io_lock, 0, "aacraid_ctlsfib", 0); } mtx_unlock(&sc->aac_io_lock); /* * Fetch the FIB header, then re-copy to get data as well. */ if ((error = copyin(ufib, cm->cm_fib, sizeof(struct aac_fib_header))) != 0) goto out; size = cm->cm_fib->Header.Size + sizeof(struct aac_fib_header); if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "incoming FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } if ((error = copyin(ufib, cm->cm_fib, size)) != 0) goto out; cm->cm_fib->Header.Size = size; cm->cm_timestamp = time_uptime; cm->cm_datalen = 0; /* * Pass the FIB to the controller, wait for it to complete. */ mtx_lock(&sc->aac_io_lock); error = aacraid_wait_command(cm); mtx_unlock(&sc->aac_io_lock); if (error != 0) { device_printf(sc->aac_dev, "aacraid_wait_command return %d\n", error); goto out; } /* * Copy the FIB and data back out to the caller. */ size = cm->cm_fib->Header.Size; if (size > sc->aac_max_fib_size) { device_printf(sc->aac_dev, "outbound FIB oversized (%d > %d)\n", size, sc->aac_max_fib_size); size = sc->aac_max_fib_size; } error = copyout(cm->cm_fib, ufib, size); out: if (cm != NULL) { mtx_lock(&sc->aac_io_lock); aacraid_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * Send a passthrough FIB supplied from userspace */ static int aac_ioctl_send_raw_srb(struct aac_softc *sc, caddr_t arg) { struct aac_command *cm; struct aac_fib *fib; struct aac_srb *srbcmd; struct aac_srb *user_srb = (struct aac_srb *)arg; void *user_reply; int error, transfer_data = 0; bus_dmamap_t orig_map = 0; u_int32_t fibsize = 0; u_int64_t srb_sg_address; u_int32_t srb_sg_bytecount; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); cm = NULL; mtx_lock(&sc->aac_io_lock); if (aacraid_alloc_command(sc, &cm)) { struct aac_event *event; event = malloc(sizeof(struct aac_event), M_AACRAIDBUF, M_NOWAIT | M_ZERO); if (event == NULL) { error = EBUSY; mtx_unlock(&sc->aac_io_lock); goto out; } event->ev_type = AAC_EVENT_CMFREE; event->ev_callback = aac_ioctl_event; event->ev_arg = &cm; aacraid_add_event(sc, event); msleep(cm, &sc->aac_io_lock, 0, "aacraid_ctlsraw", 0); } mtx_unlock(&sc->aac_io_lock); cm->cm_data = NULL; /* save original dma map */ orig_map = cm->cm_datamap; fib = cm->cm_fib; srbcmd = (struct aac_srb *)fib->data; if ((error = copyin((void *)&user_srb->data_len, &fibsize, sizeof (u_int32_t)) != 0)) goto out; if (fibsize > (sc->aac_max_fib_size-sizeof(struct aac_fib_header))) { error = EINVAL; goto out; } if ((error = copyin((void *)user_srb, srbcmd, fibsize) != 0)) goto out; srbcmd->function = 0; /* SRBF_ExecuteScsi */ srbcmd->retry_limit = 0; /* obsolete */ /* only one sg element from userspace supported */ if (srbcmd->sg_map.SgCount > 1) { error = EINVAL; goto out; } /* check fibsize */ if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry))) { struct aac_sg_entry *sgp = srbcmd->sg_map.SgEntry; srb_sg_bytecount = sgp->SgByteCount; srb_sg_address = (u_int64_t)sgp->SgAddress; } else if (fibsize == (sizeof(struct aac_srb) + srbcmd->sg_map.SgCount * sizeof(struct aac_sg_entry64))) { #ifdef __LP64__ struct aac_sg_entry64 *sgp = (struct aac_sg_entry64 *)srbcmd->sg_map.SgEntry; srb_sg_bytecount = sgp->SgByteCount; srb_sg_address = sgp->SgAddress; if (srb_sg_address > 0xffffffffull && !(sc->flags & AAC_FLAGS_SG_64BIT)) #endif { error = EINVAL; goto out; } } else { error = EINVAL; goto out; } user_reply = (char *)arg + fibsize; srbcmd->data_len = srb_sg_bytecount; if (srbcmd->sg_map.SgCount == 1) transfer_data = 1; if (transfer_data) { /* * Create DMA tag for the passthr. data buffer and allocate it. */ if (bus_dma_tag_create(sc->aac_parent_dmat, /* parent */ 1, 0, /* algnmnt, boundary */ (sc->flags & AAC_FLAGS_SG_64BIT) ? BUS_SPACE_MAXADDR_32BIT : 0x7fffffff, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ srb_sg_bytecount, /* size */ sc->aac_sg_tablesize, /* nsegments */ srb_sg_bytecount, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* No locking needed */ &cm->cm_passthr_dmat)) { error = ENOMEM; goto out; } if (bus_dmamem_alloc(cm->cm_passthr_dmat, (void **)&cm->cm_data, BUS_DMA_NOWAIT, &cm->cm_datamap)) { error = ENOMEM; goto out; } /* fill some cm variables */ cm->cm_datalen = srb_sg_bytecount; if (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN) cm->cm_flags |= AAC_CMD_DATAIN; if (srbcmd->flags & AAC_SRB_FLAGS_DATA_OUT) cm->cm_flags |= AAC_CMD_DATAOUT; if (srbcmd->flags & AAC_SRB_FLAGS_DATA_OUT) { if ((error = copyin((void *)(uintptr_t)srb_sg_address, cm->cm_data, cm->cm_datalen)) != 0) goto out; /* sync required for bus_dmamem_alloc() alloc. mem.? */ bus_dmamap_sync(cm->cm_passthr_dmat, cm->cm_datamap, BUS_DMASYNC_PREWRITE); } } /* build the FIB */ fib->Header.Size = sizeof(struct aac_fib_header) + sizeof(struct aac_srb); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC; fib->Header.Command = (sc->flags & AAC_FLAGS_SG_64BIT) ? ScsiPortCommandU64 : ScsiPortCommand; cm->cm_sgtable = (struct aac_sg_table *)&srbcmd->sg_map; /* send command */ if (transfer_data) { bus_dmamap_load(cm->cm_passthr_dmat, cm->cm_datamap, cm->cm_data, cm->cm_datalen, aacraid_map_command_sg, cm, 0); } else { aacraid_map_command_sg(cm, NULL, 0, 0); } /* wait for completion */ mtx_lock(&sc->aac_io_lock); while (!(cm->cm_flags & AAC_CMD_COMPLETED)) msleep(cm, &sc->aac_io_lock, 0, "aacraid_ctlsrw2", 0); mtx_unlock(&sc->aac_io_lock); /* copy data */ if (transfer_data && (srbcmd->flags & AAC_SRB_FLAGS_DATA_IN)) { if ((error = copyout(cm->cm_data, (void *)(uintptr_t)srb_sg_address, cm->cm_datalen)) != 0) goto out; /* sync required for bus_dmamem_alloc() allocated mem.? */ bus_dmamap_sync(cm->cm_passthr_dmat, cm->cm_datamap, BUS_DMASYNC_POSTREAD); } /* status */ error = copyout(fib->data, user_reply, sizeof(struct aac_srb_response)); out: if (cm && cm->cm_data) { if (transfer_data) bus_dmamap_unload(cm->cm_passthr_dmat, cm->cm_datamap); bus_dmamem_free(cm->cm_passthr_dmat, cm->cm_data, cm->cm_datamap); cm->cm_datamap = orig_map; } if (cm && cm->cm_passthr_dmat) bus_dma_tag_destroy(cm->cm_passthr_dmat); if (cm) { mtx_lock(&sc->aac_io_lock); aacraid_release_command(cm); mtx_unlock(&sc->aac_io_lock); } return(error); } /* * Request an AIF from the controller (new comm. type1) */ static void aac_request_aif(struct aac_softc *sc) { struct aac_command *cm; struct aac_fib *fib; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if (aacraid_alloc_command(sc, &cm)) { sc->aif_pending = 1; return; } sc->aif_pending = 0; /* build the FIB */ fib = cm->cm_fib; fib->Header.Size = sizeof(struct aac_fib); fib->Header.XferState = AAC_FIBSTATE_HOSTOWNED | AAC_FIBSTATE_INITIALISED | AAC_FIBSTATE_EMPTY | AAC_FIBSTATE_FROMHOST | AAC_FIBSTATE_REXPECTED | AAC_FIBSTATE_NORM | AAC_FIBSTATE_ASYNC; /* set AIF marker */ fib->Header.Handle = 0x00800000; fib->Header.Command = AifRequest; ((struct aac_aif_command *)fib->data)->command = AifReqEvent; aacraid_map_command_sg(cm, NULL, 0, 0); } #if __FreeBSD_version >= 702000 /* * cdevpriv interface private destructor. */ static void aac_cdevpriv_dtor(void *arg) { struct aac_softc *sc; sc = arg; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&Giant); device_unbusy(sc->aac_dev); mtx_unlock(&Giant); } #else static int aac_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct aac_softc *sc; sc = dev->si_drv1; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); return 0; } #endif /* * Handle an AIF sent to us by the controller; queue it for later reference. * If the queue fills up, then drop the older entries. */ static void aac_handle_aif(struct aac_softc *sc, struct aac_fib *fib) { struct aac_aif_command *aif; struct aac_container *co, *co_next; struct aac_fib_context *ctx; struct aac_fib *sync_fib; struct aac_mntinforesp mir; int next, current, found; int count = 0, changed = 0, i = 0; u_int32_t channel, uid; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); aif = (struct aac_aif_command*)&fib->data[0]; aacraid_print_aif(sc, aif); /* Is it an event that we should care about? */ switch (aif->command) { case AifCmdEventNotify: switch (aif->data.EN.type) { case AifEnAddContainer: case AifEnDeleteContainer: /* * A container was added or deleted, but the message * doesn't tell us anything else! Re-enumerate the * containers and sort things out. */ aac_alloc_sync_fib(sc, &sync_fib); do { /* * Ask the controller for its containers one at * a time. * XXX What if the controller's list changes * midway through this enumaration? * XXX This should be done async. */ if (aac_get_container_info(sc, sync_fib, i, &mir, &uid) != 0) continue; if (i == 0) count = mir.MntRespCount; /* * Check the container against our list. * co->co_found was already set to 0 in a * previous run. */ if ((mir.Status == ST_OK) && (mir.MntTable[0].VolType != CT_NONE)) { found = 0; TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == mir.MntTable[0].ObjectId) { co->co_found = 1; found = 1; break; } } /* * If the container matched, continue * in the list. */ if (found) { i++; continue; } /* * This is a new container. Do all the * appropriate things to set it up. */ aac_add_container(sc, &mir, 1, uid); changed = 1; } i++; } while ((i < count) && (i < AAC_MAX_CONTAINERS)); aac_release_sync_fib(sc); /* * Go through our list of containers and see which ones * were not marked 'found'. Since the controller didn't * list them they must have been deleted. Do the * appropriate steps to destroy the device. Also reset * the co->co_found field. */ co = TAILQ_FIRST(&sc->aac_container_tqh); while (co != NULL) { if (co->co_found == 0) { co_next = TAILQ_NEXT(co, co_link); TAILQ_REMOVE(&sc->aac_container_tqh, co, co_link); free(co, M_AACRAIDBUF); changed = 1; co = co_next; } else { co->co_found = 0; co = TAILQ_NEXT(co, co_link); } } /* Attach the newly created containers */ if (changed) { if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, 0, AAC_CAM_TARGET_WILDCARD); } break; case AifEnEnclosureManagement: switch (aif->data.EN.data.EEE.eventType) { case AIF_EM_DRIVE_INSERTION: case AIF_EM_DRIVE_REMOVAL: channel = aif->data.EN.data.EEE.unitID; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, ((channel>>24) & 0xF) + 1, (channel & 0xFFFF)); break; } break; case AifEnAddJBOD: case AifEnDeleteJBOD: case AifRawDeviceRemove: channel = aif->data.EN.data.ECE.container; if (sc->cam_rescan_cb != NULL) sc->cam_rescan_cb(sc, ((channel>>24) & 0xF) + 1, AAC_CAM_TARGET_WILDCARD); break; default: break; } default: break; } /* Copy the AIF data to the AIF queue for ioctl retrieval */ current = sc->aifq_idx; next = (current + 1) % AAC_AIFQ_LENGTH; if (next == 0) sc->aifq_filled = 1; bcopy(fib, &sc->aac_aifq[current], sizeof(struct aac_fib)); /* modify AIF contexts */ if (sc->aifq_filled) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (next == ctx->ctx_idx) ctx->ctx_wrap = 1; else if (current == ctx->ctx_idx && ctx->ctx_wrap) ctx->ctx_idx = next; } } sc->aifq_idx = next; /* On the off chance that someone is sleeping for an aif... */ if (sc->aac_state & AAC_STATE_AIF_SLEEPER) wakeup(sc->aac_aifq); /* Wakeup any poll()ers */ selwakeuppri(&sc->rcv_select, PRIBIO); return; } /* * Return the Revision of the driver to userspace and check to see if the * userspace app is possibly compatible. This is extremely bogus since * our driver doesn't follow Adaptec's versioning system. Cheat by just * returning what the card reported. */ static int aac_rev_check(struct aac_softc *sc, caddr_t udata) { struct aac_rev_check rev_check; struct aac_rev_check_resp rev_check_resp; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); /* * Copyin the revision struct from userspace */ if ((error = copyin(udata, (caddr_t)&rev_check, sizeof(struct aac_rev_check))) != 0) { return error; } fwprintf(sc, HBA_FLAGS_DBG_IOCTL_COMMANDS_B, "Userland revision= %d\n", rev_check.callingRevision.buildNumber); /* * Doctor up the response struct. */ rev_check_resp.possiblyCompatible = 1; rev_check_resp.adapterSWRevision.external.comp.major = AAC_DRIVER_MAJOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.minor = AAC_DRIVER_MINOR_VERSION; rev_check_resp.adapterSWRevision.external.comp.type = AAC_DRIVER_TYPE; rev_check_resp.adapterSWRevision.external.comp.dash = AAC_DRIVER_BUGFIX_LEVEL; rev_check_resp.adapterSWRevision.buildNumber = AAC_DRIVER_BUILD; return(copyout((caddr_t)&rev_check_resp, udata, sizeof(struct aac_rev_check_resp))); } /* * Pass the fib context to the caller */ static int aac_open_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *fibctx, *ctx; int error = 0; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); fibctx = malloc(sizeof(struct aac_fib_context), M_AACRAIDBUF, M_NOWAIT|M_ZERO); if (fibctx == NULL) return (ENOMEM); mtx_lock(&sc->aac_io_lock); /* all elements are already 0, add to queue */ if (sc->fibctx == NULL) sc->fibctx = fibctx; else { for (ctx = sc->fibctx; ctx->next; ctx = ctx->next) ; ctx->next = fibctx; fibctx->prev = ctx; } /* evaluate unique value */ fibctx->unique = (*(u_int32_t *)&fibctx & 0xffffffff); ctx = sc->fibctx; while (ctx != fibctx) { if (ctx->unique == fibctx->unique) { fibctx->unique++; ctx = sc->fibctx; } else { ctx = ctx->next; } } error = copyout(&fibctx->unique, (void *)arg, sizeof(u_int32_t)); mtx_unlock(&sc->aac_io_lock); if (error) aac_close_aif(sc, (caddr_t)ctx); return error; } /* * Close the caller's fib context */ static int aac_close_aif(struct aac_softc *sc, caddr_t arg) { struct aac_fib_context *ctx; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (ctx->unique == *(uint32_t *)&arg) { if (ctx == sc->fibctx) sc->fibctx = NULL; else { ctx->prev->next = ctx->next; if (ctx->next) ctx->next->prev = ctx->prev; } break; } } if (ctx) free(ctx, M_AACRAIDBUF); mtx_unlock(&sc->aac_io_lock); return 0; } /* * Pass the caller the next AIF in their queue */ static int aac_getnext_aif(struct aac_softc *sc, caddr_t arg) { struct get_adapter_fib_ioctl agf; struct aac_fib_context *ctx; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); if ((error = copyin(arg, &agf, sizeof(agf))) == 0) { for (ctx = sc->fibctx; ctx; ctx = ctx->next) { if (agf.AdapterFibContext == ctx->unique) break; } if (!ctx) { mtx_unlock(&sc->aac_io_lock); return (EFAULT); } error = aac_return_aif(sc, ctx, agf.AifFib); if (error == EAGAIN && agf.Wait) { fwprintf(sc, HBA_FLAGS_DBG_AIF_B, "aac_getnext_aif(): waiting for AIF"); sc->aac_state |= AAC_STATE_AIF_SLEEPER; while (error == EAGAIN) { mtx_unlock(&sc->aac_io_lock); error = tsleep(sc->aac_aifq, PRIBIO | PCATCH, "aacaif", 0); mtx_lock(&sc->aac_io_lock); if (error == 0) error = aac_return_aif(sc, ctx, agf.AifFib); } sc->aac_state &= ~AAC_STATE_AIF_SLEEPER; } } mtx_unlock(&sc->aac_io_lock); return(error); } /* * Hand the next AIF off the top of the queue out to userspace. */ static int aac_return_aif(struct aac_softc *sc, struct aac_fib_context *ctx, caddr_t uptr) { int current, error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); current = ctx->ctx_idx; if (current == sc->aifq_idx && !ctx->ctx_wrap) { /* empty */ return (EAGAIN); } error = copyout(&sc->aac_aifq[current], (void *)uptr, sizeof(struct aac_fib)); if (error) device_printf(sc->aac_dev, "aac_return_aif: copyout returned %d\n", error); else { ctx->ctx_wrap = 0; ctx->ctx_idx = (current + 1) % AAC_AIFQ_LENGTH; } return(error); } static int aac_get_pci_info(struct aac_softc *sc, caddr_t uptr) { struct aac_pci_info { u_int32_t bus; u_int32_t slot; } pciinf; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); pciinf.bus = pci_get_bus(sc->aac_dev); pciinf.slot = pci_get_slot(sc->aac_dev); error = copyout((caddr_t)&pciinf, uptr, sizeof(struct aac_pci_info)); return (error); } static int aac_supported_features(struct aac_softc *sc, caddr_t uptr) { struct aac_features f; int error; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); if ((error = copyin(uptr, &f, sizeof (f))) != 0) return (error); /* * When the management driver receives FSACTL_GET_FEATURES ioctl with * ALL zero in the featuresState, the driver will return the current * state of all the supported features, the data field will not be * valid. * When the management driver receives FSACTL_GET_FEATURES ioctl with * a specific bit set in the featuresState, the driver will return the * current state of this specific feature and whatever data that are * associated with the feature in the data field or perform whatever * action needed indicates in the data field. */ if (f.feat.fValue == 0) { f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; f.feat.fBits.JBODSupport = 1; /* TODO: In the future, add other features state here as well */ } else { if (f.feat.fBits.largeLBA) f.feat.fBits.largeLBA = (sc->flags & AAC_FLAGS_LBA_64BIT) ? 1 : 0; /* TODO: Add other features state and data in the future */ } error = copyout(&f, uptr, sizeof (f)); return (error); } /* * Give the userland some information about the container. The AAC arch * expects the driver to be a SCSI passthrough type driver, so it expects * the containers to have b:t:l numbers. Fake it. */ static int aac_query_disk(struct aac_softc *sc, caddr_t uptr) { struct aac_query_disk query_disk; struct aac_container *co; int error, id; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_lock(&sc->aac_io_lock); error = copyin(uptr, (caddr_t)&query_disk, sizeof(struct aac_query_disk)); if (error) { mtx_unlock(&sc->aac_io_lock); return (error); } id = query_disk.ContainerNumber; if (id == -1) { mtx_unlock(&sc->aac_io_lock); return (EINVAL); } TAILQ_FOREACH(co, &sc->aac_container_tqh, co_link) { if (co->co_mntobj.ObjectId == id) break; } if (co == NULL) { query_disk.Valid = 0; query_disk.Locked = 0; query_disk.Deleted = 1; /* XXX is this right? */ } else { query_disk.Valid = 1; query_disk.Locked = 1; query_disk.Deleted = 0; query_disk.Bus = device_get_unit(sc->aac_dev); query_disk.Target = 0; query_disk.Lun = 0; query_disk.UnMapped = 0; } error = copyout((caddr_t)&query_disk, uptr, sizeof(struct aac_query_disk)); mtx_unlock(&sc->aac_io_lock); return (error); } static void aac_container_bus(struct aac_softc *sc) { struct aac_sim *sim; device_t child; sim =(struct aac_sim *)malloc(sizeof(struct aac_sim), M_AACRAIDBUF, M_NOWAIT | M_ZERO); if (sim == NULL) { device_printf(sc->aac_dev, "No memory to add container bus\n"); panic("Out of memory?!"); }; child = device_add_child(sc->aac_dev, "aacraidp", -1); if (child == NULL) { device_printf(sc->aac_dev, "device_add_child failed for container bus\n"); free(sim, M_AACRAIDBUF); panic("Out of memory?!"); } sim->TargetsPerBus = AAC_MAX_CONTAINERS; sim->BusNumber = 0; sim->BusType = CONTAINER_BUS; sim->InitiatorBusId = -1; sim->aac_sc = sc; sim->sim_dev = child; sim->aac_cam = NULL; device_set_ivars(child, sim); device_set_desc(child, "Container Bus"); TAILQ_INSERT_TAIL(&sc->aac_sim_tqh, sim, sim_link); /* device_set_desc(child, aac_describe_code(aac_container_types, mir->MntTable[0].VolType)); */ bus_generic_attach(sc->aac_dev); } static void aac_get_bus_info(struct aac_softc *sc) { struct aac_fib *fib; struct aac_ctcfg *c_cmd; struct aac_ctcfg_resp *c_resp; struct aac_vmioctl *vmi; struct aac_vmi_businf_resp *vmi_resp; struct aac_getbusinf businfo; struct aac_sim *caminf; device_t child; int i, error; mtx_lock(&sc->aac_io_lock); aac_alloc_sync_fib(sc, &fib); c_cmd = (struct aac_ctcfg *)&fib->data[0]; bzero(c_cmd, sizeof(struct aac_ctcfg)); c_cmd->Command = VM_ContainerConfig; c_cmd->cmd = CT_GET_SCSI_METHOD; c_cmd->param = 0; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_ctcfg)); if (error) { device_printf(sc->aac_dev, "Error %d sending " "VM_ContainerConfig command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } c_resp = (struct aac_ctcfg_resp *)&fib->data[0]; if (c_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_ContainerConfig returned 0x%x\n", c_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } sc->scsi_method_id = c_resp->param; vmi = (struct aac_vmioctl *)&fib->data[0]; bzero(vmi, sizeof(struct aac_vmioctl)); vmi->Command = VM_Ioctl; vmi->ObjType = FT_DRIVE; vmi->MethId = sc->scsi_method_id; vmi->ObjId = 0; vmi->IoctlCmd = GetBusInfo; error = aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof(struct aac_vmi_businf_resp)); if (error) { device_printf(sc->aac_dev, "Error %d sending VMIoctl command\n", error); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } vmi_resp = (struct aac_vmi_businf_resp *)&fib->data[0]; if (vmi_resp->Status != ST_OK) { device_printf(sc->aac_dev, "VM_Ioctl returned %d\n", vmi_resp->Status); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); return; } bcopy(&vmi_resp->BusInf, &businfo, sizeof(struct aac_getbusinf)); aac_release_sync_fib(sc); mtx_unlock(&sc->aac_io_lock); for (i = 0; i < businfo.BusCount; i++) { if (businfo.BusValid[i] != AAC_BUS_VALID) continue; caminf = (struct aac_sim *)malloc( sizeof(struct aac_sim), M_AACRAIDBUF, M_NOWAIT | M_ZERO); if (caminf == NULL) { device_printf(sc->aac_dev, "No memory to add passthrough bus %d\n", i); break; }; child = device_add_child(sc->aac_dev, "aacraidp", -1); if (child == NULL) { device_printf(sc->aac_dev, "device_add_child failed for passthrough bus %d\n", i); free(caminf, M_AACRAIDBUF); break; } caminf->TargetsPerBus = businfo.TargetsPerBus; caminf->BusNumber = i+1; caminf->BusType = PASSTHROUGH_BUS; caminf->InitiatorBusId = businfo.InitiatorBusId[i]; caminf->aac_sc = sc; caminf->sim_dev = child; caminf->aac_cam = NULL; device_set_ivars(child, caminf); device_set_desc(child, "SCSI Passthrough Bus"); TAILQ_INSERT_TAIL(&sc->aac_sim_tqh, caminf, sim_link); } } /* * Check to see if the kernel is up and running. If we are in a * BlinkLED state, return the BlinkLED code. */ static u_int32_t aac_check_adapter_health(struct aac_softc *sc, u_int8_t *bled) { u_int32_t ret; ret = AAC_GET_FWSTATUS(sc); if (ret & AAC_UP_AND_RUNNING) ret = 0; else if (ret & AAC_KERNEL_PANIC && bled) *bled = (ret >> 16) & 0xff; return (ret); } /* * Once do an IOP reset, basically have to re-initialize the card as * if coming up from a cold boot, and the driver is responsible for * any IO that was outstanding to the adapter at the time of the IOP * RESET. And prepare the driver for IOP RESET by making the init code * modular with the ability to call it from multiple places. */ static int aac_reset_adapter(struct aac_softc *sc) { struct aac_command *cm; struct aac_fib *fib; struct aac_pause_command *pc; u_int32_t status, reset_mask, waitCount, max_msix_orig; int msi_enabled_orig; fwprintf(sc, HBA_FLAGS_DBG_FUNCTION_ENTRY_B, ""); mtx_assert(&sc->aac_io_lock, MA_OWNED); if (sc->aac_state & AAC_STATE_RESET) { device_printf(sc->aac_dev, "aac_reset_adapter() already in progress\n"); return (EINVAL); } sc->aac_state |= AAC_STATE_RESET; /* disable interrupt */ AAC_ACCESS_DEVREG(sc, AAC_DISABLE_INTERRUPT); /* * Abort all pending commands: * a) on the controller */ while ((cm = aac_dequeue_busy(sc)) != NULL) { cm->cm_flags |= AAC_CMD_RESET; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this * command */ wakeup(cm); } } /* b) in the waiting queues */ while ((cm = aac_dequeue_ready(sc)) != NULL) { cm->cm_flags |= AAC_CMD_RESET; /* is there a completion handler? */ if (cm->cm_complete != NULL) { cm->cm_complete(cm); } else { /* assume that someone is sleeping on this * command */ wakeup(cm); } } /* flush drives */ if (aac_check_adapter_health(sc, NULL) == 0) { mtx_unlock(&sc->aac_io_lock); (void) aacraid_shutdown(sc->aac_dev); mtx_lock(&sc->aac_io_lock); } /* execute IOP reset */ if (sc->aac_support_opt2 & AAC_SUPPORTED_MU_RESET) { AAC_MEM0_SETREG4(sc, AAC_IRCSR, AAC_IRCSR_CORES_RST); /* We need to wait for 5 seconds before accessing the MU again * 10000 * 100us = 1000,000us = 1000ms = 1s */ waitCount = 5 * 10000; while (waitCount) { DELAY(100); /* delay 100 microseconds */ waitCount--; } } else if ((aacraid_sync_command(sc, AAC_IOP_RESET_ALWAYS, 0, 0, 0, 0, &status, &reset_mask)) != 0) { /* call IOP_RESET for older firmware */ if ((aacraid_sync_command(sc, AAC_IOP_RESET, 0, 0, 0, 0, &status, NULL)) != 0) { if (status == AAC_SRB_STS_INVALID_REQUEST) device_printf(sc->aac_dev, "IOP_RESET not supported\n"); else /* probably timeout */ device_printf(sc->aac_dev, "IOP_RESET failed\n"); /* unwind aac_shutdown() */ aac_alloc_sync_fib(sc, &fib); pc = (struct aac_pause_command *)&fib->data[0]; pc->Command = VM_ContainerConfig; pc->Type = CT_PAUSE_IO; pc->Timeout = 1; pc->Min = 1; pc->NoRescan = 1; (void) aac_sync_fib(sc, ContainerCommand, 0, fib, sizeof (struct aac_pause_command)); aac_release_sync_fib(sc); goto finish; } } else if (sc->aac_support_opt2 & AAC_SUPPORTED_DOORBELL_RESET) { AAC_MEM0_SETREG4(sc, AAC_SRC_IDBR, reset_mask); /* * We need to wait for 5 seconds before accessing the doorbell * again, 10000 * 100us = 1000,000us = 1000ms = 1s */ waitCount = 5 * 10000; while (waitCount) { DELAY(100); /* delay 100 microseconds */ waitCount--; } } /* * Initialize the adapter. */ max_msix_orig = sc->aac_max_msix; msi_enabled_orig = sc->msi_enabled; sc->msi_enabled = FALSE; if (aac_check_firmware(sc) != 0) goto finish; if (!(sc->flags & AAC_FLAGS_SYNC_MODE)) { sc->aac_max_msix = max_msix_orig; if (msi_enabled_orig) { sc->msi_enabled = msi_enabled_orig; AAC_ACCESS_DEVREG(sc, AAC_ENABLE_MSIX); } mtx_unlock(&sc->aac_io_lock); aac_init(sc); mtx_lock(&sc->aac_io_lock); } finish: sc->aac_state &= ~AAC_STATE_RESET; AAC_ACCESS_DEVREG(sc, AAC_ENABLE_INTERRUPT); aacraid_startio(sc); return (0); } Index: head/sys/dev/advansys/adwcam.c =================================================================== --- head/sys/dev/advansys/adwcam.c (revision 280346) +++ head/sys/dev/advansys/adwcam.c (revision 280347) @@ -1,1504 +1,1504 @@ /*- * CAM SCSI interface for the Advanced Systems Inc. * Second Generation SCSI controllers. * * Product specific probe and attach routines can be found in: * * adw_pci.c ABP[3]940UW, ABP950UW, ABP3940U2W * * Copyright (c) 1998, 1999, 2000 Justin Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Ported from: * advansys.c - Linux Host Driver for AdvanSys SCSI Adapters * * Copyright (c) 1995-1998 Advanced System Products, Inc. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that redistributions of source * code retain the above copyright notice and this comment without * modification. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Definitions for our use of the SIM private CCB area */ #define ccb_acb_ptr spriv_ptr0 #define ccb_adw_ptr spriv_ptr1 static __inline struct acb* adwgetacb(struct adw_softc *adw); static __inline void adwfreeacb(struct adw_softc *adw, struct acb *acb); static void adwmapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error); static struct sg_map_node* adwallocsgmap(struct adw_softc *adw); static int adwallocacbs(struct adw_softc *adw); static void adwexecuteacb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error); static void adw_action(struct cam_sim *sim, union ccb *ccb); static void adw_intr_locked(struct adw_softc *adw); static void adw_poll(struct cam_sim *sim); static void adw_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static void adwprocesserror(struct adw_softc *adw, struct acb *acb); static void adwtimeout(void *arg); static void adw_handle_device_reset(struct adw_softc *adw, u_int target); static void adw_handle_bus_reset(struct adw_softc *adw, int initiated); static __inline struct acb* adwgetacb(struct adw_softc *adw) { struct acb* acb; if (!dumping) mtx_assert(&adw->lock, MA_OWNED); if ((acb = SLIST_FIRST(&adw->free_acb_list)) != NULL) { SLIST_REMOVE_HEAD(&adw->free_acb_list, links); } else if (adw->num_acbs < adw->max_acbs) { adwallocacbs(adw); acb = SLIST_FIRST(&adw->free_acb_list); if (acb == NULL) device_printf(adw->device, "Can't malloc ACB\n"); else { SLIST_REMOVE_HEAD(&adw->free_acb_list, links); } } return (acb); } static __inline void adwfreeacb(struct adw_softc *adw, struct acb *acb) { if (!dumping) mtx_assert(&adw->lock, MA_OWNED); if ((acb->state & ACB_ACTIVE) != 0) LIST_REMOVE(&acb->ccb->ccb_h, sim_links.le); if ((acb->state & ACB_RELEASE_SIMQ) != 0) acb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; else if ((adw->state & ADW_RESOURCE_SHORTAGE) != 0 && (acb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { acb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; adw->state &= ~ADW_RESOURCE_SHORTAGE; } acb->state = ACB_FREE; SLIST_INSERT_HEAD(&adw->free_acb_list, acb, links); } static void adwmapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *busaddrp; busaddrp = (bus_addr_t *)arg; *busaddrp = segs->ds_addr; } static struct sg_map_node * adwallocsgmap(struct adw_softc *adw) { struct sg_map_node *sg_map; sg_map = malloc(sizeof(*sg_map), M_DEVBUF, M_NOWAIT); if (sg_map == NULL) return (NULL); /* Allocate S/G space for the next batch of ACBS */ if (bus_dmamem_alloc(adw->sg_dmat, (void **)&sg_map->sg_vaddr, BUS_DMA_NOWAIT, &sg_map->sg_dmamap) != 0) { free(sg_map, M_DEVBUF); return (NULL); } SLIST_INSERT_HEAD(&adw->sg_maps, sg_map, links); bus_dmamap_load(adw->sg_dmat, sg_map->sg_dmamap, sg_map->sg_vaddr, PAGE_SIZE, adwmapmem, &sg_map->sg_physaddr, /*flags*/0); bzero(sg_map->sg_vaddr, PAGE_SIZE); return (sg_map); } /* * Allocate another chunk of CCB's. Return count of entries added. */ static int adwallocacbs(struct adw_softc *adw) { struct acb *next_acb; struct sg_map_node *sg_map; bus_addr_t busaddr; struct adw_sg_block *blocks; int newcount; int i; next_acb = &adw->acbs[adw->num_acbs]; sg_map = adwallocsgmap(adw); if (sg_map == NULL) return (0); blocks = sg_map->sg_vaddr; busaddr = sg_map->sg_physaddr; newcount = (PAGE_SIZE / (ADW_SG_BLOCKCNT * sizeof(*blocks))); for (i = 0; adw->num_acbs < adw->max_acbs && i < newcount; i++) { int error; error = bus_dmamap_create(adw->buffer_dmat, /*flags*/0, &next_acb->dmamap); if (error != 0) break; next_acb->queue.scsi_req_baddr = acbvtob(adw, next_acb); next_acb->queue.scsi_req_bo = acbvtobo(adw, next_acb); next_acb->queue.sense_baddr = acbvtob(adw, next_acb) + offsetof(struct acb, sense_data); next_acb->sg_blocks = blocks; next_acb->sg_busaddr = busaddr; next_acb->state = ACB_FREE; callout_init_mtx(&next_acb->timer, &adw->lock, 0); SLIST_INSERT_HEAD(&adw->free_acb_list, next_acb, links); blocks += ADW_SG_BLOCKCNT; busaddr += ADW_SG_BLOCKCNT * sizeof(*blocks); next_acb++; adw->num_acbs++; } return (i); } static void adwexecuteacb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct acb *acb; union ccb *ccb; struct adw_softc *adw; acb = (struct acb *)arg; ccb = acb->ccb; adw = (struct adw_softc *)ccb->ccb_h.ccb_adw_ptr; if (!dumping) mtx_assert(&adw->lock, MA_OWNED); if (error != 0) { if (error != EFBIG) device_printf(adw->device, "Unexepected error 0x%x " "returned from bus_dmamap_load\n", error); if (ccb->ccb_h.status == CAM_REQ_INPROG) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; } adwfreeacb(adw, acb); xpt_done(ccb); return; } if (nseg != 0) { bus_dmasync_op_t op; acb->queue.data_addr = dm_segs[0].ds_addr; acb->queue.data_cnt = ccb->csio.dxfer_len; if (nseg > 1) { struct adw_sg_block *sg_block; struct adw_sg_elm *sg; bus_addr_t sg_busaddr; u_int sg_index; bus_dma_segment_t *end_seg; end_seg = dm_segs + nseg; sg_busaddr = acb->sg_busaddr; sg_index = 0; /* Copy the segments into our SG list */ for (sg_block = acb->sg_blocks;; sg_block++) { u_int i; sg = sg_block->sg_list; for (i = 0; i < ADW_NO_OF_SG_PER_BLOCK; i++) { if (dm_segs >= end_seg) break; sg->sg_addr = dm_segs->ds_addr; sg->sg_count = dm_segs->ds_len; sg++; dm_segs++; } sg_block->sg_cnt = i; sg_index += i; if (dm_segs == end_seg) { sg_block->sg_busaddr_next = 0; break; } else { sg_busaddr += sizeof(struct adw_sg_block); sg_block->sg_busaddr_next = sg_busaddr; } } acb->queue.sg_real_addr = acb->sg_busaddr; } else { acb->queue.sg_real_addr = 0; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_PREREAD; else op = BUS_DMASYNC_PREWRITE; bus_dmamap_sync(adw->buffer_dmat, acb->dmamap, op); } else { acb->queue.data_addr = 0; acb->queue.data_cnt = 0; acb->queue.sg_real_addr = 0; } /* * Last time we need to check if this CCB needs to * be aborted. */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(adw->buffer_dmat, acb->dmamap); adwfreeacb(adw, acb); xpt_done(ccb); return; } acb->state |= ACB_ACTIVE; ccb->ccb_h.status |= CAM_SIM_QUEUED; LIST_INSERT_HEAD(&adw->pending_ccbs, &ccb->ccb_h, sim_links.le); callout_reset_sbt(&acb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, adwtimeout, acb, 0); adw_send_acb(adw, acb, acbvtob(adw, acb)); } static void adw_action(struct cam_sim *sim, union ccb *ccb) { struct adw_softc *adw; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("adw_action\n")); adw = (struct adw_softc *)cam_sim_softc(sim); if (!dumping) mtx_assert(&adw->lock, MA_OWNED); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_SCSI_IO: /* Execute the requested I/O operation */ { struct ccb_scsiio *csio; struct acb *acb; int error; csio = &ccb->csio; /* Max supported CDB length is 12 bytes */ if (csio->cdb_len > 12) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); return; } if ((acb = adwgetacb(adw)) == NULL) { adw->state |= ADW_RESOURCE_SHORTAGE; xpt_freeze_simq(sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } /* Link acb and ccb so we can find one from the other */ acb->ccb = ccb; ccb->ccb_h.ccb_acb_ptr = acb; ccb->ccb_h.ccb_adw_ptr = adw; acb->queue.cntl = 0; acb->queue.target_cmd = 0; acb->queue.target_id = ccb->ccb_h.target_id; acb->queue.target_lun = ccb->ccb_h.target_lun; acb->queue.mflag = 0; acb->queue.sense_len = MIN(csio->sense_len, sizeof(acb->sense_data)); acb->queue.cdb_len = csio->cdb_len; if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) { switch (csio->tag_action) { case MSG_SIMPLE_Q_TAG: acb->queue.scsi_cntl = ADW_QSC_SIMPLE_Q_TAG; break; case MSG_HEAD_OF_Q_TAG: acb->queue.scsi_cntl = ADW_QSC_HEAD_OF_Q_TAG; break; case MSG_ORDERED_Q_TAG: acb->queue.scsi_cntl = ADW_QSC_ORDERED_Q_TAG; break; default: acb->queue.scsi_cntl = ADW_QSC_NO_TAGMSG; break; } } else acb->queue.scsi_cntl = ADW_QSC_NO_TAGMSG; if ((ccb->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) acb->queue.scsi_cntl |= ADW_QSC_NO_DISC; acb->queue.done_status = 0; acb->queue.scsi_status = 0; acb->queue.host_status = 0; acb->queue.sg_wk_ix = 0; if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((ccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { bcopy(csio->cdb_io.cdb_ptr, acb->queue.cdb, csio->cdb_len); } else { /* I guess I could map it in... */ ccb->ccb_h.status = CAM_REQ_INVALID; adwfreeacb(adw, acb); xpt_done(ccb); return; } } else { bcopy(csio->cdb_io.cdb_bytes, acb->queue.cdb, csio->cdb_len); } error = bus_dmamap_load_ccb(adw->buffer_dmat, acb->dmamap, ccb, adwexecuteacb, acb, /*flags*/0); if (error == EINPROGRESS) { /* * So as to maintain ordering, freeze the controller * queue until our mapping is returned. */ xpt_freeze_simq(sim, 1); acb->state |= CAM_RELEASE_SIMQ; } break; } case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ { adw_idle_cmd_status_t status; status = adw_idle_cmd_send(adw, ADW_IDLE_CMD_DEVICE_RESET, ccb->ccb_h.target_id); if (status == ADW_IDLE_CMD_SUCCESS) { ccb->ccb_h.status = CAM_REQ_CMP; if (bootverbose) { xpt_print_path(ccb->ccb_h.path); printf("BDR Delivered\n"); } } else ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); break; } case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: { struct ccb_trans_settings_scsi *scsi; struct ccb_trans_settings_spi *spi; struct ccb_trans_settings *cts; u_int target_mask; cts = &ccb->cts; target_mask = 0x01 << ccb->ccb_h.target_id; scsi = &cts->proto_specific.scsi; spi = &cts->xport_specific.spi; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) { u_int sdtrdone; sdtrdone = adw_lram_read_16(adw, ADW_MC_SDTR_DONE); if ((spi->valid & CTS_SPI_VALID_DISC) != 0) { u_int discenb; discenb = adw_lram_read_16(adw, ADW_MC_DISC_ENABLE); if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0) discenb |= target_mask; else discenb &= ~target_mask; adw_lram_write_16(adw, ADW_MC_DISC_ENABLE, discenb); } if ((scsi->valid & CTS_SCSI_VALID_TQ) != 0) { if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0) adw->tagenb |= target_mask; else adw->tagenb &= ~target_mask; } if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) { u_int wdtrenb_orig; u_int wdtrenb; u_int wdtrdone; wdtrenb_orig = adw_lram_read_16(adw, ADW_MC_WDTR_ABLE); wdtrenb = wdtrenb_orig; wdtrdone = adw_lram_read_16(adw, ADW_MC_WDTR_DONE); switch (spi->bus_width) { case MSG_EXT_WDTR_BUS_32_BIT: case MSG_EXT_WDTR_BUS_16_BIT: wdtrenb |= target_mask; break; case MSG_EXT_WDTR_BUS_8_BIT: default: wdtrenb &= ~target_mask; break; } if (wdtrenb != wdtrenb_orig) { adw_lram_write_16(adw, ADW_MC_WDTR_ABLE, wdtrenb); wdtrdone &= ~target_mask; adw_lram_write_16(adw, ADW_MC_WDTR_DONE, wdtrdone); /* Wide negotiation forces async */ sdtrdone &= ~target_mask; adw_lram_write_16(adw, ADW_MC_SDTR_DONE, sdtrdone); } } if (((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) || ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0)) { u_int sdtr_orig; u_int sdtr; u_int sdtrable_orig; u_int sdtrable; sdtr = adw_get_chip_sdtr(adw, ccb->ccb_h.target_id); sdtr_orig = sdtr; sdtrable = adw_lram_read_16(adw, ADW_MC_SDTR_ABLE); sdtrable_orig = sdtrable; if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) { sdtr = adw_find_sdtr(adw, spi->sync_period); } if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) { if (spi->sync_offset == 0) sdtr = ADW_MC_SDTR_ASYNC; } if (sdtr == ADW_MC_SDTR_ASYNC) sdtrable &= ~target_mask; else sdtrable |= target_mask; if (sdtr != sdtr_orig || sdtrable != sdtrable_orig) { adw_set_chip_sdtr(adw, ccb->ccb_h.target_id, sdtr); sdtrdone &= ~target_mask; adw_lram_write_16(adw, ADW_MC_SDTR_ABLE, sdtrable); adw_lram_write_16(adw, ADW_MC_SDTR_DONE, sdtrdone); } } } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings_scsi *scsi; struct ccb_trans_settings_spi *spi; struct ccb_trans_settings *cts; u_int target_mask; cts = &ccb->cts; target_mask = 0x01 << ccb->ccb_h.target_id; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; scsi = &cts->proto_specific.scsi; spi = &cts->xport_specific.spi; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) { u_int mc_sdtr; spi->flags = 0; if ((adw->user_discenb & target_mask) != 0) spi->flags |= CTS_SPI_FLAGS_DISC_ENB; if ((adw->user_tagenb & target_mask) != 0) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; if ((adw->user_wdtr & target_mask) != 0) spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; else spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; mc_sdtr = adw_get_user_sdtr(adw, ccb->ccb_h.target_id); spi->sync_period = adw_find_period(adw, mc_sdtr); if (spi->sync_period != 0) spi->sync_offset = 15; /* XXX ??? */ else spi->sync_offset = 0; } else { u_int targ_tinfo; spi->flags = 0; if ((adw_lram_read_16(adw, ADW_MC_DISC_ENABLE) & target_mask) != 0) spi->flags |= CTS_SPI_FLAGS_DISC_ENB; if ((adw->tagenb & target_mask) != 0) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; targ_tinfo = adw_lram_read_16(adw, ADW_MC_DEVICE_HSHK_CFG_TABLE + (2 * ccb->ccb_h.target_id)); if ((targ_tinfo & ADW_HSHK_CFG_WIDE_XFR) != 0) spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; else spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; spi->sync_period = adw_hshk_cfg_period_factor(targ_tinfo); spi->sync_offset = targ_tinfo & ADW_HSHK_CFG_OFFSET; if (spi->sync_period == 0) spi->sync_offset = 0; if (spi->sync_offset == 0) spi->sync_period = 0; } spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { /* * XXX Use Adaptec translation until I find out how to * get this information from the card. */ cam_calc_geometry(&ccb->ccg, /*extended*/1); xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { int failure; failure = adw_reset_bus(adw); if (failure != 0) { ccb->ccb_h.status = CAM_REQ_CMP_ERR; } else { if (bootverbose) { xpt_print_path(adw->path); printf("Bus Reset Delivered\n"); } ccb->ccb_h.status = CAM_REQ_CMP; } xpt_done(ccb); break; } case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_WIDE_16|PI_SDTR_ABLE|PI_TAG_ABLE; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = ADW_MAX_TID; cpi->max_lun = ADW_MAX_LUN; cpi->initiator_id = adw->initiator_id; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "AdvanSys", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } static void adw_poll(struct cam_sim *sim) { adw_intr_locked(cam_sim_softc(sim)); } static void adw_async(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { } struct adw_softc * adw_alloc(device_t dev, struct resource *regs, int regs_type, int regs_id) { struct adw_softc *adw; adw = device_get_softc(dev); LIST_INIT(&adw->pending_ccbs); SLIST_INIT(&adw->sg_maps); mtx_init(&adw->lock, "adw", NULL, MTX_DEF); adw->device = dev; adw->regs_res_type = regs_type; adw->regs_res_id = regs_id; adw->regs = regs; return(adw); } void adw_free(struct adw_softc *adw) { switch (adw->init_level) { case 9: { struct sg_map_node *sg_map; while ((sg_map = SLIST_FIRST(&adw->sg_maps)) != NULL) { SLIST_REMOVE_HEAD(&adw->sg_maps, links); bus_dmamap_unload(adw->sg_dmat, sg_map->sg_dmamap); bus_dmamem_free(adw->sg_dmat, sg_map->sg_vaddr, sg_map->sg_dmamap); free(sg_map, M_DEVBUF); } bus_dma_tag_destroy(adw->sg_dmat); } case 8: bus_dmamap_unload(adw->acb_dmat, adw->acb_dmamap); case 7: bus_dmamem_free(adw->acb_dmat, adw->acbs, adw->acb_dmamap); case 6: bus_dma_tag_destroy(adw->acb_dmat); case 5: bus_dmamap_unload(adw->carrier_dmat, adw->carrier_dmamap); case 4: bus_dmamem_free(adw->carrier_dmat, adw->carriers, adw->carrier_dmamap); case 3: bus_dma_tag_destroy(adw->carrier_dmat); case 2: bus_dma_tag_destroy(adw->buffer_dmat); case 1: bus_dma_tag_destroy(adw->parent_dmat); case 0: break; } if (adw->regs != NULL) bus_release_resource(adw->device, adw->regs_res_type, adw->regs_res_id, adw->regs); if (adw->irq != NULL) bus_release_resource(adw->device, adw->irq_res_type, 0, adw->irq); if (adw->sim != NULL) { if (adw->path != NULL) { xpt_async(AC_LOST_DEVICE, adw->path, NULL); xpt_free_path(adw->path); } xpt_bus_deregister(cam_sim_path(adw->sim)); cam_sim_free(adw->sim, /*free_devq*/TRUE); } mtx_destroy(&adw->lock); } int adw_init(struct adw_softc *adw) { struct adw_eeprom eep_config; u_int tid; u_int i; u_int16_t checksum; u_int16_t scsicfg1; checksum = adw_eeprom_read(adw, &eep_config); bcopy(eep_config.serial_number, adw->serial_number, sizeof(adw->serial_number)); if (checksum != eep_config.checksum) { u_int16_t serial_number[3]; adw->flags |= ADW_EEPROM_FAILED; device_printf(adw->device, "EEPROM checksum failed. Restoring Defaults\n"); /* * Restore the default EEPROM settings. * Assume the 6 byte board serial number that was read * from EEPROM is correct even if the EEPROM checksum * failed. */ bcopy(adw->default_eeprom, &eep_config, sizeof(eep_config)); bcopy(adw->serial_number, eep_config.serial_number, sizeof(serial_number)); adw_eeprom_write(adw, &eep_config); } /* Pull eeprom information into our softc. */ adw->bios_ctrl = eep_config.bios_ctrl; adw->user_wdtr = eep_config.wdtr_able; for (tid = 0; tid < ADW_MAX_TID; tid++) { u_int mc_sdtr; u_int16_t tid_mask; tid_mask = 0x1 << tid; if ((adw->features & ADW_ULTRA) != 0) { /* * Ultra chips store sdtr and ultraenb * bits in their seeprom, so we must * construct valid mc_sdtr entries for * indirectly. */ if (eep_config.sync1.sync_enable & tid_mask) { if (eep_config.sync2.ultra_enable & tid_mask) mc_sdtr = ADW_MC_SDTR_20; else mc_sdtr = ADW_MC_SDTR_10; } else mc_sdtr = ADW_MC_SDTR_ASYNC; } else { switch (ADW_TARGET_GROUP(tid)) { case 3: mc_sdtr = eep_config.sync4.sdtr4; break; case 2: mc_sdtr = eep_config.sync3.sdtr3; break; case 1: mc_sdtr = eep_config.sync2.sdtr2; break; default: /* Shut up compiler */ case 0: mc_sdtr = eep_config.sync1.sdtr1; break; } mc_sdtr >>= ADW_TARGET_GROUP_SHIFT(tid); mc_sdtr &= 0xFF; } adw_set_user_sdtr(adw, tid, mc_sdtr); } adw->user_tagenb = eep_config.tagqng_able; adw->user_discenb = eep_config.disc_enable; adw->max_acbs = eep_config.max_host_qng; adw->initiator_id = (eep_config.adapter_scsi_id & ADW_MAX_TID); /* * Sanity check the number of host openings. */ if (adw->max_acbs > ADW_DEF_MAX_HOST_QNG) adw->max_acbs = ADW_DEF_MAX_HOST_QNG; else if (adw->max_acbs < ADW_DEF_MIN_HOST_QNG) { /* If the value is zero, assume it is uninitialized. */ if (adw->max_acbs == 0) adw->max_acbs = ADW_DEF_MAX_HOST_QNG; else adw->max_acbs = ADW_DEF_MIN_HOST_QNG; } scsicfg1 = 0; if ((adw->features & ADW_ULTRA2) != 0) { switch (eep_config.termination_lvd) { default: device_printf(adw->device, "Invalid EEPROM LVD Termination Settings.\n"); device_printf(adw->device, "Reverting to Automatic LVD Termination\n"); /* FALLTHROUGH */ case ADW_EEPROM_TERM_AUTO: break; case ADW_EEPROM_TERM_BOTH_ON: scsicfg1 |= ADW2_SCSI_CFG1_TERM_LVD_LO; /* FALLTHROUGH */ case ADW_EEPROM_TERM_HIGH_ON: scsicfg1 |= ADW2_SCSI_CFG1_TERM_LVD_HI; /* FALLTHROUGH */ case ADW_EEPROM_TERM_OFF: scsicfg1 |= ADW2_SCSI_CFG1_DIS_TERM_DRV; break; } } switch (eep_config.termination_se) { default: device_printf(adw->device, "Invalid SE EEPROM Termination Settings.\n"); device_printf(adw->device, "Reverting to Automatic SE Termination\n"); /* FALLTHROUGH */ case ADW_EEPROM_TERM_AUTO: break; case ADW_EEPROM_TERM_BOTH_ON: scsicfg1 |= ADW_SCSI_CFG1_TERM_CTL_L; /* FALLTHROUGH */ case ADW_EEPROM_TERM_HIGH_ON: scsicfg1 |= ADW_SCSI_CFG1_TERM_CTL_H; /* FALLTHROUGH */ case ADW_EEPROM_TERM_OFF: scsicfg1 |= ADW_SCSI_CFG1_TERM_CTL_MANUAL; break; } device_printf(adw->device, "SCSI ID %d, ", adw->initiator_id); /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create( /* parent */ adw->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ ADW_SGSIZE, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &adw->lock, &adw->buffer_dmat) != 0) { return (ENOMEM); } adw->init_level++; /* DMA tag for our ccb carrier structures */ if (bus_dma_tag_create( /* parent */ adw->parent_dmat, /* alignment */ 0x10, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ (adw->max_acbs + ADW_NUM_CARRIER_QUEUES + 1) * sizeof(struct adw_carrier), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &adw->carrier_dmat) != 0) { return (ENOMEM); } adw->init_level++; /* Allocation for our ccb carrier structures */ if (bus_dmamem_alloc(adw->carrier_dmat, (void **)&adw->carriers, BUS_DMA_NOWAIT, &adw->carrier_dmamap) != 0) { return (ENOMEM); } adw->init_level++; /* And permanently map them */ bus_dmamap_load(adw->carrier_dmat, adw->carrier_dmamap, adw->carriers, (adw->max_acbs + ADW_NUM_CARRIER_QUEUES + 1) * sizeof(struct adw_carrier), adwmapmem, &adw->carrier_busbase, /*flags*/0); /* Clear them out. */ bzero(adw->carriers, (adw->max_acbs + ADW_NUM_CARRIER_QUEUES + 1) * sizeof(struct adw_carrier)); /* Setup our free carrier list */ adw->free_carriers = adw->carriers; for (i = 0; i < adw->max_acbs + ADW_NUM_CARRIER_QUEUES; i++) { adw->carriers[i].carr_offset = carriervtobo(adw, &adw->carriers[i]); adw->carriers[i].carr_ba = carriervtob(adw, &adw->carriers[i]); adw->carriers[i].areq_ba = 0; adw->carriers[i].next_ba = carriervtobo(adw, &adw->carriers[i+1]); } /* Terminal carrier. Never leaves the freelist */ adw->carriers[i].carr_offset = carriervtobo(adw, &adw->carriers[i]); adw->carriers[i].carr_ba = carriervtob(adw, &adw->carriers[i]); adw->carriers[i].areq_ba = 0; adw->carriers[i].next_ba = ~0; adw->init_level++; /* DMA tag for our acb structures */ if (bus_dma_tag_create( /* parent */ adw->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ adw->max_acbs * sizeof(struct acb), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &adw->acb_dmat) != 0) { return (ENOMEM); } adw->init_level++; /* Allocation for our ccbs */ if (bus_dmamem_alloc(adw->acb_dmat, (void **)&adw->acbs, BUS_DMA_NOWAIT, &adw->acb_dmamap) != 0) return (ENOMEM); adw->init_level++; /* And permanently map them */ bus_dmamap_load(adw->acb_dmat, adw->acb_dmamap, adw->acbs, adw->max_acbs * sizeof(struct acb), adwmapmem, &adw->acb_busbase, /*flags*/0); /* Clear them out. */ bzero(adw->acbs, adw->max_acbs * sizeof(struct acb)); /* DMA tag for our S/G structures. We allocate in page sized chunks */ if (bus_dma_tag_create( /* parent */ adw->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ PAGE_SIZE, /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &adw->sg_dmat) != 0) { return (ENOMEM); } adw->init_level++; /* Allocate our first batch of ccbs */ mtx_lock(&adw->lock); if (adwallocacbs(adw) == 0) { mtx_unlock(&adw->lock); return (ENOMEM); } if (adw_init_chip(adw, scsicfg1) != 0) { mtx_unlock(&adw->lock); return (ENXIO); } printf("Queue Depth %d\n", adw->max_acbs); mtx_unlock(&adw->lock); return (0); } /* * Attach all the sub-devices we can find */ int adw_attach(struct adw_softc *adw) { struct ccb_setasync csa; struct cam_devq *devq; int error; /* Hook up our interrupt handler */ error = bus_setup_intr(adw->device, adw->irq, INTR_TYPE_CAM | INTR_ENTROPY | INTR_MPSAFE, NULL, adw_intr, adw, &adw->ih); if (error != 0) { device_printf(adw->device, "bus_setup_intr() failed: %d\n", error); return (error); } /* Start the Risc processor now that we are fully configured. */ adw_outw(adw, ADW_RISC_CSR, ADW_RISC_CSR_RUN); /* * Create the device queue for our SIM. */ devq = cam_simq_alloc(adw->max_acbs); if (devq == NULL) return (ENOMEM); /* * Construct our SIM entry. */ adw->sim = cam_sim_alloc(adw_action, adw_poll, "adw", adw, device_get_unit(adw->device), &adw->lock, 1, adw->max_acbs, devq); if (adw->sim == NULL) return (ENOMEM); /* * Register the bus. */ mtx_lock(&adw->lock); if (xpt_bus_register(adw->sim, adw->device, 0) != CAM_SUCCESS) { cam_sim_free(adw->sim, /*free devq*/TRUE); error = ENOMEM; goto fail; } if (xpt_create_path(&adw->path, /*periph*/NULL, cam_sim_path(adw->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) == CAM_REQ_CMP) { xpt_setup_ccb(&csa.ccb_h, adw->path, /*priority*/5); csa.ccb_h.func_code = XPT_SASYNC_CB; csa.event_enable = AC_LOST_DEVICE; csa.callback = adw_async; csa.callback_arg = adw; xpt_action((union ccb *)&csa); } fail: mtx_unlock(&adw->lock); return (error); } void adw_intr(void *arg) { struct adw_softc *adw; adw = arg; mtx_lock(&adw->lock); adw_intr_locked(adw); mtx_unlock(&adw->lock); } void adw_intr_locked(struct adw_softc *adw) { u_int int_stat; if ((adw_inw(adw, ADW_CTRL_REG) & ADW_CTRL_REG_HOST_INTR) == 0) return; /* Reading the register clears the interrupt. */ int_stat = adw_inb(adw, ADW_INTR_STATUS_REG); if ((int_stat & ADW_INTR_STATUS_INTRB) != 0) { u_int intrb_code; /* Async Microcode Event */ intrb_code = adw_lram_read_8(adw, ADW_MC_INTRB_CODE); switch (intrb_code) { case ADW_ASYNC_CARRIER_READY_FAILURE: /* * The RISC missed our update of * the commandq. */ if (LIST_FIRST(&adw->pending_ccbs) != NULL) adw_tickle_risc(adw, ADW_TICKLE_A); break; case ADW_ASYNC_SCSI_BUS_RESET_DET: /* * The firmware detected a SCSI Bus reset. */ device_printf(adw->device, "Someone Reset the Bus\n"); adw_handle_bus_reset(adw, /*initiated*/FALSE); break; case ADW_ASYNC_RDMA_FAILURE: /* * Handle RDMA failure by resetting the * SCSI Bus and chip. */ #if 0 /* XXX */ AdvResetChipAndSB(adv_dvc_varp); #endif break; case ADW_ASYNC_HOST_SCSI_BUS_RESET: /* * Host generated SCSI bus reset occurred. */ adw_handle_bus_reset(adw, /*initiated*/TRUE); break; default: printf("adw_intr: unknown async code 0x%x\n", intrb_code); break; } } /* * Run down the RequestQ. */ while ((adw->responseq->next_ba & ADW_RQ_DONE) != 0) { struct adw_carrier *free_carrier; struct acb *acb; union ccb *ccb; #if 0 printf("0x%x, 0x%x, 0x%x, 0x%x\n", adw->responseq->carr_offset, adw->responseq->carr_ba, adw->responseq->areq_ba, adw->responseq->next_ba); #endif /* * The firmware copies the adw_scsi_req_q.acb_baddr * field into the areq_ba field of the carrier. */ acb = acbbotov(adw, adw->responseq->areq_ba); /* * The least significant four bits of the next_ba * field are used as flags. Mask them out and then * advance through the list. */ free_carrier = adw->responseq; adw->responseq = carrierbotov(adw, free_carrier->next_ba & ADW_NEXT_BA_MASK); free_carrier->next_ba = adw->free_carriers->carr_offset; adw->free_carriers = free_carrier; /* Process CCB */ ccb = acb->ccb; callout_stop(&acb->timer); if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(adw->buffer_dmat, acb->dmamap, op); bus_dmamap_unload(adw->buffer_dmat, acb->dmamap); ccb->csio.resid = acb->queue.data_cnt; } else ccb->csio.resid = 0; /* Common Cases inline... */ if (acb->queue.host_status == QHSTA_NO_ERROR && (acb->queue.done_status == QD_NO_ERROR || acb->queue.done_status == QD_WITH_ERROR)) { ccb->csio.scsi_status = acb->queue.scsi_status; ccb->ccb_h.status = 0; switch (ccb->csio.scsi_status) { case SCSI_STATUS_OK: ccb->ccb_h.status |= CAM_REQ_CMP; break; case SCSI_STATUS_CHECK_COND: case SCSI_STATUS_CMD_TERMINATED: bcopy(&acb->sense_data, &ccb->csio.sense_data, ccb->csio.sense_len); ccb->ccb_h.status |= CAM_AUTOSNS_VALID; ccb->csio.sense_resid = acb->queue.sense_len; /* FALLTHROUGH */ default: ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); break; } adwfreeacb(adw, acb); xpt_done(ccb); } else { adwprocesserror(adw, acb); } } } static void adwprocesserror(struct adw_softc *adw, struct acb *acb) { union ccb *ccb; ccb = acb->ccb; if (acb->queue.done_status == QD_ABORTED_BY_HOST) { ccb->ccb_h.status = CAM_REQ_ABORTED; } else { switch (acb->queue.host_status) { case QHSTA_M_SEL_TIMEOUT: ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; case QHSTA_M_SXFR_OFF_UFLW: case QHSTA_M_SXFR_OFF_OFLW: case QHSTA_M_DATA_OVER_RUN: ccb->ccb_h.status = CAM_DATA_RUN_ERR; break; case QHSTA_M_SXFR_DESELECTED: case QHSTA_M_UNEXPECTED_BUS_FREE: ccb->ccb_h.status = CAM_UNEXP_BUSFREE; break; case QHSTA_M_SCSI_BUS_RESET: case QHSTA_M_SCSI_BUS_RESET_UNSOL: ccb->ccb_h.status = CAM_SCSI_BUS_RESET; break; case QHSTA_M_BUS_DEVICE_RESET: ccb->ccb_h.status = CAM_BDR_SENT; break; case QHSTA_M_QUEUE_ABORTED: /* BDR or Bus Reset */ xpt_print_path(adw->path); printf("Saw Queue Aborted\n"); ccb->ccb_h.status = adw->last_reset; break; case QHSTA_M_SXFR_SDMA_ERR: case QHSTA_M_SXFR_SXFR_PERR: case QHSTA_M_RDMA_PERR: ccb->ccb_h.status = CAM_UNCOR_PARITY; break; case QHSTA_M_WTM_TIMEOUT: case QHSTA_M_SXFR_WD_TMO: { /* The SCSI bus hung in a phase */ xpt_print_path(adw->path); printf("Watch Dog timer expired. Resetting bus\n"); adw_reset_bus(adw); break; } case QHSTA_M_SXFR_XFR_PH_ERR: ccb->ccb_h.status = CAM_SEQUENCE_FAIL; break; case QHSTA_M_SXFR_UNKNOWN_ERROR: break; case QHSTA_M_BAD_CMPL_STATUS_IN: /* No command complete after a status message */ ccb->ccb_h.status = CAM_SEQUENCE_FAIL; break; case QHSTA_M_AUTO_REQ_SENSE_FAIL: ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; break; case QHSTA_M_INVALID_DEVICE: ccb->ccb_h.status = CAM_PATH_INVALID; break; case QHSTA_M_NO_AUTO_REQ_SENSE: /* * User didn't request sense, but we got a * check condition. */ ccb->csio.scsi_status = acb->queue.scsi_status; ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; break; default: panic("%s: Unhandled Host status error %x", device_get_nameunit(adw->device), acb->queue.host_status); /* NOTREACHED */ } } if ((acb->state & ACB_RECOVERY_ACB) != 0) { if (ccb->ccb_h.status == CAM_SCSI_BUS_RESET || ccb->ccb_h.status == CAM_BDR_SENT) ccb->ccb_h.status = CAM_CMD_TIMEOUT; } if (ccb->ccb_h.status != CAM_REQ_CMP) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status |= CAM_DEV_QFRZN; } adwfreeacb(adw, acb); xpt_done(ccb); } static void adwtimeout(void *arg) { struct acb *acb; union ccb *ccb; struct adw_softc *adw; adw_idle_cmd_status_t status; int target_id; acb = (struct acb *)arg; ccb = acb->ccb; adw = (struct adw_softc *)ccb->ccb_h.ccb_adw_ptr; xpt_print_path(ccb->ccb_h.path); printf("ACB %p - timed out\n", (void *)acb); mtx_assert(&adw->lock, MA_OWNED); if ((acb->state & ACB_ACTIVE) == 0) { xpt_print_path(ccb->ccb_h.path); printf("ACB %p - timed out CCB already completed\n", (void *)acb); return; } acb->state |= ACB_RECOVERY_ACB; target_id = ccb->ccb_h.target_id; /* Attempt a BDR first */ status = adw_idle_cmd_send(adw, ADW_IDLE_CMD_DEVICE_RESET, ccb->ccb_h.target_id); if (status == ADW_IDLE_CMD_SUCCESS) { device_printf(adw->device, "BDR Delivered. No longer in timeout\n"); adw_handle_device_reset(adw, target_id); } else { adw_reset_bus(adw); xpt_print_path(adw->path); printf("Bus Reset Delivered. No longer in timeout\n"); } } static void adw_handle_device_reset(struct adw_softc *adw, u_int target) { struct cam_path *path; cam_status error; error = xpt_create_path(&path, /*periph*/NULL, cam_sim_path(adw->sim), target, CAM_LUN_WILDCARD); if (error == CAM_REQ_CMP) { xpt_async(AC_SENT_BDR, path, NULL); xpt_free_path(path); } adw->last_reset = CAM_BDR_SENT; } static void adw_handle_bus_reset(struct adw_softc *adw, int initiated) { if (initiated) { /* * The microcode currently sets the SCSI Bus Reset signal * while handling the AscSendIdleCmd() IDLE_CMD_SCSI_RESET * command above. But the SCSI Bus Reset Hold Time in the * microcode is not deterministic (it may in fact be for less * than the SCSI Spec. minimum of 25 us). Therefore on return * the Adv Library sets the SCSI Bus Reset signal for * ADW_SCSI_RESET_HOLD_TIME_US, which is defined to be greater * than 25 us. */ u_int scsi_ctrl; scsi_ctrl = adw_inw(adw, ADW_SCSI_CTRL) & ~ADW_SCSI_CTRL_RSTOUT; adw_outw(adw, ADW_SCSI_CTRL, scsi_ctrl | ADW_SCSI_CTRL_RSTOUT); DELAY(ADW_SCSI_RESET_HOLD_TIME_US); adw_outw(adw, ADW_SCSI_CTRL, scsi_ctrl); /* * We will perform the async notification when the * SCSI Reset interrupt occurs. */ } else xpt_async(AC_BUS_RESET, adw->path, NULL); adw->last_reset = CAM_SCSI_BUS_RESET; } MODULE_DEPEND(adw, cam, 1, 1, 1); Index: head/sys/dev/aha/aha.c =================================================================== --- head/sys/dev/aha/aha.c (revision 280346) +++ head/sys/dev/aha/aha.c (revision 280347) @@ -1,1824 +1,1824 @@ /* * Generic register and struct definitions for the Adaptech 154x/164x * SCSI host adapters. Product specific probe and attach routines can * be found in: * aha 1542A/1542B/1542C/1542CF/1542CP aha_isa.c * aha 1640 aha_mca.c */ /*- * Copyright (c) 1998 M. Warner Losh. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Derived from bt.c written by: * * Copyright (c) 1998 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PRVERB(x) do { if (bootverbose) device_printf x; } while (0) /* Macro to determine that a rev is potentially a new valid one * so that the driver doesn't keep breaking on new revs as it * did for the CF and CP. */ #define PROBABLY_NEW_BOARD(REV) (REV > 0x43 && REV < 0x56) /* MailBox Management functions */ static __inline void ahanextinbox(struct aha_softc *aha); static __inline void ahanextoutbox(struct aha_softc *aha); #define aha_name(aha) device_get_nameunit(aha->dev) static __inline void ahanextinbox(struct aha_softc *aha) { if (aha->cur_inbox == aha->last_inbox) aha->cur_inbox = aha->in_boxes; else aha->cur_inbox++; } static __inline void ahanextoutbox(struct aha_softc *aha) { if (aha->cur_outbox == aha->last_outbox) aha->cur_outbox = aha->out_boxes; else aha->cur_outbox++; } #define ahautoa24(u,s3) \ (s3)[0] = ((u) >> 16) & 0xff; \ (s3)[1] = ((u) >> 8) & 0xff; \ (s3)[2] = (u) & 0xff; #define aha_a24tou(s3) \ (((s3)[0] << 16) | ((s3)[1] << 8) | (s3)[2]) /* CCB Management functions */ static __inline uint32_t ahaccbvtop(struct aha_softc *aha, struct aha_ccb *accb); static __inline struct aha_ccb* ahaccbptov(struct aha_softc *aha, uint32_t ccb_addr); static __inline uint32_t ahaccbvtop(struct aha_softc *aha, struct aha_ccb *accb) { return (aha->aha_ccb_physbase + (uint32_t)((caddr_t)accb - (caddr_t)aha->aha_ccb_array)); } static __inline struct aha_ccb * ahaccbptov(struct aha_softc *aha, uint32_t ccb_addr) { return (aha->aha_ccb_array + + ((struct aha_ccb*)(uintptr_t)ccb_addr - (struct aha_ccb*)(uintptr_t)aha->aha_ccb_physbase)); } static struct aha_ccb* ahagetccb(struct aha_softc *aha); static __inline void ahafreeccb(struct aha_softc *aha, struct aha_ccb *accb); static void ahaallocccbs(struct aha_softc *aha); static bus_dmamap_callback_t ahaexecuteccb; static void ahadone(struct aha_softc *aha, struct aha_ccb *accb, aha_mbi_comp_code_t comp_code); static void aha_intr_locked(struct aha_softc *aha); /* Host adapter command functions */ static int ahareset(struct aha_softc* aha, int hard_reset); /* Initialization functions */ static int ahainitmboxes(struct aha_softc *aha); static bus_dmamap_callback_t ahamapmboxes; static bus_dmamap_callback_t ahamapccbs; static bus_dmamap_callback_t ahamapsgs; /* Transfer Negotiation Functions */ static void ahafetchtransinfo(struct aha_softc *aha, struct ccb_trans_settings *cts); /* CAM SIM entry points */ #define ccb_accb_ptr spriv_ptr0 #define ccb_aha_ptr spriv_ptr1 static void ahaaction(struct cam_sim *sim, union ccb *ccb); static void ahapoll(struct cam_sim *sim); /* Our timeout handler */ static void ahatimeout(void *arg); /* Exported functions */ void aha_alloc(struct aha_softc *aha) { SLIST_INIT(&aha->free_aha_ccbs); LIST_INIT(&aha->pending_ccbs); SLIST_INIT(&aha->sg_maps); aha->ccb_sg_opcode = INITIATOR_SG_CCB_WRESID; aha->ccb_ccb_opcode = INITIATOR_CCB_WRESID; mtx_init(&aha->lock, "aha", NULL, MTX_DEF); } void aha_free(struct aha_softc *aha) { switch (aha->init_level) { default: case 8: { struct sg_map_node *sg_map; while ((sg_map = SLIST_FIRST(&aha->sg_maps))!= NULL) { SLIST_REMOVE_HEAD(&aha->sg_maps, links); bus_dmamap_unload(aha->sg_dmat, sg_map->sg_dmamap); bus_dmamem_free(aha->sg_dmat, sg_map->sg_vaddr, sg_map->sg_dmamap); free(sg_map, M_DEVBUF); } bus_dma_tag_destroy(aha->sg_dmat); } case 7: bus_dmamap_unload(aha->ccb_dmat, aha->ccb_dmamap); case 6: bus_dmamem_free(aha->ccb_dmat, aha->aha_ccb_array, aha->ccb_dmamap); case 5: bus_dma_tag_destroy(aha->ccb_dmat); case 4: bus_dmamap_unload(aha->mailbox_dmat, aha->mailbox_dmamap); case 3: bus_dmamem_free(aha->mailbox_dmat, aha->in_boxes, aha->mailbox_dmamap); case 2: bus_dma_tag_destroy(aha->buffer_dmat); case 1: bus_dma_tag_destroy(aha->mailbox_dmat); case 0: break; } mtx_destroy(&aha->lock); } /* * Probe the adapter and verify that the card is an Adaptec. */ int aha_probe(struct aha_softc* aha) { u_int status; u_int intstat; int error; board_id_data_t board_id; /* * See if the three I/O ports look reasonable. * Touch the minimal number of registers in the * failure case. */ status = aha_inb(aha, STATUS_REG); if ((status == 0) || (status & (DIAG_ACTIVE|CMD_REG_BUSY | STATUS_REG_RSVD)) != 0) { PRVERB((aha->dev, "status reg test failed %x\n", status)); return (ENXIO); } intstat = aha_inb(aha, INTSTAT_REG); if ((intstat & INTSTAT_REG_RSVD) != 0) { PRVERB((aha->dev, "Failed Intstat Reg Test\n")); return (ENXIO); } /* * Looking good so far. Final test is to reset the * adapter and fetch the board ID and ensure we aren't * looking at a BusLogic. */ if ((error = ahareset(aha, /*hard_reset*/TRUE)) != 0) { PRVERB((aha->dev, "Failed Reset\n")); return (ENXIO); } /* * Get the board ID. We use this to see if we're dealing with * a buslogic card or an aha card (or clone). */ error = aha_cmd(aha, AOP_INQUIRE_BOARD_ID, NULL, /*parmlen*/0, (uint8_t*)&board_id, sizeof(board_id), DEFAULT_CMD_TIMEOUT); if (error != 0) { PRVERB((aha->dev, "INQUIRE failed %x\n", error)); return (ENXIO); } aha->fw_major = board_id.firmware_rev_major; aha->fw_minor = board_id.firmware_rev_minor; aha->boardid = board_id.board_type; /* * The Buslogic cards have an id of either 0x41 or 0x42. So * if those come up in the probe, we test the geometry register * of the board. Adaptec boards that are this old will not have * this register, and return 0xff, while buslogic cards will return * something different. * * It appears that for reasons unknow, for the for the * aha-1542B cards, we need to wait a little bit before trying * to read the geometry register. I picked 10ms since we have * reports that a for loop to 1000 did the trick, and this * errs on the side of conservatism. Besides, no one will * notice a 10mS delay here, even the 1542B card users :-) * * Some compatible cards return 0 here. Some cards also * seem to return 0x7f. * * XXX I'm not sure how this will impact other cloned cards * * This really should be replaced with the esetup command, since * that appears to be more reliable. This becomes more and more * true over time as we discover more cards that don't read the * geometry register consistantly. */ if (aha->boardid <= 0x42) { /* Wait 10ms before reading */ DELAY(10000); status = aha_inb(aha, GEOMETRY_REG); if (status != 0xff && status != 0x00 && status != 0x7f) { PRVERB((aha->dev, "Geometry Register test failed %#x\n", status)); return (ENXIO); } } return (0); } /* * Pull the boards setup information and record it in our softc. */ int aha_fetch_adapter_info(struct aha_softc *aha) { setup_data_t setup_info; config_data_t config_data; uint8_t length_param; int error; struct aha_extbios extbios; switch (aha->boardid) { case BOARD_1540_16HEAD_BIOS: snprintf(aha->model, sizeof(aha->model), "1540 16 head BIOS"); break; case BOARD_1540_64HEAD_BIOS: snprintf(aha->model, sizeof(aha->model), "1540 64 head BIOS"); break; case BOARD_1542: snprintf(aha->model, sizeof(aha->model), "1540/1542 64 head BIOS"); break; case BOARD_1640: snprintf(aha->model, sizeof(aha->model), "1640"); break; case BOARD_1740: snprintf(aha->model, sizeof(aha->model), "1740A/1742A/1744"); break; case BOARD_1542C: snprintf(aha->model, sizeof(aha->model), "1542C"); break; case BOARD_1542CF: snprintf(aha->model, sizeof(aha->model), "1542CF"); break; case BOARD_1542CP: snprintf(aha->model, sizeof(aha->model), "1542CP"); break; default: snprintf(aha->model, sizeof(aha->model), "Unknown"); break; } /* * If we are a new type of 1542 board (anything newer than a 1542C) * then disable the extended bios so that the * mailbox interface is unlocked. * This is also true for the 1542B Version 3.20. First Adaptec * board that supports >1Gb drives. * No need to check the extended bios flags as some of the * extensions that cause us problems are not flagged in that byte. */ if (PROBABLY_NEW_BOARD(aha->boardid) || (aha->boardid == 0x41 && aha->fw_major == 0x31 && aha->fw_minor >= 0x34)) { error = aha_cmd(aha, AOP_RETURN_EXT_BIOS_INFO, NULL, /*paramlen*/0, (u_char *)&extbios, sizeof(extbios), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(aha->dev, "AOP_RETURN_EXT_BIOS_INFO - Failed."); return (error); } error = aha_cmd(aha, AOP_MBOX_IF_ENABLE, (uint8_t *)&extbios, /*paramlen*/2, NULL, 0, DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(aha->dev, "AOP_MBOX_IF_ENABLE - Failed."); return (error); } } if (aha->boardid < 0x41) device_printf(aha->dev, "Warning: aha-1542A won't work.\n"); aha->max_sg = 17; /* Need >= 17 to do 64k I/O */ aha->diff_bus = 0; aha->extended_lun = 0; aha->extended_trans = 0; aha->max_ccbs = 16; /* Determine Sync/Wide/Disc settings */ length_param = sizeof(setup_info); error = aha_cmd(aha, AOP_INQUIRE_SETUP_INFO, &length_param, /*paramlen*/1, (uint8_t*)&setup_info, sizeof(setup_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(aha->dev, "aha_fetch_adapter_info - Failed " "Get Setup Info\n"); return (error); } if (setup_info.initiate_sync != 0) { aha->sync_permitted = ALL_TARGETS; } aha->disc_permitted = ALL_TARGETS; /* We need as many mailboxes as we can have ccbs */ aha->num_boxes = aha->max_ccbs; /* Determine our SCSI ID */ error = aha_cmd(aha, AOP_INQUIRE_CONFIG, NULL, /*parmlen*/0, (uint8_t*)&config_data, sizeof(config_data), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(aha->dev, "aha_fetch_adapter_info - Failed Get Config\n"); return (error); } aha->scsi_id = config_data.scsi_id; return (0); } /* * Start the board, ready for normal operation */ int aha_init(struct aha_softc* aha) { /* Announce the Adapter */ device_printf(aha->dev, "AHA-%s FW Rev. %c.%c (ID=%x) ", aha->model, aha->fw_major, aha->fw_minor, aha->boardid); if (aha->diff_bus != 0) printf("Diff "); printf("SCSI Host Adapter, SCSI ID %d, %d CCBs\n", aha->scsi_id, aha->max_ccbs); /* * Create our DMA tags. These tags define the kinds of device * accessible memory allocations and memory mappings we will * need to perform during normal operation. * * Unless we need to further restrict the allocation, we rely * on the restrictions of the parent dmat, hence the common * use of MAXADDR and MAXSIZE. */ /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create( /* parent */ aha->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ AHA_NSEG, /* maxsegsz */ BUS_SPACE_MAXSIZE_24BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &aha->lock, &aha->buffer_dmat) != 0) { goto error_exit; } aha->init_level++; /* DMA tag for our mailboxes */ if (bus_dma_tag_create( /* parent */ aha->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ aha->num_boxes * (sizeof(aha_mbox_in_t) + sizeof(aha_mbox_out_t)), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_24BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &aha->mailbox_dmat) != 0) { goto error_exit; } aha->init_level++; /* Allocation for our mailboxes */ if (bus_dmamem_alloc(aha->mailbox_dmat, (void **)&aha->out_boxes, BUS_DMA_NOWAIT, &aha->mailbox_dmamap) != 0) goto error_exit; aha->init_level++; /* And permanently map them */ bus_dmamap_load(aha->mailbox_dmat, aha->mailbox_dmamap, aha->out_boxes, aha->num_boxes * (sizeof(aha_mbox_in_t) + sizeof(aha_mbox_out_t)), ahamapmboxes, aha, /*flags*/0); aha->init_level++; aha->in_boxes = (aha_mbox_in_t *)&aha->out_boxes[aha->num_boxes]; ahainitmboxes(aha); /* DMA tag for our ccb structures */ if (bus_dma_tag_create( /* parent */ aha->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ aha->max_ccbs * sizeof(struct aha_ccb), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_24BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &aha->ccb_dmat) != 0) { goto error_exit; } aha->init_level++; /* Allocation for our ccbs */ if (bus_dmamem_alloc(aha->ccb_dmat, (void **)&aha->aha_ccb_array, BUS_DMA_NOWAIT, &aha->ccb_dmamap) != 0) goto error_exit; aha->init_level++; /* And permanently map them */ bus_dmamap_load(aha->ccb_dmat, aha->ccb_dmamap, aha->aha_ccb_array, aha->max_ccbs * sizeof(struct aha_ccb), ahamapccbs, aha, /*flags*/0); aha->init_level++; /* DMA tag for our S/G structures. We allocate in page sized chunks */ if (bus_dma_tag_create( /* parent */ aha->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ PAGE_SIZE, /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_24BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &aha->sg_dmat) != 0) goto error_exit; aha->init_level++; /* Perform initial CCB allocation */ bzero(aha->aha_ccb_array, aha->max_ccbs * sizeof(struct aha_ccb)); ahaallocccbs(aha); if (aha->num_ccbs == 0) { device_printf(aha->dev, "aha_init - Unable to allocate initial ccbs\n"); goto error_exit; } /* * Note that we are going and return (to probe) */ return (0); error_exit: return (ENXIO); } int aha_attach(struct aha_softc *aha) { int tagged_dev_openings; struct cam_devq *devq; /* * We don't do tagged queueing, since the aha cards don't * support it. */ tagged_dev_openings = 0; /* * Create the device queue for our SIM. */ devq = cam_simq_alloc(aha->max_ccbs - 1); if (devq == NULL) return (ENOMEM); /* * Construct our SIM entry */ aha->sim = cam_sim_alloc(ahaaction, ahapoll, "aha", aha, device_get_unit(aha->dev), &aha->lock, 2, tagged_dev_openings, devq); if (aha->sim == NULL) { cam_simq_free(devq); return (ENOMEM); } mtx_lock(&aha->lock); if (xpt_bus_register(aha->sim, aha->dev, 0) != CAM_SUCCESS) { cam_sim_free(aha->sim, /*free_devq*/TRUE); mtx_unlock(&aha->lock); return (ENXIO); } if (xpt_create_path(&aha->path, /*periph*/NULL, cam_sim_path(aha->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(aha->sim)); cam_sim_free(aha->sim, /*free_devq*/TRUE); mtx_unlock(&aha->lock); return (ENXIO); } mtx_unlock(&aha->lock); return (0); } static void ahaallocccbs(struct aha_softc *aha) { struct aha_ccb *next_ccb; struct sg_map_node *sg_map; bus_addr_t physaddr; aha_sg_t *segs; int newcount; int i; next_ccb = &aha->aha_ccb_array[aha->num_ccbs]; sg_map = malloc(sizeof(*sg_map), M_DEVBUF, M_NOWAIT); if (sg_map == NULL) return; /* Allocate S/G space for the next batch of CCBS */ if (bus_dmamem_alloc(aha->sg_dmat, (void **)&sg_map->sg_vaddr, BUS_DMA_NOWAIT, &sg_map->sg_dmamap) != 0) { free(sg_map, M_DEVBUF); return; } SLIST_INSERT_HEAD(&aha->sg_maps, sg_map, links); bus_dmamap_load(aha->sg_dmat, sg_map->sg_dmamap, sg_map->sg_vaddr, PAGE_SIZE, ahamapsgs, aha, /*flags*/0); segs = sg_map->sg_vaddr; physaddr = sg_map->sg_physaddr; newcount = (PAGE_SIZE / (AHA_NSEG * sizeof(aha_sg_t))); for (i = 0; aha->num_ccbs < aha->max_ccbs && i < newcount; i++) { int error; next_ccb->sg_list = segs; next_ccb->sg_list_phys = physaddr; next_ccb->flags = ACCB_FREE; callout_init_mtx(&next_ccb->timer, &aha->lock, 0); error = bus_dmamap_create(aha->buffer_dmat, /*flags*/0, &next_ccb->dmamap); if (error != 0) break; SLIST_INSERT_HEAD(&aha->free_aha_ccbs, next_ccb, links); segs += AHA_NSEG; physaddr += (AHA_NSEG * sizeof(aha_sg_t)); next_ccb++; aha->num_ccbs++; } /* Reserve a CCB for error recovery */ if (aha->recovery_accb == NULL) { aha->recovery_accb = SLIST_FIRST(&aha->free_aha_ccbs); SLIST_REMOVE_HEAD(&aha->free_aha_ccbs, links); } } static __inline void ahafreeccb(struct aha_softc *aha, struct aha_ccb *accb) { if (!dumping) mtx_assert(&aha->lock, MA_OWNED); if ((accb->flags & ACCB_ACTIVE) != 0) LIST_REMOVE(&accb->ccb->ccb_h, sim_links.le); if (aha->resource_shortage != 0 && (accb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { accb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; aha->resource_shortage = FALSE; } accb->flags = ACCB_FREE; SLIST_INSERT_HEAD(&aha->free_aha_ccbs, accb, links); aha->active_ccbs--; } static struct aha_ccb* ahagetccb(struct aha_softc *aha) { struct aha_ccb* accb; if (!dumping) mtx_assert(&aha->lock, MA_OWNED); if ((accb = SLIST_FIRST(&aha->free_aha_ccbs)) != NULL) { SLIST_REMOVE_HEAD(&aha->free_aha_ccbs, links); aha->active_ccbs++; } else if (aha->num_ccbs < aha->max_ccbs) { ahaallocccbs(aha); accb = SLIST_FIRST(&aha->free_aha_ccbs); if (accb == NULL) device_printf(aha->dev, "Can't malloc ACCB\n"); else { SLIST_REMOVE_HEAD(&aha->free_aha_ccbs, links); aha->active_ccbs++; } } return (accb); } static void ahaaction(struct cam_sim *sim, union ccb *ccb) { struct aha_softc *aha; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahaaction\n")); aha = (struct aha_softc *)cam_sim_softc(sim); mtx_assert(&aha->lock, MA_OWNED); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_SCSI_IO: /* Execute the requested I/O operation */ case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ { struct aha_ccb *accb; struct aha_hccb *hccb; /* * Get an accb to use. */ if ((accb = ahagetccb(aha)) == NULL) { aha->resource_shortage = TRUE; xpt_freeze_simq(aha->sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } hccb = &accb->hccb; /* * So we can find the ACCB when an abort is requested */ accb->ccb = ccb; ccb->ccb_h.ccb_accb_ptr = accb; ccb->ccb_h.ccb_aha_ptr = aha; /* * Put all the arguments for the xfer in the accb */ hccb->target = ccb->ccb_h.target_id; hccb->lun = ccb->ccb_h.target_lun; hccb->ahastat = 0; hccb->sdstat = 0; if (ccb->ccb_h.func_code == XPT_SCSI_IO) { struct ccb_scsiio *csio; struct ccb_hdr *ccbh; int error; csio = &ccb->csio; ccbh = &csio->ccb_h; hccb->opcode = aha->ccb_ccb_opcode; hccb->datain = (ccb->ccb_h.flags & CAM_DIR_IN) != 0; hccb->dataout = (ccb->ccb_h.flags & CAM_DIR_OUT) != 0; hccb->cmd_len = csio->cdb_len; if (hccb->cmd_len > sizeof(hccb->scsi_cdb)) { ccb->ccb_h.status = CAM_REQ_INVALID; ahafreeccb(aha, accb); xpt_done(ccb); return; } hccb->sense_len = csio->sense_len; if ((ccbh->flags & CAM_CDB_POINTER) != 0) { if ((ccbh->flags & CAM_CDB_PHYS) == 0) { bcopy(csio->cdb_io.cdb_ptr, hccb->scsi_cdb, hccb->cmd_len); } else { /* I guess I could map it in... */ ccbh->status = CAM_REQ_INVALID; ahafreeccb(aha, accb); xpt_done(ccb); return; } } else { bcopy(csio->cdb_io.cdb_bytes, hccb->scsi_cdb, hccb->cmd_len); } /* * If we have any data to send with this command, * map it into bus space. */ error = bus_dmamap_load_ccb( aha->buffer_dmat, accb->dmamap, ccb, ahaexecuteccb, accb, /*flags*/0); if (error == EINPROGRESS) { /* * So as to maintain ordering, freeze the * controller queue until our mapping is * returned. */ xpt_freeze_simq(aha->sim, 1); csio->ccb_h.status |= CAM_RELEASE_SIMQ; } } else { hccb->opcode = INITIATOR_BUS_DEV_RESET; /* No data transfer */ hccb->datain = TRUE; hccb->dataout = TRUE; hccb->cmd_len = 0; hccb->sense_len = 0; ahaexecuteccb(accb, NULL, 0, 0); } break; } case XPT_EN_LUN: /* Enable LUN as a target */ case XPT_TARGET_IO: /* Execute target I/O request */ case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: /* XXX Implement */ ccb->ccb_h.status = CAM_PROVIDE_FAIL; xpt_done(ccb); break; case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; u_int target_mask = 0x01 << ccb->ccb_h.target_id; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; if (cts->type == CTS_TYPE_USER_SETTINGS) { spi->flags = 0; if ((aha->disc_permitted & target_mask) != 0) spi->flags |= CTS_SPI_FLAGS_DISC_ENB; spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; if ((aha->sync_permitted & target_mask) != 0) { if (aha->boardid >= BOARD_1542CF) spi->sync_period = 25; else spi->sync_period = 50; } else { spi->sync_period = 0; } if (spi->sync_period != 0) spi->sync_offset = 15; spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; } else { ahafetchtransinfo(aha, cts); } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg; uint32_t size_mb; uint32_t secs_per_cylinder; ccg = &ccb->ccg; size_mb = ccg->volume_size / ((1024L * 1024L) / ccg->block_size); if (size_mb >= 1024 && (aha->extended_trans != 0)) { if (size_mb >= 2048) { ccg->heads = 255; ccg->secs_per_track = 63; } else { ccg->heads = 128; ccg->secs_per_track = 32; } } else { ccg->heads = 64; ccg->secs_per_track = 32; } secs_per_cylinder = ccg->heads * ccg->secs_per_track; ccg->cylinders = ccg->volume_size / secs_per_cylinder; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ ahareset(aha, /*hardreset*/TRUE); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_SDTR_ABLE; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = 7; cpi->max_lun = 7; cpi->initiator_id = aha->scsi_id; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } static void ahaexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct aha_ccb *accb; union ccb *ccb; struct aha_softc *aha; uint32_t paddr; accb = (struct aha_ccb *)arg; ccb = accb->ccb; aha = (struct aha_softc *)ccb->ccb_h.ccb_aha_ptr; if (error != 0) { if (error != EFBIG) device_printf(aha->dev, "Unexepected error 0x%x returned from " "bus_dmamap_load\n", error); if (ccb->ccb_h.status == CAM_REQ_INPROG) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; } ahafreeccb(aha, accb); xpt_done(ccb); return; } if (nseg != 0) { aha_sg_t *sg; bus_dma_segment_t *end_seg; bus_dmasync_op_t op; end_seg = dm_segs + nseg; /* Copy the segments into our SG list */ sg = accb->sg_list; while (dm_segs < end_seg) { ahautoa24(dm_segs->ds_len, sg->len); ahautoa24(dm_segs->ds_addr, sg->addr); sg++; dm_segs++; } if (nseg > 1) { accb->hccb.opcode = aha->ccb_sg_opcode; ahautoa24((sizeof(aha_sg_t) * nseg), accb->hccb.data_len); ahautoa24(accb->sg_list_phys, accb->hccb.data_addr); } else { bcopy(accb->sg_list->len, accb->hccb.data_len, 3); bcopy(accb->sg_list->addr, accb->hccb.data_addr, 3); } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_PREREAD; else op = BUS_DMASYNC_PREWRITE; bus_dmamap_sync(aha->buffer_dmat, accb->dmamap, op); } else { accb->hccb.opcode = INITIATOR_CCB; ahautoa24(0, accb->hccb.data_len); ahautoa24(0, accb->hccb.data_addr); } /* * Last time we need to check if this CCB needs to * be aborted. */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(aha->buffer_dmat, accb->dmamap); ahafreeccb(aha, accb); xpt_done(ccb); return; } accb->flags = ACCB_ACTIVE; ccb->ccb_h.status |= CAM_SIM_QUEUED; LIST_INSERT_HEAD(&aha->pending_ccbs, &ccb->ccb_h, sim_links.le); callout_reset_sbt(&accb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, ahatimeout, accb, 0); /* Tell the adapter about this command */ if (aha->cur_outbox->action_code != AMBO_FREE) { /* * We should never encounter a busy mailbox. * If we do, warn the user, and treat it as * a resource shortage. If the controller is * hung, one of the pending transactions will * timeout causing us to start recovery operations. */ device_printf(aha->dev, "Encountered busy mailbox with %d out of %d " "commands active!!!", aha->active_ccbs, aha->max_ccbs); callout_stop(&accb->timer); if (nseg != 0) bus_dmamap_unload(aha->buffer_dmat, accb->dmamap); ahafreeccb(aha, accb); aha->resource_shortage = TRUE; xpt_freeze_simq(aha->sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } paddr = ahaccbvtop(aha, accb); ahautoa24(paddr, aha->cur_outbox->ccb_addr); aha->cur_outbox->action_code = AMBO_START; aha_outb(aha, COMMAND_REG, AOP_START_MBOX); ahanextoutbox(aha); } void aha_intr(void *arg) { struct aha_softc *aha; aha = arg; mtx_lock(&aha->lock); aha_intr_locked(aha); mtx_unlock(&aha->lock); } void aha_intr_locked(struct aha_softc *aha) { u_int intstat; uint32_t paddr; while (((intstat = aha_inb(aha, INTSTAT_REG)) & INTR_PENDING) != 0) { if ((intstat & CMD_COMPLETE) != 0) { aha->latched_status = aha_inb(aha, STATUS_REG); aha->command_cmp = TRUE; } aha_outb(aha, CONTROL_REG, RESET_INTR); if ((intstat & IMB_LOADED) != 0) { while (aha->cur_inbox->comp_code != AMBI_FREE) { paddr = aha_a24tou(aha->cur_inbox->ccb_addr); ahadone(aha, ahaccbptov(aha, paddr), aha->cur_inbox->comp_code); aha->cur_inbox->comp_code = AMBI_FREE; ahanextinbox(aha); } } if ((intstat & SCSI_BUS_RESET) != 0) { ahareset(aha, /*hardreset*/FALSE); } } } static void ahadone(struct aha_softc *aha, struct aha_ccb *accb, aha_mbi_comp_code_t comp_code) { union ccb *ccb; struct ccb_scsiio *csio; ccb = accb->ccb; csio = &accb->ccb->csio; if ((accb->flags & ACCB_ACTIVE) == 0) { device_printf(aha->dev, "ahadone - Attempt to free non-active ACCB %p\n", (void *)accb); return; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(aha->buffer_dmat, accb->dmamap, op); bus_dmamap_unload(aha->buffer_dmat, accb->dmamap); } if (accb == aha->recovery_accb) { /* * The recovery ACCB does not have a CCB associated * with it, so short circuit the normal error handling. * We now traverse our list of pending CCBs and process * any that were terminated by the recovery CCBs action. * We also reinstate timeouts for all remaining, pending, * CCBs. */ struct cam_path *path; struct ccb_hdr *ccb_h; cam_status error; /* Notify all clients that a BDR occured */ error = xpt_create_path(&path, /*periph*/NULL, cam_sim_path(aha->sim), accb->hccb.target, CAM_LUN_WILDCARD); if (error == CAM_REQ_CMP) { xpt_async(AC_SENT_BDR, path, NULL); xpt_free_path(path); } ccb_h = LIST_FIRST(&aha->pending_ccbs); while (ccb_h != NULL) { struct aha_ccb *pending_accb; pending_accb = (struct aha_ccb *)ccb_h->ccb_accb_ptr; if (pending_accb->hccb.target == accb->hccb.target) { pending_accb->hccb.ahastat = AHASTAT_HA_BDR; ccb_h = LIST_NEXT(ccb_h, sim_links.le); ahadone(aha, pending_accb, AMBI_ERROR); } else { callout_reset_sbt(&pending_accb->timer, SBT_1MS * ccb_h->timeout, 0, ahatimeout, pending_accb, 0); ccb_h = LIST_NEXT(ccb_h, sim_links.le); } } device_printf(aha->dev, "No longer in timeout\n"); return; } callout_stop(&accb->timer); switch (comp_code) { case AMBI_FREE: device_printf(aha->dev, "ahadone - CCB completed with free status!\n"); break; case AMBI_NOT_FOUND: device_printf(aha->dev, "ahadone - CCB Abort failed to find CCB\n"); break; case AMBI_ABORT: case AMBI_ERROR: /* An error occured */ if (accb->hccb.opcode < INITIATOR_CCB_WRESID) csio->resid = 0; else csio->resid = aha_a24tou(accb->hccb.data_len); switch(accb->hccb.ahastat) { case AHASTAT_DATARUN_ERROR: { if (csio->resid <= 0) { csio->ccb_h.status = CAM_DATA_RUN_ERR; break; } /* FALLTHROUGH */ } case AHASTAT_NOERROR: csio->scsi_status = accb->hccb.sdstat; csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR; switch(csio->scsi_status) { case SCSI_STATUS_CHECK_COND: case SCSI_STATUS_CMD_TERMINATED: csio->ccb_h.status |= CAM_AUTOSNS_VALID; /* * The aha writes the sense data at different * offsets based on the scsi cmd len */ bcopy((caddr_t) &accb->hccb.scsi_cdb + accb->hccb.cmd_len, (caddr_t) &csio->sense_data, accb->hccb.sense_len); break; default: break; case SCSI_STATUS_OK: csio->ccb_h.status = CAM_REQ_CMP; break; } break; case AHASTAT_SELTIMEOUT: csio->ccb_h.status = CAM_SEL_TIMEOUT; break; case AHASTAT_UNEXPECTED_BUSFREE: csio->ccb_h.status = CAM_UNEXP_BUSFREE; break; case AHASTAT_INVALID_PHASE: csio->ccb_h.status = CAM_SEQUENCE_FAIL; break; case AHASTAT_INVALID_ACTION_CODE: panic("%s: Inavlid Action code", aha_name(aha)); break; case AHASTAT_INVALID_OPCODE: if (accb->hccb.opcode < INITIATOR_CCB_WRESID) panic("%s: Invalid CCB Opcode %x hccb = %p", aha_name(aha), accb->hccb.opcode, &accb->hccb); device_printf(aha->dev, "AHA-1540A compensation failed\n"); xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); csio->ccb_h.status = CAM_REQUEUE_REQ; break; case AHASTAT_LINKED_CCB_LUN_MISMATCH: /* We don't even support linked commands... */ panic("%s: Linked CCB Lun Mismatch", aha_name(aha)); break; case AHASTAT_INVALID_CCB_OR_SG_PARAM: panic("%s: Invalid CCB or SG list", aha_name(aha)); break; case AHASTAT_HA_SCSI_BUS_RESET: if ((csio->ccb_h.status & CAM_STATUS_MASK) != CAM_CMD_TIMEOUT) csio->ccb_h.status = CAM_SCSI_BUS_RESET; break; case AHASTAT_HA_BDR: if ((accb->flags & ACCB_DEVICE_RESET) == 0) csio->ccb_h.status = CAM_BDR_SENT; else csio->ccb_h.status = CAM_CMD_TIMEOUT; break; } if (csio->ccb_h.status != CAM_REQ_CMP) { xpt_freeze_devq(csio->ccb_h.path, /*count*/1); csio->ccb_h.status |= CAM_DEV_QFRZN; } if ((accb->flags & ACCB_RELEASE_SIMQ) != 0) ccb->ccb_h.status |= CAM_RELEASE_SIMQ; ahafreeccb(aha, accb); xpt_done(ccb); break; case AMBI_OK: /* All completed without incident */ /* XXX DO WE NEED TO COPY SENSE BYTES HERE???? XXX */ /* I don't think so since it works???? */ ccb->ccb_h.status |= CAM_REQ_CMP; if ((accb->flags & ACCB_RELEASE_SIMQ) != 0) ccb->ccb_h.status |= CAM_RELEASE_SIMQ; ahafreeccb(aha, accb); xpt_done(ccb); break; } } static int ahareset(struct aha_softc* aha, int hard_reset) { struct ccb_hdr *ccb_h; u_int status; u_int timeout; uint8_t reset_type; if (hard_reset != 0) reset_type = HARD_RESET; else reset_type = SOFT_RESET; aha_outb(aha, CONTROL_REG, reset_type); /* Wait 5sec. for Diagnostic start */ timeout = 5 * 10000; while (--timeout) { status = aha_inb(aha, STATUS_REG); if ((status & DIAG_ACTIVE) != 0) break; DELAY(100); } if (timeout == 0) { PRVERB((aha->dev, "ahareset - Diagnostic Active failed to " "assert. status = %#x\n", status)); return (ETIMEDOUT); } /* Wait 10sec. for Diagnostic end */ timeout = 10 * 10000; while (--timeout) { status = aha_inb(aha, STATUS_REG); if ((status & DIAG_ACTIVE) == 0) break; DELAY(100); } if (timeout == 0) { panic("%s: ahareset - Diagnostic Active failed to drop. " "status = 0x%x\n", aha_name(aha), status); return (ETIMEDOUT); } /* Wait for the host adapter to become ready or report a failure */ timeout = 10000; while (--timeout) { status = aha_inb(aha, STATUS_REG); if ((status & (DIAG_FAIL|HA_READY|DATAIN_REG_READY)) != 0) break; DELAY(100); } if (timeout == 0) { device_printf(aha->dev, "ahareset - Host adapter failed to " "come ready. status = 0x%x\n", status); return (ETIMEDOUT); } /* If the diagnostics failed, tell the user */ if ((status & DIAG_FAIL) != 0 || (status & HA_READY) == 0) { device_printf(aha->dev, "ahareset - Adapter failed diag\n"); if ((status & DATAIN_REG_READY) != 0) device_printf(aha->dev, "ahareset - Host Adapter " "Error code = 0x%x\n", aha_inb(aha, DATAIN_REG)); return (ENXIO); } /* If we've attached to the XPT, tell it about the event */ if (aha->path != NULL) xpt_async(AC_BUS_RESET, aha->path, NULL); /* * Perform completion processing for all outstanding CCBs. */ while ((ccb_h = LIST_FIRST(&aha->pending_ccbs)) != NULL) { struct aha_ccb *pending_accb; pending_accb = (struct aha_ccb *)ccb_h->ccb_accb_ptr; pending_accb->hccb.ahastat = AHASTAT_HA_SCSI_BUS_RESET; ahadone(aha, pending_accb, AMBI_ERROR); } /* If we've allocated mailboxes, initialize them */ /* Must be done after we've aborted our queue, or aha_cmd fails */ if (aha->init_level > 4) ahainitmboxes(aha); return (0); } /* * Send a command to the adapter. */ int aha_cmd(struct aha_softc *aha, aha_op_t opcode, uint8_t *params, u_int param_len, uint8_t *reply_data, u_int reply_len, u_int cmd_timeout) { u_int timeout; u_int status; u_int saved_status; u_int intstat; u_int reply_buf_size; int cmd_complete; int error; /* No data returned to start */ reply_buf_size = reply_len; reply_len = 0; intstat = 0; cmd_complete = 0; saved_status = 0; error = 0; /* * All commands except for the "start mailbox" and the "enable * outgoing mailbox read interrupt" commands cannot be issued * while there are pending transactions. Freeze our SIMQ * and wait for all completions to occur if necessary. */ timeout = 10000; while (LIST_FIRST(&aha->pending_ccbs) != NULL && --timeout) { /* Fire the interrupt handler in case interrupts are blocked */ aha_intr(aha); DELAY(10); } if (timeout == 0) { device_printf(aha->dev, "aha_cmd: Timeout waiting for adapter idle\n"); return (ETIMEDOUT); } aha->command_cmp = 0; /* * Wait up to 10 sec. for the adapter to become * ready to accept commands. */ timeout = 100000; while (--timeout) { status = aha_inb(aha, STATUS_REG); if ((status & HA_READY) != 0 && (status & CMD_REG_BUSY) == 0) break; /* * Throw away any pending data which may be * left over from earlier commands that we * timedout on. */ if ((status & DATAIN_REG_READY) != 0) (void)aha_inb(aha, DATAIN_REG); DELAY(100); } if (timeout == 0) { device_printf(aha->dev, "aha_cmd: Timeout waiting for adapter" " ready, status = 0x%x\n", status); return (ETIMEDOUT); } /* * Send the opcode followed by any necessary parameter bytes. */ aha_outb(aha, COMMAND_REG, opcode); /* * Wait for up to 1sec to get the parameter list sent */ timeout = 10000; while (param_len && --timeout) { DELAY(100); status = aha_inb(aha, STATUS_REG); intstat = aha_inb(aha, INTSTAT_REG); if ((intstat & (INTR_PENDING|CMD_COMPLETE)) == (INTR_PENDING|CMD_COMPLETE)) { saved_status = status; cmd_complete = 1; break; } if (aha->command_cmp != 0) { saved_status = aha->latched_status; cmd_complete = 1; break; } if ((status & DATAIN_REG_READY) != 0) break; if ((status & CMD_REG_BUSY) == 0) { aha_outb(aha, COMMAND_REG, *params++); param_len--; timeout = 10000; } } if (timeout == 0) { device_printf(aha->dev, "aha_cmd: Timeout sending parameters, " "status = 0x%x\n", status); error = ETIMEDOUT; } /* * For all other commands, we wait for any output data * and the final comand completion interrupt. */ while (cmd_complete == 0 && --cmd_timeout) { status = aha_inb(aha, STATUS_REG); intstat = aha_inb(aha, INTSTAT_REG); if (aha->command_cmp != 0) { cmd_complete = 1; saved_status = aha->latched_status; } else if ((intstat & (INTR_PENDING|CMD_COMPLETE)) == (INTR_PENDING|CMD_COMPLETE)) { /* * Our poll (in case interrupts are blocked) * saw the CMD_COMPLETE interrupt. */ cmd_complete = 1; saved_status = status; } if ((status & DATAIN_REG_READY) != 0) { uint8_t data; data = aha_inb(aha, DATAIN_REG); if (reply_len < reply_buf_size) { *reply_data++ = data; } else { device_printf(aha->dev, "aha_cmd - Discarded reply data " "byte for opcode 0x%x\n", opcode); } /* * Reset timeout to ensure at least a second * between response bytes. */ cmd_timeout = MAX(cmd_timeout, 10000); reply_len++; } DELAY(100); } if (cmd_timeout == 0) { device_printf(aha->dev, "aha_cmd: Timeout: status = 0x%x, " "intstat = 0x%x, reply_len = %d\n", status, intstat, reply_len); return (ETIMEDOUT); } /* * Clear any pending interrupts. Block interrupts so our * interrupt handler is not re-entered. */ aha_intr(aha); if (error != 0) return (error); /* * If the command was rejected by the controller, tell the caller. */ if ((saved_status & CMD_INVALID) != 0) { PRVERB((aha->dev, "Invalid Command 0x%x\n", opcode)); /* * Some early adapters may not recover properly from * an invalid command. If it appears that the controller * has wedged (i.e. status was not cleared by our interrupt * reset above), perform a soft reset. */ DELAY(1000); status = aha_inb(aha, STATUS_REG); if ((status & (CMD_INVALID|STATUS_REG_RSVD|DATAIN_REG_READY| CMD_REG_BUSY|DIAG_FAIL|DIAG_ACTIVE)) != 0 || (status & (HA_READY|INIT_REQUIRED)) != (HA_READY|INIT_REQUIRED)) ahareset(aha, /*hard_reset*/FALSE); return (EINVAL); } if (param_len > 0) { /* The controller did not accept the full argument list */ PRVERB((aha->dev, "Controller did not accept full argument " "list (%d > 0)\n", param_len)); return (E2BIG); } if (reply_len != reply_buf_size) { /* Too much or too little data received */ PRVERB((aha->dev, "data received mismatch (%d != %d)\n", reply_len, reply_buf_size)); return (EMSGSIZE); } /* We were successful */ return (0); } static int ahainitmboxes(struct aha_softc *aha) { int error; init_24b_mbox_params_t init_mbox; bzero(aha->in_boxes, sizeof(aha_mbox_in_t) * aha->num_boxes); bzero(aha->out_boxes, sizeof(aha_mbox_out_t) * aha->num_boxes); aha->cur_inbox = aha->in_boxes; aha->last_inbox = aha->in_boxes + aha->num_boxes - 1; aha->cur_outbox = aha->out_boxes; aha->last_outbox = aha->out_boxes + aha->num_boxes - 1; /* Tell the adapter about them */ init_mbox.num_mboxes = aha->num_boxes; ahautoa24(aha->mailbox_physbase, init_mbox.base_addr); error = aha_cmd(aha, AOP_INITIALIZE_MBOX, (uint8_t *)&init_mbox, /*parmlen*/sizeof(init_mbox), /*reply_buf*/NULL, /*reply_len*/0, DEFAULT_CMD_TIMEOUT); if (error != 0) printf("ahainitmboxes: Initialization command failed\n"); return (error); } /* * Update the XPT's idea of the negotiated transfer * parameters for a particular target. */ static void ahafetchtransinfo(struct aha_softc *aha, struct ccb_trans_settings* cts) { setup_data_t setup_info; u_int target; u_int targ_offset; u_int sync_period; int error; uint8_t param; targ_syncinfo_t sync_info; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; target = cts->ccb_h.target_id; targ_offset = (target & 0x7); /* * Inquire Setup Information. This command retreives * the sync info for older models. */ param = sizeof(setup_info); error = aha_cmd(aha, AOP_INQUIRE_SETUP_INFO, ¶m, /*paramlen*/1, (uint8_t*)&setup_info, sizeof(setup_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(aha->dev, "ahafetchtransinfo - Inquire Setup Info Failed %d\n", error); return; } sync_info = setup_info.syncinfo[targ_offset]; if (sync_info.sync == 0) spi->sync_offset = 0; else spi->sync_offset = sync_info.offset; spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; if (aha->boardid >= BOARD_1542CF) sync_period = 1000; else sync_period = 2000; sync_period += 500 * sync_info.period; /* Convert ns value to standard SCSI sync rate */ if (spi->sync_offset != 0) spi->sync_period = scsi_calc_syncparam(sync_period); else spi->sync_period = 0; spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH; xpt_async(AC_TRANSFER_NEG, cts->ccb_h.path, cts); } static void ahamapmboxes(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aha_softc* aha; aha = (struct aha_softc*)arg; aha->mailbox_physbase = segs->ds_addr; } static void ahamapccbs(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aha_softc* aha; aha = (struct aha_softc*)arg; aha->aha_ccb_physbase = segs->ds_addr; } static void ahamapsgs(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct aha_softc* aha; aha = (struct aha_softc*)arg; SLIST_FIRST(&aha->sg_maps)->sg_physaddr = segs->ds_addr; } static void ahapoll(struct cam_sim *sim) { aha_intr_locked(cam_sim_softc(sim)); } static void ahatimeout(void *arg) { struct aha_ccb *accb; union ccb *ccb; struct aha_softc *aha; uint32_t paddr; struct ccb_hdr *ccb_h; accb = (struct aha_ccb *)arg; ccb = accb->ccb; aha = (struct aha_softc *)ccb->ccb_h.ccb_aha_ptr; mtx_assert(&aha->lock, MA_OWNED); xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out\n", (void *)accb); if ((accb->flags & ACCB_ACTIVE) == 0) { xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out CCB already completed\n", (void *)accb); return; } /* * In order to simplify the recovery process, we ask the XPT * layer to halt the queue of new transactions and we traverse * the list of pending CCBs and remove their timeouts. This * means that the driver attempts to clear only one error * condition at a time. In general, timeouts that occur * close together are related anyway, so there is no benefit * in attempting to handle errors in parrallel. Timeouts will * be reinstated when the recovery process ends. */ if ((accb->flags & ACCB_DEVICE_RESET) == 0) { if ((accb->flags & ACCB_RELEASE_SIMQ) == 0) { xpt_freeze_simq(aha->sim, /*count*/1); accb->flags |= ACCB_RELEASE_SIMQ; } ccb_h = LIST_FIRST(&aha->pending_ccbs); while (ccb_h != NULL) { struct aha_ccb *pending_accb; pending_accb = (struct aha_ccb *)ccb_h->ccb_accb_ptr; callout_stop(&pending_accb->timer); ccb_h = LIST_NEXT(ccb_h, sim_links.le); } } if ((accb->flags & ACCB_DEVICE_RESET) != 0 || aha->cur_outbox->action_code != AMBO_FREE) { /* * Try a full host adapter/SCSI bus reset. * We do this only if we have already attempted * to clear the condition with a BDR, or we cannot * attempt a BDR for lack of mailbox resources. */ ccb->ccb_h.status = CAM_CMD_TIMEOUT; ahareset(aha, /*hardreset*/TRUE); device_printf(aha->dev, "No longer in timeout\n"); } else { /* * Send a Bus Device Reset message: * The target that is holding up the bus may not * be the same as the one that triggered this timeout * (different commands have different timeout lengths), * but we have no way of determining this from our * timeout handler. Our strategy here is to queue a * BDR message to the target of the timed out command. * If this fails, we'll get another timeout 2 seconds * later which will attempt a bus reset. */ accb->flags |= ACCB_DEVICE_RESET; callout_reset(&accb->timer, 2 * hz, ahatimeout, accb); aha->recovery_accb->hccb.opcode = INITIATOR_BUS_DEV_RESET; /* No Data Transfer */ aha->recovery_accb->hccb.datain = TRUE; aha->recovery_accb->hccb.dataout = TRUE; aha->recovery_accb->hccb.ahastat = 0; aha->recovery_accb->hccb.sdstat = 0; aha->recovery_accb->hccb.target = ccb->ccb_h.target_id; /* Tell the adapter about this command */ paddr = ahaccbvtop(aha, aha->recovery_accb); ahautoa24(paddr, aha->cur_outbox->ccb_addr); aha->cur_outbox->action_code = AMBO_START; aha_outb(aha, COMMAND_REG, AOP_START_MBOX); ahanextoutbox(aha); } } int aha_detach(struct aha_softc *aha) { mtx_lock(&aha->lock); xpt_async(AC_LOST_DEVICE, aha->path, NULL); xpt_free_path(aha->path); xpt_bus_deregister(cam_sim_path(aha->sim)); cam_sim_free(aha->sim, /*free_devq*/TRUE); mtx_unlock(&aha->lock); /* XXX: Drain all timers? */ return (0); } MODULE_DEPEND(aha, cam, 1, 1, 1); Index: head/sys/dev/ahb/ahb.c =================================================================== --- head/sys/dev/ahb/ahb.c (revision 280346) +++ head/sys/dev/ahb/ahb.c (revision 280347) @@ -1,1315 +1,1315 @@ /*- * CAM SCSI device driver for the Adaptec 174X SCSI Host adapter * * Copyright (c) 1998 Justin T. Gibbs * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice immediately at the beginning of the file, without modification, * this list of conditions, and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ccb_ecb_ptr spriv_ptr0 #define ccb_ahb_ptr spriv_ptr1 #define ahb_inb(ahb, port) \ bus_read_1((ahb)->res, port) #define ahb_inl(ahb, port) \ bus_read_4((ahb)->res, port) #define ahb_outb(ahb, port, value) \ bus_write_1((ahb)->res, port, value) #define ahb_outl(ahb, port, value) \ bus_write_4((ahb)->res, port, value) static const char *ahbmatch(eisa_id_t type); static struct ahb_softc *ahballoc(device_t dev, struct resource *res); static void ahbfree(struct ahb_softc *ahb); static int ahbreset(struct ahb_softc *ahb); static void ahbmapecbs(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int ahbxptattach(struct ahb_softc *ahb); static void ahbhandleimmed(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat); static void ahbcalcresid(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb); static __inline void ahbdone(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat); static void ahbintr(void *arg); static void ahbintr_locked(struct ahb_softc *ahb); static bus_dmamap_callback_t ahbexecuteecb; static void ahbaction(struct cam_sim *sim, union ccb *ccb); static void ahbpoll(struct cam_sim *sim); /* Our timeout handler */ static void ahbtimeout(void *arg); static __inline struct ecb* ahbecbget(struct ahb_softc *ahb); static __inline void ahbecbfree(struct ahb_softc* ahb, struct ecb* ecb); static __inline u_int32_t ahbecbvtop(struct ahb_softc *ahb, struct ecb *ecb); static __inline struct ecb* ahbecbptov(struct ahb_softc *ahb, u_int32_t ecb_addr); static __inline u_int32_t ahbstatuspaddr(u_int32_t ecb_paddr); static __inline u_int32_t ahbsensepaddr(u_int32_t ecb_paddr); static __inline u_int32_t ahbsgpaddr(u_int32_t ecb_paddr); static __inline void ahbqueuembox(struct ahb_softc *ahb, u_int32_t mboxval, u_int attn_code); static __inline struct ecb* ahbecbget(struct ahb_softc *ahb) { struct ecb* ecb; if (!dumping) mtx_assert(&ahb->lock, MA_OWNED); if ((ecb = SLIST_FIRST(&ahb->free_ecbs)) != NULL) SLIST_REMOVE_HEAD(&ahb->free_ecbs, links); return (ecb); } static __inline void ahbecbfree(struct ahb_softc* ahb, struct ecb* ecb) { if (!dumping) mtx_assert(&ahb->lock, MA_OWNED); ecb->state = ECB_FREE; SLIST_INSERT_HEAD(&ahb->free_ecbs, ecb, links); } static __inline u_int32_t ahbecbvtop(struct ahb_softc *ahb, struct ecb *ecb) { return (ahb->ecb_physbase + (u_int32_t)((caddr_t)ecb - (caddr_t)ahb->ecb_array)); } static __inline struct ecb* ahbecbptov(struct ahb_softc *ahb, u_int32_t ecb_addr) { return (ahb->ecb_array + ((struct ecb*)(uintptr_t)ecb_addr - (struct ecb*)(uintptr_t)ahb->ecb_physbase)); } static __inline u_int32_t ahbstatuspaddr(u_int32_t ecb_paddr) { return (ecb_paddr + offsetof(struct ecb, status)); } static __inline u_int32_t ahbsensepaddr(u_int32_t ecb_paddr) { return (ecb_paddr + offsetof(struct ecb, sense)); } static __inline u_int32_t ahbsgpaddr(u_int32_t ecb_paddr) { return (ecb_paddr + offsetof(struct ecb, sg_list)); } static __inline void ahbqueuembox(struct ahb_softc *ahb, u_int32_t mboxval, u_int attn_code) { u_int loopmax = 300; while (--loopmax) { u_int status; status = ahb_inb(ahb, HOSTSTAT); if ((status & (HOSTSTAT_MBOX_EMPTY|HOSTSTAT_BUSY)) == HOSTSTAT_MBOX_EMPTY) break; DELAY(20); } if (loopmax == 0) panic("%s: adapter not taking commands\n", device_get_nameunit(ahb->dev)); ahb_outl(ahb, MBOXOUT0, mboxval); ahb_outb(ahb, ATTN, attn_code); } static const char * ahbmatch(eisa_id_t type) { switch(type & 0xfffffe00) { case EISA_DEVICE_ID_ADAPTEC_1740: return ("Adaptec 174x SCSI host adapter"); break; default: break; } return (NULL); } static int ahbprobe(device_t dev) { const char *desc; u_int32_t iobase; u_int32_t irq; u_int8_t intdef; int shared; desc = ahbmatch(eisa_get_id(dev)); if (!desc) return (ENXIO); device_set_desc(dev, desc); iobase = (eisa_get_slot(dev) * EISA_SLOT_SIZE) + AHB_EISA_SLOT_OFFSET; eisa_add_iospace(dev, iobase, AHB_EISA_IOSIZE, RESVADDR_NONE); intdef = inb(INTDEF + iobase); switch (intdef & 0x7) { case INT9: irq = 9; break; case INT10: irq = 10; break; case INT11: irq = 11; break; case INT12: irq = 12; break; case INT14: irq = 14; break; case INT15: irq = 15; break; default: printf("Adaptec 174X at slot %d: illegal " "irq setting %d\n", eisa_get_slot(dev), (intdef & 0x7)); irq = 0; break; } if (irq == 0) return ENXIO; shared = (inb(INTDEF + iobase) & INTLEVEL) ? EISA_TRIGGER_LEVEL : EISA_TRIGGER_EDGE; eisa_add_intr(dev, irq, shared); return 0; } static int ahbattach(device_t dev) { /* * find unit and check we have that many defined */ struct ahb_softc *ahb; struct ecb* next_ecb; struct resource *io; struct resource *irq; int rid; void *ih; irq = NULL; rid = 0; io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (io == NULL) { device_printf(dev, "No I/O space?!\n"); return ENOMEM; } ahb = ahballoc(dev, io); if (ahbreset(ahb) != 0) goto error_exit; rid = 0; irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (irq == NULL) { device_printf(dev, "Can't allocate interrupt\n"); goto error_exit; } /* * Create our DMA tags. These tags define the kinds of device * accessible memory allocations and memory mappings we will * need to perform during normal operation. */ /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(dev), /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ AHB_NSEG, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &ahb->lock, &ahb->buffer_dmat) != 0) goto error_exit; ahb->init_level++; /* DMA tag for our ccb structures and ha inquiry data */ if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(dev), /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ (AHB_NECB * sizeof(struct ecb)) + sizeof(*ahb->ha_inq_data), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &ahb->ecb_dmat) != 0) goto error_exit; ahb->init_level++; /* Allocation for our ccbs */ if (bus_dmamem_alloc(ahb->ecb_dmat, (void **)&ahb->ecb_array, BUS_DMA_NOWAIT, &ahb->ecb_dmamap) != 0) goto error_exit; ahb->ha_inq_data = (struct ha_inquiry_data *)&ahb->ecb_array[AHB_NECB]; ahb->init_level++; /* And permanently map them */ bus_dmamap_load(ahb->ecb_dmat, ahb->ecb_dmamap, ahb->ecb_array, AHB_NSEG * sizeof(struct ecb), ahbmapecbs, ahb, /*flags*/0); ahb->init_level++; /* Allocate the buffer dmamaps for each of our ECBs */ bzero(ahb->ecb_array, (AHB_NECB * sizeof(struct ecb)) + sizeof(*ahb->ha_inq_data)); next_ecb = ahb->ecb_array; while (ahb->num_ecbs < AHB_NECB) { u_int32_t ecb_paddr; if (bus_dmamap_create(ahb->buffer_dmat, /*flags*/0, &next_ecb->dmamap)) break; callout_init_mtx(&next_ecb->timer, &ahb->lock, 0); ecb_paddr = ahbecbvtop(ahb, next_ecb); next_ecb->hecb.status_ptr = ahbstatuspaddr(ecb_paddr); next_ecb->hecb.sense_ptr = ahbsensepaddr(ecb_paddr); ahb->num_ecbs++; ahbecbfree(ahb, next_ecb); next_ecb++; } ahb->init_level++; /* * Now that we know we own the resources we need, register * our bus with the XPT. */ if (ahbxptattach(ahb)) goto error_exit; /* Enable our interrupt */ if (bus_setup_intr(dev, irq, INTR_TYPE_CAM|INTR_ENTROPY|INTR_MPSAFE, NULL, ahbintr, ahb, &ih) != 0) goto error_exit; return (0); error_exit: /* * The board's IRQ line will not be left enabled * if we can't initialize correctly, so its safe * to release the irq. */ ahbfree(ahb); if (irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, irq); bus_release_resource(dev, SYS_RES_IOPORT, 0, io); return (-1); } static struct ahb_softc * ahballoc(device_t dev, struct resource *res) { struct ahb_softc *ahb; ahb = device_get_softc(dev); SLIST_INIT(&ahb->free_ecbs); LIST_INIT(&ahb->pending_ccbs); ahb->res = res; ahb->disc_permitted = ~0; ahb->tags_permitted = ~0; ahb->dev = dev; mtx_init(&ahb->lock, "ahb", NULL, MTX_DEF); return (ahb); } static void ahbfree(struct ahb_softc *ahb) { switch (ahb->init_level) { default: case 4: bus_dmamap_unload(ahb->ecb_dmat, ahb->ecb_dmamap); case 3: bus_dmamem_free(ahb->ecb_dmat, ahb->ecb_array, ahb->ecb_dmamap); case 2: bus_dma_tag_destroy(ahb->ecb_dmat); case 1: bus_dma_tag_destroy(ahb->buffer_dmat); case 0: break; } mtx_destroy(&ahb->lock); } /* * reset board, If it doesn't respond, return failure */ static int ahbreset(struct ahb_softc *ahb) { int wait = 1000; /* 1 sec enough? */ int test; if ((ahb_inb(ahb, PORTADDR) & PORTADDR_ENHANCED) == 0) { printf("ahb_reset: Controller not in enhanced mode\n"); return (-1); } ahb_outb(ahb, CONTROL, CNTRL_HARD_RST); DELAY(1000); ahb_outb(ahb, CONTROL, 0); while (--wait) { DELAY(1000); if ((ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_BUSY) == 0) break; } if (wait == 0) { printf("ahbreset: No answer from aha1742 board\n"); return (-1); } if ((test = ahb_inb(ahb, MBOXIN0)) != 0) { printf("ahb_reset: self test failed, val = 0x%x\n", test); return (-1); } while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { ahb_outb(ahb, CONTROL, CNTRL_CLRINT); DELAY(10000); } return (0); } static void ahbmapecbs(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct ahb_softc* ahb; ahb = (struct ahb_softc*)arg; ahb->ecb_physbase = segs->ds_addr; /* * Space for adapter inquiry information is on the * tail of the ecb array. */ ahb->ha_inq_physbase = ahbecbvtop(ahb, &ahb->ecb_array[AHB_NECB]); } static int ahbxptattach(struct ahb_softc *ahb) { struct cam_devq *devq; struct ecb *ecb; u_int i; mtx_lock(&ahb->lock); /* Remember who are we on the scsi bus */ ahb->scsi_id = ahb_inb(ahb, SCSIDEF) & HSCSIID; /* Use extended translation?? */ ahb->extended_trans = ahb_inb(ahb, RESV1) & EXTENDED_TRANS; /* Fetch adapter inquiry data */ ecb = ahbecbget(ahb); /* Always succeeds - no outstanding commands */ ecb->hecb.opcode = ECBOP_READ_HA_INQDATA; ecb->hecb.flag_word1 = FW1_SUPPRESS_URUN_ERR|FW1_ERR_STATUS_BLK_ONLY; ecb->hecb.data_ptr = ahb->ha_inq_physbase; ecb->hecb.data_len = sizeof(struct ha_inquiry_data); ecb->hecb.sense_ptr = 0; ecb->state = ECB_ACTIVE; /* Tell the adapter about this command */ ahbqueuembox(ahb, ahbecbvtop(ahb, ecb), ATTN_STARTECB|ahb->scsi_id); /* Poll for interrupt completion */ for (i = 1000; ecb->state != ECB_FREE && i != 0; i--) { ahbintr_locked(ahb); DELAY(1000); } ahb->num_ecbs = MIN(ahb->num_ecbs, ahb->ha_inq_data->scsi_data.spc2_flags); device_printf(ahb->dev, "%.8s %s SCSI Adapter, FW Rev. %.4s, ID=%d, %d ECBs\n", ahb->ha_inq_data->scsi_data.product, (ahb->ha_inq_data->scsi_data.flags & 0x4) ? "Differential" : "Single Ended", ahb->ha_inq_data->scsi_data.revision, ahb->scsi_id, ahb->num_ecbs); /* Restore sense paddr for future CCB clients */ ecb->hecb.sense_ptr = ahbsensepaddr(ahbecbvtop(ahb, ecb)); ahbecbfree(ahb, ecb); /* * Create the device queue for our SIM. */ devq = cam_simq_alloc(ahb->num_ecbs); if (devq == NULL) { mtx_unlock(&ahb->lock); return (ENOMEM); } /* * Construct our SIM entry */ ahb->sim = cam_sim_alloc(ahbaction, ahbpoll, "ahb", ahb, device_get_unit(ahb->dev), &ahb->lock, 2, ahb->num_ecbs, devq); if (ahb->sim == NULL) { cam_simq_free(devq); mtx_unlock(&ahb->lock); return (ENOMEM); } if (xpt_bus_register(ahb->sim, ahb->dev, 0) != CAM_SUCCESS) { cam_sim_free(ahb->sim, /*free_devq*/TRUE); mtx_unlock(&ahb->lock); return (ENXIO); } if (xpt_create_path(&ahb->path, /*periph*/NULL, cam_sim_path(ahb->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(ahb->sim)); cam_sim_free(ahb->sim, /*free_devq*/TRUE); mtx_unlock(&ahb->lock); return (ENXIO); } /* * Allow the board to generate interrupts. */ ahb_outb(ahb, INTDEF, ahb_inb(ahb, INTDEF) | INTEN); mtx_unlock(&ahb->lock); return (0); } static void ahbhandleimmed(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) { struct ccb_hdr *ccb_h; u_int target_id; if (ahb->immed_cmd == 0) { device_printf(ahb->dev, "Immediate Command complete with no " " pending command\n"); return; } target_id = intstat & INTSTAT_TARGET_MASK; ccb_h = LIST_FIRST(&ahb->pending_ccbs); while (ccb_h != NULL) { struct ecb *pending_ecb; union ccb *ccb; pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; ccb = pending_ecb->ccb; ccb_h = LIST_NEXT(ccb_h, sim_links.le); if (ccb->ccb_h.target_id == target_id || target_id == ahb->scsi_id) { callout_stop(&pending_ecb->timer); LIST_REMOVE(&ccb->ccb_h, sim_links.le); if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) bus_dmamap_unload(ahb->buffer_dmat, pending_ecb->dmamap); if (pending_ecb == ahb->immed_ecb) ccb->ccb_h.status = CAM_CMD_TIMEOUT|CAM_RELEASE_SIMQ; else if (target_id == ahb->scsi_id) ccb->ccb_h.status = CAM_SCSI_BUS_RESET; else ccb->ccb_h.status = CAM_BDR_SENT; ahbecbfree(ahb, pending_ecb); xpt_done(ccb); } else if (ahb->immed_ecb != NULL) { /* Re-instate timeout */ callout_reset_sbt(&pending_ecb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, ahbtimeout, pending_ecb, 0); } } if (ahb->immed_ecb != NULL) { ahb->immed_ecb = NULL; device_printf(ahb->dev, "No longer in timeout\n"); } else if (target_id == ahb->scsi_id) device_printf(ahb->dev, "SCSI Bus Reset Delivered\n"); else device_printf(ahb->dev, "Bus Device Reset Delivered to target %d\n", target_id); ahb->immed_cmd = 0; } static void ahbcalcresid(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) { if (ecb->status.data_overrun != 0) { /* * Overrun Condition. The hardware doesn't * provide a meaningful byte count in this case * (the residual is always 0). Tell the XPT * layer about the error. */ ccb->ccb_h.status = CAM_DATA_RUN_ERR; } else { ccb->csio.resid = ecb->status.resid_count; if ((ecb->hecb.flag_word1 & FW1_SG_ECB) != 0) { /* * For S/G transfers, the adapter provides a pointer * to the address in the last S/G element used and a * residual for that element. So, we need to sum up * the elements that follow it in order to get a real * residual number. If we have an overrun, the residual * reported will be 0 and we already know that all S/G * segments have been exhausted, so we can skip this * step. */ ahb_sg_t *sg; int num_sg; num_sg = ecb->hecb.data_len / sizeof(ahb_sg_t); /* Find the S/G the adapter was working on */ for (sg = ecb->sg_list; num_sg != 0 && sg->addr != ecb->status.resid_addr; num_sg--, sg++) ; /* Skip it */ num_sg--; sg++; /* Sum the rest */ for (; num_sg != 0; num_sg--, sg++) ccb->csio.resid += sg->len; } /* Underruns are not errors */ ccb->ccb_h.status = CAM_REQ_CMP; } } static void ahbprocesserror(struct ahb_softc *ahb, struct ecb *ecb, union ccb *ccb) { struct hardware_ecb *hecb; struct ecb_status *status; hecb = &ecb->hecb; status = &ecb->status; switch (status->ha_status) { case HS_OK: ccb->csio.scsi_status = status->scsi_status; if (status->scsi_status != 0) { ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; if (status->sense_stored) { ccb->ccb_h.status |= CAM_AUTOSNS_VALID; ccb->csio.sense_resid = ccb->csio.sense_len - status->sense_len; bcopy(&ecb->sense, &ccb->csio.sense_data, status->sense_len); } } break; case HS_TARGET_NOT_ASSIGNED: ccb->ccb_h.status = CAM_PATH_INVALID; break; case HS_SEL_TIMEOUT: ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; case HS_DATA_RUN_ERR: ahbcalcresid(ahb, ecb, ccb); break; case HS_UNEXPECTED_BUSFREE: ccb->ccb_h.status = CAM_UNEXP_BUSFREE; break; case HS_INVALID_PHASE: ccb->ccb_h.status = CAM_SEQUENCE_FAIL; break; case HS_REQUEST_SENSE_FAILED: ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; break; case HS_TAG_MSG_REJECTED: { struct ccb_trans_settings neg; struct ccb_trans_settings_scsi *scsi = &neg.proto_specific.scsi; xpt_print_path(ccb->ccb_h.path); printf("refuses tagged commands. Performing " "non-tagged I/O\n"); memset(&neg, 0, sizeof (neg)); neg.protocol = PROTO_SCSI; neg.protocol_version = SCSI_REV_2; neg.transport = XPORT_SPI; neg.transport_version = 2; scsi->flags = CTS_SCSI_VALID_TQ; xpt_setup_ccb(&neg.ccb_h, ccb->ccb_h.path, /*priority*/1); xpt_async(AC_TRANSFER_NEG, ccb->ccb_h.path, &neg); ahb->tags_permitted &= ~(0x01 << ccb->ccb_h.target_id); ccb->ccb_h.status = CAM_MSG_REJECT_REC; break; } case HS_FIRMWARE_LOAD_REQ: case HS_HARDWARE_ERR: /* * Tell the system that the Adapter * is no longer functional. */ ccb->ccb_h.status = CAM_NO_HBA; break; case HS_CMD_ABORTED_HOST: case HS_CMD_ABORTED_ADAPTER: case HS_ATN_TARGET_FAILED: case HS_SCSI_RESET_ADAPTER: case HS_SCSI_RESET_INCOMING: ccb->ccb_h.status = CAM_SCSI_BUS_RESET; break; case HS_INVALID_ECB_PARAM: device_printf(ahb->dev, "opcode 0x%02x, flag_word1 0x%02x, flag_word2 0x%02x\n", hecb->opcode, hecb->flag_word1, hecb->flag_word2); ccb->ccb_h.status = CAM_SCSI_BUS_RESET; break; case HS_DUP_TCB_RECEIVED: case HS_INVALID_OPCODE: case HS_INVALID_CMD_LINK: case HS_PROGRAM_CKSUM_ERROR: panic("%s: Can't happen host status %x occurred", device_get_nameunit(ahb->dev), status->ha_status); break; } if (ccb->ccb_h.status != CAM_REQ_CMP) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status |= CAM_DEV_QFRZN; } } static void ahbdone(struct ahb_softc *ahb, u_int32_t mbox, u_int intstat) { struct ecb *ecb; union ccb *ccb; ecb = ahbecbptov(ahb, mbox); if ((ecb->state & ECB_ACTIVE) == 0) panic("ecb not active"); ccb = ecb->ccb; if (ccb != NULL) { callout_stop(&ecb->timer); LIST_REMOVE(&ccb->ccb_h, sim_links.le); if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); } if ((intstat & INTSTAT_MASK) == INTSTAT_ECB_OK) { ccb->ccb_h.status = CAM_REQ_CMP; ccb->csio.resid = 0; } else { ahbprocesserror(ahb, ecb, ccb); } ahbecbfree(ahb, ecb); xpt_done(ccb); } else { /* Non CCB Command */ if ((intstat & INTSTAT_MASK) != INTSTAT_ECB_OK) { device_printf(ahb->dev, "Command 0%x Failed %x:%x:%x\n", ecb->hecb.opcode, *((u_int16_t*)&ecb->status), ecb->status.ha_status, ecb->status.resid_count); } /* Client owns this ECB and will release it. */ } } /* * Catch an interrupt from the adaptor */ static void ahbintr(void *arg) { struct ahb_softc *ahb; ahb = arg; mtx_lock(&ahb->lock); ahbintr_locked(ahb); mtx_unlock(&ahb->lock); } static void ahbintr_locked(struct ahb_softc *ahb) { u_int intstat; u_int32_t mbox; while (ahb_inb(ahb, HOSTSTAT) & HOSTSTAT_INTPEND) { /* * Fetch information about this interrupt. */ intstat = ahb_inb(ahb, INTSTAT); mbox = ahb_inl(ahb, MBOXIN0); /* * Reset interrupt latch. */ ahb_outb(ahb, CONTROL, CNTRL_CLRINT); /* * Process the completed operation */ switch (intstat & INTSTAT_MASK) { case INTSTAT_ECB_OK: case INTSTAT_ECB_CMPWRETRY: case INTSTAT_ECB_CMPWERR: ahbdone(ahb, mbox, intstat); break; case INTSTAT_AEN_OCCURED: if ((intstat & INTSTAT_TARGET_MASK) == ahb->scsi_id) { /* Bus Reset */ xpt_print_path(ahb->path); switch (mbox) { case HS_SCSI_RESET_ADAPTER: printf("Host Adapter Initiated " "Bus Reset occurred\n"); break; case HS_SCSI_RESET_INCOMING: printf("Bus Reset Initiated " "by another device occurred\n"); break; } /* Notify the XPT */ xpt_async(AC_BUS_RESET, ahb->path, NULL); break; } printf("Unsupported initiator selection AEN occured\n"); break; case INTSTAT_IMMED_OK: case INTSTAT_IMMED_ERR: ahbhandleimmed(ahb, mbox, intstat); break; case INTSTAT_HW_ERR: panic("Unrecoverable hardware Error Occurred\n"); } } } static void ahbexecuteecb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct ecb *ecb; union ccb *ccb; struct ahb_softc *ahb; u_int32_t ecb_paddr; ecb = (struct ecb *)arg; ccb = ecb->ccb; ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; mtx_assert(&ahb->lock, MA_OWNED); if (error != 0) { if (error != EFBIG) device_printf(ahb->dev, "Unexepected error 0x%x returned from " "bus_dmamap_load\n", error); if (ccb->ccb_h.status == CAM_REQ_INPROG) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; } ahbecbfree(ahb, ecb); xpt_done(ccb); return; } ecb_paddr = ahbecbvtop(ahb, ecb); if (nseg != 0) { ahb_sg_t *sg; bus_dma_segment_t *end_seg; bus_dmasync_op_t op; end_seg = dm_segs + nseg; /* Copy the segments into our SG list */ sg = ecb->sg_list; while (dm_segs < end_seg) { sg->addr = dm_segs->ds_addr; sg->len = dm_segs->ds_len; sg++; dm_segs++; } if (nseg > 1) { ecb->hecb.flag_word1 |= FW1_SG_ECB; ecb->hecb.data_ptr = ahbsgpaddr(ecb_paddr); ecb->hecb.data_len = sizeof(ahb_sg_t) * nseg; } else { ecb->hecb.data_ptr = ecb->sg_list->addr; ecb->hecb.data_len = ecb->sg_list->len; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { /* ecb->hecb.flag_word2 |= FW2_DATA_DIR_IN; */ op = BUS_DMASYNC_PREREAD; } else { op = BUS_DMASYNC_PREWRITE; } /* ecb->hecb.flag_word2 |= FW2_CHECK_DATA_DIR; */ bus_dmamap_sync(ahb->buffer_dmat, ecb->dmamap, op); } else { ecb->hecb.data_ptr = 0; ecb->hecb.data_len = 0; } /* * Last time we need to check if this CCB needs to * be aborted. */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(ahb->buffer_dmat, ecb->dmamap); ahbecbfree(ahb, ecb); xpt_done(ccb); return; } ecb->state = ECB_ACTIVE; ccb->ccb_h.status |= CAM_SIM_QUEUED; LIST_INSERT_HEAD(&ahb->pending_ccbs, &ccb->ccb_h, sim_links.le); /* Tell the adapter about this command */ ahbqueuembox(ahb, ecb_paddr, ATTN_STARTECB|ccb->ccb_h.target_id); callout_reset_sbt(&ecb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, ahbtimeout, ecb, 0); } static void ahbaction(struct cam_sim *sim, union ccb *ccb) { struct ahb_softc *ahb; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahbaction\n")); ahb = (struct ahb_softc *)cam_sim_softc(sim); mtx_assert(&ahb->lock, MA_OWNED); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_SCSI_IO: /* Execute the requested I/O operation */ { struct ecb *ecb; struct hardware_ecb *hecb; int error; /* * get an ecb to use. */ if ((ecb = ahbecbget(ahb)) == NULL) { /* Should never occur */ panic("Failed to get an ecb"); } /* * So we can find the ECB when an abort is requested */ ecb->ccb = ccb; ccb->ccb_h.ccb_ecb_ptr = ecb; ccb->ccb_h.ccb_ahb_ptr = ahb; /* * Put all the arguments for the xfer in the ecb */ hecb = &ecb->hecb; hecb->opcode = ECBOP_INITIATOR_SCSI_CMD; hecb->flag_word1 = FW1_AUTO_REQUEST_SENSE | FW1_ERR_STATUS_BLK_ONLY; hecb->flag_word2 = ccb->ccb_h.target_lun | FW2_NO_RETRY_ON_BUSY; if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) { hecb->flag_word2 |= FW2_TAG_ENB | ((ccb->csio.tag_action & 0x3) << FW2_TAG_TYPE_SHIFT); } if ((ccb->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) hecb->flag_word2 |= FW2_DISABLE_DISC; hecb->sense_len = ccb->csio.sense_len; hecb->cdb_len = ccb->csio.cdb_len; if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((ccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { bcopy(ccb->csio.cdb_io.cdb_ptr, hecb->cdb, hecb->cdb_len); } else { /* I guess I could map it in... */ ccb->ccb_h.status = CAM_REQ_INVALID; ahbecbfree(ahb, ecb); xpt_done(ccb); return; } } else { bcopy(ccb->csio.cdb_io.cdb_bytes, hecb->cdb, hecb->cdb_len); } error = bus_dmamap_load_ccb( ahb->buffer_dmat, ecb->dmamap, ccb, ahbexecuteecb, ecb, /*flags*/0); if (error == EINPROGRESS) { /* * So as to maintain ordering, freeze the controller * queue until our mapping is returned. */ xpt_freeze_simq(ahb->sim, 1); ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } break; } case XPT_EN_LUN: /* Enable LUN as a target */ case XPT_TARGET_IO: /* Execute target I/O request */ case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: { ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; u_int target_mask = 0x01 << ccb->ccb_h.target_id; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; if (cts->type == CTS_TYPE_USER_SETTINGS) { cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB; if ((ahb->disc_permitted & target_mask) != 0) spi->flags |= CTS_SPI_FLAGS_DISC_ENB; if ((ahb->tags_permitted & target_mask) != 0) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; spi->sync_period = 25; /* 10MHz */ if (spi->sync_period != 0) spi->sync_offset = 15; spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; ccb->ccb_h.status = CAM_REQ_CMP; } else { ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; } xpt_done(ccb); break; } case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ { int i; ahb->immed_cmd = IMMED_RESET; ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); /* Poll for interrupt completion */ for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) { DELAY(1000); ahbintr_locked(cam_sim_softc(sim)); } break; } case XPT_CALC_GEOMETRY: { cam_calc_geometry(&ccb->ccg, ahb->extended_trans); xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { int i; ahb->immed_cmd = IMMED_RESET; ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); /* Poll for interrupt completion */ for (i = 1000; ahb->immed_cmd != 0 && i != 0; i--) DELAY(1000); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = 7; cpi->max_lun = 7; cpi->initiator_id = ahb->scsi_id; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } #if 0 /* Need these??? */ case XPT_IMMED_NOTIFY: /* Notify Host Target driver of event */ case XPT_NOTIFY_ACK: /* Acknowledgement of event */ #endif default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } static void ahbpoll(struct cam_sim *sim) { ahbintr(cam_sim_softc(sim)); } static void ahbtimeout(void *arg) { struct ecb *ecb; union ccb *ccb; struct ahb_softc *ahb; ecb = (struct ecb *)arg; ccb = ecb->ccb; ahb = (struct ahb_softc *)ccb->ccb_h.ccb_ahb_ptr; mtx_assert(&ahb->lock, MA_OWNED); xpt_print_path(ccb->ccb_h.path); printf("ECB %p - timed out\n", (void *)ecb); if ((ecb->state & ECB_ACTIVE) == 0) { xpt_print_path(ccb->ccb_h.path); printf("ECB %p - timed out ECB already completed\n", (void *)ecb); return; } /* * In order to simplify the recovery process, we ask the XPT * layer to halt the queue of new transactions and we traverse * the list of pending CCBs and remove their timeouts. This * means that the driver attempts to clear only one error * condition at a time. In general, timeouts that occur * close together are related anyway, so there is no benefit * in attempting to handle errors in parrallel. Timeouts will * be reinstated when the recovery process ends. */ if ((ecb->state & ECB_DEVICE_RESET) == 0) { struct ccb_hdr *ccb_h; if ((ecb->state & ECB_RELEASE_SIMQ) == 0) { xpt_freeze_simq(ahb->sim, /*count*/1); ecb->state |= ECB_RELEASE_SIMQ; } LIST_FOREACH(ccb_h, &ahb->pending_ccbs, sim_links.le) { struct ecb *pending_ecb; pending_ecb = (struct ecb *)ccb_h->ccb_ecb_ptr; callout_stop(&pending_ecb->timer); } /* Store for our interrupt handler */ ahb->immed_ecb = ecb; /* * Send a Bus Device Reset message: * The target that is holding up the bus may not * be the same as the one that triggered this timeout * (different commands have different timeout lengths), * but we have no way of determining this from our * timeout handler. Our strategy here is to queue a * BDR message to the target of the timed out command. * If this fails, we'll get another timeout 2 seconds * later which will attempt a bus reset. */ xpt_print_path(ccb->ccb_h.path); printf("Queuing BDR\n"); ecb->state |= ECB_DEVICE_RESET; callout_reset(&ecb->timer, 2 * hz, ahbtimeout, ecb); ahb->immed_cmd = IMMED_RESET; ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ccb->ccb_h.target_id); } else if ((ecb->state & ECB_SCSIBUS_RESET) != 0) { /* * Try a SCSI bus reset. We do this only if we * have already attempted to clear the condition with a BDR. */ xpt_print_path(ccb->ccb_h.path); printf("Attempting SCSI Bus reset\n"); ecb->state |= ECB_SCSIBUS_RESET; callout_reset(&ecb->timer, 2 * hz, ahbtimeout, ecb); ahb->immed_cmd = IMMED_RESET; ahbqueuembox(ahb, IMMED_RESET, ATTN_IMMED|ahb->scsi_id); } else { /* Bring out the hammer... */ ahbreset(ahb); /* Simulate the reset complete interrupt */ ahbhandleimmed(ahb, 0, ahb->scsi_id|INTSTAT_IMMED_OK); } } static device_method_t ahb_eisa_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ahbprobe), DEVMETHOD(device_attach, ahbattach), { 0, 0 } }; static driver_t ahb_eisa_driver = { "ahb", ahb_eisa_methods, sizeof(struct ahb_softc), }; static devclass_t ahb_devclass; DRIVER_MODULE(ahb, eisa, ahb_eisa_driver, ahb_devclass, 0, 0); MODULE_DEPEND(ahb, eisa, 1, 1, 1); MODULE_DEPEND(ahb, cam, 1, 1, 1); Index: head/sys/dev/amr/amr_pci.c =================================================================== --- head/sys/dev/amr/amr_pci.c (revision 280346) +++ head/sys/dev/amr/amr_pci.c (revision 280347) @@ -1,700 +1,703 @@ /*- * Copyright (c) 1999,2000 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Copyright (c) 2002 Eric Moore * Copyright (c) 2002, 2004 LSI Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The party using or redistributing the source code and binary forms * agrees to the disclaimer below and the terms and conditions set forth * herein. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int amr_pci_probe(device_t dev); static int amr_pci_attach(device_t dev); static int amr_pci_detach(device_t dev); static int amr_pci_shutdown(device_t dev); static int amr_pci_suspend(device_t dev); static int amr_pci_resume(device_t dev); static void amr_pci_intr(void *arg); static void amr_pci_free(struct amr_softc *sc); static void amr_sglist_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int amr_sglist_map(struct amr_softc *sc); static int amr_setup_mbox(struct amr_softc *sc); static int amr_ccb_map(struct amr_softc *sc); static u_int amr_force_sg32 = 0; SYSCTL_DECL(_hw_amr); SYSCTL_UINT(_hw_amr, OID_AUTO, force_sg32, CTLFLAG_RDTUN, &amr_force_sg32, 0, "Force the AMR driver to use 32bit scatter gather"); static device_method_t amr_methods[] = { /* Device interface */ DEVMETHOD(device_probe, amr_pci_probe), DEVMETHOD(device_attach, amr_pci_attach), DEVMETHOD(device_detach, amr_pci_detach), DEVMETHOD(device_shutdown, amr_pci_shutdown), DEVMETHOD(device_suspend, amr_pci_suspend), DEVMETHOD(device_resume, amr_pci_resume), DEVMETHOD_END }; static driver_t amr_pci_driver = { "amr", amr_methods, sizeof(struct amr_softc) }; static devclass_t amr_devclass; DRIVER_MODULE(amr, pci, amr_pci_driver, amr_devclass, 0, 0); MODULE_DEPEND(amr, pci, 1, 1, 1); MODULE_DEPEND(amr, cam, 1, 1, 1); static struct amr_ident { int vendor; int device; int flags; #define AMR_ID_PROBE_SIG (1<<0) /* generic i960RD, check signature */ #define AMR_ID_DO_SG64 (1<<1) #define AMR_ID_QUARTZ (1<<2) } amr_device_ids[] = { {0x101e, 0x9010, 0}, {0x101e, 0x9060, 0}, {0x8086, 0x1960, AMR_ID_QUARTZ | AMR_ID_PROBE_SIG}, {0x101e, 0x1960, AMR_ID_QUARTZ}, {0x1000, 0x1960, AMR_ID_QUARTZ | AMR_ID_DO_SG64 | AMR_ID_PROBE_SIG}, {0x1000, 0x0407, AMR_ID_QUARTZ | AMR_ID_DO_SG64}, {0x1000, 0x0408, AMR_ID_QUARTZ | AMR_ID_DO_SG64}, {0x1000, 0x0409, AMR_ID_QUARTZ | AMR_ID_DO_SG64}, {0x1028, 0x000e, AMR_ID_QUARTZ | AMR_ID_DO_SG64 | AMR_ID_PROBE_SIG}, /* perc4/di i960 */ {0x1028, 0x000f, AMR_ID_QUARTZ | AMR_ID_DO_SG64}, /* perc4/di Verde*/ {0x1028, 0x0013, AMR_ID_QUARTZ | AMR_ID_DO_SG64}, /* perc4/di */ {0, 0, 0} }; static struct amr_ident * amr_find_ident(device_t dev) { struct amr_ident *id; int sig; for (id = amr_device_ids; id->vendor != 0; id++) { if ((pci_get_vendor(dev) == id->vendor) && (pci_get_device(dev) == id->device)) { /* do we need to test for a signature? */ if (id->flags & AMR_ID_PROBE_SIG) { sig = pci_read_config(dev, AMR_CFG_SIG, 2); if ((sig != AMR_SIGNATURE_1) && (sig != AMR_SIGNATURE_2)) continue; } return (id); } } return (NULL); } static int amr_pci_probe(device_t dev) { debug_called(1); if (amr_find_ident(dev) != NULL) { device_set_desc(dev, LSI_DESC_PCI); return(BUS_PROBE_DEFAULT); } return(ENXIO); } static int amr_pci_attach(device_t dev) { struct amr_softc *sc; struct amr_ident *id; int rid, rtype, error; debug_called(1); /* * Initialise softc. */ sc = device_get_softc(dev); bzero(sc, sizeof(*sc)); sc->amr_dev = dev; /* assume failure is 'not configured' */ error = ENXIO; /* * Determine board type. */ if ((id = amr_find_ident(dev)) == NULL) return (ENXIO); if (id->flags & AMR_ID_QUARTZ) { sc->amr_type |= AMR_TYPE_QUARTZ; } if ((amr_force_sg32 == 0) && (id->flags & AMR_ID_DO_SG64) && (sizeof(vm_paddr_t) > 4)) { device_printf(dev, "Using 64-bit DMA\n"); sc->amr_type |= AMR_TYPE_SG64; } /* force the busmaster enable bit on */ pci_enable_busmaster(dev); /* * Allocate the PCI register window. */ rid = PCIR_BAR(0); rtype = AMR_IS_QUARTZ(sc) ? SYS_RES_MEMORY : SYS_RES_IOPORT; sc->amr_reg = bus_alloc_resource_any(dev, rtype, &rid, RF_ACTIVE); if (sc->amr_reg == NULL) { device_printf(sc->amr_dev, "can't allocate register window\n"); goto out; } sc->amr_btag = rman_get_bustag(sc->amr_reg); sc->amr_bhandle = rman_get_bushandle(sc->amr_reg); /* * Allocate and connect our interrupt. */ rid = 0; sc->amr_irq = bus_alloc_resource_any(sc->amr_dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->amr_irq == NULL) { device_printf(sc->amr_dev, "can't allocate interrupt\n"); goto out; } if (bus_setup_intr(sc->amr_dev, sc->amr_irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, amr_pci_intr, sc, &sc->amr_intr)) { device_printf(sc->amr_dev, "can't set up interrupt\n"); goto out; } debug(2, "interrupt attached"); /* assume failure is 'out of memory' */ error = ENOMEM; /* * Allocate the parent bus DMA tag appropriate for PCI. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), /* PCI parent */ 1, 0, /* alignment,boundary */ AMR_IS_SG64(sc) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE, /* maxsize */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->amr_parent_dmat)) { device_printf(dev, "can't allocate parent DMA tag\n"); goto out; } /* * Create DMA tag for mapping buffers into controller-addressable space. */ if (bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ - MAXBSIZE, /* maxsegsize */ + DFLTPHYS, /* maxsize */ + AMR_NSEG, /* nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->amr_list_lock, /* lockarg */ &sc->amr_buffer_dmat)) { device_printf(sc->amr_dev, "can't allocate buffer DMA tag\n"); goto out; } if (bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, AMR_NSEG, /* maxsize, nsegments */ - MAXBSIZE, /* maxsegsize */ + DFLTPHYS, /* maxsize */ + AMR_NSEG, /* nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->amr_list_lock, /* lockarg */ &sc->amr_buffer64_dmat)) { device_printf(sc->amr_dev, "can't allocate buffer DMA tag\n"); goto out; } debug(2, "dma tag done"); /* * Allocate and set up mailbox in a bus-visible fashion. */ mtx_init(&sc->amr_list_lock, "AMR List Lock", NULL, MTX_DEF); mtx_init(&sc->amr_hw_lock, "AMR HW Lock", NULL, MTX_DEF); if ((error = amr_setup_mbox(sc)) != 0) goto out; debug(2, "mailbox setup"); /* * Build the scatter/gather buffers. */ if ((error = amr_sglist_map(sc)) != 0) goto out; debug(2, "s/g list mapped"); if ((error = amr_ccb_map(sc)) != 0) goto out; debug(2, "ccb mapped"); /* * Do bus-independant initialisation, bring controller online. */ error = amr_attach(sc); out: if (error) amr_pci_free(sc); return(error); } /******************************************************************************** * Disconnect from the controller completely, in preparation for unload. */ static int amr_pci_detach(device_t dev) { struct amr_softc *sc = device_get_softc(dev); int error; debug_called(1); if (sc->amr_state & AMR_STATE_OPEN) return(EBUSY); if ((error = amr_pci_shutdown(dev))) return(error); amr_pci_free(sc); return(0); } /******************************************************************************** * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach, system shutdown, or before performing * an operation which may add or delete system disks. (Call amr_startup to * resume normal operation.) * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ static int amr_pci_shutdown(device_t dev) { struct amr_softc *sc = device_get_softc(dev); int i,error; debug_called(1); /* mark ourselves as in-shutdown */ sc->amr_state |= AMR_STATE_SHUTDOWN; /* flush controller */ device_printf(sc->amr_dev, "flushing cache..."); printf("%s\n", amr_flush(sc) ? "failed" : "done"); error = 0; /* delete all our child devices */ for(i = 0 ; i < AMR_MAXLD; i++) { if( sc->amr_drive[i].al_disk != 0) { if((error = device_delete_child(sc->amr_dev,sc->amr_drive[i].al_disk)) != 0) goto shutdown_out; sc->amr_drive[i].al_disk = 0; } } /* XXX disable interrupts? */ shutdown_out: return(error); } /******************************************************************************** * Bring the controller to a quiescent state, ready for system suspend. */ static int amr_pci_suspend(device_t dev) { struct amr_softc *sc = device_get_softc(dev); debug_called(1); sc->amr_state |= AMR_STATE_SUSPEND; /* flush controller */ device_printf(sc->amr_dev, "flushing cache..."); printf("%s\n", amr_flush(sc) ? "failed" : "done"); /* XXX disable interrupts? */ return(0); } /******************************************************************************** * Bring the controller back to a state ready for operation. */ static int amr_pci_resume(device_t dev) { struct amr_softc *sc = device_get_softc(dev); debug_called(1); sc->amr_state &= ~AMR_STATE_SUSPEND; /* XXX enable interrupts? */ return(0); } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ static void amr_pci_intr(void *arg) { struct amr_softc *sc = (struct amr_softc *)arg; debug_called(3); /* collect finished commands, queue anything waiting */ amr_done(sc); } /******************************************************************************** * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ static void amr_pci_free(struct amr_softc *sc) { void *p; debug_called(1); amr_free(sc); /* destroy data-transfer DMA tag */ if (sc->amr_buffer_dmat) bus_dma_tag_destroy(sc->amr_buffer_dmat); if (sc->amr_buffer64_dmat) bus_dma_tag_destroy(sc->amr_buffer64_dmat); /* free and destroy DMA memory and tag for passthrough pool */ if (sc->amr_ccb) { bus_dmamap_unload(sc->amr_ccb_dmat, sc->amr_ccb_dmamap); bus_dmamem_free(sc->amr_ccb_dmat, sc->amr_ccb, sc->amr_ccb_dmamap); } if (sc->amr_ccb_dmat) bus_dma_tag_destroy(sc->amr_ccb_dmat); /* free and destroy DMA memory and tag for s/g lists */ if (sc->amr_sgtable) { bus_dmamap_unload(sc->amr_sg_dmat, sc->amr_sg_dmamap); bus_dmamem_free(sc->amr_sg_dmat, sc->amr_sgtable, sc->amr_sg_dmamap); } if (sc->amr_sg_dmat) bus_dma_tag_destroy(sc->amr_sg_dmat); /* free and destroy DMA memory and tag for mailbox */ p = (void *)(uintptr_t)(volatile void *)sc->amr_mailbox64; if (sc->amr_mailbox) { bus_dmamap_unload(sc->amr_mailbox_dmat, sc->amr_mailbox_dmamap); bus_dmamem_free(sc->amr_mailbox_dmat, p, sc->amr_mailbox_dmamap); } if (sc->amr_mailbox_dmat) bus_dma_tag_destroy(sc->amr_mailbox_dmat); /* disconnect the interrupt handler */ if (sc->amr_intr) bus_teardown_intr(sc->amr_dev, sc->amr_irq, sc->amr_intr); if (sc->amr_irq != NULL) bus_release_resource(sc->amr_dev, SYS_RES_IRQ, 0, sc->amr_irq); /* destroy the parent DMA tag */ if (sc->amr_parent_dmat) bus_dma_tag_destroy(sc->amr_parent_dmat); /* release the register window mapping */ if (sc->amr_reg != NULL) bus_release_resource(sc->amr_dev, AMR_IS_QUARTZ(sc) ? SYS_RES_MEMORY : SYS_RES_IOPORT, PCIR_BAR(0), sc->amr_reg); } /******************************************************************************** * Allocate and map the scatter/gather table in bus space. */ static void amr_sglist_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { uint32_t *addr; debug_called(1); addr = arg; *addr = segs[0].ds_addr; } static int amr_sglist_map(struct amr_softc *sc) { size_t segsize; void *p; int error; debug_called(1); /* * Create a single tag describing a region large enough to hold all of * the s/g lists we will need. * * Note that we could probably use AMR_LIMITCMD here, but that may become * tunable. */ if (AMR_IS_SG64(sc)) segsize = sizeof(struct amr_sg64entry) * AMR_NSEG * AMR_MAXCMD; else segsize = sizeof(struct amr_sgentry) * AMR_NSEG * AMR_MAXCMD; error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 512, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ segsize, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->amr_sg_dmat); if (error != 0) { device_printf(sc->amr_dev, "can't allocate scatter/gather DMA tag\n"); return(ENOMEM); } /* * Allocate enough s/g maps for all commands and permanently map them into * controller-visible space. * * XXX this assumes we can get enough space for all the s/g maps in one * contiguous slab. We may need to switch to a more complex arrangement * where we allocate in smaller chunks and keep a lookup table from slot * to bus address. * * XXX HACK ALERT: at least some controllers don't like the s/g memory * being allocated below 0x2000. We leak some memory if * we get some below this mark and allocate again. We * should be able to avoid this with the tag setup, but * that does't seem to work. */ retry: error = bus_dmamem_alloc(sc->amr_sg_dmat, (void **)&p, BUS_DMA_NOWAIT, &sc->amr_sg_dmamap); if (error) { device_printf(sc->amr_dev, "can't allocate s/g table\n"); return(ENOMEM); } bus_dmamap_load(sc->amr_sg_dmat, sc->amr_sg_dmamap, p, segsize, amr_sglist_helper, &sc->amr_sgbusaddr, 0); if (sc->amr_sgbusaddr < 0x2000) { debug(1, "s/g table too low (0x%x), reallocating\n", sc->amr_sgbusaddr); goto retry; } if (AMR_IS_SG64(sc)) sc->amr_sg64table = (struct amr_sg64entry *)p; sc->amr_sgtable = (struct amr_sgentry *)p; return(0); } /******************************************************************************** * Allocate and set up mailbox areas for the controller (sc) * * The basic mailbox structure should be 16-byte aligned. */ static int amr_setup_mbox(struct amr_softc *sc) { int error; void *p; uint32_t baddr; debug_called(1); /* * Create a single tag describing a region large enough to hold the entire * mailbox. */ error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 16, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sizeof(struct amr_mailbox64), /* maxsize */ 1, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->amr_mailbox_dmat); if (error != 0) { device_printf(sc->amr_dev, "can't allocate mailbox tag\n"); return(ENOMEM); } /* * Allocate the mailbox structure and permanently map it into * controller-visible space. */ error = bus_dmamem_alloc(sc->amr_mailbox_dmat, (void **)&p, BUS_DMA_NOWAIT, &sc->amr_mailbox_dmamap); if (error) { device_printf(sc->amr_dev, "can't allocate mailbox memory\n"); return(ENOMEM); } bus_dmamap_load(sc->amr_mailbox_dmat, sc->amr_mailbox_dmamap, p, sizeof(struct amr_mailbox64), amr_sglist_helper, &baddr, 0); /* * Conventional mailbox is inside the mailbox64 region. */ /* save physical base of the basic mailbox structure */ sc->amr_mailboxphys = baddr + offsetof(struct amr_mailbox64, mb); bzero(p, sizeof(struct amr_mailbox64)); sc->amr_mailbox64 = (struct amr_mailbox64 *)p; sc->amr_mailbox = &sc->amr_mailbox64->mb; return(0); } static int amr_ccb_map(struct amr_softc *sc) { int ccbsize, error; /* * Passthrough and Extended passthrough structures will share the same * memory. */ ccbsize = sizeof(union amr_ccb) * AMR_MAXCMD; error = bus_dma_tag_create(sc->amr_parent_dmat, /* parent */ 128, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ ccbsize, /* maxsize */ 1, /* nsegments */ ccbsize, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->amr_ccb_dmat); if (error != 0) { device_printf(sc->amr_dev, "can't allocate ccb tag\n"); return (ENOMEM); } error = bus_dmamem_alloc(sc->amr_ccb_dmat, (void **)&sc->amr_ccb, BUS_DMA_NOWAIT, &sc->amr_ccb_dmamap); if (error) { device_printf(sc->amr_dev, "can't allocate ccb memory\n"); return (ENOMEM); } bus_dmamap_load(sc->amr_ccb_dmat, sc->amr_ccb_dmamap, sc->amr_ccb, ccbsize, amr_sglist_helper, &sc->amr_ccb_busaddr, 0); bzero(sc->amr_ccb, ccbsize); return (0); } Index: head/sys/dev/buslogic/bt.c =================================================================== --- head/sys/dev/buslogic/bt.c (revision 280346) +++ head/sys/dev/buslogic/bt.c (revision 280347) @@ -1,2393 +1,2393 @@ /*- * Generic driver for the BusLogic MultiMaster SCSI host adapters * Product specific probe and attach routines can be found in: * sys/dev/buslogic/bt_isa.c BT-54X, BT-445 cards * sys/dev/buslogic/bt_mca.c BT-64X, SDC3211B, SDC3211F * sys/dev/buslogic/bt_eisa.c BT-74X, BT-75x cards, SDC3222F * sys/dev/buslogic/bt_pci.c BT-946, BT-948, BT-956, BT-958 cards * * Copyright (c) 1998, 1999 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Special thanks to Leonard N. Zubkoff for writing such a complete and * well documented Mylex/BusLogic MultiMaster driver for Linux. Support * in this driver for the wide range of MultiMaster controllers and * firmware revisions, with their otherwise undocumented quirks, would not * have been possible without his efforts. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* MailBox Management functions */ static __inline void btnextinbox(struct bt_softc *bt); static __inline void btnextoutbox(struct bt_softc *bt); static __inline void btnextinbox(struct bt_softc *bt) { if (bt->cur_inbox == bt->last_inbox) bt->cur_inbox = bt->in_boxes; else bt->cur_inbox++; } static __inline void btnextoutbox(struct bt_softc *bt) { if (bt->cur_outbox == bt->last_outbox) bt->cur_outbox = bt->out_boxes; else bt->cur_outbox++; } /* CCB Mangement functions */ static __inline u_int32_t btccbvtop(struct bt_softc *bt, struct bt_ccb *bccb); static __inline struct bt_ccb* btccbptov(struct bt_softc *bt, u_int32_t ccb_addr); static __inline u_int32_t btsensepaddr(struct bt_softc *bt, struct bt_ccb *bccb); static __inline struct scsi_sense_data* btsensevaddr(struct bt_softc *bt, struct bt_ccb *bccb); static __inline u_int32_t btccbvtop(struct bt_softc *bt, struct bt_ccb *bccb) { return (bt->bt_ccb_physbase + (u_int32_t)((caddr_t)bccb - (caddr_t)bt->bt_ccb_array)); } static __inline struct bt_ccb * btccbptov(struct bt_softc *bt, u_int32_t ccb_addr) { return (bt->bt_ccb_array + ((struct bt_ccb*)(uintptr_t)ccb_addr - (struct bt_ccb*)(uintptr_t)bt->bt_ccb_physbase)); } static __inline u_int32_t btsensepaddr(struct bt_softc *bt, struct bt_ccb *bccb) { u_int index; index = (u_int)(bccb - bt->bt_ccb_array); return (bt->sense_buffers_physbase + (index * sizeof(struct scsi_sense_data))); } static __inline struct scsi_sense_data * btsensevaddr(struct bt_softc *bt, struct bt_ccb *bccb) { u_int index; index = (u_int)(bccb - bt->bt_ccb_array); return (bt->sense_buffers + index); } static __inline struct bt_ccb* btgetccb(struct bt_softc *bt); static __inline void btfreeccb(struct bt_softc *bt, struct bt_ccb *bccb); static void btallocccbs(struct bt_softc *bt); static bus_dmamap_callback_t btexecuteccb; static void btdone(struct bt_softc *bt, struct bt_ccb *bccb, bt_mbi_comp_code_t comp_code); static void bt_intr_locked(struct bt_softc *bt); /* Host adapter command functions */ static int btreset(struct bt_softc* bt, int hard_reset); /* Initialization functions */ static int btinitmboxes(struct bt_softc *bt); static bus_dmamap_callback_t btmapmboxes; static bus_dmamap_callback_t btmapccbs; static bus_dmamap_callback_t btmapsgs; /* Transfer Negotiation Functions */ static void btfetchtransinfo(struct bt_softc *bt, struct ccb_trans_settings *cts); /* CAM SIM entry points */ #define ccb_bccb_ptr spriv_ptr0 #define ccb_bt_ptr spriv_ptr1 static void btaction(struct cam_sim *sim, union ccb *ccb); static void btpoll(struct cam_sim *sim); /* Our timeout handler */ static void bttimeout(void *arg); /* * XXX * Do our own re-probe protection until a configuration * manager can do it for us. This ensures that we don't * reprobe a card already found by the EISA or PCI probes. */ struct bt_isa_port bt_isa_ports[] = { { 0x130, 0, 4 }, { 0x134, 0, 5 }, { 0x230, 0, 2 }, { 0x234, 0, 3 }, { 0x330, 0, 0 }, { 0x334, 0, 1 } }; /* * I/O ports listed in the order enumerated by the * card for certain op codes. */ u_int16_t bt_board_ports[] = { 0x330, 0x334, 0x230, 0x234, 0x130, 0x134 }; /* Exported functions */ void bt_init_softc(device_t dev, struct resource *port, struct resource *irq, struct resource *drq) { struct bt_softc *bt = device_get_softc(dev); SLIST_INIT(&bt->free_bt_ccbs); LIST_INIT(&bt->pending_ccbs); SLIST_INIT(&bt->sg_maps); bt->dev = dev; bt->port = port; bt->irq = irq; bt->drq = drq; mtx_init(&bt->lock, "bt", NULL, MTX_DEF); } void bt_free_softc(device_t dev) { struct bt_softc *bt = device_get_softc(dev); switch (bt->init_level) { default: case 11: bus_dmamap_unload(bt->sense_dmat, bt->sense_dmamap); case 10: bus_dmamem_free(bt->sense_dmat, bt->sense_buffers, bt->sense_dmamap); case 9: bus_dma_tag_destroy(bt->sense_dmat); case 8: { struct sg_map_node *sg_map; while ((sg_map = SLIST_FIRST(&bt->sg_maps))!= NULL) { SLIST_REMOVE_HEAD(&bt->sg_maps, links); bus_dmamap_unload(bt->sg_dmat, sg_map->sg_dmamap); bus_dmamem_free(bt->sg_dmat, sg_map->sg_vaddr, sg_map->sg_dmamap); free(sg_map, M_DEVBUF); } bus_dma_tag_destroy(bt->sg_dmat); } case 7: bus_dmamap_unload(bt->ccb_dmat, bt->ccb_dmamap); /* FALLTHROUGH */ case 6: bus_dmamem_free(bt->ccb_dmat, bt->bt_ccb_array, bt->ccb_dmamap); /* FALLTHROUGH */ case 5: bus_dma_tag_destroy(bt->ccb_dmat); /* FALLTHROUGH */ case 4: bus_dmamap_unload(bt->mailbox_dmat, bt->mailbox_dmamap); /* FALLTHROUGH */ case 3: bus_dmamem_free(bt->mailbox_dmat, bt->in_boxes, bt->mailbox_dmamap); /* FALLTHROUGH */ case 2: bus_dma_tag_destroy(bt->buffer_dmat); /* FALLTHROUGH */ case 1: bus_dma_tag_destroy(bt->mailbox_dmat); /* FALLTHROUGH */ case 0: break; } mtx_destroy(&bt->lock); } int bt_port_probe(device_t dev, struct bt_probe_info *info) { struct bt_softc *bt = device_get_softc(dev); config_data_t config_data; int error; /* See if there is really a card present */ if (bt_probe(dev) || bt_fetch_adapter_info(dev)) return(1); /* * Determine our IRQ, and DMA settings and * export them to the configuration system. */ mtx_lock(&bt->lock); error = bt_cmd(bt, BOP_INQUIRE_CONFIG, NULL, /*parmlen*/0, (u_int8_t*)&config_data, sizeof(config_data), DEFAULT_CMD_TIMEOUT); mtx_unlock(&bt->lock); if (error != 0) { printf("bt_port_probe: Could not determine IRQ or DMA " "settings for adapter.\n"); return (1); } if (bt->model[0] == '5') { /* DMA settings only make sense for ISA cards */ switch (config_data.dma_chan) { case DMA_CHAN_5: info->drq = 5; break; case DMA_CHAN_6: info->drq = 6; break; case DMA_CHAN_7: info->drq = 7; break; default: printf("bt_port_probe: Invalid DMA setting " "detected for adapter.\n"); return (1); } } else { /* VL/EISA/PCI DMA */ info->drq = -1; } switch (config_data.irq) { case IRQ_9: case IRQ_10: case IRQ_11: case IRQ_12: case IRQ_14: case IRQ_15: info->irq = ffs(config_data.irq) + 8; break; default: printf("bt_port_probe: Invalid IRQ setting %x" "detected for adapter.\n", config_data.irq); return (1); } return (0); } /* * Probe the adapter and verify that the card is a BusLogic. */ int bt_probe(device_t dev) { struct bt_softc *bt = device_get_softc(dev); esetup_info_data_t esetup_info; u_int status; u_int intstat; u_int geometry; int error; u_int8_t param; /* * See if the three I/O ports look reasonable. * Touch the minimal number of registers in the * failure case. */ status = bt_inb(bt, STATUS_REG); if ((status == 0) || (status & (DIAG_ACTIVE|CMD_REG_BUSY| STATUS_REG_RSVD|CMD_INVALID)) != 0) { if (bootverbose) device_printf(dev, "Failed Status Reg Test - %x\n", status); return (ENXIO); } intstat = bt_inb(bt, INTSTAT_REG); if ((intstat & INTSTAT_REG_RSVD) != 0) { device_printf(dev, "Failed Intstat Reg Test\n"); return (ENXIO); } geometry = bt_inb(bt, GEOMETRY_REG); if (geometry == 0xFF) { if (bootverbose) device_printf(dev, "Failed Geometry Reg Test\n"); return (ENXIO); } /* * Looking good so far. Final test is to reset the * adapter and attempt to fetch the extended setup * information. This should filter out all 1542 cards. */ mtx_lock(&bt->lock); if ((error = btreset(bt, /*hard_reset*/TRUE)) != 0) { mtx_unlock(&bt->lock); if (bootverbose) device_printf(dev, "Failed Reset\n"); return (ENXIO); } param = sizeof(esetup_info); error = bt_cmd(bt, BOP_INQUIRE_ESETUP_INFO, ¶m, /*parmlen*/1, (u_int8_t*)&esetup_info, sizeof(esetup_info), DEFAULT_CMD_TIMEOUT); mtx_unlock(&bt->lock); if (error != 0) { return (ENXIO); } return (0); } /* * Pull the boards setup information and record it in our softc. */ int bt_fetch_adapter_info(device_t dev) { struct bt_softc *bt = device_get_softc(dev); board_id_data_t board_id; esetup_info_data_t esetup_info; config_data_t config_data; int error; u_int8_t length_param; /* First record the firmware version */ mtx_lock(&bt->lock); error = bt_cmd(bt, BOP_INQUIRE_BOARD_ID, NULL, /*parmlen*/0, (u_int8_t*)&board_id, sizeof(board_id), DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed Get Board Info\n"); return (error); } bt->firmware_ver[0] = board_id.firmware_rev_major; bt->firmware_ver[1] = '.'; bt->firmware_ver[2] = board_id.firmware_rev_minor; bt->firmware_ver[3] = '\0'; /* * Depending on the firmware major and minor version, * we may be able to fetch additional minor version info. */ if (bt->firmware_ver[0] > '0') { error = bt_cmd(bt, BOP_INQUIRE_FW_VER_3DIG, NULL, /*parmlen*/0, (u_int8_t*)&bt->firmware_ver[3], 1, DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed Get " "Firmware 3rd Digit\n"); return (error); } if (bt->firmware_ver[3] == ' ') bt->firmware_ver[3] = '\0'; bt->firmware_ver[4] = '\0'; } if (strcmp(bt->firmware_ver, "3.3") >= 0) { error = bt_cmd(bt, BOP_INQUIRE_FW_VER_4DIG, NULL, /*parmlen*/0, (u_int8_t*)&bt->firmware_ver[4], 1, DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed Get " "Firmware 4th Digit\n"); return (error); } if (bt->firmware_ver[4] == ' ') bt->firmware_ver[4] = '\0'; bt->firmware_ver[5] = '\0'; } /* * Some boards do not handle the "recently documented" * Inquire Board Model Number command correctly or do not give * exact information. Use the Firmware and Extended Setup * information in these cases to come up with the right answer. * The major firmware revision number indicates: * * 5.xx BusLogic "W" Series Host Adapters: * BT-948/958/958D * 4.xx BusLogic "C" Series Host Adapters: * BT-946C/956C/956CD/747C/757C/757CD/445C/545C/540CF * 3.xx BusLogic "S" Series Host Adapters: * BT-747S/747D/757S/757D/445S/545S/542D * BT-542B/742A (revision H) * 2.xx BusLogic "A" Series Host Adapters: * BT-542B/742A (revision G and below) * 0.xx AMI FastDisk VLB/EISA BusLogic Clone Host Adapter */ length_param = sizeof(esetup_info); error = bt_cmd(bt, BOP_INQUIRE_ESETUP_INFO, &length_param, /*parmlen*/1, (u_int8_t*)&esetup_info, sizeof(esetup_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); return (error); } bt->bios_addr = esetup_info.bios_addr << 12; bt->mailbox_addrlimit = BUS_SPACE_MAXADDR; if (esetup_info.bus_type == 'A' && bt->firmware_ver[0] == '2') { snprintf(bt->model, sizeof(bt->model), "542B"); } else if (esetup_info.bus_type == 'E' && bt->firmware_ver[0] == '2') { /* * The 742A seems to object if its mailboxes are * allocated above the 16MB mark. */ bt->mailbox_addrlimit = BUS_SPACE_MAXADDR_24BIT; snprintf(bt->model, sizeof(bt->model), "742A"); } else if (esetup_info.bus_type == 'E' && bt->firmware_ver[0] == '0') { /* AMI FastDisk EISA Series 441 0.x */ snprintf(bt->model, sizeof(bt->model), "747A"); } else { ha_model_data_t model_data; int i; length_param = sizeof(model_data); error = bt_cmd(bt, BOP_INQUIRE_MODEL, &length_param, 1, (u_int8_t*)&model_data, sizeof(model_data), DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed Inquire " "Model Number\n"); return (error); } for (i = 0; i < sizeof(model_data.ascii_model); i++) { bt->model[i] = model_data.ascii_model[i]; if (bt->model[i] == ' ') break; } bt->model[i] = '\0'; } bt->level_trigger_ints = esetup_info.level_trigger_ints ? 1 : 0; /* SG element limits */ bt->max_sg = esetup_info.max_sg; /* Set feature flags */ bt->wide_bus = esetup_info.wide_bus; bt->diff_bus = esetup_info.diff_bus; bt->ultra_scsi = esetup_info.ultra_scsi; if ((bt->firmware_ver[0] == '5') || (bt->firmware_ver[0] == '4' && bt->wide_bus)) bt->extended_lun = TRUE; bt->strict_rr = (strcmp(bt->firmware_ver, "3.31") >= 0); bt->extended_trans = ((bt_inb(bt, GEOMETRY_REG) & EXTENDED_TRANSLATION) != 0); /* * Determine max CCB count and whether tagged queuing is * available based on controller type. Tagged queuing * only works on 'W' series adapters, 'C' series adapters * with firmware of rev 4.42 and higher, and 'S' series * adapters with firmware of rev 3.35 and higher. The * maximum CCB counts are as follows: * * 192 BT-948/958/958D * 100 BT-946C/956C/956CD/747C/757C/757CD/445C * 50 BT-545C/540CF * 30 BT-747S/747D/757S/757D/445S/545S/542D/542B/742A */ if (bt->firmware_ver[0] == '5') { bt->max_ccbs = 192; bt->tag_capable = TRUE; } else if (bt->firmware_ver[0] == '4') { if (bt->model[0] == '5') bt->max_ccbs = 50; else bt->max_ccbs = 100; bt->tag_capable = (strcmp(bt->firmware_ver, "4.22") >= 0); } else { bt->max_ccbs = 30; if (bt->firmware_ver[0] == '3' && (strcmp(bt->firmware_ver, "3.35") >= 0)) bt->tag_capable = TRUE; else bt->tag_capable = FALSE; } if (bt->tag_capable != FALSE) bt->tags_permitted = ALL_TARGETS; /* Determine Sync/Wide/Disc settings */ if (bt->firmware_ver[0] >= '4') { auto_scsi_data_t auto_scsi_data; fetch_lram_params_t fetch_lram_params; int error; /* * These settings are stored in the * AutoSCSI data in LRAM of 'W' and 'C' * adapters. */ fetch_lram_params.offset = AUTO_SCSI_BYTE_OFFSET; fetch_lram_params.response_len = sizeof(auto_scsi_data); error = bt_cmd(bt, BOP_FETCH_LRAM, (u_int8_t*)&fetch_lram_params, sizeof(fetch_lram_params), (u_int8_t*)&auto_scsi_data, sizeof(auto_scsi_data), DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed " "Get Auto SCSI Info\n"); return (error); } bt->disc_permitted = auto_scsi_data.low_disc_permitted | (auto_scsi_data.high_disc_permitted << 8); bt->sync_permitted = auto_scsi_data.low_sync_permitted | (auto_scsi_data.high_sync_permitted << 8); bt->fast_permitted = auto_scsi_data.low_fast_permitted | (auto_scsi_data.high_fast_permitted << 8); bt->ultra_permitted = auto_scsi_data.low_ultra_permitted | (auto_scsi_data.high_ultra_permitted << 8); bt->wide_permitted = auto_scsi_data.low_wide_permitted | (auto_scsi_data.high_wide_permitted << 8); if (bt->ultra_scsi == FALSE) bt->ultra_permitted = 0; if (bt->wide_bus == FALSE) bt->wide_permitted = 0; } else { /* * 'S' and 'A' series have this information in the setup * information structure. */ setup_data_t setup_info; length_param = sizeof(setup_info); error = bt_cmd(bt, BOP_INQUIRE_SETUP_INFO, &length_param, /*paramlen*/1, (u_int8_t*)&setup_info, sizeof(setup_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { mtx_unlock(&bt->lock); device_printf(dev, "bt_fetch_adapter_info - Failed " "Get Setup Info\n"); return (error); } if (setup_info.initiate_sync != 0) { bt->sync_permitted = ALL_TARGETS; if (bt->model[0] == '7') { if (esetup_info.sync_neg10MB != 0) bt->fast_permitted = ALL_TARGETS; if (strcmp(bt->model, "757") == 0) bt->wide_permitted = ALL_TARGETS; } } bt->disc_permitted = ALL_TARGETS; } /* We need as many mailboxes as we can have ccbs */ bt->num_boxes = bt->max_ccbs; /* Determine our SCSI ID */ error = bt_cmd(bt, BOP_INQUIRE_CONFIG, NULL, /*parmlen*/0, (u_int8_t*)&config_data, sizeof(config_data), DEFAULT_CMD_TIMEOUT); mtx_unlock(&bt->lock); if (error != 0) { device_printf(dev, "bt_fetch_adapter_info - Failed Get Config\n"); return (error); } bt->scsi_id = config_data.scsi_id; return (0); } /* * Start the board, ready for normal operation */ int bt_init(device_t dev) { struct bt_softc *bt = device_get_softc(dev); /* Announce the Adapter */ device_printf(dev, "BT-%s FW Rev. %s ", bt->model, bt->firmware_ver); if (bt->ultra_scsi != 0) printf("Ultra "); if (bt->wide_bus != 0) printf("Wide "); else printf("Narrow "); if (bt->diff_bus != 0) printf("Diff "); printf("SCSI Host Adapter, SCSI ID %d, %d CCBs\n", bt->scsi_id, bt->max_ccbs); /* * Create our DMA tags. These tags define the kinds of device * accessible memory allocations and memory mappings we will * need to perform during normal operation. * * Unless we need to further restrict the allocation, we rely * on the restrictions of the parent dmat, hence the common * use of MAXADDR and MAXSIZE. */ /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create( /* parent */ bt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ BT_NSEG, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &bt->lock, &bt->buffer_dmat) != 0) { goto error_exit; } bt->init_level++; /* DMA tag for our mailboxes */ if (bus_dma_tag_create( /* parent */ bt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ bt->mailbox_addrlimit, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ bt->num_boxes * (sizeof(bt_mbox_in_t) + sizeof(bt_mbox_out_t)), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &bt->mailbox_dmat) != 0) { goto error_exit; } bt->init_level++; /* Allocation for our mailboxes */ if (bus_dmamem_alloc(bt->mailbox_dmat, (void **)&bt->out_boxes, BUS_DMA_NOWAIT, &bt->mailbox_dmamap) != 0) { goto error_exit; } bt->init_level++; /* And permanently map them */ bus_dmamap_load(bt->mailbox_dmat, bt->mailbox_dmamap, bt->out_boxes, bt->num_boxes * (sizeof(bt_mbox_in_t) + sizeof(bt_mbox_out_t)), btmapmboxes, bt, /*flags*/0); bt->init_level++; bt->in_boxes = (bt_mbox_in_t *)&bt->out_boxes[bt->num_boxes]; mtx_lock(&bt->lock); btinitmboxes(bt); mtx_unlock(&bt->lock); /* DMA tag for our ccb structures */ if (bus_dma_tag_create( /* parent */ bt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ bt->max_ccbs * sizeof(struct bt_ccb), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &bt->ccb_dmat) != 0) { goto error_exit; } bt->init_level++; /* Allocation for our ccbs */ if (bus_dmamem_alloc(bt->ccb_dmat, (void **)&bt->bt_ccb_array, BUS_DMA_NOWAIT, &bt->ccb_dmamap) != 0) { goto error_exit; } bt->init_level++; /* And permanently map them */ bus_dmamap_load(bt->ccb_dmat, bt->ccb_dmamap, bt->bt_ccb_array, bt->max_ccbs * sizeof(struct bt_ccb), btmapccbs, bt, /*flags*/0); bt->init_level++; /* DMA tag for our S/G structures. We allocate in page sized chunks */ if (bus_dma_tag_create( /* parent */ bt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ PAGE_SIZE, /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &bt->sg_dmat) != 0) { goto error_exit; } bt->init_level++; /* Perform initial CCB allocation */ bzero(bt->bt_ccb_array, bt->max_ccbs * sizeof(struct bt_ccb)); btallocccbs(bt); if (bt->num_ccbs == 0) { device_printf(dev, "bt_init - Unable to allocate initial ccbs\n"); goto error_exit; } /* * Note that we are going and return (to attach) */ return 0; error_exit: return (ENXIO); } int bt_attach(device_t dev) { struct bt_softc *bt = device_get_softc(dev); int tagged_dev_openings; struct cam_devq *devq; int error; /* * We reserve 1 ccb for error recovery, so don't * tell the XPT about it. */ if (bt->tag_capable != 0) tagged_dev_openings = bt->max_ccbs - 1; else tagged_dev_openings = 0; /* * Create the device queue for our SIM. */ devq = cam_simq_alloc(bt->max_ccbs - 1); if (devq == NULL) return (ENOMEM); /* * Construct our SIM entry */ bt->sim = cam_sim_alloc(btaction, btpoll, "bt", bt, device_get_unit(bt->dev), &bt->lock, 2, tagged_dev_openings, devq); if (bt->sim == NULL) { cam_simq_free(devq); return (ENOMEM); } mtx_lock(&bt->lock); if (xpt_bus_register(bt->sim, dev, 0) != CAM_SUCCESS) { cam_sim_free(bt->sim, /*free_devq*/TRUE); mtx_unlock(&bt->lock); return (ENXIO); } if (xpt_create_path(&bt->path, /*periph*/NULL, cam_sim_path(bt->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(bt->sim)); cam_sim_free(bt->sim, /*free_devq*/TRUE); mtx_unlock(&bt->lock); return (ENXIO); } mtx_unlock(&bt->lock); /* * Setup interrupt. */ error = bus_setup_intr(dev, bt->irq, INTR_TYPE_CAM | INTR_ENTROPY | INTR_MPSAFE, NULL, bt_intr, bt, &bt->ih); if (error) { device_printf(dev, "bus_setup_intr() failed: %d\n", error); return (error); } return (0); } int bt_check_probed_iop(u_int ioport) { u_int i; for (i = 0; i < BT_NUM_ISAPORTS; i++) { if (bt_isa_ports[i].addr == ioport) { if (bt_isa_ports[i].probed != 0) return (1); else { return (0); } } } return (1); } void bt_mark_probed_bio(isa_compat_io_t port) { if (port < BIO_DISABLED) bt_mark_probed_iop(bt_board_ports[port]); } void bt_mark_probed_iop(u_int ioport) { u_int i; for (i = 0; i < BT_NUM_ISAPORTS; i++) { if (ioport == bt_isa_ports[i].addr) { bt_isa_ports[i].probed = 1; break; } } } void bt_find_probe_range(int ioport, int *port_index, int *max_port_index) { if (ioport > 0) { int i; for (i = 0;i < BT_NUM_ISAPORTS; i++) if (ioport <= bt_isa_ports[i].addr) break; if ((i >= BT_NUM_ISAPORTS) || (ioport != bt_isa_ports[i].addr)) { printf( "bt_find_probe_range: Invalid baseport of 0x%x specified.\n" "bt_find_probe_range: Nearest valid baseport is 0x%x.\n" "bt_find_probe_range: Failing probe.\n", ioport, (i < BT_NUM_ISAPORTS) ? bt_isa_ports[i].addr : bt_isa_ports[BT_NUM_ISAPORTS - 1].addr); *port_index = *max_port_index = -1; return; } *port_index = *max_port_index = bt_isa_ports[i].bio; } else { *port_index = 0; *max_port_index = BT_NUM_ISAPORTS - 1; } } int bt_iop_from_bio(isa_compat_io_t bio_index) { if (bio_index < BT_NUM_ISAPORTS) return (bt_board_ports[bio_index]); return (-1); } static void btallocccbs(struct bt_softc *bt) { struct bt_ccb *next_ccb; struct sg_map_node *sg_map; bus_addr_t physaddr; bt_sg_t *segs; int newcount; int i; if (bt->num_ccbs >= bt->max_ccbs) /* Can't allocate any more */ return; next_ccb = &bt->bt_ccb_array[bt->num_ccbs]; sg_map = malloc(sizeof(*sg_map), M_DEVBUF, M_NOWAIT); if (sg_map == NULL) goto error_exit; /* Allocate S/G space for the next batch of CCBS */ if (bus_dmamem_alloc(bt->sg_dmat, (void **)&sg_map->sg_vaddr, BUS_DMA_NOWAIT, &sg_map->sg_dmamap) != 0) { free(sg_map, M_DEVBUF); goto error_exit; } SLIST_INSERT_HEAD(&bt->sg_maps, sg_map, links); bus_dmamap_load(bt->sg_dmat, sg_map->sg_dmamap, sg_map->sg_vaddr, PAGE_SIZE, btmapsgs, bt, /*flags*/0); segs = sg_map->sg_vaddr; physaddr = sg_map->sg_physaddr; newcount = (PAGE_SIZE / (BT_NSEG * sizeof(bt_sg_t))); for (i = 0; bt->num_ccbs < bt->max_ccbs && i < newcount; i++) { int error; next_ccb->sg_list = segs; next_ccb->sg_list_phys = physaddr; next_ccb->flags = BCCB_FREE; callout_init_mtx(&next_ccb->timer, &bt->lock, 0); error = bus_dmamap_create(bt->buffer_dmat, /*flags*/0, &next_ccb->dmamap); if (error != 0) break; SLIST_INSERT_HEAD(&bt->free_bt_ccbs, next_ccb, links); segs += BT_NSEG; physaddr += (BT_NSEG * sizeof(bt_sg_t)); next_ccb++; bt->num_ccbs++; } /* Reserve a CCB for error recovery */ if (bt->recovery_bccb == NULL) { bt->recovery_bccb = SLIST_FIRST(&bt->free_bt_ccbs); SLIST_REMOVE_HEAD(&bt->free_bt_ccbs, links); } if (SLIST_FIRST(&bt->free_bt_ccbs) != NULL) return; error_exit: device_printf(bt->dev, "Can't malloc BCCBs\n"); } static __inline void btfreeccb(struct bt_softc *bt, struct bt_ccb *bccb) { if (!dumping) mtx_assert(&bt->lock, MA_OWNED); if ((bccb->flags & BCCB_ACTIVE) != 0) LIST_REMOVE(&bccb->ccb->ccb_h, sim_links.le); if (bt->resource_shortage != 0 && (bccb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { bccb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; bt->resource_shortage = FALSE; } bccb->flags = BCCB_FREE; SLIST_INSERT_HEAD(&bt->free_bt_ccbs, bccb, links); bt->active_ccbs--; } static __inline struct bt_ccb* btgetccb(struct bt_softc *bt) { struct bt_ccb* bccb; if (!dumping) mtx_assert(&bt->lock, MA_OWNED); if ((bccb = SLIST_FIRST(&bt->free_bt_ccbs)) != NULL) { SLIST_REMOVE_HEAD(&bt->free_bt_ccbs, links); bt->active_ccbs++; } else { btallocccbs(bt); bccb = SLIST_FIRST(&bt->free_bt_ccbs); if (bccb != NULL) { SLIST_REMOVE_HEAD(&bt->free_bt_ccbs, links); bt->active_ccbs++; } } return (bccb); } static void btaction(struct cam_sim *sim, union ccb *ccb) { struct bt_softc *bt; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("btaction\n")); bt = (struct bt_softc *)cam_sim_softc(sim); mtx_assert(&bt->lock, MA_OWNED); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_SCSI_IO: /* Execute the requested I/O operation */ case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ { struct bt_ccb *bccb; struct bt_hccb *hccb; /* * get a bccb to use. */ if ((bccb = btgetccb(bt)) == NULL) { bt->resource_shortage = TRUE; xpt_freeze_simq(bt->sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } hccb = &bccb->hccb; /* * So we can find the BCCB when an abort is requested */ bccb->ccb = ccb; ccb->ccb_h.ccb_bccb_ptr = bccb; ccb->ccb_h.ccb_bt_ptr = bt; /* * Put all the arguments for the xfer in the bccb */ hccb->target_id = ccb->ccb_h.target_id; hccb->target_lun = ccb->ccb_h.target_lun; hccb->btstat = 0; hccb->sdstat = 0; if (ccb->ccb_h.func_code == XPT_SCSI_IO) { struct ccb_scsiio *csio; struct ccb_hdr *ccbh; int error; csio = &ccb->csio; ccbh = &csio->ccb_h; hccb->opcode = INITIATOR_CCB_WRESID; hccb->datain = (ccb->ccb_h.flags & CAM_DIR_IN) ? 1 : 0; hccb->dataout =(ccb->ccb_h.flags & CAM_DIR_OUT) ? 1 : 0; hccb->cmd_len = csio->cdb_len; if (hccb->cmd_len > sizeof(hccb->scsi_cdb)) { ccb->ccb_h.status = CAM_REQ_INVALID; btfreeccb(bt, bccb); xpt_done(ccb); return; } hccb->sense_len = csio->sense_len; if ((ccbh->flags & CAM_TAG_ACTION_VALID) != 0 && ccb->csio.tag_action != CAM_TAG_ACTION_NONE) { hccb->tag_enable = TRUE; hccb->tag_type = (ccb->csio.tag_action & 0x3); } else { hccb->tag_enable = FALSE; hccb->tag_type = 0; } if ((ccbh->flags & CAM_CDB_POINTER) != 0) { if ((ccbh->flags & CAM_CDB_PHYS) == 0) { bcopy(csio->cdb_io.cdb_ptr, hccb->scsi_cdb, hccb->cmd_len); } else { /* I guess I could map it in... */ ccbh->status = CAM_REQ_INVALID; btfreeccb(bt, bccb); xpt_done(ccb); return; } } else { bcopy(csio->cdb_io.cdb_bytes, hccb->scsi_cdb, hccb->cmd_len); } /* If need be, bounce our sense buffer */ if (bt->sense_buffers != NULL) { hccb->sense_addr = btsensepaddr(bt, bccb); } else { hccb->sense_addr = vtophys(&csio->sense_data); } /* * If we have any data to send with this command, * map it into bus space. */ error = bus_dmamap_load_ccb( bt->buffer_dmat, bccb->dmamap, ccb, btexecuteccb, bccb, /*flags*/0); if (error == EINPROGRESS) { /* * So as to maintain ordering, freeze the * controller queue until our mapping is * returned. */ xpt_freeze_simq(bt->sim, 1); csio->ccb_h.status |= CAM_RELEASE_SIMQ; } } else { hccb->opcode = INITIATOR_BUS_DEV_RESET; /* No data transfer */ hccb->datain = TRUE; hccb->dataout = TRUE; hccb->cmd_len = 0; hccb->sense_len = 0; hccb->tag_enable = FALSE; hccb->tag_type = 0; btexecuteccb(bccb, NULL, 0, 0); } break; } case XPT_EN_LUN: /* Enable LUN as a target */ case XPT_TARGET_IO: /* Execute target I/O request */ case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */ case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/ case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: { /* XXX Implement */ ccb->ccb_h.status = CAM_PROVIDE_FAIL; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts; u_int target_mask; cts = &ccb->cts; target_mask = 0x01 << ccb->ccb_h.target_id; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) { struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB; spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB; if ((bt->disc_permitted & target_mask) != 0) spi->flags |= CTS_SPI_FLAGS_DISC_ENB; if ((bt->tags_permitted & target_mask) != 0) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; if ((bt->ultra_permitted & target_mask) != 0) spi->sync_period = 12; else if ((bt->fast_permitted & target_mask) != 0) spi->sync_period = 25; else if ((bt->sync_permitted & target_mask) != 0) spi->sync_period = 50; else spi->sync_period = 0; if (spi->sync_period != 0) spi->sync_offset = 15; spi->valid |= CTS_SPI_VALID_SYNC_RATE; spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->valid |= CTS_SPI_VALID_BUS_WIDTH; if ((bt->wide_permitted & target_mask) != 0) spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; else spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; if (cts->ccb_h.target_lun != CAM_LUN_WILDCARD) { scsi->valid = CTS_SCSI_VALID_TQ; spi->valid |= CTS_SPI_VALID_DISC; } else scsi->valid = 0; } else { btfetchtransinfo(bt, cts); } ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg; u_int32_t size_mb; u_int32_t secs_per_cylinder; ccg = &ccb->ccg; size_mb = ccg->volume_size / ((1024L * 1024L) / ccg->block_size); if (size_mb >= 1024 && (bt->extended_trans != 0)) { if (size_mb >= 2048) { ccg->heads = 255; ccg->secs_per_track = 63; } else { ccg->heads = 128; ccg->secs_per_track = 32; } } else { ccg->heads = 64; ccg->secs_per_track = 32; } secs_per_cylinder = ccg->heads * ccg->secs_per_track; ccg->cylinders = ccg->volume_size / secs_per_cylinder; ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { btreset(bt, /*hardreset*/TRUE); ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_SDTR_ABLE; if (bt->tag_capable != 0) cpi->hba_inquiry |= PI_TAG_ABLE; if (bt->wide_bus != 0) cpi->hba_inquiry |= PI_WIDE_16; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = bt->wide_bus ? 15 : 7; cpi->max_lun = 7; cpi->initiator_id = bt->scsi_id; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "BusLogic", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->ccb_h.status = CAM_REQ_CMP; cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; xpt_done(ccb); break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } static void btexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct bt_ccb *bccb; union ccb *ccb; struct bt_softc *bt; bccb = (struct bt_ccb *)arg; ccb = bccb->ccb; bt = (struct bt_softc *)ccb->ccb_h.ccb_bt_ptr; if (error != 0) { if (error != EFBIG) device_printf(bt->dev, "Unexepected error 0x%x returned from " "bus_dmamap_load\n", error); if (ccb->ccb_h.status == CAM_REQ_INPROG) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; } btfreeccb(bt, bccb); xpt_done(ccb); return; } if (nseg != 0) { bt_sg_t *sg; bus_dma_segment_t *end_seg; bus_dmasync_op_t op; end_seg = dm_segs + nseg; /* Copy the segments into our SG list */ sg = bccb->sg_list; while (dm_segs < end_seg) { sg->len = dm_segs->ds_len; sg->addr = dm_segs->ds_addr; sg++; dm_segs++; } if (nseg > 1) { bccb->hccb.opcode = INITIATOR_SG_CCB_WRESID; bccb->hccb.data_len = sizeof(bt_sg_t) * nseg; bccb->hccb.data_addr = bccb->sg_list_phys; } else { bccb->hccb.data_len = bccb->sg_list->len; bccb->hccb.data_addr = bccb->sg_list->addr; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_PREREAD; else op = BUS_DMASYNC_PREWRITE; bus_dmamap_sync(bt->buffer_dmat, bccb->dmamap, op); } else { bccb->hccb.opcode = INITIATOR_CCB; bccb->hccb.data_len = 0; bccb->hccb.data_addr = 0; } /* * Last time we need to check if this CCB needs to * be aborted. */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(bt->buffer_dmat, bccb->dmamap); btfreeccb(bt, bccb); xpt_done(ccb); return; } bccb->flags = BCCB_ACTIVE; ccb->ccb_h.status |= CAM_SIM_QUEUED; LIST_INSERT_HEAD(&bt->pending_ccbs, &ccb->ccb_h, sim_links.le); callout_reset_sbt(&bccb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, bttimeout, bccb, 0); /* Tell the adapter about this command */ bt->cur_outbox->ccb_addr = btccbvtop(bt, bccb); if (bt->cur_outbox->action_code != BMBO_FREE) { /* * We should never encounter a busy mailbox. * If we do, warn the user, and treat it as * a resource shortage. If the controller is * hung, one of the pending transactions will * timeout causing us to start recovery operations. */ device_printf(bt->dev, "Encountered busy mailbox with %d out of %d " "commands active!!!\n", bt->active_ccbs, bt->max_ccbs); callout_stop(&bccb->timer); if (nseg != 0) bus_dmamap_unload(bt->buffer_dmat, bccb->dmamap); btfreeccb(bt, bccb); bt->resource_shortage = TRUE; xpt_freeze_simq(bt->sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } bt->cur_outbox->action_code = BMBO_START; bt_outb(bt, COMMAND_REG, BOP_START_MBOX); btnextoutbox(bt); } void bt_intr(void *arg) { struct bt_softc *bt; bt = arg; mtx_lock(&bt->lock); bt_intr_locked(bt); mtx_unlock(&bt->lock); } void bt_intr_locked(struct bt_softc *bt) { u_int intstat; while (((intstat = bt_inb(bt, INTSTAT_REG)) & INTR_PENDING) != 0) { if ((intstat & CMD_COMPLETE) != 0) { bt->latched_status = bt_inb(bt, STATUS_REG); bt->command_cmp = TRUE; } bt_outb(bt, CONTROL_REG, RESET_INTR); if ((intstat & IMB_LOADED) != 0) { while (bt->cur_inbox->comp_code != BMBI_FREE) { btdone(bt, btccbptov(bt, bt->cur_inbox->ccb_addr), bt->cur_inbox->comp_code); bt->cur_inbox->comp_code = BMBI_FREE; btnextinbox(bt); } } if ((intstat & SCSI_BUS_RESET) != 0) { btreset(bt, /*hardreset*/FALSE); } } } static void btdone(struct bt_softc *bt, struct bt_ccb *bccb, bt_mbi_comp_code_t comp_code) { union ccb *ccb; struct ccb_scsiio *csio; ccb = bccb->ccb; csio = &bccb->ccb->csio; if ((bccb->flags & BCCB_ACTIVE) == 0) { device_printf(bt->dev, "btdone - Attempt to free non-active BCCB %p\n", (void *)bccb); return; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(bt->buffer_dmat, bccb->dmamap, op); bus_dmamap_unload(bt->buffer_dmat, bccb->dmamap); } if (bccb == bt->recovery_bccb) { /* * The recovery BCCB does not have a CCB associated * with it, so short circuit the normal error handling. * We now traverse our list of pending CCBs and process * any that were terminated by the recovery CCBs action. * We also reinstate timeouts for all remaining, pending, * CCBs. */ struct cam_path *path; struct ccb_hdr *ccb_h; cam_status error; /* Notify all clients that a BDR occured */ error = xpt_create_path(&path, /*periph*/NULL, cam_sim_path(bt->sim), bccb->hccb.target_id, CAM_LUN_WILDCARD); if (error == CAM_REQ_CMP) { xpt_async(AC_SENT_BDR, path, NULL); xpt_free_path(path); } ccb_h = LIST_FIRST(&bt->pending_ccbs); while (ccb_h != NULL) { struct bt_ccb *pending_bccb; pending_bccb = (struct bt_ccb *)ccb_h->ccb_bccb_ptr; if (pending_bccb->hccb.target_id == bccb->hccb.target_id) { pending_bccb->hccb.btstat = BTSTAT_HA_BDR; ccb_h = LIST_NEXT(ccb_h, sim_links.le); btdone(bt, pending_bccb, BMBI_ERROR); } else { callout_reset_sbt(&pending_bccb->timer, SBT_1MS * ccb_h->timeout, 0, bttimeout, pending_bccb, 0); ccb_h = LIST_NEXT(ccb_h, sim_links.le); } } device_printf(bt->dev, "No longer in timeout\n"); return; } callout_stop(&bccb->timer); switch (comp_code) { case BMBI_FREE: device_printf(bt->dev, "btdone - CCB completed with free status!\n"); break; case BMBI_NOT_FOUND: device_printf(bt->dev, "btdone - CCB Abort failed to find CCB\n"); break; case BMBI_ABORT: case BMBI_ERROR: if (bootverbose) { printf("bt: ccb %p - error %x occured. " "btstat = %x, sdstat = %x\n", (void *)bccb, comp_code, bccb->hccb.btstat, bccb->hccb.sdstat); } /* An error occured */ switch(bccb->hccb.btstat) { case BTSTAT_DATARUN_ERROR: if (bccb->hccb.data_len == 0) { /* * At least firmware 4.22, does this * for a QUEUE FULL condition. */ bccb->hccb.sdstat = SCSI_STATUS_QUEUE_FULL; } else if (bccb->hccb.data_len < 0) { csio->ccb_h.status = CAM_DATA_RUN_ERR; break; } /* FALLTHROUGH */ case BTSTAT_NOERROR: case BTSTAT_LINKED_CMD_COMPLETE: case BTSTAT_LINKED_CMD_FLAG_COMPLETE: case BTSTAT_DATAUNDERUN_ERROR: csio->scsi_status = bccb->hccb.sdstat; csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR; switch(csio->scsi_status) { case SCSI_STATUS_CHECK_COND: case SCSI_STATUS_CMD_TERMINATED: csio->ccb_h.status |= CAM_AUTOSNS_VALID; /* Bounce sense back if necessary */ if (bt->sense_buffers != NULL) { csio->sense_data = *btsensevaddr(bt, bccb); } break; default: break; case SCSI_STATUS_OK: csio->ccb_h.status = CAM_REQ_CMP; break; } csio->resid = bccb->hccb.data_len; break; case BTSTAT_SELTIMEOUT: csio->ccb_h.status = CAM_SEL_TIMEOUT; break; case BTSTAT_UNEXPECTED_BUSFREE: csio->ccb_h.status = CAM_UNEXP_BUSFREE; break; case BTSTAT_INVALID_PHASE: csio->ccb_h.status = CAM_SEQUENCE_FAIL; break; case BTSTAT_INVALID_ACTION_CODE: panic("%s: Inavlid Action code", bt_name(bt)); break; case BTSTAT_INVALID_OPCODE: panic("%s: Inavlid CCB Opcode code", bt_name(bt)); break; case BTSTAT_LINKED_CCB_LUN_MISMATCH: /* We don't even support linked commands... */ panic("%s: Linked CCB Lun Mismatch", bt_name(bt)); break; case BTSTAT_INVALID_CCB_OR_SG_PARAM: panic("%s: Invalid CCB or SG list", bt_name(bt)); break; case BTSTAT_AUTOSENSE_FAILED: csio->ccb_h.status = CAM_AUTOSENSE_FAIL; break; case BTSTAT_TAGGED_MSG_REJECTED: { struct ccb_trans_settings neg; struct ccb_trans_settings_scsi *scsi = &neg.proto_specific.scsi; neg.protocol = PROTO_SCSI; neg.protocol_version = SCSI_REV_2; neg.transport = XPORT_SPI; neg.transport_version = 2; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = 0; xpt_print_path(csio->ccb_h.path); printf("refuses tagged commands. Performing " "non-tagged I/O\n"); xpt_setup_ccb(&neg.ccb_h, csio->ccb_h.path, /*priority*/1); xpt_async(AC_TRANSFER_NEG, csio->ccb_h.path, &neg); bt->tags_permitted &= ~(0x01 << csio->ccb_h.target_id); csio->ccb_h.status = CAM_MSG_REJECT_REC; break; } case BTSTAT_UNSUPPORTED_MSG_RECEIVED: /* * XXX You would think that this is * a recoverable error... Hmmm. */ csio->ccb_h.status = CAM_REQ_CMP_ERR; break; case BTSTAT_HA_SOFTWARE_ERROR: case BTSTAT_HA_WATCHDOG_ERROR: case BTSTAT_HARDWARE_FAILURE: /* Hardware reset ??? Can we recover ??? */ csio->ccb_h.status = CAM_NO_HBA; break; case BTSTAT_TARGET_IGNORED_ATN: case BTSTAT_OTHER_SCSI_BUS_RESET: case BTSTAT_HA_SCSI_BUS_RESET: if ((csio->ccb_h.status & CAM_STATUS_MASK) != CAM_CMD_TIMEOUT) csio->ccb_h.status = CAM_SCSI_BUS_RESET; break; case BTSTAT_HA_BDR: if ((bccb->flags & BCCB_DEVICE_RESET) == 0) csio->ccb_h.status = CAM_BDR_SENT; else csio->ccb_h.status = CAM_CMD_TIMEOUT; break; case BTSTAT_INVALID_RECONNECT: case BTSTAT_ABORT_QUEUE_GENERATED: csio->ccb_h.status = CAM_REQ_TERMIO; break; case BTSTAT_SCSI_PERROR_DETECTED: csio->ccb_h.status = CAM_UNCOR_PARITY; break; } if (csio->ccb_h.status != CAM_REQ_CMP) { xpt_freeze_devq(csio->ccb_h.path, /*count*/1); csio->ccb_h.status |= CAM_DEV_QFRZN; } if ((bccb->flags & BCCB_RELEASE_SIMQ) != 0) ccb->ccb_h.status |= CAM_RELEASE_SIMQ; btfreeccb(bt, bccb); xpt_done(ccb); break; case BMBI_OK: /* All completed without incident */ ccb->ccb_h.status |= CAM_REQ_CMP; if ((bccb->flags & BCCB_RELEASE_SIMQ) != 0) ccb->ccb_h.status |= CAM_RELEASE_SIMQ; btfreeccb(bt, bccb); xpt_done(ccb); break; } } static int btreset(struct bt_softc* bt, int hard_reset) { struct ccb_hdr *ccb_h; u_int status; u_int timeout; u_int8_t reset_type; if (hard_reset != 0) reset_type = HARD_RESET; else reset_type = SOFT_RESET; bt_outb(bt, CONTROL_REG, reset_type); /* Wait 5sec. for Diagnostic start */ timeout = 5 * 10000; while (--timeout) { status = bt_inb(bt, STATUS_REG); if ((status & DIAG_ACTIVE) != 0) break; DELAY(100); } if (timeout == 0) { if (bootverbose) device_printf(bt->dev, "btreset - Diagnostic Active failed to " "assert. status = 0x%x\n", status); return (ETIMEDOUT); } /* Wait 10sec. for Diagnostic end */ timeout = 10 * 10000; while (--timeout) { status = bt_inb(bt, STATUS_REG); if ((status & DIAG_ACTIVE) == 0) break; DELAY(100); } if (timeout == 0) { panic("%s: btreset - Diagnostic Active failed to drop. " "status = 0x%x\n", bt_name(bt), status); return (ETIMEDOUT); } /* Wait for the host adapter to become ready or report a failure */ timeout = 10000; while (--timeout) { status = bt_inb(bt, STATUS_REG); if ((status & (DIAG_FAIL|HA_READY|DATAIN_REG_READY)) != 0) break; DELAY(100); } if (timeout == 0) { device_printf(bt->dev, "btreset - Host adapter failed to come ready. " "status = 0x%x\n", status); return (ETIMEDOUT); } /* If the diagnostics failed, tell the user */ if ((status & DIAG_FAIL) != 0 || (status & HA_READY) == 0) { device_printf(bt->dev, "btreset - Adapter failed diagnostics\n"); if ((status & DATAIN_REG_READY) != 0) device_printf(bt->dev, "btreset - Host Adapter Error code = 0x%x\n", bt_inb(bt, DATAIN_REG)); return (ENXIO); } /* If we've allocated mailboxes, initialize them */ if (bt->init_level > 4) btinitmboxes(bt); /* If we've attached to the XPT, tell it about the event */ if (bt->path != NULL) xpt_async(AC_BUS_RESET, bt->path, NULL); /* * Perform completion processing for all outstanding CCBs. */ while ((ccb_h = LIST_FIRST(&bt->pending_ccbs)) != NULL) { struct bt_ccb *pending_bccb; pending_bccb = (struct bt_ccb *)ccb_h->ccb_bccb_ptr; pending_bccb->hccb.btstat = BTSTAT_HA_SCSI_BUS_RESET; btdone(bt, pending_bccb, BMBI_ERROR); } return (0); } /* * Send a command to the adapter. */ int bt_cmd(struct bt_softc *bt, bt_op_t opcode, u_int8_t *params, u_int param_len, u_int8_t *reply_data, u_int reply_len, u_int cmd_timeout) { u_int timeout; u_int status; u_int saved_status; u_int intstat; u_int reply_buf_size; int cmd_complete; int error; /* No data returned to start */ reply_buf_size = reply_len; reply_len = 0; intstat = 0; cmd_complete = 0; saved_status = 0; error = 0; bt->command_cmp = 0; /* * Wait up to 10 sec. for the adapter to become * ready to accept commands. */ timeout = 100000; while (--timeout) { status = bt_inb(bt, STATUS_REG); if ((status & HA_READY) != 0 && (status & CMD_REG_BUSY) == 0) break; /* * Throw away any pending data which may be * left over from earlier commands that we * timedout on. */ if ((status & DATAIN_REG_READY) != 0) (void)bt_inb(bt, DATAIN_REG); DELAY(100); } if (timeout == 0) { device_printf(bt->dev, "bt_cmd: Timeout waiting for adapter ready, " "status = 0x%x\n", status); return (ETIMEDOUT); } /* * Send the opcode followed by any necessary parameter bytes. */ bt_outb(bt, COMMAND_REG, opcode); /* * Wait for up to 1sec for each byte of the * parameter list sent to be sent. */ timeout = 10000; while (param_len && --timeout) { DELAY(100); status = bt_inb(bt, STATUS_REG); intstat = bt_inb(bt, INTSTAT_REG); if ((intstat & (INTR_PENDING|CMD_COMPLETE)) == (INTR_PENDING|CMD_COMPLETE)) { saved_status = status; cmd_complete = 1; break; } if (bt->command_cmp != 0) { saved_status = bt->latched_status; cmd_complete = 1; break; } if ((status & DATAIN_REG_READY) != 0) break; if ((status & CMD_REG_BUSY) == 0) { bt_outb(bt, COMMAND_REG, *params++); param_len--; timeout = 10000; } } if (timeout == 0) { device_printf(bt->dev, "bt_cmd: Timeout sending parameters, " "status = 0x%x\n", status); cmd_complete = 1; saved_status = status; error = ETIMEDOUT; } /* * Wait for the command to complete. */ while (cmd_complete == 0 && --cmd_timeout) { status = bt_inb(bt, STATUS_REG); intstat = bt_inb(bt, INTSTAT_REG); /* * It may be that this command was issued with * controller interrupts disabled. We'll never * get to our command if an incoming mailbox * interrupt is pending, so take care of completed * mailbox commands by calling our interrupt handler. */ if ((intstat & (INTR_PENDING|IMB_LOADED)) == (INTR_PENDING|IMB_LOADED)) bt_intr_locked(bt); if (bt->command_cmp != 0) { /* * Our interrupt handler saw CMD_COMPLETE * status before we did. */ cmd_complete = 1; saved_status = bt->latched_status; } else if ((intstat & (INTR_PENDING|CMD_COMPLETE)) == (INTR_PENDING|CMD_COMPLETE)) { /* * Our poll (in case interrupts are blocked) * saw the CMD_COMPLETE interrupt. */ cmd_complete = 1; saved_status = status; } else if (opcode == BOP_MODIFY_IO_ADDR && (status & CMD_REG_BUSY) == 0) { /* * The BOP_MODIFY_IO_ADDR does not issue a CMD_COMPLETE, * but it should update the status register. So, we * consider this command complete when the CMD_REG_BUSY * status clears. */ saved_status = status; cmd_complete = 1; } else if ((status & DATAIN_REG_READY) != 0) { u_int8_t data; data = bt_inb(bt, DATAIN_REG); if (reply_len < reply_buf_size) { *reply_data++ = data; } else { device_printf(bt->dev, "bt_cmd - Discarded reply data byte " "for opcode 0x%x\n", opcode); } /* * Reset timeout to ensure at least a second * between response bytes. */ cmd_timeout = MAX(cmd_timeout, 10000); reply_len++; } else if ((opcode == BOP_FETCH_LRAM) && (status & HA_READY) != 0) { saved_status = status; cmd_complete = 1; } DELAY(100); } if (cmd_timeout == 0) { device_printf(bt->dev, "bt_cmd: Timeout waiting for command (%x) " "to complete.\n", opcode); device_printf(bt->dev, "status = 0x%x, intstat = 0x%x, " "rlen %d\n", status, intstat, reply_len); error = (ETIMEDOUT); } /* * Clear any pending interrupts. */ bt_intr_locked(bt); if (error != 0) return (error); /* * If the command was rejected by the controller, tell the caller. */ if ((saved_status & CMD_INVALID) != 0) { /* * Some early adapters may not recover properly from * an invalid command. If it appears that the controller * has wedged (i.e. status was not cleared by our interrupt * reset above), perform a soft reset. */ if (bootverbose) device_printf(bt->dev, "Invalid Command 0x%x\n", opcode); DELAY(1000); status = bt_inb(bt, STATUS_REG); if ((status & (CMD_INVALID|STATUS_REG_RSVD|DATAIN_REG_READY| CMD_REG_BUSY|DIAG_FAIL|DIAG_ACTIVE)) != 0 || (status & (HA_READY|INIT_REQUIRED)) != (HA_READY|INIT_REQUIRED)) { btreset(bt, /*hard_reset*/FALSE); } return (EINVAL); } if (param_len > 0) { /* The controller did not accept the full argument list */ return (E2BIG); } if (reply_len != reply_buf_size) { /* Too much or too little data received */ return (EMSGSIZE); } /* We were successful */ return (0); } static int btinitmboxes(struct bt_softc *bt) { init_32b_mbox_params_t init_mbox; int error; bzero(bt->in_boxes, sizeof(bt_mbox_in_t) * bt->num_boxes); bzero(bt->out_boxes, sizeof(bt_mbox_out_t) * bt->num_boxes); bt->cur_inbox = bt->in_boxes; bt->last_inbox = bt->in_boxes + bt->num_boxes - 1; bt->cur_outbox = bt->out_boxes; bt->last_outbox = bt->out_boxes + bt->num_boxes - 1; /* Tell the adapter about them */ init_mbox.num_boxes = bt->num_boxes; init_mbox.base_addr[0] = bt->mailbox_physbase & 0xFF; init_mbox.base_addr[1] = (bt->mailbox_physbase >> 8) & 0xFF; init_mbox.base_addr[2] = (bt->mailbox_physbase >> 16) & 0xFF; init_mbox.base_addr[3] = (bt->mailbox_physbase >> 24) & 0xFF; error = bt_cmd(bt, BOP_INITIALIZE_32BMBOX, (u_int8_t *)&init_mbox, /*parmlen*/sizeof(init_mbox), /*reply_buf*/NULL, /*reply_len*/0, DEFAULT_CMD_TIMEOUT); if (error != 0) printf("btinitmboxes: Initialization command failed\n"); else if (bt->strict_rr != 0) { /* * If the controller supports * strict round robin mode, * enable it */ u_int8_t param; param = 0; error = bt_cmd(bt, BOP_ENABLE_STRICT_RR, ¶m, 1, /*reply_buf*/NULL, /*reply_len*/0, DEFAULT_CMD_TIMEOUT); if (error != 0) { printf("btinitmboxes: Unable to enable strict RR\n"); error = 0; } else if (bootverbose) { device_printf(bt->dev, "Using Strict Round Robin Mailbox Mode\n"); } } return (error); } /* * Update the XPT's idea of the negotiated transfer * parameters for a particular target. */ static void btfetchtransinfo(struct bt_softc *bt, struct ccb_trans_settings *cts) { setup_data_t setup_info; u_int target; u_int targ_offset; u_int targ_mask; u_int sync_period; u_int sync_offset; u_int bus_width; int error; u_int8_t param; targ_syncinfo_t sync_info; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; spi->valid = 0; scsi->valid = 0; target = cts->ccb_h.target_id; targ_offset = (target & 0x7); targ_mask = (0x01 << targ_offset); /* * Inquire Setup Information. This command retreives the * Wide negotiation status for recent adapters as well as * the sync info for older models. */ param = sizeof(setup_info); error = bt_cmd(bt, BOP_INQUIRE_SETUP_INFO, ¶m, /*paramlen*/1, (u_int8_t*)&setup_info, sizeof(setup_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(bt->dev, "btfetchtransinfo - Inquire Setup Info Failed %x\n", error); return; } sync_info = (target < 8) ? setup_info.low_syncinfo[targ_offset] : setup_info.high_syncinfo[targ_offset]; if (sync_info.sync == 0) sync_offset = 0; else sync_offset = sync_info.offset; bus_width = MSG_EXT_WDTR_BUS_8_BIT; if (strcmp(bt->firmware_ver, "5.06L") >= 0) { u_int wide_active; wide_active = (target < 8) ? (setup_info.low_wide_active & targ_mask) : (setup_info.high_wide_active & targ_mask); if (wide_active) bus_width = MSG_EXT_WDTR_BUS_16_BIT; } else if ((bt->wide_permitted & targ_mask) != 0) { struct ccb_getdev cgd; /* * Prior to rev 5.06L, wide status isn't provided, * so we "guess" that wide transfers are in effect * if the user settings allow for wide and the inquiry * data for the device indicates that it can handle * wide transfers. */ xpt_setup_ccb(&cgd.ccb_h, cts->ccb_h.path, /*priority*/1); cgd.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); if ((cgd.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP && (cgd.inq_data.flags & SID_WBus16) != 0) bus_width = MSG_EXT_WDTR_BUS_16_BIT; } if (bt->firmware_ver[0] >= '3') { /* * For adapters that can do fast or ultra speeds, * use the more exact Target Sync Information command. */ target_sync_info_data_t sync_info; param = sizeof(sync_info); error = bt_cmd(bt, BOP_TARG_SYNC_INFO, ¶m, /*paramlen*/1, (u_int8_t*)&sync_info, sizeof(sync_info), DEFAULT_CMD_TIMEOUT); if (error != 0) { device_printf(bt->dev, "btfetchtransinfo - Inquire Sync " "Info Failed 0x%x\n", error); return; } sync_period = sync_info.sync_rate[target] * 100; } else { sync_period = 2000 + (500 * sync_info.period); } cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; spi->sync_period = sync_period; spi->valid |= CTS_SPI_VALID_SYNC_RATE; spi->sync_offset = sync_offset; spi->valid |= CTS_SPI_VALID_SYNC_OFFSET; spi->valid |= CTS_SPI_VALID_BUS_WIDTH; spi->bus_width = bus_width; if (cts->ccb_h.target_lun != CAM_LUN_WILDCARD) { scsi->valid = CTS_SCSI_VALID_TQ; spi->valid |= CTS_SPI_VALID_DISC; } else scsi->valid = 0; xpt_async(AC_TRANSFER_NEG, cts->ccb_h.path, cts); } static void btmapmboxes(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct bt_softc* bt; bt = (struct bt_softc*)arg; bt->mailbox_physbase = segs->ds_addr; } static void btmapccbs(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct bt_softc* bt; bt = (struct bt_softc*)arg; bt->bt_ccb_physbase = segs->ds_addr; } static void btmapsgs(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct bt_softc* bt; bt = (struct bt_softc*)arg; SLIST_FIRST(&bt->sg_maps)->sg_physaddr = segs->ds_addr; } static void btpoll(struct cam_sim *sim) { bt_intr_locked(cam_sim_softc(sim)); } void bttimeout(void *arg) { struct bt_ccb *bccb; union ccb *ccb; struct bt_softc *bt; bccb = (struct bt_ccb *)arg; ccb = bccb->ccb; bt = (struct bt_softc *)ccb->ccb_h.ccb_bt_ptr; mtx_assert(&bt->lock, MA_OWNED); xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out\n", (void *)bccb); if ((bccb->flags & BCCB_ACTIVE) == 0) { xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out CCB already completed\n", (void *)bccb); return; } /* * In order to simplify the recovery process, we ask the XPT * layer to halt the queue of new transactions and we traverse * the list of pending CCBs and remove their timeouts. This * means that the driver attempts to clear only one error * condition at a time. In general, timeouts that occur * close together are related anyway, so there is no benefit * in attempting to handle errors in parrallel. Timeouts will * be reinstated when the recovery process ends. */ if ((bccb->flags & BCCB_DEVICE_RESET) == 0) { struct ccb_hdr *ccb_h; if ((bccb->flags & BCCB_RELEASE_SIMQ) == 0) { xpt_freeze_simq(bt->sim, /*count*/1); bccb->flags |= BCCB_RELEASE_SIMQ; } ccb_h = LIST_FIRST(&bt->pending_ccbs); while (ccb_h != NULL) { struct bt_ccb *pending_bccb; pending_bccb = (struct bt_ccb *)ccb_h->ccb_bccb_ptr; callout_stop(&pending_bccb->timer); ccb_h = LIST_NEXT(ccb_h, sim_links.le); } } if ((bccb->flags & BCCB_DEVICE_RESET) != 0 || bt->cur_outbox->action_code != BMBO_FREE || ((bccb->hccb.tag_enable == TRUE) && (bt->firmware_ver[0] < '5'))) { /* * Try a full host adapter/SCSI bus reset. * We do this only if we have already attempted * to clear the condition with a BDR, or we cannot * attempt a BDR for lack of mailbox resources * or because of faulty firmware. It turns out * that firmware versions prior to 5.xx treat BDRs * as untagged commands that cannot be sent until * all outstanding tagged commands have been processed. * This makes it somewhat difficult to use a BDR to * clear up a problem with an uncompleted tagged command. */ ccb->ccb_h.status = CAM_CMD_TIMEOUT; btreset(bt, /*hardreset*/TRUE); device_printf(bt->dev, "No longer in timeout\n"); } else { /* * Send a Bus Device Reset message: * The target that is holding up the bus may not * be the same as the one that triggered this timeout * (different commands have different timeout lengths), * but we have no way of determining this from our * timeout handler. Our strategy here is to queue a * BDR message to the target of the timed out command. * If this fails, we'll get another timeout 2 seconds * later which will attempt a bus reset. */ bccb->flags |= BCCB_DEVICE_RESET; callout_reset(&bccb->timer, 2 * hz, bttimeout, bccb); bt->recovery_bccb->hccb.opcode = INITIATOR_BUS_DEV_RESET; /* No Data Transfer */ bt->recovery_bccb->hccb.datain = TRUE; bt->recovery_bccb->hccb.dataout = TRUE; bt->recovery_bccb->hccb.btstat = 0; bt->recovery_bccb->hccb.sdstat = 0; bt->recovery_bccb->hccb.target_id = ccb->ccb_h.target_id; /* Tell the adapter about this command */ bt->cur_outbox->ccb_addr = btccbvtop(bt, bt->recovery_bccb); bt->cur_outbox->action_code = BMBO_START; bt_outb(bt, COMMAND_REG, BOP_START_MBOX); btnextoutbox(bt); } } MODULE_VERSION(bt, 1); MODULE_DEPEND(bt, cam, 1, 1, 1); Index: head/sys/dev/bwi/if_bwi.c =================================================================== --- head/sys/dev/bwi/if_bwi.c (revision 280346) +++ head/sys/dev/bwi/if_bwi.c (revision 280347) @@ -1,4057 +1,4057 @@ /* * Copyright (c) 2007 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Sepherosa Ziehau * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $DragonFly: src/sys/dev/netif/bwi/if_bwi.c,v 1.19 2008/02/15 11:15:38 sephe Exp $ */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_bwi.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #include #include #include #include #include #include #include #include struct bwi_clock_freq { u_int clkfreq_min; u_int clkfreq_max; }; struct bwi_myaddr_bssid { uint8_t myaddr[IEEE80211_ADDR_LEN]; uint8_t bssid[IEEE80211_ADDR_LEN]; } __packed; static struct ieee80211vap *bwi_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void bwi_vap_delete(struct ieee80211vap *); static void bwi_init(void *); static int bwi_ioctl(struct ifnet *, u_long, caddr_t); static void bwi_start(struct ifnet *); static void bwi_start_locked(struct ifnet *); static int bwi_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void bwi_watchdog(void *); static void bwi_scan_start(struct ieee80211com *); static void bwi_set_channel(struct ieee80211com *); static void bwi_scan_end(struct ieee80211com *); static int bwi_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void bwi_updateslot(struct ifnet *); static int bwi_media_change(struct ifnet *); static void bwi_calibrate(void *); static int bwi_calc_rssi(struct bwi_softc *, const struct bwi_rxbuf_hdr *); static int bwi_calc_noise(struct bwi_softc *); static __inline uint8_t bwi_plcp2rate(uint32_t, enum ieee80211_phytype); static void bwi_rx_radiotap(struct bwi_softc *, struct mbuf *, struct bwi_rxbuf_hdr *, const void *, int, int, int); static void bwi_restart(void *, int); static void bwi_init_statechg(struct bwi_softc *, int); static void bwi_stop(struct bwi_softc *, int); static void bwi_stop_locked(struct bwi_softc *, int); static int bwi_newbuf(struct bwi_softc *, int, int); static int bwi_encap(struct bwi_softc *, int, struct mbuf *, struct ieee80211_node *); static int bwi_encap_raw(struct bwi_softc *, int, struct mbuf *, struct ieee80211_node *, const struct ieee80211_bpf_params *); static void bwi_init_rxdesc_ring32(struct bwi_softc *, uint32_t, bus_addr_t, int, int); static void bwi_reset_rx_ring32(struct bwi_softc *, uint32_t); static int bwi_init_tx_ring32(struct bwi_softc *, int); static int bwi_init_rx_ring32(struct bwi_softc *); static int bwi_init_txstats32(struct bwi_softc *); static void bwi_free_tx_ring32(struct bwi_softc *, int); static void bwi_free_rx_ring32(struct bwi_softc *); static void bwi_free_txstats32(struct bwi_softc *); static void bwi_setup_rx_desc32(struct bwi_softc *, int, bus_addr_t, int); static void bwi_setup_tx_desc32(struct bwi_softc *, struct bwi_ring_data *, int, bus_addr_t, int); static int bwi_rxeof32(struct bwi_softc *); static void bwi_start_tx32(struct bwi_softc *, uint32_t, int); static void bwi_txeof_status32(struct bwi_softc *); static int bwi_init_tx_ring64(struct bwi_softc *, int); static int bwi_init_rx_ring64(struct bwi_softc *); static int bwi_init_txstats64(struct bwi_softc *); static void bwi_free_tx_ring64(struct bwi_softc *, int); static void bwi_free_rx_ring64(struct bwi_softc *); static void bwi_free_txstats64(struct bwi_softc *); static void bwi_setup_rx_desc64(struct bwi_softc *, int, bus_addr_t, int); static void bwi_setup_tx_desc64(struct bwi_softc *, struct bwi_ring_data *, int, bus_addr_t, int); static int bwi_rxeof64(struct bwi_softc *); static void bwi_start_tx64(struct bwi_softc *, uint32_t, int); static void bwi_txeof_status64(struct bwi_softc *); static int bwi_rxeof(struct bwi_softc *, int); static void _bwi_txeof(struct bwi_softc *, uint16_t, int, int); static void bwi_txeof(struct bwi_softc *); static void bwi_txeof_status(struct bwi_softc *, int); static void bwi_enable_intrs(struct bwi_softc *, uint32_t); static void bwi_disable_intrs(struct bwi_softc *, uint32_t); static int bwi_dma_alloc(struct bwi_softc *); static void bwi_dma_free(struct bwi_softc *); static int bwi_dma_ring_alloc(struct bwi_softc *, bus_dma_tag_t, struct bwi_ring_data *, bus_size_t, uint32_t); static int bwi_dma_mbuf_create(struct bwi_softc *); static void bwi_dma_mbuf_destroy(struct bwi_softc *, int, int); static int bwi_dma_txstats_alloc(struct bwi_softc *, uint32_t, bus_size_t); static void bwi_dma_txstats_free(struct bwi_softc *); static void bwi_dma_ring_addr(void *, bus_dma_segment_t *, int, int); static void bwi_dma_buf_addr(void *, bus_dma_segment_t *, int, bus_size_t, int); static void bwi_power_on(struct bwi_softc *, int); static int bwi_power_off(struct bwi_softc *, int); static int bwi_set_clock_mode(struct bwi_softc *, enum bwi_clock_mode); static int bwi_set_clock_delay(struct bwi_softc *); static void bwi_get_clock_freq(struct bwi_softc *, struct bwi_clock_freq *); static int bwi_get_pwron_delay(struct bwi_softc *sc); static void bwi_set_addr_filter(struct bwi_softc *, uint16_t, const uint8_t *); static void bwi_set_bssid(struct bwi_softc *, const uint8_t *); static void bwi_get_card_flags(struct bwi_softc *); static void bwi_get_eaddr(struct bwi_softc *, uint16_t, uint8_t *); static int bwi_bus_attach(struct bwi_softc *); static int bwi_bbp_attach(struct bwi_softc *); static int bwi_bbp_power_on(struct bwi_softc *, enum bwi_clock_mode); static void bwi_bbp_power_off(struct bwi_softc *); static const char *bwi_regwin_name(const struct bwi_regwin *); static uint32_t bwi_regwin_disable_bits(struct bwi_softc *); static void bwi_regwin_info(struct bwi_softc *, uint16_t *, uint8_t *); static int bwi_regwin_select(struct bwi_softc *, int); static void bwi_led_attach(struct bwi_softc *); static void bwi_led_newstate(struct bwi_softc *, enum ieee80211_state); static void bwi_led_event(struct bwi_softc *, int); static void bwi_led_blink_start(struct bwi_softc *, int, int); static void bwi_led_blink_next(void *); static void bwi_led_blink_end(void *); static const struct { uint16_t did_min; uint16_t did_max; uint16_t bbp_id; } bwi_bbpid_map[] = { { 0x4301, 0x4301, 0x4301 }, { 0x4305, 0x4307, 0x4307 }, { 0x4402, 0x4403, 0x4402 }, { 0x4610, 0x4615, 0x4610 }, { 0x4710, 0x4715, 0x4710 }, { 0x4720, 0x4725, 0x4309 } }; static const struct { uint16_t bbp_id; int nregwin; } bwi_regwin_count[] = { { 0x4301, 5 }, { 0x4306, 6 }, { 0x4307, 5 }, { 0x4310, 8 }, { 0x4401, 3 }, { 0x4402, 3 }, { 0x4610, 9 }, { 0x4704, 9 }, { 0x4710, 9 }, { 0x5365, 7 } }; #define CLKSRC(src) \ [BWI_CLKSRC_ ## src] = { \ .freq_min = BWI_CLKSRC_ ##src## _FMIN, \ .freq_max = BWI_CLKSRC_ ##src## _FMAX \ } static const struct { u_int freq_min; u_int freq_max; } bwi_clkfreq[BWI_CLKSRC_MAX] = { CLKSRC(LP_OSC), CLKSRC(CS_OSC), CLKSRC(PCI) }; #undef CLKSRC #define VENDOR_LED_ACT(vendor) \ { \ .vid = PCI_VENDOR_##vendor, \ .led_act = { BWI_VENDOR_LED_ACT_##vendor } \ } static const struct { #define PCI_VENDOR_COMPAQ 0x0e11 #define PCI_VENDOR_LINKSYS 0x1737 uint16_t vid; uint8_t led_act[BWI_LED_MAX]; } bwi_vendor_led_act[] = { VENDOR_LED_ACT(COMPAQ), VENDOR_LED_ACT(LINKSYS) #undef PCI_VENDOR_LINKSYS #undef PCI_VENDOR_COMPAQ }; static const uint8_t bwi_default_led_act[BWI_LED_MAX] = { BWI_VENDOR_LED_ACT_DEFAULT }; #undef VENDOR_LED_ACT static const struct { int on_dur; int off_dur; } bwi_led_duration[109] = { [0] = { 400, 100 }, [2] = { 150, 75 }, [4] = { 90, 45 }, [11] = { 66, 34 }, [12] = { 53, 26 }, [18] = { 42, 21 }, [22] = { 35, 17 }, [24] = { 32, 16 }, [36] = { 21, 10 }, [48] = { 16, 8 }, [72] = { 11, 5 }, [96] = { 9, 4 }, [108] = { 7, 3 } }; #ifdef BWI_DEBUG #ifdef BWI_DEBUG_VERBOSE static uint32_t bwi_debug = BWI_DBG_ATTACH | BWI_DBG_INIT | BWI_DBG_TXPOWER; #else static uint32_t bwi_debug; #endif TUNABLE_INT("hw.bwi.debug", (int *)&bwi_debug); #endif /* BWI_DEBUG */ static const uint8_t bwi_zero_addr[IEEE80211_ADDR_LEN]; uint16_t bwi_read_sprom(struct bwi_softc *sc, uint16_t ofs) { return CSR_READ_2(sc, ofs + BWI_SPROM_START); } static __inline void bwi_setup_desc32(struct bwi_softc *sc, struct bwi_desc32 *desc_array, int ndesc, int desc_idx, bus_addr_t paddr, int buf_len, int tx) { struct bwi_desc32 *desc = &desc_array[desc_idx]; uint32_t ctrl, addr, addr_hi, addr_lo; addr_lo = __SHIFTOUT(paddr, BWI_DESC32_A_ADDR_MASK); addr_hi = __SHIFTOUT(paddr, BWI_DESC32_A_FUNC_MASK); addr = __SHIFTIN(addr_lo, BWI_DESC32_A_ADDR_MASK) | __SHIFTIN(BWI_DESC32_A_FUNC_TXRX, BWI_DESC32_A_FUNC_MASK); ctrl = __SHIFTIN(buf_len, BWI_DESC32_C_BUFLEN_MASK) | __SHIFTIN(addr_hi, BWI_DESC32_C_ADDRHI_MASK); if (desc_idx == ndesc - 1) ctrl |= BWI_DESC32_C_EOR; if (tx) { /* XXX */ ctrl |= BWI_DESC32_C_FRAME_START | BWI_DESC32_C_FRAME_END | BWI_DESC32_C_INTR; } desc->addr = htole32(addr); desc->ctrl = htole32(ctrl); } int bwi_attach(struct bwi_softc *sc) { struct ieee80211com *ic; device_t dev = sc->sc_dev; struct ifnet *ifp; struct bwi_mac *mac; struct bwi_phy *phy; int i, error; uint8_t bands; uint8_t macaddr[IEEE80211_ADDR_LEN]; BWI_LOCK_INIT(sc); /* * Initialize taskq and various tasks */ sc->sc_tq = taskqueue_create("bwi_taskq", M_NOWAIT | M_ZERO, taskqueue_thread_enqueue, &sc->sc_tq); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(dev)); TASK_INIT(&sc->sc_restart_task, 0, bwi_restart, sc); callout_init_mtx(&sc->sc_calib_ch, &sc->sc_mtx, 0); /* * Initialize sysctl variables */ sc->sc_fw_version = BWI_FW_VERSION3; sc->sc_led_idle = (2350 * hz) / 1000; sc->sc_led_blink = 1; sc->sc_txpwr_calib = 1; #ifdef BWI_DEBUG sc->sc_debug = bwi_debug; #endif bwi_power_on(sc, 1); error = bwi_bbp_attach(sc); if (error) goto fail; error = bwi_bbp_power_on(sc, BWI_CLOCK_MODE_FAST); if (error) goto fail; if (BWI_REGWIN_EXIST(&sc->sc_com_regwin)) { error = bwi_set_clock_delay(sc); if (error) goto fail; error = bwi_set_clock_mode(sc, BWI_CLOCK_MODE_FAST); if (error) goto fail; error = bwi_get_pwron_delay(sc); if (error) goto fail; } error = bwi_bus_attach(sc); if (error) goto fail; bwi_get_card_flags(sc); bwi_led_attach(sc); for (i = 0; i < sc->sc_nmac; ++i) { struct bwi_regwin *old; mac = &sc->sc_mac[i]; error = bwi_regwin_switch(sc, &mac->mac_regwin, &old); if (error) goto fail; error = bwi_mac_lateattach(mac); if (error) goto fail; error = bwi_regwin_switch(sc, old, NULL); if (error) goto fail; } /* * XXX First MAC is known to exist * TODO2 */ mac = &sc->sc_mac[0]; phy = &mac->mac_phy; bwi_bbp_power_off(sc); error = bwi_dma_alloc(sc); if (error) goto fail; ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); if (ifp == NULL) { device_printf(dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } ic = ifp->if_l2com; /* set these up early for if_printf use */ if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_init = bwi_init; ifp->if_ioctl = bwi_ioctl; ifp->if_start = bwi_start; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); callout_init_mtx(&sc->sc_watchdog_timer, &sc->sc_mtx, 0); /* * Setup ratesets, phytype, channels and get MAC address */ bands = 0; if (phy->phy_mode == IEEE80211_MODE_11B || phy->phy_mode == IEEE80211_MODE_11G) { setbit(&bands, IEEE80211_MODE_11B); if (phy->phy_mode == IEEE80211_MODE_11B) { ic->ic_phytype = IEEE80211_T_DS; } else { ic->ic_phytype = IEEE80211_T_OFDM; setbit(&bands, IEEE80211_MODE_11G); } bwi_get_eaddr(sc, BWI_SPROM_11BG_EADDR, macaddr); if (IEEE80211_IS_MULTICAST(macaddr)) { bwi_get_eaddr(sc, BWI_SPROM_11A_EADDR, macaddr); if (IEEE80211_IS_MULTICAST(macaddr)) { device_printf(dev, "invalid MAC address: %6D\n", macaddr, ":"); } } } else if (phy->phy_mode == IEEE80211_MODE_11A) { /* TODO:11A */ setbit(&bands, IEEE80211_MODE_11A); error = ENXIO; goto fail; } else { panic("unknown phymode %d\n", phy->phy_mode); } /* Get locale */ sc->sc_locale = __SHIFTOUT(bwi_read_sprom(sc, BWI_SPROM_CARD_INFO), BWI_SPROM_CARD_INFO_LOCALE); DPRINTF(sc, BWI_DBG_ATTACH, "locale: %d\n", sc->sc_locale); /* XXX use locale */ ieee80211_init_channels(ic, NULL, &bands); ic->ic_ifp = ifp; ic->ic_caps = IEEE80211_C_STA | IEEE80211_C_SHSLOT | IEEE80211_C_SHPREAMBLE | IEEE80211_C_WPA | IEEE80211_C_BGSCAN | IEEE80211_C_MONITOR; ic->ic_opmode = IEEE80211_M_STA; ieee80211_ifattach(ic, macaddr); ic->ic_headroom = sizeof(struct bwi_txbuf_hdr); /* override default methods */ ic->ic_vap_create = bwi_vap_create; ic->ic_vap_delete = bwi_vap_delete; ic->ic_raw_xmit = bwi_raw_xmit; ic->ic_updateslot = bwi_updateslot; ic->ic_scan_start = bwi_scan_start; ic->ic_scan_end = bwi_scan_end; ic->ic_set_channel = bwi_set_channel; sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); ieee80211_radiotap_attach(ic, &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th), BWI_TX_RADIOTAP_PRESENT, &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th), BWI_RX_RADIOTAP_PRESENT); /* * Add sysctl nodes */ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fw_version", CTLFLAG_RD, &sc->sc_fw_version, 0, "Firmware version"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "led_idle", CTLFLAG_RW, &sc->sc_led_idle, 0, "# ticks before LED enters idle state"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "led_blink", CTLFLAG_RW, &sc->sc_led_blink, 0, "Allow LED to blink"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "txpwr_calib", CTLFLAG_RW, &sc->sc_txpwr_calib, 0, "Enable software TX power calibration"); #ifdef BWI_DEBUG SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "Debug flags"); #endif if (bootverbose) ieee80211_announce(ic); return (0); fail: BWI_LOCK_DESTROY(sc); return (error); } int bwi_detach(struct bwi_softc *sc) { struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; int i; bwi_stop(sc, 1); callout_drain(&sc->sc_led_blink_ch); callout_drain(&sc->sc_calib_ch); callout_drain(&sc->sc_watchdog_timer); ieee80211_ifdetach(ic); for (i = 0; i < sc->sc_nmac; ++i) bwi_mac_detach(&sc->sc_mac[i]); bwi_dma_free(sc); if_free(ifp); taskqueue_free(sc->sc_tq); BWI_LOCK_DESTROY(sc); return (0); } static struct ieee80211vap * bwi_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct bwi_vap *bvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; bvp = (struct bwi_vap *) malloc(sizeof(struct bwi_vap), M_80211_VAP, M_WAITOK | M_ZERO); if (bvp == NULL) return NULL; vap = &bvp->bv_vap; /* enable s/w bmiss handling for sta mode */ ieee80211_vap_setup(ic, vap, name, unit, opmode, flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); /* override default methods */ bvp->bv_newstate = vap->iv_newstate; vap->iv_newstate = bwi_newstate; #if 0 vap->iv_update_beacon = bwi_beacon_update; #endif ieee80211_ratectl_init(vap); /* complete setup */ ieee80211_vap_attach(vap, bwi_media_change, ieee80211_media_status); ic->ic_opmode = opmode; return vap; } static void bwi_vap_delete(struct ieee80211vap *vap) { struct bwi_vap *bvp = BWI_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(bvp, M_80211_VAP); } void bwi_suspend(struct bwi_softc *sc) { bwi_stop(sc, 1); } void bwi_resume(struct bwi_softc *sc) { struct ifnet *ifp = sc->sc_ifp; if (ifp->if_flags & IFF_UP) bwi_init(sc); } int bwi_shutdown(struct bwi_softc *sc) { bwi_stop(sc, 1); return 0; } static void bwi_power_on(struct bwi_softc *sc, int with_pll) { uint32_t gpio_in, gpio_out, gpio_en; uint16_t status; gpio_in = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_IN, 4); if (gpio_in & BWI_PCIM_GPIO_PWR_ON) goto back; gpio_out = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, 4); gpio_en = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_ENABLE, 4); gpio_out |= BWI_PCIM_GPIO_PWR_ON; gpio_en |= BWI_PCIM_GPIO_PWR_ON; if (with_pll) { /* Turn off PLL first */ gpio_out |= BWI_PCIM_GPIO_PLL_PWR_OFF; gpio_en |= BWI_PCIM_GPIO_PLL_PWR_OFF; } pci_write_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, gpio_out, 4); pci_write_config(sc->sc_dev, BWI_PCIR_GPIO_ENABLE, gpio_en, 4); DELAY(1000); if (with_pll) { /* Turn on PLL */ gpio_out &= ~BWI_PCIM_GPIO_PLL_PWR_OFF; pci_write_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, gpio_out, 4); DELAY(5000); } back: /* Clear "Signaled Target Abort" */ status = pci_read_config(sc->sc_dev, PCIR_STATUS, 2); status &= ~PCIM_STATUS_STABORT; pci_write_config(sc->sc_dev, PCIR_STATUS, status, 2); } static int bwi_power_off(struct bwi_softc *sc, int with_pll) { uint32_t gpio_out, gpio_en; pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_IN, 4); /* dummy read */ gpio_out = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, 4); gpio_en = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_ENABLE, 4); gpio_out &= ~BWI_PCIM_GPIO_PWR_ON; gpio_en |= BWI_PCIM_GPIO_PWR_ON; if (with_pll) { gpio_out |= BWI_PCIM_GPIO_PLL_PWR_OFF; gpio_en |= BWI_PCIM_GPIO_PLL_PWR_OFF; } pci_write_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, gpio_out, 4); pci_write_config(sc->sc_dev, BWI_PCIR_GPIO_ENABLE, gpio_en, 4); return 0; } int bwi_regwin_switch(struct bwi_softc *sc, struct bwi_regwin *rw, struct bwi_regwin **old_rw) { int error; if (old_rw != NULL) *old_rw = NULL; if (!BWI_REGWIN_EXIST(rw)) return EINVAL; if (sc->sc_cur_regwin != rw) { error = bwi_regwin_select(sc, rw->rw_id); if (error) { device_printf(sc->sc_dev, "can't select regwin %d\n", rw->rw_id); return error; } } if (old_rw != NULL) *old_rw = sc->sc_cur_regwin; sc->sc_cur_regwin = rw; return 0; } static int bwi_regwin_select(struct bwi_softc *sc, int id) { uint32_t win = BWI_PCIM_REGWIN(id); int i; #define RETRY_MAX 50 for (i = 0; i < RETRY_MAX; ++i) { pci_write_config(sc->sc_dev, BWI_PCIR_SEL_REGWIN, win, 4); if (pci_read_config(sc->sc_dev, BWI_PCIR_SEL_REGWIN, 4) == win) return 0; DELAY(10); } #undef RETRY_MAX return ENXIO; } static void bwi_regwin_info(struct bwi_softc *sc, uint16_t *type, uint8_t *rev) { uint32_t val; val = CSR_READ_4(sc, BWI_ID_HI); *type = BWI_ID_HI_REGWIN_TYPE(val); *rev = BWI_ID_HI_REGWIN_REV(val); DPRINTF(sc, BWI_DBG_ATTACH, "regwin: type 0x%03x, rev %d, " "vendor 0x%04x\n", *type, *rev, __SHIFTOUT(val, BWI_ID_HI_REGWIN_VENDOR_MASK)); } static int bwi_bbp_attach(struct bwi_softc *sc) { #define N(arr) (int)(sizeof(arr) / sizeof(arr[0])) uint16_t bbp_id, rw_type; uint8_t rw_rev; uint32_t info; int error, nregwin, i; /* * Get 0th regwin information * NOTE: 0th regwin should exist */ error = bwi_regwin_select(sc, 0); if (error) { device_printf(sc->sc_dev, "can't select regwin 0\n"); return error; } bwi_regwin_info(sc, &rw_type, &rw_rev); /* * Find out BBP id */ bbp_id = 0; info = 0; if (rw_type == BWI_REGWIN_T_COM) { info = CSR_READ_4(sc, BWI_INFO); bbp_id = __SHIFTOUT(info, BWI_INFO_BBPID_MASK); BWI_CREATE_REGWIN(&sc->sc_com_regwin, 0, rw_type, rw_rev); sc->sc_cap = CSR_READ_4(sc, BWI_CAPABILITY); } else { for (i = 0; i < N(bwi_bbpid_map); ++i) { if (sc->sc_pci_did >= bwi_bbpid_map[i].did_min && sc->sc_pci_did <= bwi_bbpid_map[i].did_max) { bbp_id = bwi_bbpid_map[i].bbp_id; break; } } if (bbp_id == 0) { device_printf(sc->sc_dev, "no BBP id for device id " "0x%04x\n", sc->sc_pci_did); return ENXIO; } info = __SHIFTIN(sc->sc_pci_revid, BWI_INFO_BBPREV_MASK) | __SHIFTIN(0, BWI_INFO_BBPPKG_MASK); } /* * Find out number of regwins */ nregwin = 0; if (rw_type == BWI_REGWIN_T_COM && rw_rev >= 4) { nregwin = __SHIFTOUT(info, BWI_INFO_NREGWIN_MASK); } else { for (i = 0; i < N(bwi_regwin_count); ++i) { if (bwi_regwin_count[i].bbp_id == bbp_id) { nregwin = bwi_regwin_count[i].nregwin; break; } } if (nregwin == 0) { device_printf(sc->sc_dev, "no number of win for " "BBP id 0x%04x\n", bbp_id); return ENXIO; } } /* Record BBP id/rev for later using */ sc->sc_bbp_id = bbp_id; sc->sc_bbp_rev = __SHIFTOUT(info, BWI_INFO_BBPREV_MASK); sc->sc_bbp_pkg = __SHIFTOUT(info, BWI_INFO_BBPPKG_MASK); device_printf(sc->sc_dev, "BBP: id 0x%04x, rev 0x%x, pkg %d\n", sc->sc_bbp_id, sc->sc_bbp_rev, sc->sc_bbp_pkg); DPRINTF(sc, BWI_DBG_ATTACH, "nregwin %d, cap 0x%08x\n", nregwin, sc->sc_cap); /* * Create rest of the regwins */ /* Don't re-create common regwin, if it is already created */ i = BWI_REGWIN_EXIST(&sc->sc_com_regwin) ? 1 : 0; for (; i < nregwin; ++i) { /* * Get regwin information */ error = bwi_regwin_select(sc, i); if (error) { device_printf(sc->sc_dev, "can't select regwin %d\n", i); return error; } bwi_regwin_info(sc, &rw_type, &rw_rev); /* * Try attach: * 1) Bus (PCI/PCIE) regwin * 2) MAC regwin * Ignore rest types of regwin */ if (rw_type == BWI_REGWIN_T_BUSPCI || rw_type == BWI_REGWIN_T_BUSPCIE) { if (BWI_REGWIN_EXIST(&sc->sc_bus_regwin)) { device_printf(sc->sc_dev, "bus regwin already exists\n"); } else { BWI_CREATE_REGWIN(&sc->sc_bus_regwin, i, rw_type, rw_rev); } } else if (rw_type == BWI_REGWIN_T_MAC) { /* XXX ignore return value */ bwi_mac_attach(sc, i, rw_rev); } } /* At least one MAC shold exist */ if (!BWI_REGWIN_EXIST(&sc->sc_mac[0].mac_regwin)) { device_printf(sc->sc_dev, "no MAC was found\n"); return ENXIO; } KASSERT(sc->sc_nmac > 0, ("no mac's")); /* Bus regwin must exist */ if (!BWI_REGWIN_EXIST(&sc->sc_bus_regwin)) { device_printf(sc->sc_dev, "no bus regwin was found\n"); return ENXIO; } /* Start with first MAC */ error = bwi_regwin_switch(sc, &sc->sc_mac[0].mac_regwin, NULL); if (error) return error; return 0; #undef N } int bwi_bus_init(struct bwi_softc *sc, struct bwi_mac *mac) { struct bwi_regwin *old, *bus; uint32_t val; int error; bus = &sc->sc_bus_regwin; KASSERT(sc->sc_cur_regwin == &mac->mac_regwin, ("not cur regwin")); /* * Tell bus to generate requested interrupts */ if (bus->rw_rev < 6 && bus->rw_type == BWI_REGWIN_T_BUSPCI) { /* * NOTE: Read BWI_FLAGS from MAC regwin */ val = CSR_READ_4(sc, BWI_FLAGS); error = bwi_regwin_switch(sc, bus, &old); if (error) return error; CSR_SETBITS_4(sc, BWI_INTRVEC, (val & BWI_FLAGS_INTR_MASK)); } else { uint32_t mac_mask; mac_mask = 1 << mac->mac_id; error = bwi_regwin_switch(sc, bus, &old); if (error) return error; val = pci_read_config(sc->sc_dev, BWI_PCIR_INTCTL, 4); val |= mac_mask << 8; pci_write_config(sc->sc_dev, BWI_PCIR_INTCTL, val, 4); } if (sc->sc_flags & BWI_F_BUS_INITED) goto back; if (bus->rw_type == BWI_REGWIN_T_BUSPCI) { /* * Enable prefetch and burst */ CSR_SETBITS_4(sc, BWI_BUS_CONFIG, BWI_BUS_CONFIG_PREFETCH | BWI_BUS_CONFIG_BURST); if (bus->rw_rev < 5) { struct bwi_regwin *com = &sc->sc_com_regwin; /* * Configure timeouts for bus operation */ /* * Set service timeout and request timeout */ CSR_SETBITS_4(sc, BWI_CONF_LO, __SHIFTIN(BWI_CONF_LO_SERVTO, BWI_CONF_LO_SERVTO_MASK) | __SHIFTIN(BWI_CONF_LO_REQTO, BWI_CONF_LO_REQTO_MASK)); /* * If there is common regwin, we switch to that regwin * and switch back to bus regwin once we have done. */ if (BWI_REGWIN_EXIST(com)) { error = bwi_regwin_switch(sc, com, NULL); if (error) return error; } /* Let bus know what we have changed */ CSR_WRITE_4(sc, BWI_BUS_ADDR, BWI_BUS_ADDR_MAGIC); CSR_READ_4(sc, BWI_BUS_ADDR); /* Flush */ CSR_WRITE_4(sc, BWI_BUS_DATA, 0); CSR_READ_4(sc, BWI_BUS_DATA); /* Flush */ if (BWI_REGWIN_EXIST(com)) { error = bwi_regwin_switch(sc, bus, NULL); if (error) return error; } } else if (bus->rw_rev >= 11) { /* * Enable memory read multiple */ CSR_SETBITS_4(sc, BWI_BUS_CONFIG, BWI_BUS_CONFIG_MRM); } } else { /* TODO:PCIE */ } sc->sc_flags |= BWI_F_BUS_INITED; back: return bwi_regwin_switch(sc, old, NULL); } static void bwi_get_card_flags(struct bwi_softc *sc) { #define PCI_VENDOR_APPLE 0x106b #define PCI_VENDOR_DELL 0x1028 sc->sc_card_flags = bwi_read_sprom(sc, BWI_SPROM_CARD_FLAGS); if (sc->sc_card_flags == 0xffff) sc->sc_card_flags = 0; if (sc->sc_pci_subvid == PCI_VENDOR_DELL && sc->sc_bbp_id == BWI_BBPID_BCM4301 && sc->sc_pci_revid == 0x74) sc->sc_card_flags |= BWI_CARD_F_BT_COEXIST; if (sc->sc_pci_subvid == PCI_VENDOR_APPLE && sc->sc_pci_subdid == 0x4e && /* XXX */ sc->sc_pci_revid > 0x40) sc->sc_card_flags |= BWI_CARD_F_PA_GPIO9; DPRINTF(sc, BWI_DBG_ATTACH, "card flags 0x%04x\n", sc->sc_card_flags); #undef PCI_VENDOR_DELL #undef PCI_VENDOR_APPLE } static void bwi_get_eaddr(struct bwi_softc *sc, uint16_t eaddr_ofs, uint8_t *eaddr) { int i; for (i = 0; i < 3; ++i) { *((uint16_t *)eaddr + i) = htobe16(bwi_read_sprom(sc, eaddr_ofs + 2 * i)); } } static void bwi_get_clock_freq(struct bwi_softc *sc, struct bwi_clock_freq *freq) { struct bwi_regwin *com; uint32_t val; u_int div; int src; bzero(freq, sizeof(*freq)); com = &sc->sc_com_regwin; KASSERT(BWI_REGWIN_EXIST(com), ("regwin does not exist")); KASSERT(sc->sc_cur_regwin == com, ("wrong regwin")); KASSERT(sc->sc_cap & BWI_CAP_CLKMODE, ("wrong clock mode")); /* * Calculate clock frequency */ src = -1; div = 0; if (com->rw_rev < 6) { val = pci_read_config(sc->sc_dev, BWI_PCIR_GPIO_OUT, 4); if (val & BWI_PCIM_GPIO_OUT_CLKSRC) { src = BWI_CLKSRC_PCI; div = 64; } else { src = BWI_CLKSRC_CS_OSC; div = 32; } } else if (com->rw_rev < 10) { val = CSR_READ_4(sc, BWI_CLOCK_CTRL); src = __SHIFTOUT(val, BWI_CLOCK_CTRL_CLKSRC); if (src == BWI_CLKSRC_LP_OSC) { div = 1; } else { div = (__SHIFTOUT(val, BWI_CLOCK_CTRL_FDIV) + 1) << 2; /* Unknown source */ if (src >= BWI_CLKSRC_MAX) src = BWI_CLKSRC_CS_OSC; } } else { val = CSR_READ_4(sc, BWI_CLOCK_INFO); src = BWI_CLKSRC_CS_OSC; div = (__SHIFTOUT(val, BWI_CLOCK_INFO_FDIV) + 1) << 2; } KASSERT(src >= 0 && src < BWI_CLKSRC_MAX, ("bad src %d", src)); KASSERT(div != 0, ("div zero")); DPRINTF(sc, BWI_DBG_ATTACH, "clksrc %s\n", src == BWI_CLKSRC_PCI ? "PCI" : (src == BWI_CLKSRC_LP_OSC ? "LP_OSC" : "CS_OSC")); freq->clkfreq_min = bwi_clkfreq[src].freq_min / div; freq->clkfreq_max = bwi_clkfreq[src].freq_max / div; DPRINTF(sc, BWI_DBG_ATTACH, "clkfreq min %u, max %u\n", freq->clkfreq_min, freq->clkfreq_max); } static int bwi_set_clock_mode(struct bwi_softc *sc, enum bwi_clock_mode clk_mode) { struct bwi_regwin *old, *com; uint32_t clk_ctrl, clk_src; int error, pwr_off = 0; com = &sc->sc_com_regwin; if (!BWI_REGWIN_EXIST(com)) return 0; if (com->rw_rev >= 10 || com->rw_rev < 6) return 0; /* * For common regwin whose rev is [6, 10), the chip * must be capable to change clock mode. */ if ((sc->sc_cap & BWI_CAP_CLKMODE) == 0) return 0; error = bwi_regwin_switch(sc, com, &old); if (error) return error; if (clk_mode == BWI_CLOCK_MODE_FAST) bwi_power_on(sc, 0); /* Don't turn on PLL */ clk_ctrl = CSR_READ_4(sc, BWI_CLOCK_CTRL); clk_src = __SHIFTOUT(clk_ctrl, BWI_CLOCK_CTRL_CLKSRC); switch (clk_mode) { case BWI_CLOCK_MODE_FAST: clk_ctrl &= ~BWI_CLOCK_CTRL_SLOW; clk_ctrl |= BWI_CLOCK_CTRL_IGNPLL; break; case BWI_CLOCK_MODE_SLOW: clk_ctrl |= BWI_CLOCK_CTRL_SLOW; break; case BWI_CLOCK_MODE_DYN: clk_ctrl &= ~(BWI_CLOCK_CTRL_SLOW | BWI_CLOCK_CTRL_IGNPLL | BWI_CLOCK_CTRL_NODYN); if (clk_src != BWI_CLKSRC_CS_OSC) { clk_ctrl |= BWI_CLOCK_CTRL_NODYN; pwr_off = 1; } break; } CSR_WRITE_4(sc, BWI_CLOCK_CTRL, clk_ctrl); if (pwr_off) bwi_power_off(sc, 0); /* Leave PLL as it is */ return bwi_regwin_switch(sc, old, NULL); } static int bwi_set_clock_delay(struct bwi_softc *sc) { struct bwi_regwin *old, *com; int error; com = &sc->sc_com_regwin; if (!BWI_REGWIN_EXIST(com)) return 0; error = bwi_regwin_switch(sc, com, &old); if (error) return error; if (sc->sc_bbp_id == BWI_BBPID_BCM4321) { if (sc->sc_bbp_rev == 0) CSR_WRITE_4(sc, BWI_CONTROL, BWI_CONTROL_MAGIC0); else if (sc->sc_bbp_rev == 1) CSR_WRITE_4(sc, BWI_CONTROL, BWI_CONTROL_MAGIC1); } if (sc->sc_cap & BWI_CAP_CLKMODE) { if (com->rw_rev >= 10) { CSR_FILT_SETBITS_4(sc, BWI_CLOCK_INFO, 0xffff, 0x40000); } else { struct bwi_clock_freq freq; bwi_get_clock_freq(sc, &freq); CSR_WRITE_4(sc, BWI_PLL_ON_DELAY, howmany(freq.clkfreq_max * 150, 1000000)); CSR_WRITE_4(sc, BWI_FREQ_SEL_DELAY, howmany(freq.clkfreq_max * 15, 1000000)); } } return bwi_regwin_switch(sc, old, NULL); } static void bwi_init(void *xsc) { struct bwi_softc *sc = xsc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; BWI_LOCK(sc); bwi_init_statechg(sc, 1); BWI_UNLOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) ieee80211_start_all(ic); /* start all vap's */ } static void bwi_init_statechg(struct bwi_softc *sc, int statechg) { struct ifnet *ifp = sc->sc_ifp; struct bwi_mac *mac; int error; bwi_stop_locked(sc, statechg); bwi_bbp_power_on(sc, BWI_CLOCK_MODE_FAST); /* TODO: 2 MAC */ mac = &sc->sc_mac[0]; error = bwi_regwin_switch(sc, &mac->mac_regwin, NULL); if (error) { if_printf(ifp, "%s: error %d on regwin switch\n", __func__, error); goto bad; } error = bwi_mac_init(mac); if (error) { if_printf(ifp, "%s: error %d on MAC init\n", __func__, error); goto bad; } bwi_bbp_power_on(sc, BWI_CLOCK_MODE_DYN); bwi_set_bssid(sc, bwi_zero_addr); /* Clear BSSID */ bwi_set_addr_filter(sc, BWI_ADDR_FILTER_MYADDR, IF_LLADDR(ifp)); bwi_mac_reset_hwkeys(mac); if ((mac->mac_flags & BWI_MAC_F_HAS_TXSTATS) == 0) { int i; #define NRETRY 1000 /* * Drain any possible pending TX status */ for (i = 0; i < NRETRY; ++i) { if ((CSR_READ_4(sc, BWI_TXSTATUS0) & BWI_TXSTATUS0_VALID) == 0) break; CSR_READ_4(sc, BWI_TXSTATUS1); } if (i == NRETRY) if_printf(ifp, "%s: can't drain TX status\n", __func__); #undef NRETRY } if (mac->mac_phy.phy_mode == IEEE80211_MODE_11G) bwi_mac_updateslot(mac, 1); /* Start MAC */ error = bwi_mac_start(mac); if (error) { if_printf(ifp, "%s: error %d starting MAC\n", __func__, error); goto bad; } /* Clear stop flag before enabling interrupt */ sc->sc_flags &= ~BWI_F_STOP; ifp->if_drv_flags |= IFF_DRV_RUNNING; callout_reset(&sc->sc_watchdog_timer, hz, bwi_watchdog, sc); /* Enable intrs */ bwi_enable_intrs(sc, BWI_INIT_INTRS); return; bad: bwi_stop_locked(sc, 1); } static int bwi_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { #define IS_RUNNING(ifp) \ ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) struct bwi_softc *sc = ifp->if_softc; struct ieee80211com *ic = ifp->if_l2com; struct ifreq *ifr = (struct ifreq *) data; int error = 0, startall = 0; switch (cmd) { case SIOCSIFFLAGS: BWI_LOCK(sc); if (IS_RUNNING(ifp)) { struct bwi_mac *mac; int promisc = -1; KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; if ((ifp->if_flags & IFF_PROMISC) && (sc->sc_flags & BWI_F_PROMISC) == 0) { promisc = 1; sc->sc_flags |= BWI_F_PROMISC; } else if ((ifp->if_flags & IFF_PROMISC) == 0 && (sc->sc_flags & BWI_F_PROMISC)) { promisc = 0; sc->sc_flags &= ~BWI_F_PROMISC; } if (promisc >= 0) bwi_mac_set_promisc(mac, promisc); } if (ifp->if_flags & IFF_UP) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { bwi_init_statechg(sc, 1); startall = 1; } } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) bwi_stop_locked(sc, 1); } BWI_UNLOCK(sc); if (startall) ieee80211_start_all(ic); break; case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); break; case SIOCGIFADDR: error = ether_ioctl(ifp, cmd, data); break; default: error = EINVAL; break; } return error; #undef IS_RUNNING } static void bwi_start(struct ifnet *ifp) { struct bwi_softc *sc = ifp->if_softc; BWI_LOCK(sc); bwi_start_locked(ifp); BWI_UNLOCK(sc); } static void bwi_start_locked(struct ifnet *ifp) { struct bwi_softc *sc = ifp->if_softc; struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[BWI_TX_DATA_RING]; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_key *k; struct mbuf *m; int trans, idx; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; trans = 0; idx = tbd->tbd_idx; while (tbd->tbd_buf[idx].tb_mbuf == NULL) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); /* XXX: LOCK */ if (m == NULL) break; ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { ieee80211_free_node(ni); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } } wh = NULL; /* Catch any invalid use */ if (bwi_encap(sc, idx, m, ni) != 0) { /* 'm' is freed in bwi_encap() if we reach here */ if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } trans = 1; tbd->tbd_used++; idx = (idx + 1) % BWI_TX_NDESC; if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if (tbd->tbd_used + BWI_TX_NSPRDESC >= BWI_TX_NDESC) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } } tbd->tbd_idx = idx; if (trans) sc->sc_tx_timer = 5; } static int bwi_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; struct bwi_softc *sc = ifp->if_softc; /* XXX wme? */ struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[BWI_TX_DATA_RING]; int idx, error; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { ieee80211_free_node(ni); m_freem(m); return ENETDOWN; } BWI_LOCK(sc); idx = tbd->tbd_idx; KASSERT(tbd->tbd_buf[idx].tb_mbuf == NULL, ("slot %d not empty", idx)); if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ error = bwi_encap(sc, idx, m, ni); } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ error = bwi_encap_raw(sc, idx, m, ni, params); } if (error == 0) { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if (++tbd->tbd_used + BWI_TX_NSPRDESC >= BWI_TX_NDESC) ifp->if_drv_flags |= IFF_DRV_OACTIVE; tbd->tbd_idx = (idx + 1) % BWI_TX_NDESC; sc->sc_tx_timer = 5; } else { /* NB: m is reclaimed on encap failure */ ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); } BWI_UNLOCK(sc); return error; } static void bwi_watchdog(void *arg) { struct bwi_softc *sc; struct ifnet *ifp; sc = arg; ifp = sc->sc_ifp; BWI_ASSERT_LOCKED(sc); if (sc->sc_tx_timer != 0 && --sc->sc_tx_timer == 0) { if_printf(ifp, "watchdog timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); taskqueue_enqueue(sc->sc_tq, &sc->sc_restart_task); } callout_reset(&sc->sc_watchdog_timer, hz, bwi_watchdog, sc); } static void bwi_stop(struct bwi_softc *sc, int statechg) { BWI_LOCK(sc); bwi_stop_locked(sc, statechg); BWI_UNLOCK(sc); } static void bwi_stop_locked(struct bwi_softc *sc, int statechg) { struct ifnet *ifp = sc->sc_ifp; struct bwi_mac *mac; int i, error, pwr_off = 0; BWI_ASSERT_LOCKED(sc); callout_stop(&sc->sc_calib_ch); callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; sc->sc_flags |= BWI_F_STOP; if (ifp->if_drv_flags & IFF_DRV_RUNNING) { KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; bwi_disable_intrs(sc, BWI_ALL_INTRS); CSR_READ_4(sc, BWI_MAC_INTR_MASK); bwi_mac_stop(mac); } for (i = 0; i < sc->sc_nmac; ++i) { struct bwi_regwin *old_rw; mac = &sc->sc_mac[i]; if ((mac->mac_flags & BWI_MAC_F_INITED) == 0) continue; error = bwi_regwin_switch(sc, &mac->mac_regwin, &old_rw); if (error) continue; bwi_mac_shutdown(mac); pwr_off = 1; bwi_regwin_switch(sc, old_rw, NULL); } if (pwr_off) bwi_bbp_power_off(sc); sc->sc_tx_timer = 0; callout_stop(&sc->sc_watchdog_timer); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } void bwi_intr(void *xsc) { struct bwi_softc *sc = xsc; struct ifnet *ifp = sc->sc_ifp; struct bwi_mac *mac; uint32_t intr_status; uint32_t txrx_intr_status[BWI_TXRX_NRING]; int i, txrx_error, tx = 0, rx_data = -1; BWI_LOCK(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || (sc->sc_flags & BWI_F_STOP)) { BWI_UNLOCK(sc); return; } /* * Get interrupt status */ intr_status = CSR_READ_4(sc, BWI_MAC_INTR_STATUS); if (intr_status == 0xffffffff) { /* Not for us */ BWI_UNLOCK(sc); return; } DPRINTF(sc, BWI_DBG_INTR, "intr status 0x%08x\n", intr_status); intr_status &= CSR_READ_4(sc, BWI_MAC_INTR_MASK); if (intr_status == 0) { /* Nothing is interesting */ BWI_UNLOCK(sc); return; } KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; txrx_error = 0; DPRINTF(sc, BWI_DBG_INTR, "%s\n", "TX/RX intr"); for (i = 0; i < BWI_TXRX_NRING; ++i) { uint32_t mask; if (BWI_TXRX_IS_RX(i)) mask = BWI_TXRX_RX_INTRS; else mask = BWI_TXRX_TX_INTRS; txrx_intr_status[i] = CSR_READ_4(sc, BWI_TXRX_INTR_STATUS(i)) & mask; _DPRINTF(sc, BWI_DBG_INTR, ", %d 0x%08x", i, txrx_intr_status[i]); if (txrx_intr_status[i] & BWI_TXRX_INTR_ERROR) { if_printf(ifp, "%s: intr fatal TX/RX (%d) error 0x%08x\n", __func__, i, txrx_intr_status[i]); txrx_error = 1; } } _DPRINTF(sc, BWI_DBG_INTR, "%s\n", ""); /* * Acknowledge interrupt */ CSR_WRITE_4(sc, BWI_MAC_INTR_STATUS, intr_status); for (i = 0; i < BWI_TXRX_NRING; ++i) CSR_WRITE_4(sc, BWI_TXRX_INTR_STATUS(i), txrx_intr_status[i]); /* Disable all interrupts */ bwi_disable_intrs(sc, BWI_ALL_INTRS); /* * http://bcm-specs.sipsolutions.net/Interrupts * Says for this bit (0x800): * "Fatal Error * * We got this one while testing things when by accident the * template ram wasn't set to big endian when it should have * been after writing the initial values. It keeps on being * triggered, the only way to stop it seems to shut down the * chip." * * Suggesting that we should never get it and if we do we're not * feeding TX packets into the MAC correctly if we do... Apparently, * it is valid only on mac version 5 and higher, but I couldn't * find a reference for that... Since I see them from time to time * on my card, this suggests an error in the tx path still... */ if (intr_status & BWI_INTR_PHY_TXERR) { if (mac->mac_flags & BWI_MAC_F_PHYE_RESET) { if_printf(ifp, "%s: intr PHY TX error\n", __func__); taskqueue_enqueue(sc->sc_tq, &sc->sc_restart_task); BWI_UNLOCK(sc); return; } } if (txrx_error) { /* TODO: reset device */ } if (intr_status & BWI_INTR_TBTT) bwi_mac_config_ps(mac); if (intr_status & BWI_INTR_EO_ATIM) if_printf(ifp, "EO_ATIM\n"); if (intr_status & BWI_INTR_PMQ) { for (;;) { if ((CSR_READ_4(sc, BWI_MAC_PS_STATUS) & 0x8) == 0) break; } CSR_WRITE_2(sc, BWI_MAC_PS_STATUS, 0x2); } if (intr_status & BWI_INTR_NOISE) if_printf(ifp, "intr noise\n"); if (txrx_intr_status[0] & BWI_TXRX_INTR_RX) { rx_data = sc->sc_rxeof(sc); if (sc->sc_flags & BWI_F_STOP) { BWI_UNLOCK(sc); return; } } if (txrx_intr_status[3] & BWI_TXRX_INTR_RX) { sc->sc_txeof_status(sc); tx = 1; } if (intr_status & BWI_INTR_TX_DONE) { bwi_txeof(sc); tx = 1; } /* Re-enable interrupts */ bwi_enable_intrs(sc, BWI_INIT_INTRS); if (sc->sc_blink_led != NULL && sc->sc_led_blink) { int evt = BWI_LED_EVENT_NONE; if (tx && rx_data > 0) { if (sc->sc_rx_rate > sc->sc_tx_rate) evt = BWI_LED_EVENT_RX; else evt = BWI_LED_EVENT_TX; } else if (tx) { evt = BWI_LED_EVENT_TX; } else if (rx_data > 0) { evt = BWI_LED_EVENT_RX; } else if (rx_data == 0) { evt = BWI_LED_EVENT_POLL; } if (evt != BWI_LED_EVENT_NONE) bwi_led_event(sc, evt); } BWI_UNLOCK(sc); } static void bwi_scan_start(struct ieee80211com *ic) { struct bwi_softc *sc = ic->ic_ifp->if_softc; BWI_LOCK(sc); /* Enable MAC beacon promiscuity */ CSR_SETBITS_4(sc, BWI_MAC_STATUS, BWI_MAC_STATUS_PASS_BCN); BWI_UNLOCK(sc); } static void bwi_set_channel(struct ieee80211com *ic) { struct bwi_softc *sc = ic->ic_ifp->if_softc; struct ieee80211_channel *c = ic->ic_curchan; struct bwi_mac *mac; BWI_LOCK(sc); KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; bwi_rf_set_chan(mac, ieee80211_chan2ieee(ic, c), 0); sc->sc_rates = ieee80211_get_ratetable(c); /* * Setup radio tap channel freq and flags */ sc->sc_tx_th.wt_chan_freq = sc->sc_rx_th.wr_chan_freq = htole16(c->ic_freq); sc->sc_tx_th.wt_chan_flags = sc->sc_rx_th.wr_chan_flags = htole16(c->ic_flags & 0xffff); BWI_UNLOCK(sc); } static void bwi_scan_end(struct ieee80211com *ic) { struct bwi_softc *sc = ic->ic_ifp->if_softc; BWI_LOCK(sc); CSR_CLRBITS_4(sc, BWI_MAC_STATUS, BWI_MAC_STATUS_PASS_BCN); BWI_UNLOCK(sc); } static int bwi_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct bwi_vap *bvp = BWI_VAP(vap); struct ieee80211com *ic= vap->iv_ic; struct ifnet *ifp = ic->ic_ifp; enum ieee80211_state ostate = vap->iv_state; struct bwi_softc *sc = ifp->if_softc; struct bwi_mac *mac; int error; BWI_LOCK(sc); callout_stop(&sc->sc_calib_ch); if (nstate == IEEE80211_S_INIT) sc->sc_txpwrcb_type = BWI_TXPWR_INIT; bwi_led_newstate(sc, nstate); error = bvp->bv_newstate(vap, nstate, arg); if (error != 0) goto back; /* * Clear the BSSID when we stop a STA */ if (vap->iv_opmode == IEEE80211_M_STA) { if (ostate == IEEE80211_S_RUN && nstate != IEEE80211_S_RUN) { /* * Clear out the BSSID. If we reassociate to * the same AP, this will reinialize things * correctly... */ if (ic->ic_opmode == IEEE80211_M_STA && !(sc->sc_flags & BWI_F_STOP)) bwi_set_bssid(sc, bwi_zero_addr); } } if (vap->iv_opmode == IEEE80211_M_MONITOR) { /* Nothing to do */ } else if (nstate == IEEE80211_S_RUN) { bwi_set_bssid(sc, vap->iv_bss->ni_bssid); KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; /* Initial TX power calibration */ bwi_mac_calibrate_txpower(mac, BWI_TXPWR_INIT); #ifdef notyet sc->sc_txpwrcb_type = BWI_TXPWR_FORCE; #else sc->sc_txpwrcb_type = BWI_TXPWR_CALIB; #endif callout_reset(&sc->sc_calib_ch, hz, bwi_calibrate, sc); } back: BWI_UNLOCK(sc); return error; } static int bwi_media_change(struct ifnet *ifp) { int error = ieee80211_media_change(ifp); /* NB: only the fixed rate can change and that doesn't need a reset */ return (error == ENETRESET ? 0 : error); } static int bwi_dma_alloc(struct bwi_softc *sc) { int error, i, has_txstats; bus_addr_t lowaddr = 0; bus_size_t tx_ring_sz, rx_ring_sz, desc_sz = 0; uint32_t txrx_ctrl_step = 0; has_txstats = 0; for (i = 0; i < sc->sc_nmac; ++i) { if (sc->sc_mac[i].mac_flags & BWI_MAC_F_HAS_TXSTATS) { has_txstats = 1; break; } } switch (sc->sc_bus_space) { case BWI_BUS_SPACE_30BIT: case BWI_BUS_SPACE_32BIT: if (sc->sc_bus_space == BWI_BUS_SPACE_30BIT) lowaddr = BWI_BUS_SPACE_MAXADDR; else lowaddr = BUS_SPACE_MAXADDR_32BIT; desc_sz = sizeof(struct bwi_desc32); txrx_ctrl_step = 0x20; sc->sc_init_tx_ring = bwi_init_tx_ring32; sc->sc_free_tx_ring = bwi_free_tx_ring32; sc->sc_init_rx_ring = bwi_init_rx_ring32; sc->sc_free_rx_ring = bwi_free_rx_ring32; sc->sc_setup_rxdesc = bwi_setup_rx_desc32; sc->sc_setup_txdesc = bwi_setup_tx_desc32; sc->sc_rxeof = bwi_rxeof32; sc->sc_start_tx = bwi_start_tx32; if (has_txstats) { sc->sc_init_txstats = bwi_init_txstats32; sc->sc_free_txstats = bwi_free_txstats32; sc->sc_txeof_status = bwi_txeof_status32; } break; case BWI_BUS_SPACE_64BIT: lowaddr = BUS_SPACE_MAXADDR; /* XXX */ desc_sz = sizeof(struct bwi_desc64); txrx_ctrl_step = 0x40; sc->sc_init_tx_ring = bwi_init_tx_ring64; sc->sc_free_tx_ring = bwi_free_tx_ring64; sc->sc_init_rx_ring = bwi_init_rx_ring64; sc->sc_free_rx_ring = bwi_free_rx_ring64; sc->sc_setup_rxdesc = bwi_setup_rx_desc64; sc->sc_setup_txdesc = bwi_setup_tx_desc64; sc->sc_rxeof = bwi_rxeof64; sc->sc_start_tx = bwi_start_tx64; if (has_txstats) { sc->sc_init_txstats = bwi_init_txstats64; sc->sc_free_txstats = bwi_free_txstats64; sc->sc_txeof_status = bwi_txeof_status64; } break; } KASSERT(lowaddr != 0, ("lowaddr zero")); KASSERT(desc_sz != 0, ("desc_sz zero")); KASSERT(txrx_ctrl_step != 0, ("txrx_ctrl_step zero")); tx_ring_sz = roundup(desc_sz * BWI_TX_NDESC, BWI_RING_ALIGN); rx_ring_sz = roundup(desc_sz * BWI_RX_NDESC, BWI_RING_ALIGN); /* * Create top level DMA tag */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), /* parent */ BWI_ALIGN, 0, /* alignment, bounds */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, /* maxsize */ + BUS_SPACE_MAXSIZE, /* maxsize */ BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->sc_parent_dtag); if (error) { device_printf(sc->sc_dev, "can't create parent DMA tag\n"); return error; } #define TXRX_CTRL(idx) (BWI_TXRX_CTRL_BASE + (idx) * txrx_ctrl_step) /* * Create TX ring DMA stuffs */ error = bus_dma_tag_create(sc->sc_parent_dtag, BWI_RING_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, tx_ring_sz, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_txring_dtag); if (error) { device_printf(sc->sc_dev, "can't create TX ring DMA tag\n"); return error; } for (i = 0; i < BWI_TX_NRING; ++i) { error = bwi_dma_ring_alloc(sc, sc->sc_txring_dtag, &sc->sc_tx_rdata[i], tx_ring_sz, TXRX_CTRL(i)); if (error) { device_printf(sc->sc_dev, "%dth TX ring " "DMA alloc failed\n", i); return error; } } /* * Create RX ring DMA stuffs */ error = bus_dma_tag_create(sc->sc_parent_dtag, BWI_RING_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, rx_ring_sz, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_rxring_dtag); if (error) { device_printf(sc->sc_dev, "can't create RX ring DMA tag\n"); return error; } error = bwi_dma_ring_alloc(sc, sc->sc_rxring_dtag, &sc->sc_rx_rdata, rx_ring_sz, TXRX_CTRL(0)); if (error) { device_printf(sc->sc_dev, "RX ring DMA alloc failed\n"); return error; } if (has_txstats) { error = bwi_dma_txstats_alloc(sc, TXRX_CTRL(3), desc_sz); if (error) { device_printf(sc->sc_dev, "TX stats DMA alloc failed\n"); return error; } } #undef TXRX_CTRL return bwi_dma_mbuf_create(sc); } static void bwi_dma_free(struct bwi_softc *sc) { if (sc->sc_txring_dtag != NULL) { int i; for (i = 0; i < BWI_TX_NRING; ++i) { struct bwi_ring_data *rd = &sc->sc_tx_rdata[i]; if (rd->rdata_desc != NULL) { bus_dmamap_unload(sc->sc_txring_dtag, rd->rdata_dmap); bus_dmamem_free(sc->sc_txring_dtag, rd->rdata_desc, rd->rdata_dmap); } } bus_dma_tag_destroy(sc->sc_txring_dtag); } if (sc->sc_rxring_dtag != NULL) { struct bwi_ring_data *rd = &sc->sc_rx_rdata; if (rd->rdata_desc != NULL) { bus_dmamap_unload(sc->sc_rxring_dtag, rd->rdata_dmap); bus_dmamem_free(sc->sc_rxring_dtag, rd->rdata_desc, rd->rdata_dmap); } bus_dma_tag_destroy(sc->sc_rxring_dtag); } bwi_dma_txstats_free(sc); bwi_dma_mbuf_destroy(sc, BWI_TX_NRING, 1); if (sc->sc_parent_dtag != NULL) bus_dma_tag_destroy(sc->sc_parent_dtag); } static int bwi_dma_ring_alloc(struct bwi_softc *sc, bus_dma_tag_t dtag, struct bwi_ring_data *rd, bus_size_t size, uint32_t txrx_ctrl) { int error; error = bus_dmamem_alloc(dtag, &rd->rdata_desc, BUS_DMA_WAITOK | BUS_DMA_ZERO, &rd->rdata_dmap); if (error) { device_printf(sc->sc_dev, "can't allocate DMA mem\n"); return error; } error = bus_dmamap_load(dtag, rd->rdata_dmap, rd->rdata_desc, size, bwi_dma_ring_addr, &rd->rdata_paddr, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "can't load DMA mem\n"); bus_dmamem_free(dtag, rd->rdata_desc, rd->rdata_dmap); rd->rdata_desc = NULL; return error; } rd->rdata_txrx_ctrl = txrx_ctrl; return 0; } static int bwi_dma_txstats_alloc(struct bwi_softc *sc, uint32_t ctrl_base, bus_size_t desc_sz) { struct bwi_txstats_data *st; bus_size_t dma_size; int error; st = malloc(sizeof(*st), M_DEVBUF, M_NOWAIT | M_ZERO); if (st == NULL) { device_printf(sc->sc_dev, "can't allocate txstats data\n"); return ENOMEM; } sc->sc_txstats = st; /* * Create TX stats descriptor DMA stuffs */ dma_size = roundup(desc_sz * BWI_TXSTATS_NDESC, BWI_RING_ALIGN); error = bus_dma_tag_create(sc->sc_parent_dtag, BWI_RING_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, dma_size, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &st->stats_ring_dtag); if (error) { device_printf(sc->sc_dev, "can't create txstats ring " "DMA tag\n"); return error; } error = bus_dmamem_alloc(st->stats_ring_dtag, &st->stats_ring, BUS_DMA_WAITOK | BUS_DMA_ZERO, &st->stats_ring_dmap); if (error) { device_printf(sc->sc_dev, "can't allocate txstats ring " "DMA mem\n"); bus_dma_tag_destroy(st->stats_ring_dtag); st->stats_ring_dtag = NULL; return error; } error = bus_dmamap_load(st->stats_ring_dtag, st->stats_ring_dmap, st->stats_ring, dma_size, bwi_dma_ring_addr, &st->stats_ring_paddr, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "can't load txstats ring DMA mem\n"); bus_dmamem_free(st->stats_ring_dtag, st->stats_ring, st->stats_ring_dmap); bus_dma_tag_destroy(st->stats_ring_dtag); st->stats_ring_dtag = NULL; return error; } /* * Create TX stats DMA stuffs */ dma_size = roundup(sizeof(struct bwi_txstats) * BWI_TXSTATS_NDESC, BWI_ALIGN); error = bus_dma_tag_create(sc->sc_parent_dtag, BWI_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, dma_size, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &st->stats_dtag); if (error) { device_printf(sc->sc_dev, "can't create txstats DMA tag\n"); return error; } error = bus_dmamem_alloc(st->stats_dtag, (void **)&st->stats, BUS_DMA_WAITOK | BUS_DMA_ZERO, &st->stats_dmap); if (error) { device_printf(sc->sc_dev, "can't allocate txstats DMA mem\n"); bus_dma_tag_destroy(st->stats_dtag); st->stats_dtag = NULL; return error; } error = bus_dmamap_load(st->stats_dtag, st->stats_dmap, st->stats, dma_size, bwi_dma_ring_addr, &st->stats_paddr, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "can't load txstats DMA mem\n"); bus_dmamem_free(st->stats_dtag, st->stats, st->stats_dmap); bus_dma_tag_destroy(st->stats_dtag); st->stats_dtag = NULL; return error; } st->stats_ctrl_base = ctrl_base; return 0; } static void bwi_dma_txstats_free(struct bwi_softc *sc) { struct bwi_txstats_data *st; if (sc->sc_txstats == NULL) return; st = sc->sc_txstats; if (st->stats_ring_dtag != NULL) { bus_dmamap_unload(st->stats_ring_dtag, st->stats_ring_dmap); bus_dmamem_free(st->stats_ring_dtag, st->stats_ring, st->stats_ring_dmap); bus_dma_tag_destroy(st->stats_ring_dtag); } if (st->stats_dtag != NULL) { bus_dmamap_unload(st->stats_dtag, st->stats_dmap); bus_dmamem_free(st->stats_dtag, st->stats, st->stats_dmap); bus_dma_tag_destroy(st->stats_dtag); } free(st, M_DEVBUF); } static void bwi_dma_ring_addr(void *arg, bus_dma_segment_t *seg, int nseg, int error) { KASSERT(nseg == 1, ("too many segments\n")); *((bus_addr_t *)arg) = seg->ds_addr; } static int bwi_dma_mbuf_create(struct bwi_softc *sc) { struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; int i, j, k, ntx, error; /* * Create TX/RX mbuf DMA tag */ error = bus_dma_tag_create(sc->sc_parent_dtag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_buf_dtag); if (error) { device_printf(sc->sc_dev, "can't create mbuf DMA tag\n"); return error; } ntx = 0; /* * Create TX mbuf DMA map */ for (i = 0; i < BWI_TX_NRING; ++i) { struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[i]; for (j = 0; j < BWI_TX_NDESC; ++j) { error = bus_dmamap_create(sc->sc_buf_dtag, 0, &tbd->tbd_buf[j].tb_dmap); if (error) { device_printf(sc->sc_dev, "can't create " "%dth tbd, %dth DMA map\n", i, j); ntx = i; for (k = 0; k < j; ++k) { bus_dmamap_destroy(sc->sc_buf_dtag, tbd->tbd_buf[k].tb_dmap); } goto fail; } } } ntx = BWI_TX_NRING; /* * Create RX mbuf DMA map and a spare DMA map */ error = bus_dmamap_create(sc->sc_buf_dtag, 0, &rbd->rbd_tmp_dmap); if (error) { device_printf(sc->sc_dev, "can't create spare RX buf DMA map\n"); goto fail; } for (j = 0; j < BWI_RX_NDESC; ++j) { error = bus_dmamap_create(sc->sc_buf_dtag, 0, &rbd->rbd_buf[j].rb_dmap); if (error) { device_printf(sc->sc_dev, "can't create %dth " "RX buf DMA map\n", j); for (k = 0; k < j; ++k) { bus_dmamap_destroy(sc->sc_buf_dtag, rbd->rbd_buf[j].rb_dmap); } bus_dmamap_destroy(sc->sc_buf_dtag, rbd->rbd_tmp_dmap); goto fail; } } return 0; fail: bwi_dma_mbuf_destroy(sc, ntx, 0); return error; } static void bwi_dma_mbuf_destroy(struct bwi_softc *sc, int ntx, int nrx) { int i, j; if (sc->sc_buf_dtag == NULL) return; for (i = 0; i < ntx; ++i) { struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[i]; for (j = 0; j < BWI_TX_NDESC; ++j) { struct bwi_txbuf *tb = &tbd->tbd_buf[j]; if (tb->tb_mbuf != NULL) { bus_dmamap_unload(sc->sc_buf_dtag, tb->tb_dmap); m_freem(tb->tb_mbuf); } if (tb->tb_ni != NULL) ieee80211_free_node(tb->tb_ni); bus_dmamap_destroy(sc->sc_buf_dtag, tb->tb_dmap); } } if (nrx) { struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; bus_dmamap_destroy(sc->sc_buf_dtag, rbd->rbd_tmp_dmap); for (j = 0; j < BWI_RX_NDESC; ++j) { struct bwi_rxbuf *rb = &rbd->rbd_buf[j]; if (rb->rb_mbuf != NULL) { bus_dmamap_unload(sc->sc_buf_dtag, rb->rb_dmap); m_freem(rb->rb_mbuf); } bus_dmamap_destroy(sc->sc_buf_dtag, rb->rb_dmap); } } bus_dma_tag_destroy(sc->sc_buf_dtag); sc->sc_buf_dtag = NULL; } static void bwi_enable_intrs(struct bwi_softc *sc, uint32_t enable_intrs) { CSR_SETBITS_4(sc, BWI_MAC_INTR_MASK, enable_intrs); } static void bwi_disable_intrs(struct bwi_softc *sc, uint32_t disable_intrs) { CSR_CLRBITS_4(sc, BWI_MAC_INTR_MASK, disable_intrs); } static int bwi_init_tx_ring32(struct bwi_softc *sc, int ring_idx) { struct bwi_ring_data *rd; struct bwi_txbuf_data *tbd; uint32_t val, addr_hi, addr_lo; KASSERT(ring_idx < BWI_TX_NRING, ("ring_idx %d", ring_idx)); rd = &sc->sc_tx_rdata[ring_idx]; tbd = &sc->sc_tx_bdata[ring_idx]; tbd->tbd_idx = 0; tbd->tbd_used = 0; bzero(rd->rdata_desc, sizeof(struct bwi_desc32) * BWI_TX_NDESC); bus_dmamap_sync(sc->sc_txring_dtag, rd->rdata_dmap, BUS_DMASYNC_PREWRITE); addr_lo = __SHIFTOUT(rd->rdata_paddr, BWI_TXRX32_RINGINFO_ADDR_MASK); addr_hi = __SHIFTOUT(rd->rdata_paddr, BWI_TXRX32_RINGINFO_FUNC_MASK); val = __SHIFTIN(addr_lo, BWI_TXRX32_RINGINFO_ADDR_MASK) | __SHIFTIN(BWI_TXRX32_RINGINFO_FUNC_TXRX, BWI_TXRX32_RINGINFO_FUNC_MASK); CSR_WRITE_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_RINGINFO, val); val = __SHIFTIN(addr_hi, BWI_TXRX32_CTRL_ADDRHI_MASK) | BWI_TXRX32_CTRL_ENABLE; CSR_WRITE_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_CTRL, val); return 0; } static void bwi_init_rxdesc_ring32(struct bwi_softc *sc, uint32_t ctrl_base, bus_addr_t paddr, int hdr_size, int ndesc) { uint32_t val, addr_hi, addr_lo; addr_lo = __SHIFTOUT(paddr, BWI_TXRX32_RINGINFO_ADDR_MASK); addr_hi = __SHIFTOUT(paddr, BWI_TXRX32_RINGINFO_FUNC_MASK); val = __SHIFTIN(addr_lo, BWI_TXRX32_RINGINFO_ADDR_MASK) | __SHIFTIN(BWI_TXRX32_RINGINFO_FUNC_TXRX, BWI_TXRX32_RINGINFO_FUNC_MASK); CSR_WRITE_4(sc, ctrl_base + BWI_RX32_RINGINFO, val); val = __SHIFTIN(hdr_size, BWI_RX32_CTRL_HDRSZ_MASK) | __SHIFTIN(addr_hi, BWI_TXRX32_CTRL_ADDRHI_MASK) | BWI_TXRX32_CTRL_ENABLE; CSR_WRITE_4(sc, ctrl_base + BWI_RX32_CTRL, val); CSR_WRITE_4(sc, ctrl_base + BWI_RX32_INDEX, (ndesc - 1) * sizeof(struct bwi_desc32)); } static int bwi_init_rx_ring32(struct bwi_softc *sc) { struct bwi_ring_data *rd = &sc->sc_rx_rdata; int i, error; sc->sc_rx_bdata.rbd_idx = 0; for (i = 0; i < BWI_RX_NDESC; ++i) { error = bwi_newbuf(sc, i, 1); if (error) { device_printf(sc->sc_dev, "can't allocate %dth RX buffer\n", i); return error; } } bus_dmamap_sync(sc->sc_rxring_dtag, rd->rdata_dmap, BUS_DMASYNC_PREWRITE); bwi_init_rxdesc_ring32(sc, rd->rdata_txrx_ctrl, rd->rdata_paddr, sizeof(struct bwi_rxbuf_hdr), BWI_RX_NDESC); return 0; } static int bwi_init_txstats32(struct bwi_softc *sc) { struct bwi_txstats_data *st = sc->sc_txstats; bus_addr_t stats_paddr; int i; bzero(st->stats, BWI_TXSTATS_NDESC * sizeof(struct bwi_txstats)); bus_dmamap_sync(st->stats_dtag, st->stats_dmap, BUS_DMASYNC_PREWRITE); st->stats_idx = 0; stats_paddr = st->stats_paddr; for (i = 0; i < BWI_TXSTATS_NDESC; ++i) { bwi_setup_desc32(sc, st->stats_ring, BWI_TXSTATS_NDESC, i, stats_paddr, sizeof(struct bwi_txstats), 0); stats_paddr += sizeof(struct bwi_txstats); } bus_dmamap_sync(st->stats_ring_dtag, st->stats_ring_dmap, BUS_DMASYNC_PREWRITE); bwi_init_rxdesc_ring32(sc, st->stats_ctrl_base, st->stats_ring_paddr, 0, BWI_TXSTATS_NDESC); return 0; } static void bwi_setup_rx_desc32(struct bwi_softc *sc, int buf_idx, bus_addr_t paddr, int buf_len) { struct bwi_ring_data *rd = &sc->sc_rx_rdata; KASSERT(buf_idx < BWI_RX_NDESC, ("buf_idx %d", buf_idx)); bwi_setup_desc32(sc, rd->rdata_desc, BWI_RX_NDESC, buf_idx, paddr, buf_len, 0); } static void bwi_setup_tx_desc32(struct bwi_softc *sc, struct bwi_ring_data *rd, int buf_idx, bus_addr_t paddr, int buf_len) { KASSERT(buf_idx < BWI_TX_NDESC, ("buf_idx %d", buf_idx)); bwi_setup_desc32(sc, rd->rdata_desc, BWI_TX_NDESC, buf_idx, paddr, buf_len, 1); } static int bwi_init_tx_ring64(struct bwi_softc *sc, int ring_idx) { /* TODO:64 */ return EOPNOTSUPP; } static int bwi_init_rx_ring64(struct bwi_softc *sc) { /* TODO:64 */ return EOPNOTSUPP; } static int bwi_init_txstats64(struct bwi_softc *sc) { /* TODO:64 */ return EOPNOTSUPP; } static void bwi_setup_rx_desc64(struct bwi_softc *sc, int buf_idx, bus_addr_t paddr, int buf_len) { /* TODO:64 */ } static void bwi_setup_tx_desc64(struct bwi_softc *sc, struct bwi_ring_data *rd, int buf_idx, bus_addr_t paddr, int buf_len) { /* TODO:64 */ } static void bwi_dma_buf_addr(void *arg, bus_dma_segment_t *seg, int nseg, bus_size_t mapsz __unused, int error) { if (!error) { KASSERT(nseg == 1, ("too many segments(%d)\n", nseg)); *((bus_addr_t *)arg) = seg->ds_addr; } } static int bwi_newbuf(struct bwi_softc *sc, int buf_idx, int init) { struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; struct bwi_rxbuf *rxbuf = &rbd->rbd_buf[buf_idx]; struct bwi_rxbuf_hdr *hdr; bus_dmamap_t map; bus_addr_t paddr; struct mbuf *m; int error; KASSERT(buf_idx < BWI_RX_NDESC, ("buf_idx %d", buf_idx)); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { error = ENOBUFS; /* * If the NIC is up and running, we need to: * - Clear RX buffer's header. * - Restore RX descriptor settings. */ if (init) return error; else goto back; } m->m_len = m->m_pkthdr.len = MCLBYTES; /* * Try to load RX buf into temporary DMA map */ error = bus_dmamap_load_mbuf(sc->sc_buf_dtag, rbd->rbd_tmp_dmap, m, bwi_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error) { m_freem(m); /* * See the comment above */ if (init) return error; else goto back; } if (!init) bus_dmamap_unload(sc->sc_buf_dtag, rxbuf->rb_dmap); rxbuf->rb_mbuf = m; rxbuf->rb_paddr = paddr; /* * Swap RX buf's DMA map with the loaded temporary one */ map = rxbuf->rb_dmap; rxbuf->rb_dmap = rbd->rbd_tmp_dmap; rbd->rbd_tmp_dmap = map; back: /* * Clear RX buf header */ hdr = mtod(rxbuf->rb_mbuf, struct bwi_rxbuf_hdr *); bzero(hdr, sizeof(*hdr)); bus_dmamap_sync(sc->sc_buf_dtag, rxbuf->rb_dmap, BUS_DMASYNC_PREWRITE); /* * Setup RX buf descriptor */ sc->sc_setup_rxdesc(sc, buf_idx, rxbuf->rb_paddr, rxbuf->rb_mbuf->m_len - sizeof(*hdr)); return error; } static void bwi_set_addr_filter(struct bwi_softc *sc, uint16_t addr_ofs, const uint8_t *addr) { int i; CSR_WRITE_2(sc, BWI_ADDR_FILTER_CTRL, BWI_ADDR_FILTER_CTRL_SET | addr_ofs); for (i = 0; i < (IEEE80211_ADDR_LEN / 2); ++i) { uint16_t addr_val; addr_val = (uint16_t)addr[i * 2] | (((uint16_t)addr[(i * 2) + 1]) << 8); CSR_WRITE_2(sc, BWI_ADDR_FILTER_DATA, addr_val); } } static int bwi_rxeof(struct bwi_softc *sc, int end_idx) { struct bwi_ring_data *rd = &sc->sc_rx_rdata; struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; int idx, rx_data = 0; idx = rbd->rbd_idx; while (idx != end_idx) { struct bwi_rxbuf *rb = &rbd->rbd_buf[idx]; struct bwi_rxbuf_hdr *hdr; struct ieee80211_frame_min *wh; struct ieee80211_node *ni; struct mbuf *m; uint32_t plcp; uint16_t flags2; int buflen, wh_ofs, hdr_extra, rssi, noise, type, rate; m = rb->rb_mbuf; bus_dmamap_sync(sc->sc_buf_dtag, rb->rb_dmap, BUS_DMASYNC_POSTREAD); if (bwi_newbuf(sc, idx, 0)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); goto next; } hdr = mtod(m, struct bwi_rxbuf_hdr *); flags2 = le16toh(hdr->rxh_flags2); hdr_extra = 0; if (flags2 & BWI_RXH_F2_TYPE2FRAME) hdr_extra = 2; wh_ofs = hdr_extra + 6; /* XXX magic number */ buflen = le16toh(hdr->rxh_buflen); if (buflen < BWI_FRAME_MIN_LEN(wh_ofs)) { if_printf(ifp, "%s: zero length data, hdr_extra %d\n", __func__, hdr_extra); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); goto next; } bcopy((uint8_t *)(hdr + 1) + hdr_extra, &plcp, sizeof(plcp)); rssi = bwi_calc_rssi(sc, hdr); noise = bwi_calc_noise(sc); m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = buflen + sizeof(*hdr); m_adj(m, sizeof(*hdr) + wh_ofs); if (htole16(hdr->rxh_flags1) & BWI_RXH_F1_OFDM) rate = bwi_plcp2rate(plcp, IEEE80211_T_OFDM); else rate = bwi_plcp2rate(plcp, IEEE80211_T_CCK); /* RX radio tap */ if (ieee80211_radiotap_active(ic)) bwi_rx_radiotap(sc, m, hdr, &plcp, rate, rssi, noise); m_adj(m, -IEEE80211_CRC_LEN); BWI_UNLOCK(sc); wh = mtod(m, struct ieee80211_frame_min *); ni = ieee80211_find_rxnode(ic, wh); if (ni != NULL) { type = ieee80211_input(ni, m, rssi - noise, noise); ieee80211_free_node(ni); } else type = ieee80211_input_all(ic, m, rssi - noise, noise); if (type == IEEE80211_FC0_TYPE_DATA) { rx_data = 1; sc->sc_rx_rate = rate; } BWI_LOCK(sc); next: idx = (idx + 1) % BWI_RX_NDESC; if (sc->sc_flags & BWI_F_STOP) { /* * Take the fast lane, don't do * any damage to softc */ return -1; } } rbd->rbd_idx = idx; bus_dmamap_sync(sc->sc_rxring_dtag, rd->rdata_dmap, BUS_DMASYNC_PREWRITE); return rx_data; } static int bwi_rxeof32(struct bwi_softc *sc) { uint32_t val, rx_ctrl; int end_idx, rx_data; rx_ctrl = sc->sc_rx_rdata.rdata_txrx_ctrl; val = CSR_READ_4(sc, rx_ctrl + BWI_RX32_STATUS); end_idx = __SHIFTOUT(val, BWI_RX32_STATUS_INDEX_MASK) / sizeof(struct bwi_desc32); rx_data = bwi_rxeof(sc, end_idx); if (rx_data >= 0) { CSR_WRITE_4(sc, rx_ctrl + BWI_RX32_INDEX, end_idx * sizeof(struct bwi_desc32)); } return rx_data; } static int bwi_rxeof64(struct bwi_softc *sc) { /* TODO:64 */ return 0; } static void bwi_reset_rx_ring32(struct bwi_softc *sc, uint32_t rx_ctrl) { int i; CSR_WRITE_4(sc, rx_ctrl + BWI_RX32_CTRL, 0); #define NRETRY 10 for (i = 0; i < NRETRY; ++i) { uint32_t status; status = CSR_READ_4(sc, rx_ctrl + BWI_RX32_STATUS); if (__SHIFTOUT(status, BWI_RX32_STATUS_STATE_MASK) == BWI_RX32_STATUS_STATE_DISABLED) break; DELAY(1000); } if (i == NRETRY) device_printf(sc->sc_dev, "reset rx ring timedout\n"); #undef NRETRY CSR_WRITE_4(sc, rx_ctrl + BWI_RX32_RINGINFO, 0); } static void bwi_free_txstats32(struct bwi_softc *sc) { bwi_reset_rx_ring32(sc, sc->sc_txstats->stats_ctrl_base); } static void bwi_free_rx_ring32(struct bwi_softc *sc) { struct bwi_ring_data *rd = &sc->sc_rx_rdata; struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata; int i; bwi_reset_rx_ring32(sc, rd->rdata_txrx_ctrl); for (i = 0; i < BWI_RX_NDESC; ++i) { struct bwi_rxbuf *rb = &rbd->rbd_buf[i]; if (rb->rb_mbuf != NULL) { bus_dmamap_unload(sc->sc_buf_dtag, rb->rb_dmap); m_freem(rb->rb_mbuf); rb->rb_mbuf = NULL; } } } static void bwi_free_tx_ring32(struct bwi_softc *sc, int ring_idx) { struct bwi_ring_data *rd; struct bwi_txbuf_data *tbd; struct ifnet *ifp = sc->sc_ifp; uint32_t state, val; int i; KASSERT(ring_idx < BWI_TX_NRING, ("ring_idx %d", ring_idx)); rd = &sc->sc_tx_rdata[ring_idx]; tbd = &sc->sc_tx_bdata[ring_idx]; #define NRETRY 10 for (i = 0; i < NRETRY; ++i) { val = CSR_READ_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_STATUS); state = __SHIFTOUT(val, BWI_TX32_STATUS_STATE_MASK); if (state == BWI_TX32_STATUS_STATE_DISABLED || state == BWI_TX32_STATUS_STATE_IDLE || state == BWI_TX32_STATUS_STATE_STOPPED) break; DELAY(1000); } if (i == NRETRY) { if_printf(ifp, "%s: wait for TX ring(%d) stable timed out\n", __func__, ring_idx); } CSR_WRITE_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_CTRL, 0); for (i = 0; i < NRETRY; ++i) { val = CSR_READ_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_STATUS); state = __SHIFTOUT(val, BWI_TX32_STATUS_STATE_MASK); if (state == BWI_TX32_STATUS_STATE_DISABLED) break; DELAY(1000); } if (i == NRETRY) if_printf(ifp, "%s: reset TX ring (%d) timed out\n", __func__, ring_idx); #undef NRETRY DELAY(1000); CSR_WRITE_4(sc, rd->rdata_txrx_ctrl + BWI_TX32_RINGINFO, 0); for (i = 0; i < BWI_TX_NDESC; ++i) { struct bwi_txbuf *tb = &tbd->tbd_buf[i]; if (tb->tb_mbuf != NULL) { bus_dmamap_unload(sc->sc_buf_dtag, tb->tb_dmap); m_freem(tb->tb_mbuf); tb->tb_mbuf = NULL; } if (tb->tb_ni != NULL) { ieee80211_free_node(tb->tb_ni); tb->tb_ni = NULL; } } } static void bwi_free_txstats64(struct bwi_softc *sc) { /* TODO:64 */ } static void bwi_free_rx_ring64(struct bwi_softc *sc) { /* TODO:64 */ } static void bwi_free_tx_ring64(struct bwi_softc *sc, int ring_idx) { /* TODO:64 */ } /* XXX does not belong here */ #define IEEE80211_OFDM_PLCP_RATE_MASK __BITS(3, 0) #define IEEE80211_OFDM_PLCP_LEN_MASK __BITS(16, 5) static __inline void bwi_ofdm_plcp_header(uint32_t *plcp0, int pkt_len, uint8_t rate) { uint32_t plcp; plcp = __SHIFTIN(ieee80211_rate2plcp(rate, IEEE80211_T_OFDM), IEEE80211_OFDM_PLCP_RATE_MASK) | __SHIFTIN(pkt_len, IEEE80211_OFDM_PLCP_LEN_MASK); *plcp0 = htole32(plcp); } static __inline void bwi_ds_plcp_header(struct ieee80211_ds_plcp_hdr *plcp, int pkt_len, uint8_t rate) { int len, service, pkt_bitlen; pkt_bitlen = pkt_len * NBBY; len = howmany(pkt_bitlen * 2, rate); service = IEEE80211_PLCP_SERVICE_LOCKED; if (rate == (11 * 2)) { int pkt_bitlen1; /* * PLCP service field needs to be adjusted, * if TX rate is 11Mbytes/s */ pkt_bitlen1 = len * 11; if (pkt_bitlen1 - pkt_bitlen >= NBBY) service |= IEEE80211_PLCP_SERVICE_LENEXT7; } plcp->i_signal = ieee80211_rate2plcp(rate, IEEE80211_T_CCK); plcp->i_service = service; plcp->i_length = htole16(len); /* NOTE: do NOT touch i_crc */ } static __inline void bwi_plcp_header(const struct ieee80211_rate_table *rt, void *plcp, int pkt_len, uint8_t rate) { enum ieee80211_phytype modtype; /* * Assume caller has zeroed 'plcp' */ modtype = ieee80211_rate2phytype(rt, rate); if (modtype == IEEE80211_T_OFDM) bwi_ofdm_plcp_header(plcp, pkt_len, rate); else if (modtype == IEEE80211_T_DS) bwi_ds_plcp_header(plcp, pkt_len, rate); else panic("unsupport modulation type %u\n", modtype); } static int bwi_encap(struct bwi_softc *sc, int idx, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; struct bwi_ring_data *rd = &sc->sc_tx_rdata[BWI_TX_DATA_RING]; struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[BWI_TX_DATA_RING]; struct bwi_txbuf *tb = &tbd->tbd_buf[idx]; struct bwi_mac *mac; struct bwi_txbuf_hdr *hdr; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp; uint8_t rate, rate_fb; uint32_t mac_ctrl; uint16_t phy_ctrl; bus_addr_t paddr; int type, ismcast, pkt_len, error, rix; #if 0 const uint8_t *p; int i; #endif KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); /* Get 802.11 frame len before prepending TX header */ pkt_len = m->m_pkthdr.len + IEEE80211_CRC_LEN; /* * Find TX rate */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (type != IEEE80211_FC0_TYPE_DATA || (m->m_flags & M_EAPOL)) { rate = rate_fb = tp->mgmtrate; } else if (ismcast) { rate = rate_fb = tp->mcastrate; } else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { rate = rate_fb = tp->ucastrate; } else { rix = ieee80211_ratectl_rate(ni, NULL, pkt_len); rate = ni->ni_txrate; if (rix > 0) { rate_fb = ni->ni_rates.rs_rates[rix-1] & IEEE80211_RATE_VAL; } else { rate_fb = rate; } } tb->tb_rate[0] = rate; tb->tb_rate[1] = rate_fb; sc->sc_tx_rate = rate; /* * TX radio tap */ if (ieee80211_radiotap_active_vap(vap)) { sc->sc_tx_th.wt_flags = 0; if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_DS && (ic->ic_flags & IEEE80211_F_SHPREAMBLE) && rate != (1 * 2)) { sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; } sc->sc_tx_th.wt_rate = rate; ieee80211_radiotap_tx(vap, m); } /* * Setup the embedded TX header */ M_PREPEND(m, sizeof(*hdr), M_NOWAIT); if (m == NULL) { if_printf(ifp, "%s: prepend TX header failed\n", __func__); return ENOBUFS; } hdr = mtod(m, struct bwi_txbuf_hdr *); bzero(hdr, sizeof(*hdr)); bcopy(wh->i_fc, hdr->txh_fc, sizeof(hdr->txh_fc)); bcopy(wh->i_addr1, hdr->txh_addr1, sizeof(hdr->txh_addr1)); if (!ismcast) { uint16_t dur; dur = ieee80211_ack_duration(sc->sc_rates, rate, ic->ic_flags & ~IEEE80211_F_SHPREAMBLE); hdr->txh_fb_duration = htole16(dur); } hdr->txh_id = __SHIFTIN(BWI_TX_DATA_RING, BWI_TXH_ID_RING_MASK) | __SHIFTIN(idx, BWI_TXH_ID_IDX_MASK); bwi_plcp_header(sc->sc_rates, hdr->txh_plcp, pkt_len, rate); bwi_plcp_header(sc->sc_rates, hdr->txh_fb_plcp, pkt_len, rate_fb); phy_ctrl = __SHIFTIN(mac->mac_rf.rf_ant_mode, BWI_TXH_PHY_C_ANTMODE_MASK); if (ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) phy_ctrl |= BWI_TXH_PHY_C_OFDM; else if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && rate != (2 * 1)) phy_ctrl |= BWI_TXH_PHY_C_SHPREAMBLE; mac_ctrl = BWI_TXH_MAC_C_HWSEQ | BWI_TXH_MAC_C_FIRST_FRAG; if (!ismcast) mac_ctrl |= BWI_TXH_MAC_C_ACK; if (ieee80211_rate2phytype(sc->sc_rates, rate_fb) == IEEE80211_T_OFDM) mac_ctrl |= BWI_TXH_MAC_C_FB_OFDM; hdr->txh_mac_ctrl = htole32(mac_ctrl); hdr->txh_phy_ctrl = htole16(phy_ctrl); /* Catch any further usage */ hdr = NULL; wh = NULL; /* DMA load */ error = bus_dmamap_load_mbuf(sc->sc_buf_dtag, tb->tb_dmap, m, bwi_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error && error != EFBIG) { if_printf(ifp, "%s: can't load TX buffer (1) %d\n", __func__, error); goto back; } if (error) { /* error == EFBIG */ struct mbuf *m_new; m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { if_printf(ifp, "%s: can't defrag TX buffer\n", __func__); error = ENOBUFS; goto back; } else { m = m_new; } error = bus_dmamap_load_mbuf(sc->sc_buf_dtag, tb->tb_dmap, m, bwi_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error) { if_printf(ifp, "%s: can't load TX buffer (2) %d\n", __func__, error); goto back; } } error = 0; bus_dmamap_sync(sc->sc_buf_dtag, tb->tb_dmap, BUS_DMASYNC_PREWRITE); tb->tb_mbuf = m; tb->tb_ni = ni; #if 0 p = mtod(m, const uint8_t *); for (i = 0; i < m->m_pkthdr.len; ++i) { if (i != 0 && i % 8 == 0) printf("\n"); printf("%02x ", p[i]); } printf("\n"); #endif DPRINTF(sc, BWI_DBG_TX, "idx %d, pkt_len %d, buflen %d\n", idx, pkt_len, m->m_pkthdr.len); /* Setup TX descriptor */ sc->sc_setup_txdesc(sc, rd, idx, paddr, m->m_pkthdr.len); bus_dmamap_sync(sc->sc_txring_dtag, rd->rdata_dmap, BUS_DMASYNC_PREWRITE); /* Kick start */ sc->sc_start_tx(sc, rd->rdata_txrx_ctrl, idx); back: if (error) m_freem(m); return error; } static int bwi_encap_raw(struct bwi_softc *sc, int idx, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ifnet *ifp = sc->sc_ifp; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct bwi_ring_data *rd = &sc->sc_tx_rdata[BWI_TX_DATA_RING]; struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[BWI_TX_DATA_RING]; struct bwi_txbuf *tb = &tbd->tbd_buf[idx]; struct bwi_mac *mac; struct bwi_txbuf_hdr *hdr; struct ieee80211_frame *wh; uint8_t rate, rate_fb; uint32_t mac_ctrl; uint16_t phy_ctrl; bus_addr_t paddr; int ismcast, pkt_len, error; KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; wh = mtod(m, struct ieee80211_frame *); ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); /* Get 802.11 frame len before prepending TX header */ pkt_len = m->m_pkthdr.len + IEEE80211_CRC_LEN; /* * Find TX rate */ rate = params->ibp_rate0; if (!ieee80211_isratevalid(ic->ic_rt, rate)) { /* XXX fall back to mcast/mgmt rate? */ m_freem(m); return EINVAL; } if (params->ibp_try1 != 0) { rate_fb = params->ibp_rate1; if (!ieee80211_isratevalid(ic->ic_rt, rate_fb)) { /* XXX fall back to rate0? */ m_freem(m); return EINVAL; } } else rate_fb = rate; tb->tb_rate[0] = rate; tb->tb_rate[1] = rate_fb; sc->sc_tx_rate = rate; /* * TX radio tap */ if (ieee80211_radiotap_active_vap(vap)) { sc->sc_tx_th.wt_flags = 0; /* XXX IEEE80211_BPF_CRYPTO */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (params->ibp_flags & IEEE80211_BPF_SHORTPRE) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; sc->sc_tx_th.wt_rate = rate; ieee80211_radiotap_tx(vap, m); } /* * Setup the embedded TX header */ M_PREPEND(m, sizeof(*hdr), M_NOWAIT); if (m == NULL) { if_printf(ifp, "%s: prepend TX header failed\n", __func__); return ENOBUFS; } hdr = mtod(m, struct bwi_txbuf_hdr *); bzero(hdr, sizeof(*hdr)); bcopy(wh->i_fc, hdr->txh_fc, sizeof(hdr->txh_fc)); bcopy(wh->i_addr1, hdr->txh_addr1, sizeof(hdr->txh_addr1)); mac_ctrl = BWI_TXH_MAC_C_HWSEQ | BWI_TXH_MAC_C_FIRST_FRAG; if (!ismcast && (params->ibp_flags & IEEE80211_BPF_NOACK) == 0) { uint16_t dur; dur = ieee80211_ack_duration(sc->sc_rates, rate_fb, 0); hdr->txh_fb_duration = htole16(dur); mac_ctrl |= BWI_TXH_MAC_C_ACK; } hdr->txh_id = __SHIFTIN(BWI_TX_DATA_RING, BWI_TXH_ID_RING_MASK) | __SHIFTIN(idx, BWI_TXH_ID_IDX_MASK); bwi_plcp_header(sc->sc_rates, hdr->txh_plcp, pkt_len, rate); bwi_plcp_header(sc->sc_rates, hdr->txh_fb_plcp, pkt_len, rate_fb); phy_ctrl = __SHIFTIN(mac->mac_rf.rf_ant_mode, BWI_TXH_PHY_C_ANTMODE_MASK); if (ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) { phy_ctrl |= BWI_TXH_PHY_C_OFDM; mac_ctrl |= BWI_TXH_MAC_C_FB_OFDM; } else if (params->ibp_flags & IEEE80211_BPF_SHORTPRE) phy_ctrl |= BWI_TXH_PHY_C_SHPREAMBLE; hdr->txh_mac_ctrl = htole32(mac_ctrl); hdr->txh_phy_ctrl = htole16(phy_ctrl); /* Catch any further usage */ hdr = NULL; wh = NULL; /* DMA load */ error = bus_dmamap_load_mbuf(sc->sc_buf_dtag, tb->tb_dmap, m, bwi_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0) { struct mbuf *m_new; if (error != EFBIG) { if_printf(ifp, "%s: can't load TX buffer (1) %d\n", __func__, error); goto back; } m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { if_printf(ifp, "%s: can't defrag TX buffer\n", __func__); error = ENOBUFS; goto back; } m = m_new; error = bus_dmamap_load_mbuf(sc->sc_buf_dtag, tb->tb_dmap, m, bwi_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error) { if_printf(ifp, "%s: can't load TX buffer (2) %d\n", __func__, error); goto back; } } bus_dmamap_sync(sc->sc_buf_dtag, tb->tb_dmap, BUS_DMASYNC_PREWRITE); tb->tb_mbuf = m; tb->tb_ni = ni; DPRINTF(sc, BWI_DBG_TX, "idx %d, pkt_len %d, buflen %d\n", idx, pkt_len, m->m_pkthdr.len); /* Setup TX descriptor */ sc->sc_setup_txdesc(sc, rd, idx, paddr, m->m_pkthdr.len); bus_dmamap_sync(sc->sc_txring_dtag, rd->rdata_dmap, BUS_DMASYNC_PREWRITE); /* Kick start */ sc->sc_start_tx(sc, rd->rdata_txrx_ctrl, idx); back: if (error) m_freem(m); return error; } static void bwi_start_tx32(struct bwi_softc *sc, uint32_t tx_ctrl, int idx) { idx = (idx + 1) % BWI_TX_NDESC; CSR_WRITE_4(sc, tx_ctrl + BWI_TX32_INDEX, idx * sizeof(struct bwi_desc32)); } static void bwi_start_tx64(struct bwi_softc *sc, uint32_t tx_ctrl, int idx) { /* TODO:64 */ } static void bwi_txeof_status32(struct bwi_softc *sc) { struct ifnet *ifp = sc->sc_ifp; uint32_t val, ctrl_base; int end_idx; ctrl_base = sc->sc_txstats->stats_ctrl_base; val = CSR_READ_4(sc, ctrl_base + BWI_RX32_STATUS); end_idx = __SHIFTOUT(val, BWI_RX32_STATUS_INDEX_MASK) / sizeof(struct bwi_desc32); bwi_txeof_status(sc, end_idx); CSR_WRITE_4(sc, ctrl_base + BWI_RX32_INDEX, end_idx * sizeof(struct bwi_desc32)); if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0) ifp->if_start(ifp); } static void bwi_txeof_status64(struct bwi_softc *sc) { /* TODO:64 */ } static void _bwi_txeof(struct bwi_softc *sc, uint16_t tx_id, int acked, int data_txcnt) { struct ifnet *ifp = sc->sc_ifp; struct bwi_txbuf_data *tbd; struct bwi_txbuf *tb; int ring_idx, buf_idx; struct ieee80211_node *ni; struct ieee80211vap *vap; if (tx_id == 0) { if_printf(ifp, "%s: zero tx id\n", __func__); return; } ring_idx = __SHIFTOUT(tx_id, BWI_TXH_ID_RING_MASK); buf_idx = __SHIFTOUT(tx_id, BWI_TXH_ID_IDX_MASK); KASSERT(ring_idx == BWI_TX_DATA_RING, ("ring_idx %d", ring_idx)); KASSERT(buf_idx < BWI_TX_NDESC, ("buf_idx %d", buf_idx)); tbd = &sc->sc_tx_bdata[ring_idx]; KASSERT(tbd->tbd_used > 0, ("tbd_used %d", tbd->tbd_used)); tbd->tbd_used--; tb = &tbd->tbd_buf[buf_idx]; DPRINTF(sc, BWI_DBG_TXEOF, "txeof idx %d, " "acked %d, data_txcnt %d, ni %p\n", buf_idx, acked, data_txcnt, tb->tb_ni); bus_dmamap_unload(sc->sc_buf_dtag, tb->tb_dmap); ni = tb->tb_ni; if (tb->tb_ni != NULL) { const struct bwi_txbuf_hdr *hdr = mtod(tb->tb_mbuf, const struct bwi_txbuf_hdr *); vap = ni->ni_vap; /* NB: update rate control only for unicast frames */ if (hdr->txh_mac_ctrl & htole32(BWI_TXH_MAC_C_ACK)) { /* * Feed back 'acked and data_txcnt'. Note that the * generic AMRR code only understands one tx rate * and the estimator doesn't handle real retry counts * well so to avoid over-aggressive downshifting we * treat any number of retries as "1". */ ieee80211_ratectl_tx_complete(vap, ni, (data_txcnt > 1) ? IEEE80211_RATECTL_TX_SUCCESS : IEEE80211_RATECTL_TX_FAILURE, &acked, NULL); } /* * Do any tx complete callback. Note this must * be done before releasing the node reference. */ if (tb->tb_mbuf->m_flags & M_TXCB) ieee80211_process_callback(ni, tb->tb_mbuf, !acked); ieee80211_free_node(tb->tb_ni); tb->tb_ni = NULL; } m_freem(tb->tb_mbuf); tb->tb_mbuf = NULL; if (tbd->tbd_used == 0) sc->sc_tx_timer = 0; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; } static void bwi_txeof_status(struct bwi_softc *sc, int end_idx) { struct bwi_txstats_data *st = sc->sc_txstats; int idx; bus_dmamap_sync(st->stats_dtag, st->stats_dmap, BUS_DMASYNC_POSTREAD); idx = st->stats_idx; while (idx != end_idx) { const struct bwi_txstats *stats = &st->stats[idx]; if ((stats->txs_flags & BWI_TXS_F_PENDING) == 0) { int data_txcnt; data_txcnt = __SHIFTOUT(stats->txs_txcnt, BWI_TXS_TXCNT_DATA); _bwi_txeof(sc, le16toh(stats->txs_id), stats->txs_flags & BWI_TXS_F_ACKED, data_txcnt); } idx = (idx + 1) % BWI_TXSTATS_NDESC; } st->stats_idx = idx; } static void bwi_txeof(struct bwi_softc *sc) { struct ifnet *ifp = sc->sc_ifp; for (;;) { uint32_t tx_status0, tx_status1; uint16_t tx_id; int data_txcnt; tx_status0 = CSR_READ_4(sc, BWI_TXSTATUS0); if ((tx_status0 & BWI_TXSTATUS0_VALID) == 0) break; tx_status1 = CSR_READ_4(sc, BWI_TXSTATUS1); tx_id = __SHIFTOUT(tx_status0, BWI_TXSTATUS0_TXID_MASK); data_txcnt = __SHIFTOUT(tx_status0, BWI_TXSTATUS0_DATA_TXCNT_MASK); if (tx_status0 & (BWI_TXSTATUS0_AMPDU | BWI_TXSTATUS0_PENDING)) continue; _bwi_txeof(sc, le16toh(tx_id), tx_status0 & BWI_TXSTATUS0_ACKED, data_txcnt); } if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0) ifp->if_start(ifp); } static int bwi_bbp_power_on(struct bwi_softc *sc, enum bwi_clock_mode clk_mode) { bwi_power_on(sc, 1); return bwi_set_clock_mode(sc, clk_mode); } static void bwi_bbp_power_off(struct bwi_softc *sc) { bwi_set_clock_mode(sc, BWI_CLOCK_MODE_SLOW); bwi_power_off(sc, 1); } static int bwi_get_pwron_delay(struct bwi_softc *sc) { struct bwi_regwin *com, *old; struct bwi_clock_freq freq; uint32_t val; int error; com = &sc->sc_com_regwin; KASSERT(BWI_REGWIN_EXIST(com), ("no regwin")); if ((sc->sc_cap & BWI_CAP_CLKMODE) == 0) return 0; error = bwi_regwin_switch(sc, com, &old); if (error) return error; bwi_get_clock_freq(sc, &freq); val = CSR_READ_4(sc, BWI_PLL_ON_DELAY); sc->sc_pwron_delay = howmany((val + 2) * 1000000, freq.clkfreq_min); DPRINTF(sc, BWI_DBG_ATTACH, "power on delay %u\n", sc->sc_pwron_delay); return bwi_regwin_switch(sc, old, NULL); } static int bwi_bus_attach(struct bwi_softc *sc) { struct bwi_regwin *bus, *old; int error; bus = &sc->sc_bus_regwin; error = bwi_regwin_switch(sc, bus, &old); if (error) return error; if (!bwi_regwin_is_enabled(sc, bus)) bwi_regwin_enable(sc, bus, 0); /* Disable interripts */ CSR_WRITE_4(sc, BWI_INTRVEC, 0); return bwi_regwin_switch(sc, old, NULL); } static const char * bwi_regwin_name(const struct bwi_regwin *rw) { switch (rw->rw_type) { case BWI_REGWIN_T_COM: return "COM"; case BWI_REGWIN_T_BUSPCI: return "PCI"; case BWI_REGWIN_T_MAC: return "MAC"; case BWI_REGWIN_T_BUSPCIE: return "PCIE"; } panic("unknown regwin type 0x%04x\n", rw->rw_type); return NULL; } static uint32_t bwi_regwin_disable_bits(struct bwi_softc *sc) { uint32_t busrev; /* XXX cache this */ busrev = __SHIFTOUT(CSR_READ_4(sc, BWI_ID_LO), BWI_ID_LO_BUSREV_MASK); DPRINTF(sc, BWI_DBG_ATTACH | BWI_DBG_INIT | BWI_DBG_MISC, "bus rev %u\n", busrev); if (busrev == BWI_BUSREV_0) return BWI_STATE_LO_DISABLE1; else if (busrev == BWI_BUSREV_1) return BWI_STATE_LO_DISABLE2; else return (BWI_STATE_LO_DISABLE1 | BWI_STATE_LO_DISABLE2); } int bwi_regwin_is_enabled(struct bwi_softc *sc, struct bwi_regwin *rw) { uint32_t val, disable_bits; disable_bits = bwi_regwin_disable_bits(sc); val = CSR_READ_4(sc, BWI_STATE_LO); if ((val & (BWI_STATE_LO_CLOCK | BWI_STATE_LO_RESET | disable_bits)) == BWI_STATE_LO_CLOCK) { DPRINTF(sc, BWI_DBG_ATTACH | BWI_DBG_INIT, "%s is enabled\n", bwi_regwin_name(rw)); return 1; } else { DPRINTF(sc, BWI_DBG_ATTACH | BWI_DBG_INIT, "%s is disabled\n", bwi_regwin_name(rw)); return 0; } } void bwi_regwin_disable(struct bwi_softc *sc, struct bwi_regwin *rw, uint32_t flags) { uint32_t state_lo, disable_bits; int i; state_lo = CSR_READ_4(sc, BWI_STATE_LO); /* * If current regwin is in 'reset' state, it was already disabled. */ if (state_lo & BWI_STATE_LO_RESET) { DPRINTF(sc, BWI_DBG_ATTACH | BWI_DBG_INIT, "%s was already disabled\n", bwi_regwin_name(rw)); return; } disable_bits = bwi_regwin_disable_bits(sc); /* * Disable normal clock */ state_lo = BWI_STATE_LO_CLOCK | disable_bits; CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* * Wait until normal clock is disabled */ #define NRETRY 1000 for (i = 0; i < NRETRY; ++i) { state_lo = CSR_READ_4(sc, BWI_STATE_LO); if (state_lo & disable_bits) break; DELAY(10); } if (i == NRETRY) { device_printf(sc->sc_dev, "%s disable clock timeout\n", bwi_regwin_name(rw)); } for (i = 0; i < NRETRY; ++i) { uint32_t state_hi; state_hi = CSR_READ_4(sc, BWI_STATE_HI); if ((state_hi & BWI_STATE_HI_BUSY) == 0) break; DELAY(10); } if (i == NRETRY) { device_printf(sc->sc_dev, "%s wait BUSY unset timeout\n", bwi_regwin_name(rw)); } #undef NRETRY /* * Reset and disable regwin with gated clock */ state_lo = BWI_STATE_LO_RESET | disable_bits | BWI_STATE_LO_CLOCK | BWI_STATE_LO_GATED_CLOCK | __SHIFTIN(flags, BWI_STATE_LO_FLAGS_MASK); CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* Flush pending bus write */ CSR_READ_4(sc, BWI_STATE_LO); DELAY(1); /* Reset and disable regwin */ state_lo = BWI_STATE_LO_RESET | disable_bits | __SHIFTIN(flags, BWI_STATE_LO_FLAGS_MASK); CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* Flush pending bus write */ CSR_READ_4(sc, BWI_STATE_LO); DELAY(1); } void bwi_regwin_enable(struct bwi_softc *sc, struct bwi_regwin *rw, uint32_t flags) { uint32_t state_lo, state_hi, imstate; bwi_regwin_disable(sc, rw, flags); /* Reset regwin with gated clock */ state_lo = BWI_STATE_LO_RESET | BWI_STATE_LO_CLOCK | BWI_STATE_LO_GATED_CLOCK | __SHIFTIN(flags, BWI_STATE_LO_FLAGS_MASK); CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* Flush pending bus write */ CSR_READ_4(sc, BWI_STATE_LO); DELAY(1); state_hi = CSR_READ_4(sc, BWI_STATE_HI); if (state_hi & BWI_STATE_HI_SERROR) CSR_WRITE_4(sc, BWI_STATE_HI, 0); imstate = CSR_READ_4(sc, BWI_IMSTATE); if (imstate & (BWI_IMSTATE_INBAND_ERR | BWI_IMSTATE_TIMEOUT)) { imstate &= ~(BWI_IMSTATE_INBAND_ERR | BWI_IMSTATE_TIMEOUT); CSR_WRITE_4(sc, BWI_IMSTATE, imstate); } /* Enable regwin with gated clock */ state_lo = BWI_STATE_LO_CLOCK | BWI_STATE_LO_GATED_CLOCK | __SHIFTIN(flags, BWI_STATE_LO_FLAGS_MASK); CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* Flush pending bus write */ CSR_READ_4(sc, BWI_STATE_LO); DELAY(1); /* Enable regwin with normal clock */ state_lo = BWI_STATE_LO_CLOCK | __SHIFTIN(flags, BWI_STATE_LO_FLAGS_MASK); CSR_WRITE_4(sc, BWI_STATE_LO, state_lo); /* Flush pending bus write */ CSR_READ_4(sc, BWI_STATE_LO); DELAY(1); } static void bwi_set_bssid(struct bwi_softc *sc, const uint8_t *bssid) { struct ifnet *ifp = sc->sc_ifp; struct bwi_mac *mac; struct bwi_myaddr_bssid buf; const uint8_t *p; uint32_t val; int n, i; KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; bwi_set_addr_filter(sc, BWI_ADDR_FILTER_BSSID, bssid); bcopy(IF_LLADDR(ifp), buf.myaddr, sizeof(buf.myaddr)); bcopy(bssid, buf.bssid, sizeof(buf.bssid)); n = sizeof(buf) / sizeof(val); p = (const uint8_t *)&buf; for (i = 0; i < n; ++i) { int j; val = 0; for (j = 0; j < sizeof(val); ++j) val |= ((uint32_t)(*p++)) << (j * 8); TMPLT_WRITE_4(mac, 0x20 + (i * sizeof(val)), val); } } static void bwi_updateslot(struct ifnet *ifp) { struct bwi_softc *sc = ifp->if_softc; struct ieee80211com *ic = ifp->if_l2com; struct bwi_mac *mac; BWI_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { DPRINTF(sc, BWI_DBG_80211, "%s\n", __func__); KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; bwi_mac_updateslot(mac, (ic->ic_flags & IEEE80211_F_SHSLOT)); } BWI_UNLOCK(sc); } static void bwi_calibrate(void *xsc) { struct bwi_softc *sc = xsc; #ifdef INVARIANTS struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; #endif struct bwi_mac *mac; BWI_ASSERT_LOCKED(sc); KASSERT(ic->ic_opmode != IEEE80211_M_MONITOR, ("opmode %d", ic->ic_opmode)); KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; bwi_mac_calibrate_txpower(mac, sc->sc_txpwrcb_type); sc->sc_txpwrcb_type = BWI_TXPWR_CALIB; /* XXX 15 seconds */ callout_reset(&sc->sc_calib_ch, hz * 15, bwi_calibrate, sc); } static int bwi_calc_rssi(struct bwi_softc *sc, const struct bwi_rxbuf_hdr *hdr) { struct bwi_mac *mac; KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; return bwi_rf_calc_rssi(mac, hdr); } static int bwi_calc_noise(struct bwi_softc *sc) { struct bwi_mac *mac; KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC, ("current regwin type %d", sc->sc_cur_regwin->rw_type)); mac = (struct bwi_mac *)sc->sc_cur_regwin; return bwi_rf_calc_noise(mac); } static __inline uint8_t bwi_plcp2rate(const uint32_t plcp0, enum ieee80211_phytype type) { uint32_t plcp = le32toh(plcp0) & IEEE80211_OFDM_PLCP_RATE_MASK; return (ieee80211_plcp2rate(plcp, type)); } static void bwi_rx_radiotap(struct bwi_softc *sc, struct mbuf *m, struct bwi_rxbuf_hdr *hdr, const void *plcp, int rate, int rssi, int noise) { const struct ieee80211_frame_min *wh; sc->sc_rx_th.wr_flags = IEEE80211_RADIOTAP_F_FCS; if (htole16(hdr->rxh_flags1) & BWI_RXH_F1_SHPREAMBLE) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; wh = mtod(m, const struct ieee80211_frame_min *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_WEP; sc->sc_rx_th.wr_tsf = hdr->rxh_tsf; /* No endian convertion */ sc->sc_rx_th.wr_rate = rate; sc->sc_rx_th.wr_antsignal = rssi; sc->sc_rx_th.wr_antnoise = noise; } static void bwi_led_attach(struct bwi_softc *sc) { const uint8_t *led_act = NULL; uint16_t gpio, val[BWI_LED_MAX]; int i; #define N(arr) (int)(sizeof(arr) / sizeof(arr[0])) for (i = 0; i < N(bwi_vendor_led_act); ++i) { if (sc->sc_pci_subvid == bwi_vendor_led_act[i].vid) { led_act = bwi_vendor_led_act[i].led_act; break; } } if (led_act == NULL) led_act = bwi_default_led_act; #undef N gpio = bwi_read_sprom(sc, BWI_SPROM_GPIO01); val[0] = __SHIFTOUT(gpio, BWI_SPROM_GPIO_0); val[1] = __SHIFTOUT(gpio, BWI_SPROM_GPIO_1); gpio = bwi_read_sprom(sc, BWI_SPROM_GPIO23); val[2] = __SHIFTOUT(gpio, BWI_SPROM_GPIO_2); val[3] = __SHIFTOUT(gpio, BWI_SPROM_GPIO_3); for (i = 0; i < BWI_LED_MAX; ++i) { struct bwi_led *led = &sc->sc_leds[i]; if (val[i] == 0xff) { led->l_act = led_act[i]; } else { if (val[i] & BWI_LED_ACT_LOW) led->l_flags |= BWI_LED_F_ACTLOW; led->l_act = __SHIFTOUT(val[i], BWI_LED_ACT_MASK); } led->l_mask = (1 << i); if (led->l_act == BWI_LED_ACT_BLINK_SLOW || led->l_act == BWI_LED_ACT_BLINK_POLL || led->l_act == BWI_LED_ACT_BLINK) { led->l_flags |= BWI_LED_F_BLINK; if (led->l_act == BWI_LED_ACT_BLINK_POLL) led->l_flags |= BWI_LED_F_POLLABLE; else if (led->l_act == BWI_LED_ACT_BLINK_SLOW) led->l_flags |= BWI_LED_F_SLOW; if (sc->sc_blink_led == NULL) { sc->sc_blink_led = led; if (led->l_flags & BWI_LED_F_SLOW) BWI_LED_SLOWDOWN(sc->sc_led_idle); } } DPRINTF(sc, BWI_DBG_LED | BWI_DBG_ATTACH, "%dth led, act %d, lowact %d\n", i, led->l_act, led->l_flags & BWI_LED_F_ACTLOW); } callout_init_mtx(&sc->sc_led_blink_ch, &sc->sc_mtx, 0); } static __inline uint16_t bwi_led_onoff(const struct bwi_led *led, uint16_t val, int on) { if (led->l_flags & BWI_LED_F_ACTLOW) on = !on; if (on) val |= led->l_mask; else val &= ~led->l_mask; return val; } static void bwi_led_newstate(struct bwi_softc *sc, enum ieee80211_state nstate) { struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint16_t val; int i; if (nstate == IEEE80211_S_INIT) { callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; } if ((ic->ic_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; val = CSR_READ_2(sc, BWI_MAC_GPIO_CTRL); for (i = 0; i < BWI_LED_MAX; ++i) { struct bwi_led *led = &sc->sc_leds[i]; int on; if (led->l_act == BWI_LED_ACT_UNKN || led->l_act == BWI_LED_ACT_NULL) continue; if ((led->l_flags & BWI_LED_F_BLINK) && nstate != IEEE80211_S_INIT) continue; switch (led->l_act) { case BWI_LED_ACT_ON: /* Always on */ on = 1; break; case BWI_LED_ACT_OFF: /* Always off */ case BWI_LED_ACT_5GHZ: /* TODO: 11A */ on = 0; break; default: on = 1; switch (nstate) { case IEEE80211_S_INIT: on = 0; break; case IEEE80211_S_RUN: if (led->l_act == BWI_LED_ACT_11G && ic->ic_curmode != IEEE80211_MODE_11G) on = 0; break; default: if (led->l_act == BWI_LED_ACT_ASSOC) on = 0; break; } break; } val = bwi_led_onoff(led, val, on); } CSR_WRITE_2(sc, BWI_MAC_GPIO_CTRL, val); } static void bwi_led_event(struct bwi_softc *sc, int event) { struct bwi_led *led = sc->sc_blink_led; int rate; if (event == BWI_LED_EVENT_POLL) { if ((led->l_flags & BWI_LED_F_POLLABLE) == 0) return; if (ticks - sc->sc_led_ticks < sc->sc_led_idle) return; } sc->sc_led_ticks = ticks; if (sc->sc_led_blinking) return; switch (event) { case BWI_LED_EVENT_RX: rate = sc->sc_rx_rate; break; case BWI_LED_EVENT_TX: rate = sc->sc_tx_rate; break; case BWI_LED_EVENT_POLL: rate = 0; break; default: panic("unknown LED event %d\n", event); break; } bwi_led_blink_start(sc, bwi_led_duration[rate].on_dur, bwi_led_duration[rate].off_dur); } static void bwi_led_blink_start(struct bwi_softc *sc, int on_dur, int off_dur) { struct bwi_led *led = sc->sc_blink_led; uint16_t val; val = CSR_READ_2(sc, BWI_MAC_GPIO_CTRL); val = bwi_led_onoff(led, val, 1); CSR_WRITE_2(sc, BWI_MAC_GPIO_CTRL, val); if (led->l_flags & BWI_LED_F_SLOW) { BWI_LED_SLOWDOWN(on_dur); BWI_LED_SLOWDOWN(off_dur); } sc->sc_led_blinking = 1; sc->sc_led_blink_offdur = off_dur; callout_reset(&sc->sc_led_blink_ch, on_dur, bwi_led_blink_next, sc); } static void bwi_led_blink_next(void *xsc) { struct bwi_softc *sc = xsc; uint16_t val; val = CSR_READ_2(sc, BWI_MAC_GPIO_CTRL); val = bwi_led_onoff(sc->sc_blink_led, val, 0); CSR_WRITE_2(sc, BWI_MAC_GPIO_CTRL, val); callout_reset(&sc->sc_led_blink_ch, sc->sc_led_blink_offdur, bwi_led_blink_end, sc); } static void bwi_led_blink_end(void *xsc) { struct bwi_softc *sc = xsc; sc->sc_led_blinking = 0; } static void bwi_restart(void *xsc, int pending) { struct bwi_softc *sc = xsc; struct ifnet *ifp = sc->sc_ifp; if_printf(ifp, "%s begin, help!\n", __func__); BWI_LOCK(sc); bwi_init_statechg(xsc, 0); #if 0 bwi_start_locked(ifp); #endif BWI_UNLOCK(sc); } Index: head/sys/dev/bwn/if_bwn.c =================================================================== --- head/sys/dev/bwn/if_bwn.c (revision 280346) +++ head/sys/dev/bwn/if_bwn.c (revision 280347) @@ -1,14241 +1,14241 @@ /*- * Copyright (c) 2009-2010 Weongyo Jeong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * The Broadcom Wireless LAN controller driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SYSCTL_NODE(_hw, OID_AUTO, bwn, CTLFLAG_RD, 0, "Broadcom driver parameters"); /* * Tunable & sysctl variables. */ #ifdef BWN_DEBUG static int bwn_debug = 0; SYSCTL_INT(_hw_bwn, OID_AUTO, debug, CTLFLAG_RWTUN, &bwn_debug, 0, "Broadcom debugging printfs"); enum { BWN_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ BWN_DEBUG_RECV = 0x00000002, /* basic recv operation */ BWN_DEBUG_STATE = 0x00000004, /* 802.11 state transitions */ BWN_DEBUG_TXPOW = 0x00000008, /* tx power processing */ BWN_DEBUG_RESET = 0x00000010, /* reset processing */ BWN_DEBUG_OPS = 0x00000020, /* bwn_ops processing */ BWN_DEBUG_BEACON = 0x00000040, /* beacon handling */ BWN_DEBUG_WATCHDOG = 0x00000080, /* watchdog timeout */ BWN_DEBUG_INTR = 0x00000100, /* ISR */ BWN_DEBUG_CALIBRATE = 0x00000200, /* periodic calibration */ BWN_DEBUG_NODE = 0x00000400, /* node management */ BWN_DEBUG_LED = 0x00000800, /* led management */ BWN_DEBUG_CMD = 0x00001000, /* cmd submission */ BWN_DEBUG_LO = 0x00002000, /* LO */ BWN_DEBUG_FW = 0x00004000, /* firmware */ BWN_DEBUG_WME = 0x00008000, /* WME */ BWN_DEBUG_RF = 0x00010000, /* RF */ BWN_DEBUG_FATAL = 0x80000000, /* fatal errors */ BWN_DEBUG_ANY = 0xffffffff }; #define DPRINTF(sc, m, fmt, ...) do { \ if (sc->sc_debug & (m)) \ printf(fmt, __VA_ARGS__); \ } while (0) #else #define DPRINTF(sc, m, fmt, ...) do { (void) sc; } while (0) #endif static int bwn_bfp = 0; /* use "Bad Frames Preemption" */ SYSCTL_INT(_hw_bwn, OID_AUTO, bfp, CTLFLAG_RW, &bwn_bfp, 0, "uses Bad Frames Preemption"); static int bwn_bluetooth = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, bluetooth, CTLFLAG_RW, &bwn_bluetooth, 0, "turns on Bluetooth Coexistence"); static int bwn_hwpctl = 0; SYSCTL_INT(_hw_bwn, OID_AUTO, hwpctl, CTLFLAG_RW, &bwn_hwpctl, 0, "uses H/W power control"); static int bwn_msi_disable = 0; /* MSI disabled */ TUNABLE_INT("hw.bwn.msi_disable", &bwn_msi_disable); static int bwn_usedma = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, usedma, CTLFLAG_RD, &bwn_usedma, 0, "uses DMA"); TUNABLE_INT("hw.bwn.usedma", &bwn_usedma); static int bwn_wme = 1; SYSCTL_INT(_hw_bwn, OID_AUTO, wme, CTLFLAG_RW, &bwn_wme, 0, "uses WME support"); static int bwn_attach_pre(struct bwn_softc *); static int bwn_attach_post(struct bwn_softc *); static void bwn_sprom_bugfixes(device_t); static void bwn_init(void *); static int bwn_init_locked(struct bwn_softc *); static int bwn_ioctl(struct ifnet *, u_long, caddr_t); static void bwn_start(struct ifnet *); static int bwn_attach_core(struct bwn_mac *); static void bwn_reset_core(struct bwn_mac *, uint32_t); static int bwn_phy_getinfo(struct bwn_mac *, int); static int bwn_chiptest(struct bwn_mac *); static int bwn_setup_channels(struct bwn_mac *, int, int); static int bwn_phy_g_attach(struct bwn_mac *); static void bwn_phy_g_detach(struct bwn_mac *); static void bwn_phy_g_init_pre(struct bwn_mac *); static int bwn_phy_g_prepare_hw(struct bwn_mac *); static int bwn_phy_g_init(struct bwn_mac *); static void bwn_phy_g_exit(struct bwn_mac *); static uint16_t bwn_phy_g_read(struct bwn_mac *, uint16_t); static void bwn_phy_g_write(struct bwn_mac *, uint16_t, uint16_t); static uint16_t bwn_phy_g_rf_read(struct bwn_mac *, uint16_t); static void bwn_phy_g_rf_write(struct bwn_mac *, uint16_t, uint16_t); static int bwn_phy_g_hwpctl(struct bwn_mac *); static void bwn_phy_g_rf_onoff(struct bwn_mac *, int); static int bwn_phy_g_switch_channel(struct bwn_mac *, uint32_t); static uint32_t bwn_phy_g_get_default_chan(struct bwn_mac *); static void bwn_phy_g_set_antenna(struct bwn_mac *, int); static int bwn_phy_g_im(struct bwn_mac *, int); static int bwn_phy_g_recalc_txpwr(struct bwn_mac *, int); static void bwn_phy_g_set_txpwr(struct bwn_mac *); static void bwn_phy_g_task_15s(struct bwn_mac *); static void bwn_phy_g_task_60s(struct bwn_mac *); static uint16_t bwn_phy_g_txctl(struct bwn_mac *); static void bwn_phy_switch_analog(struct bwn_mac *, int); static uint16_t bwn_shm_read_2(struct bwn_mac *, uint16_t, uint16_t); static void bwn_shm_write_2(struct bwn_mac *, uint16_t, uint16_t, uint16_t); static uint32_t bwn_shm_read_4(struct bwn_mac *, uint16_t, uint16_t); static void bwn_shm_write_4(struct bwn_mac *, uint16_t, uint16_t, uint32_t); static void bwn_shm_ctlword(struct bwn_mac *, uint16_t, uint16_t); static void bwn_addchannels(struct ieee80211_channel [], int, int *, const struct bwn_channelinfo *, int); static int bwn_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void bwn_updateslot(struct ifnet *); static void bwn_update_promisc(struct ifnet *); static void bwn_wme_init(struct bwn_mac *); static int bwn_wme_update(struct ieee80211com *); static void bwn_wme_clear(struct bwn_softc *); static void bwn_wme_load(struct bwn_mac *); static void bwn_wme_loadparams(struct bwn_mac *, const struct wmeParams *, uint16_t); static void bwn_scan_start(struct ieee80211com *); static void bwn_scan_end(struct ieee80211com *); static void bwn_set_channel(struct ieee80211com *); static struct ieee80211vap *bwn_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void bwn_vap_delete(struct ieee80211vap *); static void bwn_stop(struct bwn_softc *, int); static void bwn_stop_locked(struct bwn_softc *, int); static int bwn_core_init(struct bwn_mac *); static void bwn_core_start(struct bwn_mac *); static void bwn_core_exit(struct bwn_mac *); static void bwn_bt_disable(struct bwn_mac *); static int bwn_chip_init(struct bwn_mac *); static uint64_t bwn_hf_read(struct bwn_mac *); static void bwn_hf_write(struct bwn_mac *, uint64_t); static void bwn_set_txretry(struct bwn_mac *, int, int); static void bwn_rate_init(struct bwn_mac *); static void bwn_set_phytxctl(struct bwn_mac *); static void bwn_spu_setdelay(struct bwn_mac *, int); static void bwn_bt_enable(struct bwn_mac *); static void bwn_set_macaddr(struct bwn_mac *); static void bwn_crypt_init(struct bwn_mac *); static void bwn_chip_exit(struct bwn_mac *); static int bwn_fw_fillinfo(struct bwn_mac *); static int bwn_fw_loaducode(struct bwn_mac *); static int bwn_gpio_init(struct bwn_mac *); static int bwn_fw_loadinitvals(struct bwn_mac *); static int bwn_phy_init(struct bwn_mac *); static void bwn_set_txantenna(struct bwn_mac *, int); static void bwn_set_opmode(struct bwn_mac *); static void bwn_rate_write(struct bwn_mac *, uint16_t, int); static uint8_t bwn_plcp_getcck(const uint8_t); static uint8_t bwn_plcp_getofdm(const uint8_t); static void bwn_pio_init(struct bwn_mac *); static uint16_t bwn_pio_idx2base(struct bwn_mac *, int); static void bwn_pio_set_txqueue(struct bwn_mac *, struct bwn_pio_txqueue *, int); static void bwn_pio_setupqueue_rx(struct bwn_mac *, struct bwn_pio_rxqueue *, int); static void bwn_destroy_queue_tx(struct bwn_pio_txqueue *); static uint16_t bwn_pio_read_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t); static void bwn_pio_cancel_tx_packets(struct bwn_pio_txqueue *); static int bwn_pio_rx(struct bwn_pio_rxqueue *); static uint8_t bwn_pio_rxeof(struct bwn_pio_rxqueue *); static void bwn_pio_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static uint16_t bwn_pio_rx_read_2(struct bwn_pio_rxqueue *, uint16_t); static uint32_t bwn_pio_rx_read_4(struct bwn_pio_rxqueue *, uint16_t); static void bwn_pio_rx_write_2(struct bwn_pio_rxqueue *, uint16_t, uint16_t); static void bwn_pio_rx_write_4(struct bwn_pio_rxqueue *, uint16_t, uint32_t); static int bwn_pio_tx_start(struct bwn_mac *, struct ieee80211_node *, struct mbuf *); static struct bwn_pio_txqueue *bwn_pio_select(struct bwn_mac *, uint8_t); static uint32_t bwn_pio_write_multi_4(struct bwn_mac *, struct bwn_pio_txqueue *, uint32_t, const void *, int); static void bwn_pio_write_4(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, uint32_t); static uint16_t bwn_pio_write_multi_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, const void *, int); static uint16_t bwn_pio_write_mbuf_2(struct bwn_mac *, struct bwn_pio_txqueue *, uint16_t, struct mbuf *); static struct bwn_pio_txqueue *bwn_pio_parse_cookie(struct bwn_mac *, uint16_t, struct bwn_pio_txpkt **); static void bwn_dma_init(struct bwn_mac *); static void bwn_dma_rxdirectfifo(struct bwn_mac *, int, uint8_t); static int bwn_dma_mask2type(uint64_t); static uint64_t bwn_dma_mask(struct bwn_mac *); static uint16_t bwn_dma_base(int, int); static void bwn_dma_ringfree(struct bwn_dma_ring **); static void bwn_dma_32_getdesc(struct bwn_dma_ring *, int, struct bwn_dmadesc_generic **, struct bwn_dmadesc_meta **); static void bwn_dma_32_setdesc(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, bus_addr_t, uint16_t, int, int, int); static void bwn_dma_32_start_transfer(struct bwn_dma_ring *, int); static void bwn_dma_32_suspend(struct bwn_dma_ring *); static void bwn_dma_32_resume(struct bwn_dma_ring *); static int bwn_dma_32_get_curslot(struct bwn_dma_ring *); static void bwn_dma_32_set_curslot(struct bwn_dma_ring *, int); static void bwn_dma_64_getdesc(struct bwn_dma_ring *, int, struct bwn_dmadesc_generic **, struct bwn_dmadesc_meta **); static void bwn_dma_64_setdesc(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, bus_addr_t, uint16_t, int, int, int); static void bwn_dma_64_start_transfer(struct bwn_dma_ring *, int); static void bwn_dma_64_suspend(struct bwn_dma_ring *); static void bwn_dma_64_resume(struct bwn_dma_ring *); static int bwn_dma_64_get_curslot(struct bwn_dma_ring *); static void bwn_dma_64_set_curslot(struct bwn_dma_ring *, int); static int bwn_dma_allocringmemory(struct bwn_dma_ring *); static void bwn_dma_setup(struct bwn_dma_ring *); static void bwn_dma_free_ringmemory(struct bwn_dma_ring *); static void bwn_dma_cleanup(struct bwn_dma_ring *); static void bwn_dma_free_descbufs(struct bwn_dma_ring *); static int bwn_dma_tx_reset(struct bwn_mac *, uint16_t, int); static void bwn_dma_rx(struct bwn_dma_ring *); static int bwn_dma_rx_reset(struct bwn_mac *, uint16_t, int); static void bwn_dma_free_descbuf(struct bwn_dma_ring *, struct bwn_dmadesc_meta *); static void bwn_dma_set_redzone(struct bwn_dma_ring *, struct mbuf *); static int bwn_dma_gettype(struct bwn_mac *); static void bwn_dma_ring_addr(void *, bus_dma_segment_t *, int, int); static int bwn_dma_freeslot(struct bwn_dma_ring *); static int bwn_dma_nextslot(struct bwn_dma_ring *, int); static void bwn_dma_rxeof(struct bwn_dma_ring *, int *); static int bwn_dma_newbuf(struct bwn_dma_ring *, struct bwn_dmadesc_generic *, struct bwn_dmadesc_meta *, int); static void bwn_dma_buf_addr(void *, bus_dma_segment_t *, int, bus_size_t, int); static uint8_t bwn_dma_check_redzone(struct bwn_dma_ring *, struct mbuf *); static void bwn_dma_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static int bwn_dma_tx_start(struct bwn_mac *, struct ieee80211_node *, struct mbuf *); static int bwn_dma_getslot(struct bwn_dma_ring *); static struct bwn_dma_ring *bwn_dma_select(struct bwn_mac *, uint8_t); static int bwn_dma_attach(struct bwn_mac *); static struct bwn_dma_ring *bwn_dma_ringsetup(struct bwn_mac *, int, int, int); static struct bwn_dma_ring *bwn_dma_parse_cookie(struct bwn_mac *, const struct bwn_txstatus *, uint16_t, int *); static void bwn_dma_free(struct bwn_mac *); static void bwn_phy_g_init_sub(struct bwn_mac *); static uint8_t bwn_has_hwpctl(struct bwn_mac *); static void bwn_phy_init_b5(struct bwn_mac *); static void bwn_phy_init_b6(struct bwn_mac *); static void bwn_phy_init_a(struct bwn_mac *); static void bwn_loopback_calcgain(struct bwn_mac *); static uint16_t bwn_rf_init_bcm2050(struct bwn_mac *); static void bwn_lo_g_init(struct bwn_mac *); static void bwn_lo_g_adjust(struct bwn_mac *); static void bwn_lo_get_powervector(struct bwn_mac *); static struct bwn_lo_calib *bwn_lo_calibset(struct bwn_mac *, const struct bwn_bbatt *, const struct bwn_rfatt *); static void bwn_lo_write(struct bwn_mac *, struct bwn_loctl *); static void bwn_phy_hwpctl_init(struct bwn_mac *); static void bwn_phy_g_switch_chan(struct bwn_mac *, int, uint8_t); static void bwn_phy_g_set_txpwr_sub(struct bwn_mac *, const struct bwn_bbatt *, const struct bwn_rfatt *, uint8_t); static void bwn_phy_g_set_bbatt(struct bwn_mac *, uint16_t); static uint16_t bwn_rf_2050_rfoverval(struct bwn_mac *, uint16_t, uint32_t); static void bwn_spu_workaround(struct bwn_mac *, uint8_t); static void bwn_wa_init(struct bwn_mac *); static void bwn_ofdmtab_write_2(struct bwn_mac *, uint16_t, uint16_t, uint16_t); static void bwn_dummy_transmission(struct bwn_mac *, int, int); static void bwn_ofdmtab_write_4(struct bwn_mac *, uint16_t, uint16_t, uint32_t); static void bwn_gtab_write(struct bwn_mac *, uint16_t, uint16_t, uint16_t); static void bwn_ram_write(struct bwn_mac *, uint16_t, uint32_t); static void bwn_mac_suspend(struct bwn_mac *); static void bwn_mac_enable(struct bwn_mac *); static void bwn_psctl(struct bwn_mac *, uint32_t); static int16_t bwn_nrssi_read(struct bwn_mac *, uint16_t); static void bwn_nrssi_offset(struct bwn_mac *); static void bwn_nrssi_threshold(struct bwn_mac *); static void bwn_nrssi_slope_11g(struct bwn_mac *); static void bwn_set_all_gains(struct bwn_mac *, int16_t, int16_t, int16_t); static void bwn_set_original_gains(struct bwn_mac *); static void bwn_hwpctl_early_init(struct bwn_mac *); static void bwn_hwpctl_init_gphy(struct bwn_mac *); static uint16_t bwn_phy_g_chan2freq(uint8_t); static int bwn_fw_gets(struct bwn_mac *, enum bwn_fwtype); static int bwn_fw_get(struct bwn_mac *, enum bwn_fwtype, const char *, struct bwn_fwfile *); static void bwn_release_firmware(struct bwn_mac *); static void bwn_do_release_fw(struct bwn_fwfile *); static uint16_t bwn_fwcaps_read(struct bwn_mac *); static int bwn_fwinitvals_write(struct bwn_mac *, const struct bwn_fwinitvals *, size_t, size_t); static int bwn_switch_channel(struct bwn_mac *, int); static uint16_t bwn_ant2phy(int); static void bwn_mac_write_bssid(struct bwn_mac *); static void bwn_mac_setfilter(struct bwn_mac *, uint16_t, const uint8_t *); static void bwn_key_dowrite(struct bwn_mac *, uint8_t, uint8_t, const uint8_t *, size_t, const uint8_t *); static void bwn_key_macwrite(struct bwn_mac *, uint8_t, const uint8_t *); static void bwn_key_write(struct bwn_mac *, uint8_t, uint8_t, const uint8_t *); static void bwn_phy_exit(struct bwn_mac *); static void bwn_core_stop(struct bwn_mac *); static int bwn_switch_band(struct bwn_softc *, struct ieee80211_channel *); static void bwn_phy_reset(struct bwn_mac *); static int bwn_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void bwn_set_pretbtt(struct bwn_mac *); static int bwn_intr(void *); static void bwn_intrtask(void *, int); static void bwn_restart(struct bwn_mac *, const char *); static void bwn_intr_ucode_debug(struct bwn_mac *); static void bwn_intr_tbtt_indication(struct bwn_mac *); static void bwn_intr_atim_end(struct bwn_mac *); static void bwn_intr_beacon(struct bwn_mac *); static void bwn_intr_pmq(struct bwn_mac *); static void bwn_intr_noise(struct bwn_mac *); static void bwn_intr_txeof(struct bwn_mac *); static void bwn_hwreset(void *, int); static void bwn_handle_fwpanic(struct bwn_mac *); static void bwn_load_beacon0(struct bwn_mac *); static void bwn_load_beacon1(struct bwn_mac *); static uint32_t bwn_jssi_read(struct bwn_mac *); static void bwn_noise_gensample(struct bwn_mac *); static void bwn_handle_txeof(struct bwn_mac *, const struct bwn_txstatus *); static void bwn_rxeof(struct bwn_mac *, struct mbuf *, const void *); static void bwn_phy_txpower_check(struct bwn_mac *, uint32_t); static void bwn_start_locked(struct ifnet *); static int bwn_tx_start(struct bwn_softc *, struct ieee80211_node *, struct mbuf *); static int bwn_tx_isfull(struct bwn_softc *, struct mbuf *); static int bwn_set_txhdr(struct bwn_mac *, struct ieee80211_node *, struct mbuf *, struct bwn_txhdr *, uint16_t); static void bwn_plcp_genhdr(struct bwn_plcp4 *, const uint16_t, const uint8_t); static uint8_t bwn_antenna_sanitize(struct bwn_mac *, uint8_t); static uint8_t bwn_get_fbrate(uint8_t); static int bwn_phy_shm_tssi_read(struct bwn_mac *, uint16_t); static void bwn_phy_g_setatt(struct bwn_mac *, int *, int *); static void bwn_phy_lock(struct bwn_mac *); static void bwn_phy_unlock(struct bwn_mac *); static void bwn_rf_lock(struct bwn_mac *); static void bwn_rf_unlock(struct bwn_mac *); static void bwn_txpwr(void *, int); static void bwn_tasks(void *); static void bwn_task_15s(struct bwn_mac *); static void bwn_task_30s(struct bwn_mac *); static void bwn_task_60s(struct bwn_mac *); static int bwn_plcp_get_ofdmrate(struct bwn_mac *, struct bwn_plcp6 *, uint8_t); static int bwn_plcp_get_cckrate(struct bwn_mac *, struct bwn_plcp6 *); static void bwn_rx_radiotap(struct bwn_mac *, struct mbuf *, const struct bwn_rxhdr4 *, struct bwn_plcp6 *, int, int, int); static void bwn_tsf_read(struct bwn_mac *, uint64_t *); static void bwn_phy_g_dc_lookup_init(struct bwn_mac *, uint8_t); static void bwn_set_slot_time(struct bwn_mac *, uint16_t); static void bwn_watchdog(void *); static void bwn_dma_stop(struct bwn_mac *); static void bwn_pio_stop(struct bwn_mac *); static void bwn_dma_ringstop(struct bwn_dma_ring **); static void bwn_led_attach(struct bwn_mac *); static void bwn_led_newstate(struct bwn_mac *, enum ieee80211_state); static void bwn_led_event(struct bwn_mac *, int); static void bwn_led_blink_start(struct bwn_mac *, int, int); static void bwn_led_blink_next(void *); static void bwn_led_blink_end(void *); static void bwn_rfswitch(void *); static void bwn_rf_turnon(struct bwn_mac *); static void bwn_rf_turnoff(struct bwn_mac *); static void bwn_phy_lp_init_pre(struct bwn_mac *); static int bwn_phy_lp_init(struct bwn_mac *); static uint16_t bwn_phy_lp_read(struct bwn_mac *, uint16_t); static void bwn_phy_lp_write(struct bwn_mac *, uint16_t, uint16_t); static void bwn_phy_lp_maskset(struct bwn_mac *, uint16_t, uint16_t, uint16_t); static uint16_t bwn_phy_lp_rf_read(struct bwn_mac *, uint16_t); static void bwn_phy_lp_rf_write(struct bwn_mac *, uint16_t, uint16_t); static void bwn_phy_lp_rf_onoff(struct bwn_mac *, int); static int bwn_phy_lp_switch_channel(struct bwn_mac *, uint32_t); static uint32_t bwn_phy_lp_get_default_chan(struct bwn_mac *); static void bwn_phy_lp_set_antenna(struct bwn_mac *, int); static void bwn_phy_lp_task_60s(struct bwn_mac *); static void bwn_phy_lp_readsprom(struct bwn_mac *); static void bwn_phy_lp_bbinit(struct bwn_mac *); static void bwn_phy_lp_txpctl_init(struct bwn_mac *); static void bwn_phy_lp_calib(struct bwn_mac *); static void bwn_phy_lp_switch_analog(struct bwn_mac *, int); static int bwn_phy_lp_b2062_switch_channel(struct bwn_mac *, uint8_t); static int bwn_phy_lp_b2063_switch_channel(struct bwn_mac *, uint8_t); static void bwn_phy_lp_set_anafilter(struct bwn_mac *, uint8_t); static void bwn_phy_lp_set_gaintbl(struct bwn_mac *, uint32_t); static void bwn_phy_lp_digflt_save(struct bwn_mac *); static void bwn_phy_lp_get_txpctlmode(struct bwn_mac *); static void bwn_phy_lp_set_txpctlmode(struct bwn_mac *, uint8_t); static void bwn_phy_lp_bugfix(struct bwn_mac *); static void bwn_phy_lp_digflt_restore(struct bwn_mac *); static void bwn_phy_lp_tblinit(struct bwn_mac *); static void bwn_phy_lp_bbinit_r2(struct bwn_mac *); static void bwn_phy_lp_bbinit_r01(struct bwn_mac *); static void bwn_phy_lp_b2062_init(struct bwn_mac *); static void bwn_phy_lp_b2063_init(struct bwn_mac *); static void bwn_phy_lp_rxcal_r2(struct bwn_mac *); static void bwn_phy_lp_rccal_r12(struct bwn_mac *); static void bwn_phy_lp_set_rccap(struct bwn_mac *); static uint32_t bwn_phy_lp_roundup(uint32_t, uint32_t, uint8_t); static void bwn_phy_lp_b2062_reset_pllbias(struct bwn_mac *); static void bwn_phy_lp_b2062_vco_calib(struct bwn_mac *); static void bwn_tab_write_multi(struct bwn_mac *, uint32_t, int, const void *); static void bwn_tab_read_multi(struct bwn_mac *, uint32_t, int, void *); static struct bwn_txgain bwn_phy_lp_get_txgain(struct bwn_mac *); static uint8_t bwn_phy_lp_get_bbmult(struct bwn_mac *); static void bwn_phy_lp_set_txgain(struct bwn_mac *, struct bwn_txgain *); static void bwn_phy_lp_set_bbmult(struct bwn_mac *, uint8_t); static void bwn_phy_lp_set_trsw_over(struct bwn_mac *, uint8_t, uint8_t); static void bwn_phy_lp_set_rxgain(struct bwn_mac *, uint32_t); static void bwn_phy_lp_set_deaf(struct bwn_mac *, uint8_t); static int bwn_phy_lp_calc_rx_iq_comp(struct bwn_mac *, uint16_t); static void bwn_phy_lp_clear_deaf(struct bwn_mac *, uint8_t); static void bwn_phy_lp_tblinit_r01(struct bwn_mac *); static void bwn_phy_lp_tblinit_r2(struct bwn_mac *); static void bwn_phy_lp_tblinit_txgain(struct bwn_mac *); static void bwn_tab_write(struct bwn_mac *, uint32_t, uint32_t); static void bwn_phy_lp_b2062_tblinit(struct bwn_mac *); static void bwn_phy_lp_b2063_tblinit(struct bwn_mac *); static int bwn_phy_lp_loopback(struct bwn_mac *); static void bwn_phy_lp_set_rxgain_idx(struct bwn_mac *, uint16_t); static void bwn_phy_lp_ddfs_turnon(struct bwn_mac *, int, int, int, int, int); static uint8_t bwn_phy_lp_rx_iq_est(struct bwn_mac *, uint16_t, uint8_t, struct bwn_phy_lp_iq_est *); static void bwn_phy_lp_ddfs_turnoff(struct bwn_mac *); static uint32_t bwn_tab_read(struct bwn_mac *, uint32_t); static void bwn_phy_lp_set_txgain_dac(struct bwn_mac *, uint16_t); static void bwn_phy_lp_set_txgain_pa(struct bwn_mac *, uint16_t); static void bwn_phy_lp_set_txgain_override(struct bwn_mac *); static uint16_t bwn_phy_lp_get_pa_gain(struct bwn_mac *); static uint8_t bwn_nbits(int32_t); static void bwn_phy_lp_gaintbl_write_multi(struct bwn_mac *, int, int, struct bwn_txgain_entry *); static void bwn_phy_lp_gaintbl_write(struct bwn_mac *, int, struct bwn_txgain_entry); static void bwn_phy_lp_gaintbl_write_r2(struct bwn_mac *, int, struct bwn_txgain_entry); static void bwn_phy_lp_gaintbl_write_r01(struct bwn_mac *, int, struct bwn_txgain_entry); static void bwn_sysctl_node(struct bwn_softc *); static struct resource_spec bwn_res_spec_legacy[] = { { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; static struct resource_spec bwn_res_spec_msi[] = { { SYS_RES_IRQ, 1, RF_ACTIVE }, { -1, 0, 0 } }; static const struct bwn_channelinfo bwn_chantable_bg = { .channels = { { 2412, 1, 30 }, { 2417, 2, 30 }, { 2422, 3, 30 }, { 2427, 4, 30 }, { 2432, 5, 30 }, { 2437, 6, 30 }, { 2442, 7, 30 }, { 2447, 8, 30 }, { 2452, 9, 30 }, { 2457, 10, 30 }, { 2462, 11, 30 }, { 2467, 12, 30 }, { 2472, 13, 30 }, { 2484, 14, 30 } }, .nchannels = 14 }; static const struct bwn_channelinfo bwn_chantable_a = { .channels = { { 5170, 34, 30 }, { 5180, 36, 30 }, { 5190, 38, 30 }, { 5200, 40, 30 }, { 5210, 42, 30 }, { 5220, 44, 30 }, { 5230, 46, 30 }, { 5240, 48, 30 }, { 5260, 52, 30 }, { 5280, 56, 30 }, { 5300, 60, 30 }, { 5320, 64, 30 }, { 5500, 100, 30 }, { 5520, 104, 30 }, { 5540, 108, 30 }, { 5560, 112, 30 }, { 5580, 116, 30 }, { 5600, 120, 30 }, { 5620, 124, 30 }, { 5640, 128, 30 }, { 5660, 132, 30 }, { 5680, 136, 30 }, { 5700, 140, 30 }, { 5745, 149, 30 }, { 5765, 153, 30 }, { 5785, 157, 30 }, { 5805, 161, 30 }, { 5825, 165, 30 }, { 5920, 184, 30 }, { 5940, 188, 30 }, { 5960, 192, 30 }, { 5980, 196, 30 }, { 6000, 200, 30 }, { 6020, 204, 30 }, { 6040, 208, 30 }, { 6060, 212, 30 }, { 6080, 216, 30 } }, .nchannels = 37 }; static const struct bwn_channelinfo bwn_chantable_n = { .channels = { { 5160, 32, 30 }, { 5170, 34, 30 }, { 5180, 36, 30 }, { 5190, 38, 30 }, { 5200, 40, 30 }, { 5210, 42, 30 }, { 5220, 44, 30 }, { 5230, 46, 30 }, { 5240, 48, 30 }, { 5250, 50, 30 }, { 5260, 52, 30 }, { 5270, 54, 30 }, { 5280, 56, 30 }, { 5290, 58, 30 }, { 5300, 60, 30 }, { 5310, 62, 30 }, { 5320, 64, 30 }, { 5330, 66, 30 }, { 5340, 68, 30 }, { 5350, 70, 30 }, { 5360, 72, 30 }, { 5370, 74, 30 }, { 5380, 76, 30 }, { 5390, 78, 30 }, { 5400, 80, 30 }, { 5410, 82, 30 }, { 5420, 84, 30 }, { 5430, 86, 30 }, { 5440, 88, 30 }, { 5450, 90, 30 }, { 5460, 92, 30 }, { 5470, 94, 30 }, { 5480, 96, 30 }, { 5490, 98, 30 }, { 5500, 100, 30 }, { 5510, 102, 30 }, { 5520, 104, 30 }, { 5530, 106, 30 }, { 5540, 108, 30 }, { 5550, 110, 30 }, { 5560, 112, 30 }, { 5570, 114, 30 }, { 5580, 116, 30 }, { 5590, 118, 30 }, { 5600, 120, 30 }, { 5610, 122, 30 }, { 5620, 124, 30 }, { 5630, 126, 30 }, { 5640, 128, 30 }, { 5650, 130, 30 }, { 5660, 132, 30 }, { 5670, 134, 30 }, { 5680, 136, 30 }, { 5690, 138, 30 }, { 5700, 140, 30 }, { 5710, 142, 30 }, { 5720, 144, 30 }, { 5725, 145, 30 }, { 5730, 146, 30 }, { 5735, 147, 30 }, { 5740, 148, 30 }, { 5745, 149, 30 }, { 5750, 150, 30 }, { 5755, 151, 30 }, { 5760, 152, 30 }, { 5765, 153, 30 }, { 5770, 154, 30 }, { 5775, 155, 30 }, { 5780, 156, 30 }, { 5785, 157, 30 }, { 5790, 158, 30 }, { 5795, 159, 30 }, { 5800, 160, 30 }, { 5805, 161, 30 }, { 5810, 162, 30 }, { 5815, 163, 30 }, { 5820, 164, 30 }, { 5825, 165, 30 }, { 5830, 166, 30 }, { 5840, 168, 30 }, { 5850, 170, 30 }, { 5860, 172, 30 }, { 5870, 174, 30 }, { 5880, 176, 30 }, { 5890, 178, 30 }, { 5900, 180, 30 }, { 5910, 182, 30 }, { 5920, 184, 30 }, { 5930, 186, 30 }, { 5940, 188, 30 }, { 5950, 190, 30 }, { 5960, 192, 30 }, { 5970, 194, 30 }, { 5980, 196, 30 }, { 5990, 198, 30 }, { 6000, 200, 30 }, { 6010, 202, 30 }, { 6020, 204, 30 }, { 6030, 206, 30 }, { 6040, 208, 30 }, { 6050, 210, 30 }, { 6060, 212, 30 }, { 6070, 214, 30 }, { 6080, 216, 30 }, { 6090, 218, 30 }, { 6100, 220, 30 }, { 6110, 222, 30 }, { 6120, 224, 30 }, { 6130, 226, 30 }, { 6140, 228, 30 } }, .nchannels = 110 }; static const uint8_t bwn_b2063_chantable_data[33][12] = { { 0x6f, 0x3c, 0x3c, 0x4, 0x5, 0x5, 0x5, 0x5, 0x77, 0x80, 0x80, 0x70 }, { 0x6f, 0x2c, 0x2c, 0x4, 0x5, 0x5, 0x5, 0x5, 0x77, 0x80, 0x80, 0x70 }, { 0x6f, 0x1c, 0x1c, 0x4, 0x5, 0x5, 0x5, 0x5, 0x77, 0x80, 0x80, 0x70 }, { 0x6e, 0x1c, 0x1c, 0x4, 0x5, 0x5, 0x5, 0x5, 0x77, 0x80, 0x80, 0x70 }, { 0x6e, 0xc, 0xc, 0x4, 0x5, 0x5, 0x5, 0x5, 0x77, 0x80, 0x80, 0x70 }, { 0x6a, 0xc, 0xc, 0, 0x2, 0x5, 0xd, 0xd, 0x77, 0x80, 0x20, 0 }, { 0x6a, 0xc, 0xc, 0, 0x1, 0x5, 0xd, 0xc, 0x77, 0x80, 0x20, 0 }, { 0x6a, 0xc, 0xc, 0, 0x1, 0x4, 0xc, 0xc, 0x77, 0x80, 0x20, 0 }, { 0x69, 0xc, 0xc, 0, 0x1, 0x4, 0xc, 0xc, 0x77, 0x70, 0x20, 0 }, { 0x69, 0xc, 0xc, 0, 0x1, 0x4, 0xb, 0xc, 0x77, 0x70, 0x20, 0 }, { 0x69, 0xc, 0xc, 0, 0, 0x4, 0xb, 0xb, 0x77, 0x60, 0x20, 0 }, { 0x69, 0xc, 0xc, 0, 0, 0x3, 0xa, 0xb, 0x77, 0x60, 0x20, 0 }, { 0x69, 0xc, 0xc, 0, 0, 0x3, 0xa, 0xa, 0x77, 0x60, 0x20, 0 }, { 0x68, 0xc, 0xc, 0, 0, 0x2, 0x9, 0x9, 0x77, 0x60, 0x20, 0 }, { 0x68, 0xc, 0xc, 0, 0, 0x1, 0x8, 0x8, 0x77, 0x50, 0x10, 0 }, { 0x67, 0xc, 0xc, 0, 0, 0, 0x8, 0x8, 0x77, 0x50, 0x10, 0 }, { 0x64, 0xc, 0xc, 0, 0, 0, 0x2, 0x1, 0x77, 0x20, 0, 0 }, { 0x64, 0xc, 0xc, 0, 0, 0, 0x1, 0x1, 0x77, 0x20, 0, 0 }, { 0x63, 0xc, 0xc, 0, 0, 0, 0x1, 0, 0x77, 0x10, 0, 0 }, { 0x63, 0xc, 0xc, 0, 0, 0, 0, 0, 0x77, 0x10, 0, 0 }, { 0x62, 0xc, 0xc, 0, 0, 0, 0, 0, 0x77, 0x10, 0, 0 }, { 0x62, 0xc, 0xc, 0, 0, 0, 0, 0, 0x77, 0, 0, 0 }, { 0x61, 0xc, 0xc, 0, 0, 0, 0, 0, 0x77, 0, 0, 0 }, { 0x60, 0xc, 0xc, 0, 0, 0, 0, 0, 0x77, 0, 0, 0 }, { 0x6e, 0xc, 0xc, 0, 0x9, 0xe, 0xf, 0xf, 0x77, 0xc0, 0x50, 0 }, { 0x6e, 0xc, 0xc, 0, 0x9, 0xd, 0xf, 0xf, 0x77, 0xb0, 0x50, 0 }, { 0x6e, 0xc, 0xc, 0, 0x8, 0xc, 0xf, 0xf, 0x77, 0xb0, 0x50, 0 }, { 0x6d, 0xc, 0xc, 0, 0x8, 0xc, 0xf, 0xf, 0x77, 0xa0, 0x40, 0 }, { 0x6d, 0xc, 0xc, 0, 0x8, 0xb, 0xf, 0xf, 0x77, 0xa0, 0x40, 0 }, { 0x6d, 0xc, 0xc, 0, 0x8, 0xa, 0xf, 0xf, 0x77, 0xa0, 0x40, 0 }, { 0x6c, 0xc, 0xc, 0, 0x7, 0x9, 0xf, 0xf, 0x77, 0x90, 0x40, 0 }, { 0x6c, 0xc, 0xc, 0, 0x6, 0x8, 0xf, 0xf, 0x77, 0x90, 0x40, 0 }, { 0x6c, 0xc, 0xc, 0, 0x5, 0x8, 0xf, 0xf, 0x77, 0x90, 0x40, 0 } }; static const struct bwn_b206x_chan bwn_b2063_chantable[] = { { 1, 2412, bwn_b2063_chantable_data[0] }, { 2, 2417, bwn_b2063_chantable_data[0] }, { 3, 2422, bwn_b2063_chantable_data[0] }, { 4, 2427, bwn_b2063_chantable_data[1] }, { 5, 2432, bwn_b2063_chantable_data[1] }, { 6, 2437, bwn_b2063_chantable_data[1] }, { 7, 2442, bwn_b2063_chantable_data[1] }, { 8, 2447, bwn_b2063_chantable_data[1] }, { 9, 2452, bwn_b2063_chantable_data[2] }, { 10, 2457, bwn_b2063_chantable_data[2] }, { 11, 2462, bwn_b2063_chantable_data[3] }, { 12, 2467, bwn_b2063_chantable_data[3] }, { 13, 2472, bwn_b2063_chantable_data[3] }, { 14, 2484, bwn_b2063_chantable_data[4] }, { 34, 5170, bwn_b2063_chantable_data[5] }, { 36, 5180, bwn_b2063_chantable_data[6] }, { 38, 5190, bwn_b2063_chantable_data[7] }, { 40, 5200, bwn_b2063_chantable_data[8] }, { 42, 5210, bwn_b2063_chantable_data[9] }, { 44, 5220, bwn_b2063_chantable_data[10] }, { 46, 5230, bwn_b2063_chantable_data[11] }, { 48, 5240, bwn_b2063_chantable_data[12] }, { 52, 5260, bwn_b2063_chantable_data[13] }, { 56, 5280, bwn_b2063_chantable_data[14] }, { 60, 5300, bwn_b2063_chantable_data[14] }, { 64, 5320, bwn_b2063_chantable_data[15] }, { 100, 5500, bwn_b2063_chantable_data[16] }, { 104, 5520, bwn_b2063_chantable_data[17] }, { 108, 5540, bwn_b2063_chantable_data[18] }, { 112, 5560, bwn_b2063_chantable_data[19] }, { 116, 5580, bwn_b2063_chantable_data[20] }, { 120, 5600, bwn_b2063_chantable_data[21] }, { 124, 5620, bwn_b2063_chantable_data[21] }, { 128, 5640, bwn_b2063_chantable_data[22] }, { 132, 5660, bwn_b2063_chantable_data[22] }, { 136, 5680, bwn_b2063_chantable_data[22] }, { 140, 5700, bwn_b2063_chantable_data[23] }, { 149, 5745, bwn_b2063_chantable_data[23] }, { 153, 5765, bwn_b2063_chantable_data[23] }, { 157, 5785, bwn_b2063_chantable_data[23] }, { 161, 5805, bwn_b2063_chantable_data[23] }, { 165, 5825, bwn_b2063_chantable_data[23] }, { 184, 4920, bwn_b2063_chantable_data[24] }, { 188, 4940, bwn_b2063_chantable_data[25] }, { 192, 4960, bwn_b2063_chantable_data[26] }, { 196, 4980, bwn_b2063_chantable_data[27] }, { 200, 5000, bwn_b2063_chantable_data[28] }, { 204, 5020, bwn_b2063_chantable_data[29] }, { 208, 5040, bwn_b2063_chantable_data[30] }, { 212, 5060, bwn_b2063_chantable_data[31] }, { 216, 5080, bwn_b2063_chantable_data[32] } }; static const uint8_t bwn_b2062_chantable_data[22][12] = { { 0xff, 0xff, 0xb5, 0x1b, 0x24, 0x32, 0x32, 0x88, 0x88, 0, 0, 0 }, { 0, 0x22, 0x20, 0x84, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0x11, 0x10, 0x83, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x83, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0x11, 0x20, 0x83, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0x11, 0x10, 0x84, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0x11, 0, 0x83, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x63, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x62, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x30, 0x3c, 0x77, 0x37, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x20, 0x3c, 0x77, 0x37, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0x10, 0x3c, 0x77, 0x37, 0xff, 0x88, 0, 0, 0 }, { 0, 0, 0, 0, 0x3c, 0x77, 0x37, 0xff, 0x88, 0, 0, 0 }, { 0x55, 0x77, 0x90, 0xf7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x44, 0x77, 0x80, 0xe7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x44, 0x66, 0x80, 0xe7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x33, 0x66, 0x70, 0xc7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x22, 0x55, 0x60, 0xd7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x22, 0x55, 0x60, 0xc7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x22, 0x44, 0x50, 0xc7, 0x3c, 0x77, 0x35, 0xff, 0xff, 0, 0, 0 }, { 0x11, 0x44, 0x50, 0xa5, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 }, { 0, 0x44, 0x40, 0xb6, 0x3c, 0x77, 0x35, 0xff, 0x88, 0, 0, 0 } }; static const struct bwn_b206x_chan bwn_b2062_chantable[] = { { 1, 2412, bwn_b2062_chantable_data[0] }, { 2, 2417, bwn_b2062_chantable_data[0] }, { 3, 2422, bwn_b2062_chantable_data[0] }, { 4, 2427, bwn_b2062_chantable_data[0] }, { 5, 2432, bwn_b2062_chantable_data[0] }, { 6, 2437, bwn_b2062_chantable_data[0] }, { 7, 2442, bwn_b2062_chantable_data[0] }, { 8, 2447, bwn_b2062_chantable_data[0] }, { 9, 2452, bwn_b2062_chantable_data[0] }, { 10, 2457, bwn_b2062_chantable_data[0] }, { 11, 2462, bwn_b2062_chantable_data[0] }, { 12, 2467, bwn_b2062_chantable_data[0] }, { 13, 2472, bwn_b2062_chantable_data[0] }, { 14, 2484, bwn_b2062_chantable_data[0] }, { 34, 5170, bwn_b2062_chantable_data[1] }, { 38, 5190, bwn_b2062_chantable_data[2] }, { 42, 5210, bwn_b2062_chantable_data[2] }, { 46, 5230, bwn_b2062_chantable_data[3] }, { 36, 5180, bwn_b2062_chantable_data[4] }, { 40, 5200, bwn_b2062_chantable_data[5] }, { 44, 5220, bwn_b2062_chantable_data[6] }, { 48, 5240, bwn_b2062_chantable_data[3] }, { 52, 5260, bwn_b2062_chantable_data[3] }, { 56, 5280, bwn_b2062_chantable_data[3] }, { 60, 5300, bwn_b2062_chantable_data[7] }, { 64, 5320, bwn_b2062_chantable_data[8] }, { 100, 5500, bwn_b2062_chantable_data[9] }, { 104, 5520, bwn_b2062_chantable_data[10] }, { 108, 5540, bwn_b2062_chantable_data[10] }, { 112, 5560, bwn_b2062_chantable_data[10] }, { 116, 5580, bwn_b2062_chantable_data[11] }, { 120, 5600, bwn_b2062_chantable_data[12] }, { 124, 5620, bwn_b2062_chantable_data[12] }, { 128, 5640, bwn_b2062_chantable_data[12] }, { 132, 5660, bwn_b2062_chantable_data[12] }, { 136, 5680, bwn_b2062_chantable_data[12] }, { 140, 5700, bwn_b2062_chantable_data[12] }, { 149, 5745, bwn_b2062_chantable_data[12] }, { 153, 5765, bwn_b2062_chantable_data[12] }, { 157, 5785, bwn_b2062_chantable_data[12] }, { 161, 5805, bwn_b2062_chantable_data[12] }, { 165, 5825, bwn_b2062_chantable_data[12] }, { 184, 4920, bwn_b2062_chantable_data[13] }, { 188, 4940, bwn_b2062_chantable_data[14] }, { 192, 4960, bwn_b2062_chantable_data[15] }, { 196, 4980, bwn_b2062_chantable_data[16] }, { 200, 5000, bwn_b2062_chantable_data[17] }, { 204, 5020, bwn_b2062_chantable_data[18] }, { 208, 5040, bwn_b2062_chantable_data[19] }, { 212, 5060, bwn_b2062_chantable_data[20] }, { 216, 5080, bwn_b2062_chantable_data[21] } }; /* for LP PHY */ static const struct bwn_rxcompco bwn_rxcompco_5354[] = { { 1, -66, 15 }, { 2, -66, 15 }, { 3, -66, 15 }, { 4, -66, 15 }, { 5, -66, 15 }, { 6, -66, 15 }, { 7, -66, 14 }, { 8, -66, 14 }, { 9, -66, 14 }, { 10, -66, 14 }, { 11, -66, 14 }, { 12, -66, 13 }, { 13, -66, 13 }, { 14, -66, 13 }, }; /* for LP PHY */ static const struct bwn_rxcompco bwn_rxcompco_r12[] = { { 1, -64, 13 }, { 2, -64, 13 }, { 3, -64, 13 }, { 4, -64, 13 }, { 5, -64, 12 }, { 6, -64, 12 }, { 7, -64, 12 }, { 8, -64, 12 }, { 9, -64, 12 }, { 10, -64, 11 }, { 11, -64, 11 }, { 12, -64, 11 }, { 13, -64, 11 }, { 14, -64, 10 }, { 34, -62, 24 }, { 38, -62, 24 }, { 42, -62, 24 }, { 46, -62, 23 }, { 36, -62, 24 }, { 40, -62, 24 }, { 44, -62, 23 }, { 48, -62, 23 }, { 52, -62, 23 }, { 56, -62, 22 }, { 60, -62, 22 }, { 64, -62, 22 }, { 100, -62, 16 }, { 104, -62, 16 }, { 108, -62, 15 }, { 112, -62, 14 }, { 116, -62, 14 }, { 120, -62, 13 }, { 124, -62, 12 }, { 128, -62, 12 }, { 132, -62, 12 }, { 136, -62, 11 }, { 140, -62, 10 }, { 149, -61, 9 }, { 153, -61, 9 }, { 157, -61, 9 }, { 161, -61, 8 }, { 165, -61, 8 }, { 184, -62, 25 }, { 188, -62, 25 }, { 192, -62, 25 }, { 196, -62, 25 }, { 200, -62, 25 }, { 204, -62, 25 }, { 208, -62, 25 }, { 212, -62, 25 }, { 216, -62, 26 }, }; static const struct bwn_rxcompco bwn_rxcompco_r2 = { 0, -64, 0 }; static const uint8_t bwn_tab_sigsq_tbl[] = { 0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2, 0xcf, 0xcd, 0xca, 0xc7, 0xc4, 0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0x00, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xc1, 0xc4, 0xc7, 0xca, 0xcd, 0xcf, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, }; static const uint8_t bwn_tab_pllfrac_tbl[] = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; static const uint16_t bwn_tabl_iqlocal_tbl[] = { 0x0200, 0x0300, 0x0400, 0x0600, 0x0800, 0x0b00, 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1707, 0x2007, 0x2d07, 0x4007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0200, 0x0300, 0x0400, 0x0600, 0x0800, 0x0b00, 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1707, 0x2007, 0x2d07, 0x4007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, }; static const uint16_t bwn_tab_noise_g1[] = BWN_TAB_NOISE_G1; static const uint16_t bwn_tab_noise_g2[] = BWN_TAB_NOISE_G2; static const uint16_t bwn_tab_noisescale_g1[] = BWN_TAB_NOISESCALE_G1; static const uint16_t bwn_tab_noisescale_g2[] = BWN_TAB_NOISESCALE_G2; static const uint16_t bwn_tab_noisescale_g3[] = BWN_TAB_NOISESCALE_G3; const uint8_t bwn_bitrev_table[256] = BWN_BITREV_TABLE; #define VENDOR_LED_ACT(vendor) \ { \ .vid = PCI_VENDOR_##vendor, \ .led_act = { BWN_VENDOR_LED_ACT_##vendor } \ } static const struct { uint16_t vid; uint8_t led_act[BWN_LED_MAX]; } bwn_vendor_led_act[] = { VENDOR_LED_ACT(COMPAQ), VENDOR_LED_ACT(ASUSTEK) }; static const uint8_t bwn_default_led_act[BWN_LED_MAX] = { BWN_VENDOR_LED_ACT_DEFAULT }; #undef VENDOR_LED_ACT static const struct { int on_dur; int off_dur; } bwn_led_duration[109] = { [0] = { 400, 100 }, [2] = { 150, 75 }, [4] = { 90, 45 }, [11] = { 66, 34 }, [12] = { 53, 26 }, [18] = { 42, 21 }, [22] = { 35, 17 }, [24] = { 32, 16 }, [36] = { 21, 10 }, [48] = { 16, 8 }, [72] = { 11, 5 }, [96] = { 9, 4 }, [108] = { 7, 3 } }; static const uint16_t bwn_wme_shm_offsets[] = { [0] = BWN_WME_BESTEFFORT, [1] = BWN_WME_BACKGROUND, [2] = BWN_WME_VOICE, [3] = BWN_WME_VIDEO, }; static const struct siba_devid bwn_devs[] = { SIBA_DEV(BROADCOM, 80211, 5, "Revision 5"), SIBA_DEV(BROADCOM, 80211, 6, "Revision 6"), SIBA_DEV(BROADCOM, 80211, 7, "Revision 7"), SIBA_DEV(BROADCOM, 80211, 9, "Revision 9"), SIBA_DEV(BROADCOM, 80211, 10, "Revision 10"), SIBA_DEV(BROADCOM, 80211, 11, "Revision 11"), SIBA_DEV(BROADCOM, 80211, 13, "Revision 13"), SIBA_DEV(BROADCOM, 80211, 15, "Revision 15"), SIBA_DEV(BROADCOM, 80211, 16, "Revision 16") }; static int bwn_probe(device_t dev) { int i; for (i = 0; i < sizeof(bwn_devs) / sizeof(bwn_devs[0]); i++) { if (siba_get_vendor(dev) == bwn_devs[i].sd_vendor && siba_get_device(dev) == bwn_devs[i].sd_device && siba_get_revid(dev) == bwn_devs[i].sd_rev) return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int bwn_attach(device_t dev) { struct bwn_mac *mac; struct bwn_softc *sc = device_get_softc(dev); int error, i, msic, reg; sc->sc_dev = dev; #ifdef BWN_DEBUG sc->sc_debug = bwn_debug; #endif if ((sc->sc_flags & BWN_FLAG_ATTACHED) == 0) { error = bwn_attach_pre(sc); if (error != 0) return (error); bwn_sprom_bugfixes(dev); sc->sc_flags |= BWN_FLAG_ATTACHED; } if (!TAILQ_EMPTY(&sc->sc_maclist)) { if (siba_get_pci_device(dev) != 0x4313 && siba_get_pci_device(dev) != 0x431a && siba_get_pci_device(dev) != 0x4321) { device_printf(sc->sc_dev, "skip 802.11 cores\n"); return (ENODEV); } } mac = (struct bwn_mac *)malloc(sizeof(*mac), M_DEVBUF, M_NOWAIT | M_ZERO); if (mac == NULL) return (ENOMEM); mac->mac_sc = sc; mac->mac_status = BWN_MAC_STATUS_UNINIT; if (bwn_bfp != 0) mac->mac_flags |= BWN_MAC_FLAG_BADFRAME_PREEMP; TASK_INIT(&mac->mac_hwreset, 0, bwn_hwreset, mac); TASK_INIT(&mac->mac_intrtask, 0, bwn_intrtask, mac); TASK_INIT(&mac->mac_txpower, 0, bwn_txpwr, mac); error = bwn_attach_core(mac); if (error) goto fail0; bwn_led_attach(mac); device_printf(sc->sc_dev, "WLAN (chipid %#x rev %u) " "PHY (analog %d type %d rev %d) RADIO (manuf %#x ver %#x rev %d)\n", siba_get_chipid(sc->sc_dev), siba_get_revid(sc->sc_dev), mac->mac_phy.analog, mac->mac_phy.type, mac->mac_phy.rev, mac->mac_phy.rf_manuf, mac->mac_phy.rf_ver, mac->mac_phy.rf_rev); if (mac->mac_flags & BWN_MAC_FLAG_DMA) device_printf(sc->sc_dev, "DMA (%d bits)\n", mac->mac_method.dma.dmatype); else device_printf(sc->sc_dev, "PIO\n"); /* * setup PCI resources and interrupt. */ if (pci_find_cap(dev, PCIY_EXPRESS, ®) == 0) { msic = pci_msi_count(dev); if (bootverbose) device_printf(sc->sc_dev, "MSI count : %d\n", msic); } else msic = 0; mac->mac_intr_spec = bwn_res_spec_legacy; if (msic == BWN_MSI_MESSAGES && bwn_msi_disable == 0) { if (pci_alloc_msi(dev, &msic) == 0) { device_printf(sc->sc_dev, "Using %d MSI messages\n", msic); mac->mac_intr_spec = bwn_res_spec_msi; mac->mac_msi = 1; } } error = bus_alloc_resources(dev, mac->mac_intr_spec, mac->mac_res_irq); if (error) { device_printf(sc->sc_dev, "couldn't allocate IRQ resources (%d)\n", error); goto fail1; } if (mac->mac_msi == 0) error = bus_setup_intr(dev, mac->mac_res_irq[0], INTR_TYPE_NET | INTR_MPSAFE, bwn_intr, NULL, mac, &mac->mac_intrhand[0]); else { for (i = 0; i < BWN_MSI_MESSAGES; i++) { error = bus_setup_intr(dev, mac->mac_res_irq[i], INTR_TYPE_NET | INTR_MPSAFE, bwn_intr, NULL, mac, &mac->mac_intrhand[i]); if (error != 0) { device_printf(sc->sc_dev, "couldn't setup interrupt (%d)\n", error); break; } } } TAILQ_INSERT_TAIL(&sc->sc_maclist, mac, mac_list); /* * calls attach-post routine */ if ((sc->sc_flags & BWN_FLAG_ATTACHED) != 0) bwn_attach_post(sc); return (0); fail1: if (msic == BWN_MSI_MESSAGES && bwn_msi_disable == 0) pci_release_msi(dev); fail0: free(mac, M_DEVBUF); return (error); } static int bwn_is_valid_ether_addr(uint8_t *addr) { char zero_addr[6] = { 0, 0, 0, 0, 0, 0 }; if ((addr[0] & 1) || (!bcmp(addr, zero_addr, ETHER_ADDR_LEN))) return (FALSE); return (TRUE); } static int bwn_attach_post(struct bwn_softc *sc) { struct ieee80211com *ic; struct ifnet *ifp = sc->sc_ifp; ic = ifp->if_l2com; ic->ic_ifp = ifp; /* XXX not right but it's not used anywhere important */ ic->ic_phytype = IEEE80211_T_OFDM; ic->ic_opmode = IEEE80211_M_STA; ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_MONITOR /* monitor mode */ | IEEE80211_C_AHDEMO /* adhoc demo mode */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_WME /* WME/WMM supported */ | IEEE80211_C_WPA /* capable of WPA1+WPA2 */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_TXPMGT /* capable of txpow mgt */ ; ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; /* s/w bmiss */ /* call MI attach routine. */ ieee80211_ifattach(ic, bwn_is_valid_ether_addr(siba_sprom_get_mac_80211a(sc->sc_dev)) ? siba_sprom_get_mac_80211a(sc->sc_dev) : siba_sprom_get_mac_80211bg(sc->sc_dev)); ic->ic_headroom = sizeof(struct bwn_txhdr); /* override default methods */ ic->ic_raw_xmit = bwn_raw_xmit; ic->ic_updateslot = bwn_updateslot; ic->ic_update_promisc = bwn_update_promisc; ic->ic_wme.wme_update = bwn_wme_update; ic->ic_scan_start = bwn_scan_start; ic->ic_scan_end = bwn_scan_end; ic->ic_set_channel = bwn_set_channel; ic->ic_vap_create = bwn_vap_create; ic->ic_vap_delete = bwn_vap_delete; ieee80211_radiotap_attach(ic, &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th), BWN_TX_RADIOTAP_PRESENT, &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th), BWN_RX_RADIOTAP_PRESENT); bwn_sysctl_node(sc); if (bootverbose) ieee80211_announce(ic); return (0); } static void bwn_phy_detach(struct bwn_mac *mac) { if (mac->mac_phy.detach != NULL) mac->mac_phy.detach(mac); } static int bwn_detach(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); struct bwn_mac *mac = sc->sc_curmac; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; int i; sc->sc_flags |= BWN_FLAG_INVALID; if (device_is_attached(sc->sc_dev)) { bwn_stop(sc, 1); bwn_dma_free(mac); callout_drain(&sc->sc_led_blink_ch); callout_drain(&sc->sc_rfswitch_ch); callout_drain(&sc->sc_task_ch); callout_drain(&sc->sc_watchdog_ch); bwn_phy_detach(mac); if (ifp != NULL) { ieee80211_draintask(ic, &mac->mac_hwreset); ieee80211_draintask(ic, &mac->mac_txpower); ieee80211_ifdetach(ic); if_free(ifp); } } taskqueue_drain(sc->sc_tq, &mac->mac_intrtask); taskqueue_free(sc->sc_tq); for (i = 0; i < BWN_MSI_MESSAGES; i++) { if (mac->mac_intrhand[i] != NULL) { bus_teardown_intr(dev, mac->mac_res_irq[i], mac->mac_intrhand[i]); mac->mac_intrhand[i] = NULL; } } bus_release_resources(dev, mac->mac_intr_spec, mac->mac_res_irq); if (mac->mac_msi != 0) pci_release_msi(dev); BWN_LOCK_DESTROY(sc); return (0); } static int bwn_attach_pre(struct bwn_softc *sc) { struct ifnet *ifp; int error = 0; BWN_LOCK_INIT(sc); TAILQ_INIT(&sc->sc_maclist); callout_init_mtx(&sc->sc_rfswitch_ch, &sc->sc_mtx, 0); callout_init_mtx(&sc->sc_task_ch, &sc->sc_mtx, 0); callout_init_mtx(&sc->sc_watchdog_ch, &sc->sc_mtx, 0); sc->sc_tq = taskqueue_create_fast("bwn_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->sc_tq); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->sc_dev)); ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); if (ifp == NULL) { device_printf(sc->sc_dev, "can not if_alloc()\n"); error = ENOSPC; goto fail; } /* set these up early for if_printf use */ if_initname(ifp, device_get_name(sc->sc_dev), device_get_unit(sc->sc_dev)); ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_init = bwn_init; ifp->if_ioctl = bwn_ioctl; ifp->if_start = bwn_start; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); return (0); fail: BWN_LOCK_DESTROY(sc); return (error); } static void bwn_sprom_bugfixes(device_t dev) { #define BWN_ISDEV(_vendor, _device, _subvendor, _subdevice) \ ((siba_get_pci_vendor(dev) == PCI_VENDOR_##_vendor) && \ (siba_get_pci_device(dev) == _device) && \ (siba_get_pci_subvendor(dev) == PCI_VENDOR_##_subvendor) && \ (siba_get_pci_subdevice(dev) == _subdevice)) if (siba_get_pci_subvendor(dev) == PCI_VENDOR_APPLE && siba_get_pci_subdevice(dev) == 0x4e && siba_get_pci_revid(dev) > 0x40) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) | BWN_BFL_PACTRL); if (siba_get_pci_subvendor(dev) == SIBA_BOARDVENDOR_DELL && siba_get_chipid(dev) == 0x4301 && siba_get_pci_revid(dev) == 0x74) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) | BWN_BFL_BTCOEXIST); if (siba_get_type(dev) == SIBA_TYPE_PCI) { if (BWN_ISDEV(BROADCOM, 0x4318, ASUSTEK, 0x100f) || BWN_ISDEV(BROADCOM, 0x4320, DELL, 0x0003) || BWN_ISDEV(BROADCOM, 0x4320, HP, 0x12f8) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0013) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0014) || BWN_ISDEV(BROADCOM, 0x4320, LINKSYS, 0x0015) || BWN_ISDEV(BROADCOM, 0x4320, MOTOROLA, 0x7010)) siba_sprom_set_bf_lo(dev, siba_sprom_get_bf_lo(dev) & ~BWN_BFL_BTCOEXIST); } #undef BWN_ISDEV } static int bwn_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { #define IS_RUNNING(ifp) \ ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) struct bwn_softc *sc = ifp->if_softc; struct ieee80211com *ic = ifp->if_l2com; struct ifreq *ifr = (struct ifreq *)data; int error = 0, startall; switch (cmd) { case SIOCSIFFLAGS: startall = 0; if (IS_RUNNING(ifp)) { bwn_update_promisc(ifp); } else if (ifp->if_flags & IFF_UP) { if ((sc->sc_flags & BWN_FLAG_INVALID) == 0) { bwn_init(sc); startall = 1; } } else bwn_stop(sc, 1); if (startall) ieee80211_start_all(ic); break; case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); break; case SIOCGIFADDR: error = ether_ioctl(ifp, cmd, data); break; default: error = EINVAL; break; } return (error); } static void bwn_start(struct ifnet *ifp) { struct bwn_softc *sc = ifp->if_softc; BWN_LOCK(sc); bwn_start_locked(ifp); BWN_UNLOCK(sc); } static void bwn_start_locked(struct ifnet *ifp) { struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_key *k; struct mbuf *m; BWN_ASSERT_LOCKED(sc); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || mac == NULL || mac->mac_status < BWN_MAC_STATUS_STARTED) return; for (;;) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); /* XXX: LOCK */ if (m == NULL) break; if (bwn_tx_isfull(sc, m)) break; ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; if (ni == NULL) { device_printf(sc->sc_dev, "unexpected NULL ni\n"); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } KASSERT(ni != NULL, ("%s:%d: fail", __func__, __LINE__)); wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { ieee80211_free_node(ni); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } } wh = NULL; /* Catch any invalid use */ if (bwn_tx_start(sc, ni, m) != 0) { if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } sc->sc_watchdog_timer = 5; } } static int bwn_tx_isfull(struct bwn_softc *sc, struct mbuf *m) { struct bwn_dma_ring *dr; struct bwn_mac *mac = sc->sc_curmac; struct bwn_pio_txqueue *tq; struct ifnet *ifp = sc->sc_ifp; int pktlen = roundup(m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); BWN_ASSERT_LOCKED(sc); if (mac->mac_flags & BWN_MAC_FLAG_DMA) { dr = bwn_dma_select(mac, M_WME_GETAC(m)); if (dr->dr_stop == 1 || bwn_dma_freeslot(dr) < BWN_TX_SLOTS_PER_FRAME) { dr->dr_stop = 1; goto full; } } else { tq = bwn_pio_select(mac, M_WME_GETAC(m)); if (tq->tq_free == 0 || pktlen > tq->tq_size || pktlen > (tq->tq_size - tq->tq_used)) { tq->tq_stop = 1; goto full; } } return (0); full: IFQ_DRV_PREPEND(&ifp->if_snd, m); ifp->if_drv_flags |= IFF_DRV_OACTIVE; return (1); } static int bwn_tx_start(struct bwn_softc *sc, struct ieee80211_node *ni, struct mbuf *m) { struct bwn_mac *mac = sc->sc_curmac; int error; BWN_ASSERT_LOCKED(sc); if (m->m_pkthdr.len < IEEE80211_MIN_LEN || mac == NULL) { m_freem(m); return (ENXIO); } error = (mac->mac_flags & BWN_MAC_FLAG_DMA) ? bwn_dma_tx_start(mac, ni, m) : bwn_pio_tx_start(mac, ni, m); if (error) { m_freem(m); return (error); } return (0); } static int bwn_pio_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m) { struct bwn_pio_txpkt *tp; struct bwn_pio_txqueue *tq = bwn_pio_select(mac, M_WME_GETAC(m)); struct bwn_softc *sc = mac->mac_sc; struct bwn_txhdr txhdr; struct mbuf *m_new; uint32_t ctl32; int error; uint16_t ctl16; BWN_ASSERT_LOCKED(sc); /* XXX TODO send packets after DTIM */ KASSERT(!TAILQ_EMPTY(&tq->tq_pktlist), ("%s: fail", __func__)); tp = TAILQ_FIRST(&tq->tq_pktlist); tp->tp_ni = ni; tp->tp_m = m; error = bwn_set_txhdr(mac, ni, m, &txhdr, BWN_PIO_COOKIE(tq, tp)); if (error) { device_printf(sc->sc_dev, "tx fail\n"); return (error); } TAILQ_REMOVE(&tq->tq_pktlist, tp, tp_list); tq->tq_used += roundup(m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); tq->tq_free--; if (siba_get_revid(sc->sc_dev) >= 8) { /* * XXX please removes m_defrag(9) */ m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { device_printf(sc->sc_dev, "%s: can't defrag TX buffer\n", __func__); return (ENOBUFS); } if (m_new->m_next != NULL) device_printf(sc->sc_dev, "TODO: fragmented packets for PIO\n"); tp->tp_m = m_new; /* send HEADER */ ctl32 = bwn_pio_write_multi_4(mac, tq, (BWN_PIO_READ_4(mac, tq, BWN_PIO8_TXCTL) | BWN_PIO8_TXCTL_FRAMEREADY) & ~BWN_PIO8_TXCTL_EOF, (const uint8_t *)&txhdr, BWN_HDRSIZE(mac)); /* send BODY */ ctl32 = bwn_pio_write_multi_4(mac, tq, ctl32, mtod(m_new, const void *), m_new->m_pkthdr.len); bwn_pio_write_4(mac, tq, BWN_PIO_TXCTL, ctl32 | BWN_PIO8_TXCTL_EOF); } else { ctl16 = bwn_pio_write_multi_2(mac, tq, (bwn_pio_read_2(mac, tq, BWN_PIO_TXCTL) | BWN_PIO_TXCTL_FRAMEREADY) & ~BWN_PIO_TXCTL_EOF, (const uint8_t *)&txhdr, BWN_HDRSIZE(mac)); ctl16 = bwn_pio_write_mbuf_2(mac, tq, ctl16, m); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl16 | BWN_PIO_TXCTL_EOF); } return (0); } static struct bwn_pio_txqueue * bwn_pio_select(struct bwn_mac *mac, uint8_t prio) { if ((mac->mac_flags & BWN_MAC_FLAG_WME) == 0) return (&mac->mac_method.pio.wme[WME_AC_BE]); switch (prio) { case 0: return (&mac->mac_method.pio.wme[WME_AC_BE]); case 1: return (&mac->mac_method.pio.wme[WME_AC_BK]); case 2: return (&mac->mac_method.pio.wme[WME_AC_VI]); case 3: return (&mac->mac_method.pio.wme[WME_AC_VO]); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (NULL); } static int bwn_dma_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m) { #define BWN_GET_TXHDRCACHE(slot) \ &(txhdr_cache[(slot / BWN_TX_SLOTS_PER_FRAME) * BWN_HDRSIZE(mac)]) struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr = bwn_dma_select(mac, M_WME_GETAC(m)); struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *mt; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; uint8_t *txhdr_cache = (uint8_t *)dr->dr_txhdr_cache; int error, slot, backup[2] = { dr->dr_curslot, dr->dr_usedslot }; BWN_ASSERT_LOCKED(sc); KASSERT(!dr->dr_stop, ("%s:%d: fail", __func__, __LINE__)); /* XXX send after DTIM */ slot = bwn_dma_getslot(dr); dr->getdesc(dr, slot, &desc, &mt); KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_HEADER, ("%s:%d: fail", __func__, __LINE__)); error = bwn_set_txhdr(dr->dr_mac, ni, m, (struct bwn_txhdr *)BWN_GET_TXHDRCACHE(slot), BWN_DMA_COOKIE(dr, slot)); if (error) goto fail; error = bus_dmamap_load(dr->dr_txring_dtag, mt->mt_dmap, BWN_GET_TXHDRCACHE(slot), BWN_HDRSIZE(mac), bwn_dma_ring_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error) { if_printf(ifp, "%s: can't load TX buffer (1) %d\n", __func__, error); goto fail; } bus_dmamap_sync(dr->dr_txring_dtag, mt->mt_dmap, BUS_DMASYNC_PREWRITE); dr->setdesc(dr, desc, mt->mt_paddr, BWN_HDRSIZE(mac), 1, 0, 0); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); slot = bwn_dma_getslot(dr); dr->getdesc(dr, slot, &desc, &mt); KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_BODY && mt->mt_islast == 1, ("%s:%d: fail", __func__, __LINE__)); mt->mt_m = m; mt->mt_ni = ni; error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error && error != EFBIG) { if_printf(ifp, "%s: can't load TX buffer (1) %d\n", __func__, error); goto fail; } if (error) { /* error == EFBIG */ struct mbuf *m_new; m_new = m_defrag(m, M_NOWAIT); if (m_new == NULL) { if_printf(ifp, "%s: can't defrag TX buffer\n", __func__); error = ENOBUFS; goto fail; } else { m = m_new; } mt->mt_m = m; error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); if (error) { if_printf(ifp, "%s: can't load TX buffer (2) %d\n", __func__, error); goto fail; } } bus_dmamap_sync(dma->txbuf_dtag, mt->mt_dmap, BUS_DMASYNC_PREWRITE); dr->setdesc(dr, desc, mt->mt_paddr, m->m_pkthdr.len, 0, 1, 1); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); /* XXX send after DTIM */ dr->start_transfer(dr, bwn_dma_nextslot(dr, slot)); return (0); fail: dr->dr_curslot = backup[0]; dr->dr_usedslot = backup[1]; return (error); #undef BWN_GET_TXHDRCACHE } static void bwn_watchdog(void *arg) { struct bwn_softc *sc = arg; struct ifnet *ifp = sc->sc_ifp; if (sc->sc_watchdog_timer != 0 && --sc->sc_watchdog_timer == 0) { if_printf(ifp, "device timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); } callout_schedule(&sc->sc_watchdog_ch, hz); } static int bwn_attach_core(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int error, have_bg = 0, have_a = 0; uint32_t high; KASSERT(siba_get_revid(sc->sc_dev) >= 5, ("unsupported revision %d", siba_get_revid(sc->sc_dev))); siba_powerup(sc->sc_dev, 0); high = siba_read_4(sc->sc_dev, SIBA_TGSHIGH); bwn_reset_core(mac, (high & BWN_TGSHIGH_HAVE_2GHZ) ? BWN_TGSLOW_SUPPORT_G : 0); error = bwn_phy_getinfo(mac, high); if (error) goto fail; have_a = (high & BWN_TGSHIGH_HAVE_5GHZ) ? 1 : 0; have_bg = (high & BWN_TGSHIGH_HAVE_2GHZ) ? 1 : 0; if (siba_get_pci_device(sc->sc_dev) != 0x4312 && siba_get_pci_device(sc->sc_dev) != 0x4319 && siba_get_pci_device(sc->sc_dev) != 0x4324) { have_a = have_bg = 0; if (mac->mac_phy.type == BWN_PHYTYPE_A) have_a = 1; else if (mac->mac_phy.type == BWN_PHYTYPE_G || mac->mac_phy.type == BWN_PHYTYPE_N || mac->mac_phy.type == BWN_PHYTYPE_LP) have_bg = 1; else KASSERT(0 == 1, ("%s: unknown phy type (%d)", __func__, mac->mac_phy.type)); } /* XXX turns off PHY A because it's not supported */ if (mac->mac_phy.type != BWN_PHYTYPE_LP && mac->mac_phy.type != BWN_PHYTYPE_N) { have_a = 0; have_bg = 1; } if (mac->mac_phy.type == BWN_PHYTYPE_G) { mac->mac_phy.attach = bwn_phy_g_attach; mac->mac_phy.detach = bwn_phy_g_detach; mac->mac_phy.prepare_hw = bwn_phy_g_prepare_hw; mac->mac_phy.init_pre = bwn_phy_g_init_pre; mac->mac_phy.init = bwn_phy_g_init; mac->mac_phy.exit = bwn_phy_g_exit; mac->mac_phy.phy_read = bwn_phy_g_read; mac->mac_phy.phy_write = bwn_phy_g_write; mac->mac_phy.rf_read = bwn_phy_g_rf_read; mac->mac_phy.rf_write = bwn_phy_g_rf_write; mac->mac_phy.use_hwpctl = bwn_phy_g_hwpctl; mac->mac_phy.rf_onoff = bwn_phy_g_rf_onoff; mac->mac_phy.switch_analog = bwn_phy_switch_analog; mac->mac_phy.switch_channel = bwn_phy_g_switch_channel; mac->mac_phy.get_default_chan = bwn_phy_g_get_default_chan; mac->mac_phy.set_antenna = bwn_phy_g_set_antenna; mac->mac_phy.set_im = bwn_phy_g_im; mac->mac_phy.recalc_txpwr = bwn_phy_g_recalc_txpwr; mac->mac_phy.set_txpwr = bwn_phy_g_set_txpwr; mac->mac_phy.task_15s = bwn_phy_g_task_15s; mac->mac_phy.task_60s = bwn_phy_g_task_60s; } else if (mac->mac_phy.type == BWN_PHYTYPE_LP) { mac->mac_phy.init_pre = bwn_phy_lp_init_pre; mac->mac_phy.init = bwn_phy_lp_init; mac->mac_phy.phy_read = bwn_phy_lp_read; mac->mac_phy.phy_write = bwn_phy_lp_write; mac->mac_phy.phy_maskset = bwn_phy_lp_maskset; mac->mac_phy.rf_read = bwn_phy_lp_rf_read; mac->mac_phy.rf_write = bwn_phy_lp_rf_write; mac->mac_phy.rf_onoff = bwn_phy_lp_rf_onoff; mac->mac_phy.switch_analog = bwn_phy_lp_switch_analog; mac->mac_phy.switch_channel = bwn_phy_lp_switch_channel; mac->mac_phy.get_default_chan = bwn_phy_lp_get_default_chan; mac->mac_phy.set_antenna = bwn_phy_lp_set_antenna; mac->mac_phy.task_60s = bwn_phy_lp_task_60s; } else { device_printf(sc->sc_dev, "unsupported PHY type (%d)\n", mac->mac_phy.type); error = ENXIO; goto fail; } mac->mac_phy.gmode = have_bg; if (mac->mac_phy.attach != NULL) { error = mac->mac_phy.attach(mac); if (error) { device_printf(sc->sc_dev, "failed\n"); goto fail; } } bwn_reset_core(mac, have_bg ? BWN_TGSLOW_SUPPORT_G : 0); error = bwn_chiptest(mac); if (error) goto fail; error = bwn_setup_channels(mac, have_bg, have_a); if (error) { device_printf(sc->sc_dev, "failed to setup channels\n"); goto fail; } if (sc->sc_curmac == NULL) sc->sc_curmac = mac; error = bwn_dma_attach(mac); if (error != 0) { device_printf(sc->sc_dev, "failed to initialize DMA\n"); goto fail; } mac->mac_phy.switch_analog(mac, 0); siba_dev_down(sc->sc_dev, 0); fail: siba_powerdown(sc->sc_dev); return (error); } static void bwn_reset_core(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; uint32_t low, ctl; flags |= (BWN_TGSLOW_PHYCLOCK_ENABLE | BWN_TGSLOW_PHYRESET); siba_dev_up(sc->sc_dev, flags); DELAY(2000); low = (siba_read_4(sc->sc_dev, SIBA_TGSLOW) | SIBA_TGSLOW_FGC) & ~BWN_TGSLOW_PHYRESET; siba_write_4(sc->sc_dev, SIBA_TGSLOW, low); siba_read_4(sc->sc_dev, SIBA_TGSLOW); DELAY(1000); siba_write_4(sc->sc_dev, SIBA_TGSLOW, low & ~SIBA_TGSLOW_FGC); siba_read_4(sc->sc_dev, SIBA_TGSLOW); DELAY(1000); if (mac->mac_phy.switch_analog != NULL) mac->mac_phy.switch_analog(mac, 1); ctl = BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_GMODE; if (flags & BWN_TGSLOW_SUPPORT_G) ctl |= BWN_MACCTL_GMODE; BWN_WRITE_4(mac, BWN_MACCTL, ctl | BWN_MACCTL_IHR_ON); } static int bwn_phy_getinfo(struct bwn_mac *mac, int tgshigh) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; /* PHY */ tmp = BWN_READ_2(mac, BWN_PHYVER); phy->gmode = (tgshigh & BWN_TGSHIGH_HAVE_2GHZ) ? 1 : 0; phy->rf_on = 1; phy->analog = (tmp & BWN_PHYVER_ANALOG) >> 12; phy->type = (tmp & BWN_PHYVER_TYPE) >> 8; phy->rev = (tmp & BWN_PHYVER_VERSION); if ((phy->type == BWN_PHYTYPE_A && phy->rev >= 4) || (phy->type == BWN_PHYTYPE_B && phy->rev != 2 && phy->rev != 4 && phy->rev != 6 && phy->rev != 7) || (phy->type == BWN_PHYTYPE_G && phy->rev > 9) || (phy->type == BWN_PHYTYPE_N && phy->rev > 4) || (phy->type == BWN_PHYTYPE_LP && phy->rev > 2)) goto unsupphy; /* RADIO */ if (siba_get_chipid(sc->sc_dev) == 0x4317) { if (siba_get_chiprev(sc->sc_dev) == 0) tmp = 0x3205017f; else if (siba_get_chiprev(sc->sc_dev) == 1) tmp = 0x4205017f; else tmp = 0x5205017f; } else { BWN_WRITE_2(mac, BWN_RFCTL, BWN_RFCTL_ID); tmp = BWN_READ_2(mac, BWN_RFDATALO); BWN_WRITE_2(mac, BWN_RFCTL, BWN_RFCTL_ID); tmp |= (uint32_t)BWN_READ_2(mac, BWN_RFDATAHI) << 16; } phy->rf_rev = (tmp & 0xf0000000) >> 28; phy->rf_ver = (tmp & 0x0ffff000) >> 12; phy->rf_manuf = (tmp & 0x00000fff); if (phy->rf_manuf != 0x17f) /* 0x17f is broadcom */ goto unsupradio; if ((phy->type == BWN_PHYTYPE_A && (phy->rf_ver != 0x2060 || phy->rf_rev != 1 || phy->rf_manuf != 0x17f)) || (phy->type == BWN_PHYTYPE_B && (phy->rf_ver & 0xfff0) != 0x2050) || (phy->type == BWN_PHYTYPE_G && phy->rf_ver != 0x2050) || (phy->type == BWN_PHYTYPE_N && phy->rf_ver != 0x2055 && phy->rf_ver != 0x2056) || (phy->type == BWN_PHYTYPE_LP && phy->rf_ver != 0x2062 && phy->rf_ver != 0x2063)) goto unsupradio; return (0); unsupphy: device_printf(sc->sc_dev, "unsupported PHY (type %#x, rev %#x, " "analog %#x)\n", phy->type, phy->rev, phy->analog); return (ENXIO); unsupradio: device_printf(sc->sc_dev, "unsupported radio (manuf %#x, ver %#x, " "rev %#x)\n", phy->rf_manuf, phy->rf_ver, phy->rf_rev); return (ENXIO); } static int bwn_chiptest(struct bwn_mac *mac) { #define TESTVAL0 0x55aaaa55 #define TESTVAL1 0xaa5555aa struct bwn_softc *sc = mac->mac_sc; uint32_t v, backup; BWN_LOCK(sc); backup = bwn_shm_read_4(mac, BWN_SHARED, 0); bwn_shm_write_4(mac, BWN_SHARED, 0, TESTVAL0); if (bwn_shm_read_4(mac, BWN_SHARED, 0) != TESTVAL0) goto error; bwn_shm_write_4(mac, BWN_SHARED, 0, TESTVAL1); if (bwn_shm_read_4(mac, BWN_SHARED, 0) != TESTVAL1) goto error; bwn_shm_write_4(mac, BWN_SHARED, 0, backup); if ((siba_get_revid(sc->sc_dev) >= 3) && (siba_get_revid(sc->sc_dev) <= 10)) { BWN_WRITE_2(mac, BWN_TSF_CFP_START, 0xaaaa); BWN_WRITE_4(mac, BWN_TSF_CFP_START, 0xccccbbbb); if (BWN_READ_2(mac, BWN_TSF_CFP_START_LOW) != 0xbbbb) goto error; if (BWN_READ_2(mac, BWN_TSF_CFP_START_HIGH) != 0xcccc) goto error; } BWN_WRITE_4(mac, BWN_TSF_CFP_START, 0); v = BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_GMODE; if (v != (BWN_MACCTL_GMODE | BWN_MACCTL_IHR_ON)) goto error; BWN_UNLOCK(sc); return (0); error: BWN_UNLOCK(sc); device_printf(sc->sc_dev, "failed to validate the chipaccess\n"); return (ENODEV); } #define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT | IEEE80211_CHAN_G) #define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT | IEEE80211_CHAN_A) static int bwn_setup_channels(struct bwn_mac *mac, int have_bg, int have_a) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; memset(ic->ic_channels, 0, sizeof(ic->ic_channels)); ic->ic_nchans = 0; if (have_bg) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_bg, IEEE80211_CHAN_G); if (mac->mac_phy.type == BWN_PHYTYPE_N) { if (have_a) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_n, IEEE80211_CHAN_HTA); } else { if (have_a) bwn_addchannels(ic->ic_channels, IEEE80211_CHAN_MAX, &ic->ic_nchans, &bwn_chantable_a, IEEE80211_CHAN_A); } mac->mac_phy.supports_2ghz = have_bg; mac->mac_phy.supports_5ghz = have_a; return (ic->ic_nchans == 0 ? ENXIO : 0); } static uint32_t bwn_shm_read_4(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint32_t ret; BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); ret = BWN_READ_2(mac, BWN_SHM_DATA_UNALIGNED); ret <<= 16; bwn_shm_ctlword(mac, way, (offset >> 2) + 1); ret |= BWN_READ_2(mac, BWN_SHM_DATA); goto out; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); ret = BWN_READ_4(mac, BWN_SHM_DATA); out: return (ret); } static uint16_t bwn_shm_read_2(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint16_t ret; BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); ret = BWN_READ_2(mac, BWN_SHM_DATA_UNALIGNED); goto out; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); ret = BWN_READ_2(mac, BWN_SHM_DATA); out: return (ret); } static void bwn_shm_ctlword(struct bwn_mac *mac, uint16_t way, uint16_t offset) { uint32_t control; control = way; control <<= 16; control |= offset; BWN_WRITE_4(mac, BWN_SHM_CONTROL, control); } static void bwn_shm_write_4(struct bwn_mac *mac, uint16_t way, uint16_t offset, uint32_t value) { BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); BWN_WRITE_2(mac, BWN_SHM_DATA_UNALIGNED, (value >> 16) & 0xffff); bwn_shm_ctlword(mac, way, (offset >> 2) + 1); BWN_WRITE_2(mac, BWN_SHM_DATA, value & 0xffff); return; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); BWN_WRITE_4(mac, BWN_SHM_DATA, value); } static void bwn_shm_write_2(struct bwn_mac *mac, uint16_t way, uint16_t offset, uint16_t value) { BWN_ASSERT_LOCKED(mac->mac_sc); if (way == BWN_SHARED) { KASSERT((offset & 0x0001) == 0, ("%s:%d warn", __func__, __LINE__)); if (offset & 0x0003) { bwn_shm_ctlword(mac, way, offset >> 2); BWN_WRITE_2(mac, BWN_SHM_DATA_UNALIGNED, value); return; } offset >>= 2; } bwn_shm_ctlword(mac, way, offset); BWN_WRITE_2(mac, BWN_SHM_DATA, value); } static void bwn_addchan(struct ieee80211_channel *c, int freq, int flags, int ieee, int txpow) { c->ic_freq = freq; c->ic_flags = flags; c->ic_ieee = ieee; c->ic_minpower = 0; c->ic_maxpower = 2 * txpow; c->ic_maxregpower = txpow; } static void bwn_addchannels(struct ieee80211_channel chans[], int maxchans, int *nchans, const struct bwn_channelinfo *ci, int flags) { struct ieee80211_channel *c; int i; c = &chans[*nchans]; for (i = 0; i < ci->nchannels; i++) { const struct bwn_channel *hc; hc = &ci->channels[i]; if (*nchans >= maxchans) break; bwn_addchan(c, hc->freq, flags, hc->ieee, hc->maxTxPow); c++, (*nchans)++; if (flags == IEEE80211_CHAN_G || flags == IEEE80211_CHAN_HTG) { /* g channel have a separate b-only entry */ if (*nchans >= maxchans) break; c[0] = c[-1]; c[-1].ic_flags = IEEE80211_CHAN_B; c++, (*nchans)++; } if (flags == IEEE80211_CHAN_HTG) { /* HT g channel have a separate g-only entry */ if (*nchans >= maxchans) break; c[-1].ic_flags = IEEE80211_CHAN_G; c[0] = c[-1]; c[0].ic_flags &= ~IEEE80211_CHAN_HT; c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ c++, (*nchans)++; } if (flags == IEEE80211_CHAN_HTA) { /* HT a channel have a separate a-only entry */ if (*nchans >= maxchans) break; c[-1].ic_flags = IEEE80211_CHAN_A; c[0] = c[-1]; c[0].ic_flags &= ~IEEE80211_CHAN_HT; c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ c++, (*nchans)++; } } } static int bwn_phy_g_attach(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; unsigned int i; int16_t pab0, pab1, pab2; static int8_t bwn_phy_g_tssi2dbm_table[] = BWN_PHY_G_TSSI2DBM_TABLE; int8_t bg; bg = (int8_t)siba_sprom_get_tssi_bg(sc->sc_dev); pab0 = (int16_t)siba_sprom_get_pa0b0(sc->sc_dev); pab1 = (int16_t)siba_sprom_get_pa0b1(sc->sc_dev); pab2 = (int16_t)siba_sprom_get_pa0b2(sc->sc_dev); if ((siba_get_chipid(sc->sc_dev) == 0x4301) && (phy->rf_ver != 0x2050)) device_printf(sc->sc_dev, "not supported anymore\n"); pg->pg_flags = 0; if (pab0 == 0 || pab1 == 0 || pab2 == 0 || pab0 == -1 || pab1 == -1 || pab2 == -1) { pg->pg_idletssi = 52; pg->pg_tssi2dbm = bwn_phy_g_tssi2dbm_table; return (0); } pg->pg_idletssi = (bg == 0 || bg == -1) ? 62 : bg; pg->pg_tssi2dbm = (uint8_t *)malloc(64, M_DEVBUF, M_NOWAIT | M_ZERO); if (pg->pg_tssi2dbm == NULL) { device_printf(sc->sc_dev, "failed to allocate buffer\n"); return (ENOMEM); } for (i = 0; i < 64; i++) { int32_t m1, m2, f, q, delta; int8_t j = 0; m1 = BWN_TSSI2DBM(16 * pab0 + i * pab1, 32); m2 = MAX(BWN_TSSI2DBM(32768 + i * pab2, 256), 1); f = 256; do { if (j > 15) { device_printf(sc->sc_dev, "failed to generate tssi2dBm\n"); free(pg->pg_tssi2dbm, M_DEVBUF); return (ENOMEM); } q = BWN_TSSI2DBM(f * 4096 - BWN_TSSI2DBM(m2 * f, 16) * f, 2048); delta = abs(q - f); f = q; j++; } while (delta >= 2); pg->pg_tssi2dbm[i] = MIN(MAX(BWN_TSSI2DBM(m1 * f, 8192), -127), 128); } pg->pg_flags |= BWN_PHY_G_FLAG_TSSITABLE_ALLOC; return (0); } static void bwn_phy_g_detach(struct bwn_mac *mac) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; if (pg->pg_flags & BWN_PHY_G_FLAG_TSSITABLE_ALLOC) { free(pg->pg_tssi2dbm, M_DEVBUF); pg->pg_tssi2dbm = NULL; } pg->pg_flags = 0; } static void bwn_phy_g_init_pre(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; void *tssi2dbm; int idletssi; unsigned int i; tssi2dbm = pg->pg_tssi2dbm; idletssi = pg->pg_idletssi; memset(pg, 0, sizeof(*pg)); pg->pg_tssi2dbm = tssi2dbm; pg->pg_idletssi = idletssi; memset(pg->pg_minlowsig, 0xff, sizeof(pg->pg_minlowsig)); for (i = 0; i < N(pg->pg_nrssi); i++) pg->pg_nrssi[i] = -1000; for (i = 0; i < N(pg->pg_nrssi_lt); i++) pg->pg_nrssi_lt[i] = i; pg->pg_lofcal = 0xffff; pg->pg_initval = 0xffff; pg->pg_immode = BWN_IMMODE_NONE; pg->pg_ofdmtab_dir = BWN_OFDMTAB_DIR_UNKNOWN; pg->pg_avgtssi = 0xff; pg->pg_loctl.tx_bias = 0xff; TAILQ_INIT(&pg->pg_loctl.calib_list); } static int bwn_phy_g_prepare_hw(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; static const struct bwn_rfatt rfatt0[] = { { 3, 0 }, { 1, 0 }, { 5, 0 }, { 7, 0 }, { 9, 0 }, { 2, 0 }, { 0, 0 }, { 4, 0 }, { 6, 0 }, { 8, 0 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 } }; static const struct bwn_rfatt rfatt1[] = { { 2, 1 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 10, 1 }, { 12, 1 }, { 14, 1 } }; static const struct bwn_rfatt rfatt2[] = { { 0, 1 }, { 2, 1 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 9, 1 }, { 9, 1 } }; static const struct bwn_bbatt bbatt_0[] = { { 0 }, { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 6 }, { 7 }, { 8 } }; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s fail", __func__)); if (phy->rf_ver == 0x2050 && phy->rf_rev < 6) pg->pg_bbatt.att = 0; else pg->pg_bbatt.att = 2; /* prepare Radio Attenuation */ pg->pg_rfatt.padmix = 0; if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BCM4309G) { if (siba_get_pci_revid(sc->sc_dev) < 0x43) { pg->pg_rfatt.att = 2; goto done; } else if (siba_get_pci_revid(sc->sc_dev) < 0x51) { pg->pg_rfatt.att = 3; goto done; } } if (phy->type == BWN_PHYTYPE_A) { pg->pg_rfatt.att = 0x60; goto done; } switch (phy->rf_ver) { case 0x2050: switch (phy->rf_rev) { case 0: pg->pg_rfatt.att = 5; goto done; case 1: if (phy->type == BWN_PHYTYPE_G) { if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BCM4309G && siba_get_pci_revid(sc->sc_dev) >= 30) pg->pg_rfatt.att = 3; else if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BU4306) pg->pg_rfatt.att = 3; else pg->pg_rfatt.att = 1; } else { if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BCM4309G && siba_get_pci_revid(sc->sc_dev) >= 30) pg->pg_rfatt.att = 7; else pg->pg_rfatt.att = 6; } goto done; case 2: if (phy->type == BWN_PHYTYPE_G) { if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BCM4309G && siba_get_pci_revid(sc->sc_dev) >= 30) pg->pg_rfatt.att = 3; else if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BU4306) pg->pg_rfatt.att = 5; else if (siba_get_chipid(sc->sc_dev) == 0x4320) pg->pg_rfatt.att = 4; else pg->pg_rfatt.att = 3; } else pg->pg_rfatt.att = 6; goto done; case 3: pg->pg_rfatt.att = 5; goto done; case 4: case 5: pg->pg_rfatt.att = 1; goto done; case 6: case 7: pg->pg_rfatt.att = 5; goto done; case 8: pg->pg_rfatt.att = 0xa; pg->pg_rfatt.padmix = 1; goto done; case 9: default: pg->pg_rfatt.att = 5; goto done; } break; case 0x2053: switch (phy->rf_rev) { case 1: pg->pg_rfatt.att = 6; goto done; } break; } pg->pg_rfatt.att = 5; done: pg->pg_txctl = (bwn_phy_g_txctl(mac) << 4); if (!bwn_has_hwpctl(mac)) { lo->rfatt.array = rfatt0; lo->rfatt.len = N(rfatt0); lo->rfatt.min = 0; lo->rfatt.max = 9; goto genbbatt; } if (phy->rf_ver == 0x2050 && phy->rf_rev == 8) { lo->rfatt.array = rfatt1; lo->rfatt.len = N(rfatt1); lo->rfatt.min = 0; lo->rfatt.max = 14; goto genbbatt; } lo->rfatt.array = rfatt2; lo->rfatt.len = N(rfatt2); lo->rfatt.min = 0; lo->rfatt.max = 9; genbbatt: lo->bbatt.array = bbatt_0; lo->bbatt.len = N(bbatt_0); lo->bbatt.min = 0; lo->bbatt.max = 8; BWN_READ_4(mac, BWN_MACCTL); if (phy->rev == 1) { phy->gmode = 0; bwn_reset_core(mac, 0); bwn_phy_g_init_sub(mac); phy->gmode = 1; bwn_reset_core(mac, BWN_TGSLOW_SUPPORT_G); } return (0); } static uint16_t bwn_phy_g_txctl(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; if (phy->rf_ver != 0x2050) return (0); if (phy->rf_rev == 1) return (BWN_TXCTL_PA2DB | BWN_TXCTL_TXMIX); if (phy->rf_rev < 6) return (BWN_TXCTL_PA2DB); if (phy->rf_rev == 8) return (BWN_TXCTL_TXMIX); return (0); } static int bwn_phy_g_init(struct bwn_mac *mac) { bwn_phy_g_init_sub(mac); return (0); } static void bwn_phy_g_exit(struct bwn_mac *mac) { struct bwn_txpwr_loctl *lo = &mac->mac_phy.phy_g.pg_loctl; struct bwn_lo_calib *cal, *tmp; if (lo == NULL) return; TAILQ_FOREACH_SAFE(cal, &lo->calib_list, list, tmp) { TAILQ_REMOVE(&lo->calib_list, cal, list); free(cal, M_DEVBUF); } } static uint16_t bwn_phy_g_read(struct bwn_mac *mac, uint16_t reg) { BWN_WRITE_2(mac, BWN_PHYCTL, reg); return (BWN_READ_2(mac, BWN_PHYDATA)); } static void bwn_phy_g_write(struct bwn_mac *mac, uint16_t reg, uint16_t value) { BWN_WRITE_2(mac, BWN_PHYCTL, reg); BWN_WRITE_2(mac, BWN_PHYDATA, value); } static uint16_t bwn_phy_g_rf_read(struct bwn_mac *mac, uint16_t reg) { KASSERT(reg != 1, ("%s:%d: fail", __func__, __LINE__)); BWN_WRITE_2(mac, BWN_RFCTL, reg | 0x80); return (BWN_READ_2(mac, BWN_RFDATALO)); } static void bwn_phy_g_rf_write(struct bwn_mac *mac, uint16_t reg, uint16_t value) { KASSERT(reg != 1, ("%s:%d: fail", __func__, __LINE__)); BWN_WRITE_2(mac, BWN_RFCTL, reg); BWN_WRITE_2(mac, BWN_RFDATALO, value); } static int bwn_phy_g_hwpctl(struct bwn_mac *mac) { return (mac->mac_phy.rev >= 6); } static void bwn_phy_g_rf_onoff(struct bwn_mac *mac, int on) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; unsigned int channel; uint16_t rfover, rfoverval; if (on) { if (phy->rf_on) return; BWN_PHY_WRITE(mac, 0x15, 0x8000); BWN_PHY_WRITE(mac, 0x15, 0xcc00); BWN_PHY_WRITE(mac, 0x15, (phy->gmode ? 0xc0 : 0x0)); if (pg->pg_flags & BWN_PHY_G_FLAG_RADIOCTX_VALID) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, pg->pg_radioctx_over); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, pg->pg_radioctx_overval); pg->pg_flags &= ~BWN_PHY_G_FLAG_RADIOCTX_VALID; } channel = phy->chan; bwn_phy_g_switch_chan(mac, 6, 1); bwn_phy_g_switch_chan(mac, channel, 0); return; } rfover = BWN_PHY_READ(mac, BWN_PHY_RFOVER); rfoverval = BWN_PHY_READ(mac, BWN_PHY_RFOVERVAL); pg->pg_radioctx_over = rfover; pg->pg_radioctx_overval = rfoverval; pg->pg_flags |= BWN_PHY_G_FLAG_RADIOCTX_VALID; BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, rfover | 0x008c); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, rfoverval & 0xff73); } static int bwn_phy_g_switch_channel(struct bwn_mac *mac, uint32_t newchan) { if ((newchan < 1) || (newchan > 14)) return (EINVAL); bwn_phy_g_switch_chan(mac, newchan, 0); return (0); } static uint32_t bwn_phy_g_get_default_chan(struct bwn_mac *mac) { return (1); } static void bwn_phy_g_set_antenna(struct bwn_mac *mac, int antenna) { struct bwn_phy *phy = &mac->mac_phy; uint64_t hf; int autodiv = 0; uint16_t tmp; if (antenna == BWN_ANTAUTO0 || antenna == BWN_ANTAUTO1) autodiv = 1; hf = bwn_hf_read(mac) & ~BWN_HF_UCODE_ANTDIV_HELPER; bwn_hf_write(mac, hf); BWN_PHY_WRITE(mac, BWN_PHY_BBANDCFG, (BWN_PHY_READ(mac, BWN_PHY_BBANDCFG) & ~BWN_PHY_BBANDCFG_RXANT) | ((autodiv ? BWN_ANTAUTO1 : antenna) << BWN_PHY_BBANDCFG_RXANT_SHIFT)); if (autodiv) { tmp = BWN_PHY_READ(mac, BWN_PHY_ANTDWELL); if (antenna == BWN_ANTAUTO1) tmp &= ~BWN_PHY_ANTDWELL_AUTODIV1; else tmp |= BWN_PHY_ANTDWELL_AUTODIV1; BWN_PHY_WRITE(mac, BWN_PHY_ANTDWELL, tmp); } tmp = BWN_PHY_READ(mac, BWN_PHY_ANTWRSETT); if (autodiv) tmp |= BWN_PHY_ANTWRSETT_ARXDIV; else tmp &= ~BWN_PHY_ANTWRSETT_ARXDIV; BWN_PHY_WRITE(mac, BWN_PHY_ANTWRSETT, tmp); if (phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_OFDM61, BWN_PHY_READ(mac, BWN_PHY_OFDM61) | BWN_PHY_OFDM61_10); BWN_PHY_WRITE(mac, BWN_PHY_DIVSRCHGAINBACK, (BWN_PHY_READ(mac, BWN_PHY_DIVSRCHGAINBACK) & 0xff00) | 0x15); if (phy->rev == 2) BWN_PHY_WRITE(mac, BWN_PHY_ADIVRELATED, 8); else BWN_PHY_WRITE(mac, BWN_PHY_ADIVRELATED, (BWN_PHY_READ(mac, BWN_PHY_ADIVRELATED) & 0xff00) | 8); } if (phy->rev >= 6) BWN_PHY_WRITE(mac, BWN_PHY_OFDM9B, 0xdc); hf |= BWN_HF_UCODE_ANTDIV_HELPER; bwn_hf_write(mac, hf); } static int bwn_phy_g_im(struct bwn_mac *mac, int mode) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s: fail", __func__)); KASSERT(mode == BWN_IMMODE_NONE, ("%s: fail", __func__)); if (phy->rev == 0 || !phy->gmode) return (ENODEV); pg->pg_aci_wlan_automatic = 0; return (0); } static int bwn_phy_g_recalc_txpwr(struct bwn_mac *mac, int ignore_tssi) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; unsigned int tssi; int cck, ofdm; int power; int rfatt, bbatt; unsigned int max; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s: fail", __func__)); cck = bwn_phy_shm_tssi_read(mac, BWN_SHARED_TSSI_CCK); ofdm = bwn_phy_shm_tssi_read(mac, BWN_SHARED_TSSI_OFDM_G); if (cck < 0 && ofdm < 0) { if (ignore_tssi == 0) return (BWN_TXPWR_RES_DONE); cck = 0; ofdm = 0; } tssi = (cck < 0) ? ofdm : ((ofdm < 0) ? cck : (cck + ofdm) / 2); if (pg->pg_avgtssi != 0xff) tssi = (tssi + pg->pg_avgtssi) / 2; pg->pg_avgtssi = tssi; KASSERT(tssi < BWN_TSSI_MAX, ("%s:%d: fail", __func__, __LINE__)); max = siba_sprom_get_maxpwr_bg(sc->sc_dev); if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) max -= 3; if (max >= 120) { device_printf(sc->sc_dev, "invalid max TX-power value\n"); max = 80; siba_sprom_set_maxpwr_bg(sc->sc_dev, max); } power = MIN(MAX((phy->txpower < 0) ? 0 : (phy->txpower << 2), 0), max) - (pg->pg_tssi2dbm[MIN(MAX(pg->pg_idletssi - pg->pg_curtssi + tssi, 0x00), 0x3f)]); if (power == 0) return (BWN_TXPWR_RES_DONE); rfatt = -((power + 7) / 8); bbatt = (-(power / 2)) - (4 * rfatt); if ((rfatt == 0) && (bbatt == 0)) return (BWN_TXPWR_RES_DONE); pg->pg_bbatt_delta = bbatt; pg->pg_rfatt_delta = rfatt; return (BWN_TXPWR_RES_NEED_ADJUST); } static void bwn_phy_g_set_txpwr(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; int rfatt, bbatt; uint8_t txctl; bwn_mac_suspend(mac); BWN_ASSERT_LOCKED(sc); bbatt = pg->pg_bbatt.att; bbatt += pg->pg_bbatt_delta; rfatt = pg->pg_rfatt.att; rfatt += pg->pg_rfatt_delta; bwn_phy_g_setatt(mac, &bbatt, &rfatt); txctl = pg->pg_txctl; if ((phy->rf_ver == 0x2050) && (phy->rf_rev == 2)) { if (rfatt <= 1) { if (txctl == 0) { txctl = BWN_TXCTL_PA2DB | BWN_TXCTL_TXMIX; rfatt += 2; bbatt += 2; } else if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) { bbatt += 4 * (rfatt - 2); rfatt = 2; } } else if (rfatt > 4 && txctl) { txctl = 0; if (bbatt < 3) { rfatt -= 3; bbatt += 2; } else { rfatt -= 2; bbatt -= 2; } } } pg->pg_txctl = txctl; bwn_phy_g_setatt(mac, &bbatt, &rfatt); pg->pg_rfatt.att = rfatt; pg->pg_bbatt.att = bbatt; DPRINTF(sc, BWN_DEBUG_TXPOW, "%s: adjust TX power\n", __func__); bwn_phy_lock(mac); bwn_rf_lock(mac); bwn_phy_g_set_txpwr_sub(mac, &pg->pg_bbatt, &pg->pg_rfatt, pg->pg_txctl); bwn_rf_unlock(mac); bwn_phy_unlock(mac); bwn_mac_enable(mac); } static void bwn_phy_g_task_15s(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; unsigned long expire, now; struct bwn_lo_calib *cal, *tmp; uint8_t expired = 0; bwn_mac_suspend(mac); if (lo == NULL) goto fail; BWN_GETTIME(now); if (bwn_has_hwpctl(mac)) { expire = now - BWN_LO_PWRVEC_EXPIRE; if (time_before(lo->pwr_vec_read_time, expire)) { bwn_lo_get_powervector(mac); bwn_phy_g_dc_lookup_init(mac, 0); } goto fail; } expire = now - BWN_LO_CALIB_EXPIRE; TAILQ_FOREACH_SAFE(cal, &lo->calib_list, list, tmp) { if (!time_before(cal->calib_time, expire)) continue; if (BWN_BBATTCMP(&cal->bbatt, &pg->pg_bbatt) && BWN_RFATTCMP(&cal->rfatt, &pg->pg_rfatt)) { KASSERT(!expired, ("%s:%d: fail", __func__, __LINE__)); expired = 1; } DPRINTF(sc, BWN_DEBUG_LO, "expired BB %u RF %u %u I %d Q %d\n", cal->bbatt.att, cal->rfatt.att, cal->rfatt.padmix, cal->ctl.i, cal->ctl.q); TAILQ_REMOVE(&lo->calib_list, cal, list); free(cal, M_DEVBUF); } if (expired || TAILQ_EMPTY(&lo->calib_list)) { cal = bwn_lo_calibset(mac, &pg->pg_bbatt, &pg->pg_rfatt); if (cal == NULL) { device_printf(sc->sc_dev, "failed to recalibrate LO\n"); goto fail; } TAILQ_INSERT_TAIL(&lo->calib_list, cal, list); bwn_lo_write(mac, &cal->ctl); } fail: bwn_mac_enable(mac); } static void bwn_phy_g_task_60s(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; uint8_t old = phy->chan; if (!(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_RSSI)) return; bwn_mac_suspend(mac); bwn_nrssi_slope_11g(mac); if ((phy->rf_ver == 0x2050) && (phy->rf_rev == 8)) { bwn_switch_channel(mac, (old >= 8) ? 1 : 13); bwn_switch_channel(mac, old); } bwn_mac_enable(mac); } static void bwn_phy_switch_analog(struct bwn_mac *mac, int on) { BWN_WRITE_2(mac, BWN_PHY0, on ? 0 : 0xf4); } static int bwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || mac->mac_status < BWN_MAC_STATUS_STARTED) { ieee80211_free_node(ni); m_freem(m); return (ENETDOWN); } BWN_LOCK(sc); if (bwn_tx_isfull(sc, m)) { ieee80211_free_node(ni); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); BWN_UNLOCK(sc); return (ENOBUFS); } if (bwn_tx_start(sc, ni, m) != 0) { if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); } sc->sc_watchdog_timer = 5; BWN_UNLOCK(sc); return (0); } /* * Callback from the 802.11 layer to update the slot time * based on the current setting. We use it to notify the * firmware of ERP changes and the f/w takes care of things * like slot time and preamble. */ static void bwn_updateslot(struct ifnet *ifp) { struct bwn_softc *sc = ifp->if_softc; struct ieee80211com *ic = ifp->if_l2com; struct bwn_mac *mac; BWN_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { mac = (struct bwn_mac *)sc->sc_curmac; bwn_set_slot_time(mac, (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20); } BWN_UNLOCK(sc); } /* * Callback from the 802.11 layer after a promiscuous mode change. * Note this interface does not check the operating mode as this * is an internal callback and we are expected to honor the current * state (e.g. this is used for setting the interface in promiscuous * mode when operating in hostap mode to do ACS). */ static void bwn_update_promisc(struct ifnet *ifp) { struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { if (ifp->if_flags & IFF_PROMISC) sc->sc_filters |= BWN_MACCTL_PROMISC; else sc->sc_filters &= ~BWN_MACCTL_PROMISC; bwn_set_opmode(mac); } BWN_UNLOCK(sc); } /* * Callback from the 802.11 layer to update WME parameters. */ static int bwn_wme_update(struct ieee80211com *ic) { struct bwn_softc *sc = ic->ic_ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; struct wmeParams *wmep; int i; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { bwn_mac_suspend(mac); for (i = 0; i < N(sc->sc_wmeParams); i++) { wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[i]; bwn_wme_loadparams(mac, wmep, bwn_wme_shm_offsets[i]); } bwn_mac_enable(mac); } BWN_UNLOCK(sc); return (0); } static void bwn_scan_start(struct ieee80211com *ic) { struct ifnet *ifp = ic->ic_ifp; struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { sc->sc_filters |= BWN_MACCTL_BEACON_PROMISC; bwn_set_opmode(mac); /* disable CFP update during scan */ bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_SKIP_CFP_UPDATE); } BWN_UNLOCK(sc); } static void bwn_scan_end(struct ieee80211com *ic) { struct ifnet *ifp = ic->ic_ifp; struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac; BWN_LOCK(sc); mac = sc->sc_curmac; if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { sc->sc_filters &= ~BWN_MACCTL_BEACON_PROMISC; bwn_set_opmode(mac); bwn_hf_write(mac, bwn_hf_read(mac) & ~BWN_HF_SKIP_CFP_UPDATE); } BWN_UNLOCK(sc); } static void bwn_set_channel(struct ieee80211com *ic) { struct ifnet *ifp = ic->ic_ifp; struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; struct bwn_phy *phy = &mac->mac_phy; int chan, error; BWN_LOCK(sc); error = bwn_switch_band(sc, ic->ic_curchan); if (error) goto fail; bwn_mac_suspend(mac); bwn_set_txretry(mac, BWN_RETRY_SHORT, BWN_RETRY_LONG); chan = ieee80211_chan2ieee(ic, ic->ic_curchan); if (chan != phy->chan) bwn_switch_channel(mac, chan); /* TX power level */ if (ic->ic_curchan->ic_maxpower != 0 && ic->ic_curchan->ic_maxpower != phy->txpower) { phy->txpower = ic->ic_curchan->ic_maxpower / 2; bwn_phy_txpower_check(mac, BWN_TXPWR_IGNORE_TIME | BWN_TXPWR_IGNORE_TSSI); } bwn_set_txantenna(mac, BWN_ANT_DEFAULT); if (phy->set_antenna) phy->set_antenna(mac, BWN_ANT_DEFAULT); if (sc->sc_rf_enabled != phy->rf_on) { if (sc->sc_rf_enabled) { bwn_rf_turnon(mac); if (!(mac->mac_flags & BWN_MAC_FLAG_RADIO_ON)) device_printf(sc->sc_dev, "please turn on the RF switch\n"); } else bwn_rf_turnoff(mac); } bwn_mac_enable(mac); fail: /* * Setup radio tap channel freq and flags */ sc->sc_tx_th.wt_chan_freq = sc->sc_rx_th.wr_chan_freq = htole16(ic->ic_curchan->ic_freq); sc->sc_tx_th.wt_chan_flags = sc->sc_rx_th.wr_chan_flags = htole16(ic->ic_curchan->ic_flags & 0xffff); BWN_UNLOCK(sc); } static struct ieee80211vap * bwn_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac0[IEEE80211_ADDR_LEN]) { struct ifnet *ifp = ic->ic_ifp; struct bwn_softc *sc = ifp->if_softc; struct ieee80211vap *vap; struct bwn_vap *bvp; uint8_t mac[IEEE80211_ADDR_LEN]; IEEE80211_ADDR_COPY(mac, mac0); switch (opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: case IEEE80211_M_STA: case IEEE80211_M_WDS: case IEEE80211_M_MONITOR: case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: break; default: return (NULL); } IEEE80211_ADDR_COPY(sc->sc_macaddr, mac0); bvp = (struct bwn_vap *) malloc(sizeof(struct bwn_vap), M_80211_VAP, M_NOWAIT | M_ZERO); if (bvp == NULL) { device_printf(sc->sc_dev, "failed to allocate a buffer\n"); return (NULL); } vap = &bvp->bv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac); IEEE80211_ADDR_COPY(vap->iv_myaddr, mac); /* override with driver methods */ bvp->bv_newstate = vap->iv_newstate; vap->iv_newstate = bwn_newstate; /* override max aid so sta's cannot assoc when we're out of sta id's */ vap->iv_max_aid = BWN_STAID_MAX; ieee80211_ratectl_init(vap); /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); return (vap); } static void bwn_vap_delete(struct ieee80211vap *vap) { struct bwn_vap *bvp = BWN_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(bvp, M_80211_VAP); } static void bwn_init(void *arg) { struct bwn_softc *sc = arg; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; int error = 0; DPRINTF(sc, BWN_DEBUG_ANY, "%s: if_flags 0x%x\n", __func__, ifp->if_flags); BWN_LOCK(sc); error = bwn_init_locked(sc); BWN_UNLOCK(sc); if (error == 0) ieee80211_start_all(ic); /* start all vap's */ } static int bwn_init_locked(struct bwn_softc *sc) { struct bwn_mac *mac; struct ifnet *ifp = sc->sc_ifp; int error; BWN_ASSERT_LOCKED(sc); bzero(sc->sc_bssid, IEEE80211_ADDR_LEN); sc->sc_flags |= BWN_FLAG_NEED_BEACON_TP; sc->sc_filters = 0; bwn_wme_clear(sc); sc->sc_beacons[0] = sc->sc_beacons[1] = 0; sc->sc_rf_enabled = 1; mac = sc->sc_curmac; if (mac->mac_status == BWN_MAC_STATUS_UNINIT) { error = bwn_core_init(mac); if (error != 0) return (error); } if (mac->mac_status == BWN_MAC_STATUS_INITED) bwn_core_start(mac); bwn_set_opmode(mac); bwn_set_pretbtt(mac); bwn_spu_setdelay(mac, 0); bwn_set_macaddr(mac); ifp->if_drv_flags |= IFF_DRV_RUNNING; callout_reset(&sc->sc_rfswitch_ch, hz, bwn_rfswitch, sc); callout_reset(&sc->sc_watchdog_ch, hz, bwn_watchdog, sc); return (0); } static void bwn_stop(struct bwn_softc *sc, int statechg) { BWN_LOCK(sc); bwn_stop_locked(sc, statechg); BWN_UNLOCK(sc); } static void bwn_stop_locked(struct bwn_softc *sc, int statechg) { struct bwn_mac *mac = sc->sc_curmac; struct ifnet *ifp = sc->sc_ifp; BWN_ASSERT_LOCKED(sc); if (mac->mac_status >= BWN_MAC_STATUS_INITED) { /* XXX FIXME opmode not based on VAP */ bwn_set_opmode(mac); bwn_set_macaddr(mac); } if (mac->mac_status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(mac); callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; bwn_core_exit(mac); sc->sc_rf_enabled = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } static void bwn_wme_clear(struct bwn_softc *sc) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct wmeParams *p; unsigned int i; KASSERT(N(bwn_wme_shm_offsets) == N(sc->sc_wmeParams), ("%s:%d: fail", __func__, __LINE__)); for (i = 0; i < N(sc->sc_wmeParams); i++) { p = &(sc->sc_wmeParams[i]); switch (bwn_wme_shm_offsets[i]) { case BWN_WME_VOICE: p->wmep_txopLimit = 0; p->wmep_aifsn = 2; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x0001, WME_PARAM_LOGCWMAX); break; case BWN_WME_VIDEO: p->wmep_txopLimit = 0; p->wmep_aifsn = 2; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x0001, WME_PARAM_LOGCWMAX); break; case BWN_WME_BESTEFFORT: p->wmep_txopLimit = 0; p->wmep_aifsn = 3; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x03ff, WME_PARAM_LOGCWMAX); break; case BWN_WME_BACKGROUND: p->wmep_txopLimit = 0; p->wmep_aifsn = 7; /* XXX FIXME: log2(cwmin) */ p->wmep_logcwmin = MS(0x0001, WME_PARAM_LOGCWMIN); p->wmep_logcwmax = MS(0x03ff, WME_PARAM_LOGCWMAX); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } } static int bwn_core_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint64_t hf; int error; KASSERT(mac->mac_status == BWN_MAC_STATUS_UNINIT, ("%s:%d: fail", __func__, __LINE__)); siba_powerup(sc->sc_dev, 0); if (!siba_dev_isup(sc->sc_dev)) bwn_reset_core(mac, mac->mac_phy.gmode ? BWN_TGSLOW_SUPPORT_G : 0); mac->mac_flags &= ~BWN_MAC_FLAG_DFQVALID; mac->mac_flags |= BWN_MAC_FLAG_RADIO_ON; mac->mac_phy.hwpctl = (bwn_hwpctl) ? 1 : 0; BWN_GETTIME(mac->mac_phy.nexttime); mac->mac_phy.txerrors = BWN_TXERROR_MAX; bzero(&mac->mac_stats, sizeof(mac->mac_stats)); mac->mac_stats.link_noise = -95; mac->mac_reason_intr = 0; bzero(mac->mac_reason, sizeof(mac->mac_reason)); mac->mac_intr_mask = BWN_INTR_MASKTEMPLATE; #ifdef BWN_DEBUG if (sc->sc_debug & BWN_DEBUG_XMIT) mac->mac_intr_mask &= ~BWN_INTR_PHY_TXERR; #endif mac->mac_suspended = 1; mac->mac_task_state = 0; memset(&mac->mac_noise, 0, sizeof(mac->mac_noise)); mac->mac_phy.init_pre(mac); siba_pcicore_intr(sc->sc_dev); siba_fix_imcfglobug(sc->sc_dev); bwn_bt_disable(mac); if (mac->mac_phy.prepare_hw) { error = mac->mac_phy.prepare_hw(mac); if (error) goto fail0; } error = bwn_chip_init(mac); if (error) goto fail0; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_COREREV, siba_get_revid(sc->sc_dev)); hf = bwn_hf_read(mac); if (mac->mac_phy.type == BWN_PHYTYPE_G) { hf |= BWN_HF_GPHY_SYM_WORKAROUND; if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) hf |= BWN_HF_PAGAINBOOST_OFDM_ON; if (mac->mac_phy.rev == 1) hf |= BWN_HF_GPHY_DC_CANCELFILTER; } if (mac->mac_phy.rf_ver == 0x2050) { if (mac->mac_phy.rf_rev < 6) hf |= BWN_HF_FORCE_VCO_RECALC; if (mac->mac_phy.rf_rev == 6) hf |= BWN_HF_4318_TSSI; } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_CRYSTAL_NOSLOW) hf |= BWN_HF_SLOWCLOCK_REQ_OFF; if ((siba_get_type(sc->sc_dev) == SIBA_TYPE_PCI) && (siba_get_pcicore_revid(sc->sc_dev) <= 10)) hf |= BWN_HF_PCI_SLOWCLOCK_WORKAROUND; hf &= ~BWN_HF_SKIP_CFP_UPDATE; bwn_hf_write(mac, hf); bwn_set_txretry(mac, BWN_RETRY_SHORT, BWN_RETRY_LONG); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_SHORT_RETRY_FALLBACK, 3); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_LONG_RETRY_FALLBACK, 2); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_MAXTIME, 1); bwn_rate_init(mac); bwn_set_phytxctl(mac); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_CONT_MIN, (mac->mac_phy.type == BWN_PHYTYPE_B) ? 0x1f : 0xf); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_CONT_MAX, 0x3ff); if (siba_get_type(sc->sc_dev) == SIBA_TYPE_PCMCIA || bwn_usedma == 0) bwn_pio_init(mac); else bwn_dma_init(mac); bwn_wme_init(mac); bwn_spu_setdelay(mac, 1); bwn_bt_enable(mac); siba_powerup(sc->sc_dev, !(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_CRYSTAL_NOSLOW)); bwn_set_macaddr(mac); bwn_crypt_init(mac); /* XXX LED initializatin */ mac->mac_status = BWN_MAC_STATUS_INITED; return (error); fail0: siba_powerdown(sc->sc_dev); KASSERT(mac->mac_status == BWN_MAC_STATUS_UNINIT, ("%s:%d: fail", __func__, __LINE__)); return (error); } static void bwn_core_start(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; KASSERT(mac->mac_status == BWN_MAC_STATUS_INITED, ("%s:%d: fail", __func__, __LINE__)); if (siba_get_revid(sc->sc_dev) < 5) return; while (1) { tmp = BWN_READ_4(mac, BWN_XMITSTAT_0); if (!(tmp & 0x00000001)) break; tmp = BWN_READ_4(mac, BWN_XMITSTAT_1); } bwn_mac_enable(mac); BWN_WRITE_4(mac, BWN_INTR_MASK, mac->mac_intr_mask); callout_reset(&sc->sc_task_ch, hz * 15, bwn_tasks, mac); mac->mac_status = BWN_MAC_STATUS_STARTED; } static void bwn_core_exit(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t macctl; BWN_ASSERT_LOCKED(mac->mac_sc); KASSERT(mac->mac_status <= BWN_MAC_STATUS_INITED, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_status != BWN_MAC_STATUS_INITED) return; mac->mac_status = BWN_MAC_STATUS_UNINIT; macctl = BWN_READ_4(mac, BWN_MACCTL); macctl &= ~BWN_MACCTL_MCODE_RUN; macctl |= BWN_MACCTL_MCODE_JMP0; BWN_WRITE_4(mac, BWN_MACCTL, macctl); bwn_dma_stop(mac); bwn_pio_stop(mac); bwn_chip_exit(mac); mac->mac_phy.switch_analog(mac, 0); siba_dev_down(sc->sc_dev, 0); siba_powerdown(sc->sc_dev); } static void bwn_bt_disable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; (void)sc; /* XXX do nothing yet */ } static int bwn_chip_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy *phy = &mac->mac_phy; uint32_t macctl; int error; macctl = BWN_MACCTL_IHR_ON | BWN_MACCTL_SHM_ON | BWN_MACCTL_STA; if (phy->gmode) macctl |= BWN_MACCTL_GMODE; BWN_WRITE_4(mac, BWN_MACCTL, macctl); error = bwn_fw_fillinfo(mac); if (error) return (error); error = bwn_fw_loaducode(mac); if (error) return (error); error = bwn_gpio_init(mac); if (error) return (error); error = bwn_fw_loadinitvals(mac); if (error) { siba_gpio_set(sc->sc_dev, 0); return (error); } phy->switch_analog(mac, 1); error = bwn_phy_init(mac); if (error) { siba_gpio_set(sc->sc_dev, 0); return (error); } if (phy->set_im) phy->set_im(mac, BWN_IMMODE_NONE); if (phy->set_antenna) phy->set_antenna(mac, BWN_ANT_DEFAULT); bwn_set_txantenna(mac, BWN_ANT_DEFAULT); if (phy->type == BWN_PHYTYPE_B) BWN_WRITE_2(mac, 0x005e, BWN_READ_2(mac, 0x005e) | 0x0004); BWN_WRITE_4(mac, 0x0100, 0x01000000); if (siba_get_revid(sc->sc_dev) < 5) BWN_WRITE_4(mac, 0x010c, 0x01000000); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_STA); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_STA); bwn_shm_write_2(mac, BWN_SHARED, 0x0074, 0x0000); bwn_set_opmode(mac); if (siba_get_revid(sc->sc_dev) < 3) { BWN_WRITE_2(mac, 0x060e, 0x0000); BWN_WRITE_2(mac, 0x0610, 0x8000); BWN_WRITE_2(mac, 0x0604, 0x0000); BWN_WRITE_2(mac, 0x0606, 0x0200); } else { BWN_WRITE_4(mac, 0x0188, 0x80000000); BWN_WRITE_4(mac, 0x018c, 0x02000000); } BWN_WRITE_4(mac, BWN_INTR_REASON, 0x00004000); BWN_WRITE_4(mac, BWN_DMA0_INTR_MASK, 0x0001dc00); BWN_WRITE_4(mac, BWN_DMA1_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA2_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA3_INTR_MASK, 0x0001dc00); BWN_WRITE_4(mac, BWN_DMA4_INTR_MASK, 0x0000dc00); BWN_WRITE_4(mac, BWN_DMA5_INTR_MASK, 0x0000dc00); siba_write_4(sc->sc_dev, SIBA_TGSLOW, siba_read_4(sc->sc_dev, SIBA_TGSLOW) | 0x00100000); BWN_WRITE_2(mac, BWN_POWERUP_DELAY, siba_get_cc_powerdelay(sc->sc_dev)); return (error); } /* read hostflags */ static uint64_t bwn_hf_read(struct bwn_mac *mac) { uint64_t ret; ret = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFHI); ret <<= 16; ret |= bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFMI); ret <<= 16; ret |= bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFLO); return (ret); } static void bwn_hf_write(struct bwn_mac *mac, uint64_t value) { bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFLO, (value & 0x00000000ffffull)); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFMI, (value & 0x0000ffff0000ull) >> 16); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_HFHI, (value & 0xffff00000000ULL) >> 32); } static void bwn_set_txretry(struct bwn_mac *mac, int s, int l) { bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_SHORT_RETRY, MIN(s, 0xf)); bwn_shm_write_2(mac, BWN_SCRATCH, BWN_SCRATCH_LONG_RETRY, MIN(l, 0xf)); } static void bwn_rate_init(struct bwn_mac *mac) { switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: case BWN_PHYTYPE_G: case BWN_PHYTYPE_LP: case BWN_PHYTYPE_N: bwn_rate_write(mac, BWN_OFDM_RATE_6MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_12MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_18MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_24MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_36MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_48MB, 1); bwn_rate_write(mac, BWN_OFDM_RATE_54MB, 1); if (mac->mac_phy.type == BWN_PHYTYPE_A) break; /* FALLTHROUGH */ case BWN_PHYTYPE_B: bwn_rate_write(mac, BWN_CCK_RATE_1MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_2MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_5MB, 0); bwn_rate_write(mac, BWN_CCK_RATE_11MB, 0); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } static void bwn_rate_write(struct bwn_mac *mac, uint16_t rate, int ofdm) { uint16_t offset; if (ofdm) { offset = 0x480; offset += (bwn_plcp_getofdm(rate) & 0x000f) * 2; } else { offset = 0x4c0; offset += (bwn_plcp_getcck(rate) & 0x000f) * 2; } bwn_shm_write_2(mac, BWN_SHARED, offset + 0x20, bwn_shm_read_2(mac, BWN_SHARED, offset)); } static uint8_t bwn_plcp_getcck(const uint8_t bitrate) { switch (bitrate) { case BWN_CCK_RATE_1MB: return (0x0a); case BWN_CCK_RATE_2MB: return (0x14); case BWN_CCK_RATE_5MB: return (0x37); case BWN_CCK_RATE_11MB: return (0x6e); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static uint8_t bwn_plcp_getofdm(const uint8_t bitrate) { switch (bitrate) { case BWN_OFDM_RATE_6MB: return (0xb); case BWN_OFDM_RATE_9MB: return (0xf); case BWN_OFDM_RATE_12MB: return (0xa); case BWN_OFDM_RATE_18MB: return (0xe); case BWN_OFDM_RATE_24MB: return (0x9); case BWN_OFDM_RATE_36MB: return (0xd); case BWN_OFDM_RATE_48MB: return (0x8); case BWN_OFDM_RATE_54MB: return (0xc); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static void bwn_set_phytxctl(struct bwn_mac *mac) { uint16_t ctl; ctl = (BWN_TX_PHY_ENC_CCK | BWN_TX_PHY_ANT01AUTO | BWN_TX_PHY_TXPWR); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_BEACON_PHYCTL, ctl); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL, ctl); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL, ctl); } static void bwn_pio_init(struct bwn_mac *mac) { struct bwn_pio *pio = &mac->mac_method.pio; BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_BIGENDIAN); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_RX_PADOFFSET, 0); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_BK], 0); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_BE], 1); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_VI], 2); bwn_pio_set_txqueue(mac, &pio->wme[WME_AC_VO], 3); bwn_pio_set_txqueue(mac, &pio->mcast, 4); bwn_pio_setupqueue_rx(mac, &pio->rx, 0); } static void bwn_pio_set_txqueue(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, int index) { struct bwn_pio_txpkt *tp; struct bwn_softc *sc = mac->mac_sc; unsigned int i; tq->tq_base = bwn_pio_idx2base(mac, index) + BWN_PIO_TXQOFFSET(mac); tq->tq_index = index; tq->tq_free = BWN_PIO_MAX_TXPACKETS; if (siba_get_revid(sc->sc_dev) >= 8) tq->tq_size = 1920; else { tq->tq_size = bwn_pio_read_2(mac, tq, BWN_PIO_TXQBUFSIZE); tq->tq_size -= 80; } TAILQ_INIT(&tq->tq_pktlist); for (i = 0; i < N(tq->tq_pkts); i++) { tp = &(tq->tq_pkts[i]); tp->tp_index = i; tp->tp_queue = tq; TAILQ_INSERT_TAIL(&tq->tq_pktlist, tp, tp_list); } } static uint16_t bwn_pio_idx2base(struct bwn_mac *mac, int index) { struct bwn_softc *sc = mac->mac_sc; static const uint16_t bases[] = { BWN_PIO_BASE0, BWN_PIO_BASE1, BWN_PIO_BASE2, BWN_PIO_BASE3, BWN_PIO_BASE4, BWN_PIO_BASE5, BWN_PIO_BASE6, BWN_PIO_BASE7, }; static const uint16_t bases_rev11[] = { BWN_PIO11_BASE0, BWN_PIO11_BASE1, BWN_PIO11_BASE2, BWN_PIO11_BASE3, BWN_PIO11_BASE4, BWN_PIO11_BASE5, }; if (siba_get_revid(sc->sc_dev) >= 11) { if (index >= N(bases_rev11)) device_printf(sc->sc_dev, "%s: warning\n", __func__); return (bases_rev11[index]); } if (index >= N(bases)) device_printf(sc->sc_dev, "%s: warning\n", __func__); return (bases[index]); } static void bwn_pio_setupqueue_rx(struct bwn_mac *mac, struct bwn_pio_rxqueue *prq, int index) { struct bwn_softc *sc = mac->mac_sc; prq->prq_mac = mac; prq->prq_rev = siba_get_revid(sc->sc_dev); prq->prq_base = bwn_pio_idx2base(mac, index) + BWN_PIO_RXQOFFSET(mac); bwn_dma_rxdirectfifo(mac, index, 1); } static void bwn_destroy_pioqueue_tx(struct bwn_pio_txqueue *tq) { if (tq == NULL) return; bwn_pio_cancel_tx_packets(tq); } static void bwn_destroy_queue_tx(struct bwn_pio_txqueue *pio) { bwn_destroy_pioqueue_tx(pio); } static uint16_t bwn_pio_read_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t offset) { return (BWN_READ_2(mac, tq->tq_base + offset)); } static void bwn_dma_rxdirectfifo(struct bwn_mac *mac, int idx, uint8_t enable) { uint32_t ctl; int type; uint16_t base; type = bwn_dma_mask2type(bwn_dma_mask(mac)); base = bwn_dma_base(type, idx); if (type == BWN_DMA_64BIT) { ctl = BWN_READ_4(mac, base + BWN_DMA64_RXCTL); ctl &= ~BWN_DMA64_RXDIRECTFIFO; if (enable) ctl |= BWN_DMA64_RXDIRECTFIFO; BWN_WRITE_4(mac, base + BWN_DMA64_RXCTL, ctl); } else { ctl = BWN_READ_4(mac, base + BWN_DMA32_RXCTL); ctl &= ~BWN_DMA32_RXDIRECTFIFO; if (enable) ctl |= BWN_DMA32_RXDIRECTFIFO; BWN_WRITE_4(mac, base + BWN_DMA32_RXCTL, ctl); } } static uint64_t bwn_dma_mask(struct bwn_mac *mac) { uint32_t tmp; uint16_t base; tmp = BWN_READ_4(mac, SIBA_TGSHIGH); if (tmp & SIBA_TGSHIGH_DMA64) return (BWN_DMA_BIT_MASK(64)); base = bwn_dma_base(0, 0); BWN_WRITE_4(mac, base + BWN_DMA32_TXCTL, BWN_DMA32_TXADDREXT_MASK); tmp = BWN_READ_4(mac, base + BWN_DMA32_TXCTL); if (tmp & BWN_DMA32_TXADDREXT_MASK) return (BWN_DMA_BIT_MASK(32)); return (BWN_DMA_BIT_MASK(30)); } static int bwn_dma_mask2type(uint64_t dmamask) { if (dmamask == BWN_DMA_BIT_MASK(30)) return (BWN_DMA_30BIT); if (dmamask == BWN_DMA_BIT_MASK(32)) return (BWN_DMA_32BIT); if (dmamask == BWN_DMA_BIT_MASK(64)) return (BWN_DMA_64BIT); KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (BWN_DMA_30BIT); } static void bwn_pio_cancel_tx_packets(struct bwn_pio_txqueue *tq) { struct bwn_pio_txpkt *tp; unsigned int i; for (i = 0; i < N(tq->tq_pkts); i++) { tp = &(tq->tq_pkts[i]); if (tp->tp_m) { m_freem(tp->tp_m); tp->tp_m = NULL; } } } static uint16_t bwn_dma_base(int type, int controller_idx) { static const uint16_t map64[] = { BWN_DMA64_BASE0, BWN_DMA64_BASE1, BWN_DMA64_BASE2, BWN_DMA64_BASE3, BWN_DMA64_BASE4, BWN_DMA64_BASE5, }; static const uint16_t map32[] = { BWN_DMA32_BASE0, BWN_DMA32_BASE1, BWN_DMA32_BASE2, BWN_DMA32_BASE3, BWN_DMA32_BASE4, BWN_DMA32_BASE5, }; if (type == BWN_DMA_64BIT) { KASSERT(controller_idx >= 0 && controller_idx < N(map64), ("%s:%d: fail", __func__, __LINE__)); return (map64[controller_idx]); } KASSERT(controller_idx >= 0 && controller_idx < N(map32), ("%s:%d: fail", __func__, __LINE__)); return (map32[controller_idx]); } static void bwn_dma_init(struct bwn_mac *mac) { struct bwn_dma *dma = &mac->mac_method.dma; /* setup TX DMA channels. */ bwn_dma_setup(dma->wme[WME_AC_BK]); bwn_dma_setup(dma->wme[WME_AC_BE]); bwn_dma_setup(dma->wme[WME_AC_VI]); bwn_dma_setup(dma->wme[WME_AC_VO]); bwn_dma_setup(dma->mcast); /* setup RX DMA channel. */ bwn_dma_setup(dma->rx); } static struct bwn_dma_ring * bwn_dma_ringsetup(struct bwn_mac *mac, int controller_index, int for_tx, int type) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *mt; struct bwn_softc *sc = mac->mac_sc; int error, i; dr = malloc(sizeof(*dr), M_DEVBUF, M_NOWAIT | M_ZERO); if (dr == NULL) goto out; dr->dr_numslots = BWN_RXRING_SLOTS; if (for_tx) dr->dr_numslots = BWN_TXRING_SLOTS; dr->dr_meta = malloc(dr->dr_numslots * sizeof(struct bwn_dmadesc_meta), M_DEVBUF, M_NOWAIT | M_ZERO); if (dr->dr_meta == NULL) goto fail0; dr->dr_type = type; dr->dr_mac = mac; dr->dr_base = bwn_dma_base(type, controller_index); dr->dr_index = controller_index; if (type == BWN_DMA_64BIT) { dr->getdesc = bwn_dma_64_getdesc; dr->setdesc = bwn_dma_64_setdesc; dr->start_transfer = bwn_dma_64_start_transfer; dr->suspend = bwn_dma_64_suspend; dr->resume = bwn_dma_64_resume; dr->get_curslot = bwn_dma_64_get_curslot; dr->set_curslot = bwn_dma_64_set_curslot; } else { dr->getdesc = bwn_dma_32_getdesc; dr->setdesc = bwn_dma_32_setdesc; dr->start_transfer = bwn_dma_32_start_transfer; dr->suspend = bwn_dma_32_suspend; dr->resume = bwn_dma_32_resume; dr->get_curslot = bwn_dma_32_get_curslot; dr->set_curslot = bwn_dma_32_set_curslot; } if (for_tx) { dr->dr_tx = 1; dr->dr_curslot = -1; } else { if (dr->dr_index == 0) { dr->dr_rx_bufsize = BWN_DMA0_RX_BUFFERSIZE; dr->dr_frameoffset = BWN_DMA0_RX_FRAMEOFFSET; } else KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } error = bwn_dma_allocringmemory(dr); if (error) goto fail2; if (for_tx) { /* * Assumption: BWN_TXRING_SLOTS can be divided by * BWN_TX_SLOTS_PER_FRAME */ KASSERT(BWN_TXRING_SLOTS % BWN_TX_SLOTS_PER_FRAME == 0, ("%s:%d: fail", __func__, __LINE__)); dr->dr_txhdr_cache = malloc((dr->dr_numslots / BWN_TX_SLOTS_PER_FRAME) * BWN_HDRSIZE(mac), M_DEVBUF, M_NOWAIT | M_ZERO); KASSERT(dr->dr_txhdr_cache != NULL, ("%s:%d: fail", __func__, __LINE__)); /* * Create TX ring DMA stuffs */ error = bus_dma_tag_create(dma->parent_dtag, BWN_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BWN_HDRSIZE(mac), 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dr->dr_txring_dtag); if (error) { device_printf(sc->sc_dev, "can't create TX ring DMA tag: TODO frees\n"); goto fail1; } for (i = 0; i < dr->dr_numslots; i += 2) { dr->getdesc(dr, i, &desc, &mt); mt->mt_txtype = BWN_DMADESC_METATYPE_HEADER; mt->mt_m = NULL; mt->mt_ni = NULL; mt->mt_islast = 0; error = bus_dmamap_create(dr->dr_txring_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto fail1; } dr->getdesc(dr, i + 1, &desc, &mt); mt->mt_txtype = BWN_DMADESC_METATYPE_BODY; mt->mt_m = NULL; mt->mt_ni = NULL; mt->mt_islast = 1; error = bus_dmamap_create(dma->txbuf_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto fail1; } } } else { error = bus_dmamap_create(dma->rxbuf_dtag, 0, &dr->dr_spare_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto out; /* XXX wrong! */ } for (i = 0; i < dr->dr_numslots; i++) { dr->getdesc(dr, i, &desc, &mt); error = bus_dmamap_create(dma->rxbuf_dtag, 0, &mt->mt_dmap); if (error) { device_printf(sc->sc_dev, "can't create RX buf DMA map\n"); goto out; /* XXX wrong! */ } error = bwn_dma_newbuf(dr, desc, mt, 1); if (error) { device_printf(sc->sc_dev, "failed to allocate RX buf\n"); goto out; /* XXX wrong! */ } } bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); dr->dr_usedslot = dr->dr_numslots; } out: return (dr); fail2: free(dr->dr_txhdr_cache, M_DEVBUF); fail1: free(dr->dr_meta, M_DEVBUF); fail0: free(dr, M_DEVBUF); return (NULL); } static void bwn_dma_ringfree(struct bwn_dma_ring **dr) { if (dr == NULL) return; bwn_dma_free_descbufs(*dr); bwn_dma_free_ringmemory(*dr); free((*dr)->dr_txhdr_cache, M_DEVBUF); free((*dr)->dr_meta, M_DEVBUF); free(*dr, M_DEVBUF); *dr = NULL; } static void bwn_dma_32_getdesc(struct bwn_dma_ring *dr, int slot, struct bwn_dmadesc_generic **gdesc, struct bwn_dmadesc_meta **meta) { struct bwn_dmadesc32 *desc; *meta = &(dr->dr_meta[slot]); desc = dr->dr_ring_descbase; desc = &(desc[slot]); *gdesc = (struct bwn_dmadesc_generic *)desc; } static void bwn_dma_32_setdesc(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, bus_addr_t dmaaddr, uint16_t bufsize, int start, int end, int irq) { struct bwn_dmadesc32 *descbase = dr->dr_ring_descbase; struct bwn_softc *sc = dr->dr_mac->mac_sc; uint32_t addr, addrext, ctl; int slot; slot = (int)(&(desc->dma.dma32) - descbase); KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); addr = (uint32_t) (dmaaddr & ~SIBA_DMA_TRANSLATION_MASK); addrext = (uint32_t) (dmaaddr & SIBA_DMA_TRANSLATION_MASK) >> 30; addr |= siba_dma_translation(sc->sc_dev); ctl = bufsize & BWN_DMA32_DCTL_BYTECNT; if (slot == dr->dr_numslots - 1) ctl |= BWN_DMA32_DCTL_DTABLEEND; if (start) ctl |= BWN_DMA32_DCTL_FRAMESTART; if (end) ctl |= BWN_DMA32_DCTL_FRAMEEND; if (irq) ctl |= BWN_DMA32_DCTL_IRQ; ctl |= (addrext << BWN_DMA32_DCTL_ADDREXT_SHIFT) & BWN_DMA32_DCTL_ADDREXT_MASK; desc->dma.dma32.control = htole32(ctl); desc->dma.dma32.address = htole32(addr); } static void bwn_dma_32_start_transfer(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA32_TXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc32))); } static void bwn_dma_32_suspend(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, BWN_DMA_READ(dr, BWN_DMA32_TXCTL) | BWN_DMA32_TXSUSPEND); } static void bwn_dma_32_resume(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, BWN_DMA_READ(dr, BWN_DMA32_TXCTL) & ~BWN_DMA32_TXSUSPEND); } static int bwn_dma_32_get_curslot(struct bwn_dma_ring *dr) { uint32_t val; val = BWN_DMA_READ(dr, BWN_DMA32_RXSTATUS); val &= BWN_DMA32_RXDPTR; return (val / sizeof(struct bwn_dmadesc32)); } static void bwn_dma_32_set_curslot(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA32_RXINDEX, (uint32_t) (slot * sizeof(struct bwn_dmadesc32))); } static void bwn_dma_64_getdesc(struct bwn_dma_ring *dr, int slot, struct bwn_dmadesc_generic **gdesc, struct bwn_dmadesc_meta **meta) { struct bwn_dmadesc64 *desc; *meta = &(dr->dr_meta[slot]); desc = dr->dr_ring_descbase; desc = &(desc[slot]); *gdesc = (struct bwn_dmadesc_generic *)desc; } static void bwn_dma_64_setdesc(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, bus_addr_t dmaaddr, uint16_t bufsize, int start, int end, int irq) { struct bwn_dmadesc64 *descbase = dr->dr_ring_descbase; struct bwn_softc *sc = dr->dr_mac->mac_sc; int slot; uint32_t ctl0 = 0, ctl1 = 0; uint32_t addrlo, addrhi; uint32_t addrext; slot = (int)(&(desc->dma.dma64) - descbase); KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); addrlo = (uint32_t) (dmaaddr & 0xffffffff); addrhi = (((uint64_t) dmaaddr >> 32) & ~SIBA_DMA_TRANSLATION_MASK); addrext = (((uint64_t) dmaaddr >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; addrhi |= (siba_dma_translation(sc->sc_dev) << 1); if (slot == dr->dr_numslots - 1) ctl0 |= BWN_DMA64_DCTL0_DTABLEEND; if (start) ctl0 |= BWN_DMA64_DCTL0_FRAMESTART; if (end) ctl0 |= BWN_DMA64_DCTL0_FRAMEEND; if (irq) ctl0 |= BWN_DMA64_DCTL0_IRQ; ctl1 |= bufsize & BWN_DMA64_DCTL1_BYTECNT; ctl1 |= (addrext << BWN_DMA64_DCTL1_ADDREXT_SHIFT) & BWN_DMA64_DCTL1_ADDREXT_MASK; desc->dma.dma64.control0 = htole32(ctl0); desc->dma.dma64.control1 = htole32(ctl1); desc->dma.dma64.address_low = htole32(addrlo); desc->dma.dma64.address_high = htole32(addrhi); } static void bwn_dma_64_start_transfer(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA64_TXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc64))); } static void bwn_dma_64_suspend(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, BWN_DMA_READ(dr, BWN_DMA64_TXCTL) | BWN_DMA64_TXSUSPEND); } static void bwn_dma_64_resume(struct bwn_dma_ring *dr) { BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, BWN_DMA_READ(dr, BWN_DMA64_TXCTL) & ~BWN_DMA64_TXSUSPEND); } static int bwn_dma_64_get_curslot(struct bwn_dma_ring *dr) { uint32_t val; val = BWN_DMA_READ(dr, BWN_DMA64_RXSTATUS); val &= BWN_DMA64_RXSTATDPTR; return (val / sizeof(struct bwn_dmadesc64)); } static void bwn_dma_64_set_curslot(struct bwn_dma_ring *dr, int slot) { BWN_DMA_WRITE(dr, BWN_DMA64_RXINDEX, (uint32_t)(slot * sizeof(struct bwn_dmadesc64))); } static int bwn_dma_allocringmemory(struct bwn_dma_ring *dr) { struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; int error; error = bus_dma_tag_create(dma->parent_dtag, BWN_ALIGN, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BWN_DMA_RINGMEMSIZE, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dr->dr_ring_dtag); if (error) { device_printf(sc->sc_dev, "can't create TX ring DMA tag: TODO frees\n"); return (-1); } error = bus_dmamem_alloc(dr->dr_ring_dtag, &dr->dr_ring_descbase, BUS_DMA_WAITOK | BUS_DMA_ZERO, &dr->dr_ring_dmap); if (error) { device_printf(sc->sc_dev, "can't allocate DMA mem: TODO frees\n"); return (-1); } error = bus_dmamap_load(dr->dr_ring_dtag, dr->dr_ring_dmap, dr->dr_ring_descbase, BWN_DMA_RINGMEMSIZE, bwn_dma_ring_addr, &dr->dr_ring_dmabase, BUS_DMA_NOWAIT); if (error) { device_printf(sc->sc_dev, "can't load DMA mem: TODO free\n"); return (-1); } return (0); } static void bwn_dma_setup(struct bwn_dma_ring *dr) { struct bwn_softc *sc = dr->dr_mac->mac_sc; uint64_t ring64; uint32_t addrext, ring32, value; uint32_t trans = siba_dma_translation(sc->sc_dev); if (dr->dr_tx) { dr->dr_curslot = -1; if (dr->dr_type == BWN_DMA_64BIT) { ring64 = (uint64_t)(dr->dr_ring_dmabase); addrext = ((ring64 >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; value = BWN_DMA64_TXENABLE; value |= (addrext << BWN_DMA64_TXADDREXT_SHIFT) & BWN_DMA64_TXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA64_TXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGLO, (ring64 & 0xffffffff)); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGHI, ((ring64 >> 32) & ~SIBA_DMA_TRANSLATION_MASK) | (trans << 1)); } else { ring32 = (uint32_t)(dr->dr_ring_dmabase); addrext = (ring32 & SIBA_DMA_TRANSLATION_MASK) >> 30; value = BWN_DMA32_TXENABLE; value |= (addrext << BWN_DMA32_TXADDREXT_SHIFT) & BWN_DMA32_TXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA32_TXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA32_TXRING, (ring32 & ~SIBA_DMA_TRANSLATION_MASK) | trans); } return; } /* * set for RX */ dr->dr_usedslot = dr->dr_numslots; if (dr->dr_type == BWN_DMA_64BIT) { ring64 = (uint64_t)(dr->dr_ring_dmabase); addrext = ((ring64 >> 32) & SIBA_DMA_TRANSLATION_MASK) >> 30; value = (dr->dr_frameoffset << BWN_DMA64_RXFROFF_SHIFT); value |= BWN_DMA64_RXENABLE; value |= (addrext << BWN_DMA64_RXADDREXT_SHIFT) & BWN_DMA64_RXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA64_RXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGLO, (ring64 & 0xffffffff)); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGHI, ((ring64 >> 32) & ~SIBA_DMA_TRANSLATION_MASK) | (trans << 1)); BWN_DMA_WRITE(dr, BWN_DMA64_RXINDEX, dr->dr_numslots * sizeof(struct bwn_dmadesc64)); } else { ring32 = (uint32_t)(dr->dr_ring_dmabase); addrext = (ring32 & SIBA_DMA_TRANSLATION_MASK) >> 30; value = (dr->dr_frameoffset << BWN_DMA32_RXFROFF_SHIFT); value |= BWN_DMA32_RXENABLE; value |= (addrext << BWN_DMA32_RXADDREXT_SHIFT) & BWN_DMA32_RXADDREXT_MASK; BWN_DMA_WRITE(dr, BWN_DMA32_RXCTL, value); BWN_DMA_WRITE(dr, BWN_DMA32_RXRING, (ring32 & ~SIBA_DMA_TRANSLATION_MASK) | trans); BWN_DMA_WRITE(dr, BWN_DMA32_RXINDEX, dr->dr_numslots * sizeof(struct bwn_dmadesc32)); } } static void bwn_dma_free_ringmemory(struct bwn_dma_ring *dr) { bus_dmamap_unload(dr->dr_ring_dtag, dr->dr_ring_dmap); bus_dmamem_free(dr->dr_ring_dtag, dr->dr_ring_descbase, dr->dr_ring_dmap); } static void bwn_dma_cleanup(struct bwn_dma_ring *dr) { if (dr->dr_tx) { bwn_dma_tx_reset(dr->dr_mac, dr->dr_base, dr->dr_type); if (dr->dr_type == BWN_DMA_64BIT) { BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGLO, 0); BWN_DMA_WRITE(dr, BWN_DMA64_TXRINGHI, 0); } else BWN_DMA_WRITE(dr, BWN_DMA32_TXRING, 0); } else { bwn_dma_rx_reset(dr->dr_mac, dr->dr_base, dr->dr_type); if (dr->dr_type == BWN_DMA_64BIT) { BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGLO, 0); BWN_DMA_WRITE(dr, BWN_DMA64_RXRINGHI, 0); } else BWN_DMA_WRITE(dr, BWN_DMA32_RXRING, 0); } } static void bwn_dma_free_descbufs(struct bwn_dma_ring *dr) { struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; int i; if (!dr->dr_usedslot) return; for (i = 0; i < dr->dr_numslots; i++) { dr->getdesc(dr, i, &desc, &meta); if (meta->mt_m == NULL) { if (!dr->dr_tx) device_printf(sc->sc_dev, "%s: not TX?\n", __func__); continue; } if (dr->dr_tx) { if (meta->mt_txtype == BWN_DMADESC_METATYPE_HEADER) bus_dmamap_unload(dr->dr_txring_dtag, meta->mt_dmap); else if (meta->mt_txtype == BWN_DMADESC_METATYPE_BODY) bus_dmamap_unload(dma->txbuf_dtag, meta->mt_dmap); } else bus_dmamap_unload(dma->rxbuf_dtag, meta->mt_dmap); bwn_dma_free_descbuf(dr, meta); } } static int bwn_dma_tx_reset(struct bwn_mac *mac, uint16_t base, int type) { struct bwn_softc *sc = mac->mac_sc; uint32_t value; int i; uint16_t offset; for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXSTATUS : BWN_DMA32_TXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_TXSTAT; if (value == BWN_DMA64_TXSTAT_DISABLED || value == BWN_DMA64_TXSTAT_IDLEWAIT || value == BWN_DMA64_TXSTAT_STOPPED) break; } else { value &= BWN_DMA32_TXSTATE; if (value == BWN_DMA32_TXSTAT_DISABLED || value == BWN_DMA32_TXSTAT_IDLEWAIT || value == BWN_DMA32_TXSTAT_STOPPED) break; } DELAY(1000); } offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXCTL : BWN_DMA32_TXCTL; BWN_WRITE_4(mac, base + offset, 0); for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_TXSTATUS : BWN_DMA32_TXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_TXSTAT; if (value == BWN_DMA64_TXSTAT_DISABLED) { i = -1; break; } } else { value &= BWN_DMA32_TXSTATE; if (value == BWN_DMA32_TXSTAT_DISABLED) { i = -1; break; } } DELAY(1000); } if (i != -1) { device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (ENODEV); } DELAY(1000); return (0); } static int bwn_dma_rx_reset(struct bwn_mac *mac, uint16_t base, int type) { struct bwn_softc *sc = mac->mac_sc; uint32_t value; int i; uint16_t offset; offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_RXCTL : BWN_DMA32_RXCTL; BWN_WRITE_4(mac, base + offset, 0); for (i = 0; i < 10; i++) { offset = (type == BWN_DMA_64BIT) ? BWN_DMA64_RXSTATUS : BWN_DMA32_RXSTATUS; value = BWN_READ_4(mac, base + offset); if (type == BWN_DMA_64BIT) { value &= BWN_DMA64_RXSTAT; if (value == BWN_DMA64_RXSTAT_DISABLED) { i = -1; break; } } else { value &= BWN_DMA32_RXSTATE; if (value == BWN_DMA32_RXSTAT_DISABLED) { i = -1; break; } } DELAY(1000); } if (i != -1) { device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (ENODEV); } return (0); } static void bwn_dma_free_descbuf(struct bwn_dma_ring *dr, struct bwn_dmadesc_meta *meta) { if (meta->mt_m != NULL) { m_freem(meta->mt_m); meta->mt_m = NULL; } if (meta->mt_ni != NULL) { ieee80211_free_node(meta->mt_ni); meta->mt_ni = NULL; } } static void bwn_dma_set_redzone(struct bwn_dma_ring *dr, struct mbuf *m) { struct bwn_rxhdr4 *rxhdr; unsigned char *frame; rxhdr = mtod(m, struct bwn_rxhdr4 *); rxhdr->frame_len = 0; KASSERT(dr->dr_rx_bufsize >= dr->dr_frameoffset + sizeof(struct bwn_plcp6) + 2, ("%s:%d: fail", __func__, __LINE__)); frame = mtod(m, char *) + dr->dr_frameoffset; memset(frame, 0xff, sizeof(struct bwn_plcp6) + 2 /* padding */); } static uint8_t bwn_dma_check_redzone(struct bwn_dma_ring *dr, struct mbuf *m) { unsigned char *f = mtod(m, char *) + dr->dr_frameoffset; return ((f[0] & f[1] & f[2] & f[3] & f[4] & f[5] & f[6] & f[7]) == 0xff); } static void bwn_wme_init(struct bwn_mac *mac) { bwn_wme_load(mac); /* enable WME support. */ bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_EDCF); BWN_WRITE_2(mac, BWN_IFSCTL, BWN_READ_2(mac, BWN_IFSCTL) | BWN_IFSCTL_USE_EDCF); } static void bwn_spu_setdelay(struct bwn_mac *mac, int idle) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; uint16_t delay; /* microsec */ delay = (mac->mac_phy.type == BWN_PHYTYPE_A) ? 3700 : 1050; if (ic->ic_opmode == IEEE80211_M_IBSS || idle) delay = 500; if ((mac->mac_phy.rf_ver == 0x2050) && (mac->mac_phy.rf_rev == 8)) delay = max(delay, (uint16_t)2400); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_SPU_WAKEUP, delay); } static void bwn_bt_enable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint64_t hf; if (bwn_bluetooth == 0) return; if ((siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_BTCOEXIST) == 0) return; if (mac->mac_phy.type != BWN_PHYTYPE_B && !mac->mac_phy.gmode) return; hf = bwn_hf_read(mac); if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_BTCMOD) hf |= BWN_HF_BT_COEXISTALT; else hf |= BWN_HF_BT_COEXIST; bwn_hf_write(mac, hf); } static void bwn_set_macaddr(struct bwn_mac *mac) { bwn_mac_write_bssid(mac); bwn_mac_setfilter(mac, BWN_MACFILTER_SELF, mac->mac_sc->sc_macaddr); } static void bwn_clear_keys(struct bwn_mac *mac) { int i; for (i = 0; i < mac->mac_max_nr_keys; i++) { KASSERT(i >= 0 && i < mac->mac_max_nr_keys, ("%s:%d: fail", __func__, __LINE__)); bwn_key_dowrite(mac, i, BWN_SEC_ALGO_NONE, NULL, BWN_SEC_KEYSIZE, NULL); if ((i <= 3) && !BWN_SEC_NEWAPI(mac)) { bwn_key_dowrite(mac, i + 4, BWN_SEC_ALGO_NONE, NULL, BWN_SEC_KEYSIZE, NULL); } mac->mac_key[i].keyconf = NULL; } } static void bwn_crypt_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; mac->mac_max_nr_keys = (siba_get_revid(sc->sc_dev) >= 5) ? 58 : 20; KASSERT(mac->mac_max_nr_keys <= N(mac->mac_key), ("%s:%d: fail", __func__, __LINE__)); mac->mac_ktp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_KEY_TABLEP); mac->mac_ktp *= 2; if (siba_get_revid(sc->sc_dev) >= 5) BWN_WRITE_2(mac, BWN_RCMTA_COUNT, mac->mac_max_nr_keys - 8); bwn_clear_keys(mac); } static void bwn_chip_exit(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; bwn_phy_exit(mac); siba_gpio_set(sc->sc_dev, 0); } static int bwn_fw_fillinfo(struct bwn_mac *mac) { int error; error = bwn_fw_gets(mac, BWN_FWTYPE_DEFAULT); if (error == 0) return (0); error = bwn_fw_gets(mac, BWN_FWTYPE_OPENSOURCE); if (error == 0) return (0); return (error); } static int bwn_gpio_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t mask = 0x1f, set = 0xf, value; BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_GPOUT_MASK); BWN_WRITE_2(mac, BWN_GPIO_MASK, BWN_READ_2(mac, BWN_GPIO_MASK) | 0x000f); if (siba_get_chipid(sc->sc_dev) == 0x4301) { mask |= 0x0060; set |= 0x0060; } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) { BWN_WRITE_2(mac, BWN_GPIO_MASK, BWN_READ_2(mac, BWN_GPIO_MASK) | 0x0200); mask |= 0x0200; set |= 0x0200; } if (siba_get_revid(sc->sc_dev) >= 2) mask |= 0x0010; value = siba_gpio_get(sc->sc_dev); if (value == -1) return (0); siba_gpio_set(sc->sc_dev, (value & mask) | set); return (0); } static int bwn_fw_loadinitvals(struct bwn_mac *mac) { #define GETFWOFFSET(fwp, offset) \ ((const struct bwn_fwinitvals *)((const char *)fwp.fw->data + offset)) const size_t hdr_len = sizeof(struct bwn_fwhdr); const struct bwn_fwhdr *hdr; struct bwn_fw *fw = &mac->mac_fw; int error; hdr = (const struct bwn_fwhdr *)(fw->initvals.fw->data); error = bwn_fwinitvals_write(mac, GETFWOFFSET(fw->initvals, hdr_len), be32toh(hdr->size), fw->initvals.fw->datasize - hdr_len); if (error) return (error); if (fw->initvals_band.fw) { hdr = (const struct bwn_fwhdr *)(fw->initvals_band.fw->data); error = bwn_fwinitvals_write(mac, GETFWOFFSET(fw->initvals_band, hdr_len), be32toh(hdr->size), fw->initvals_band.fw->datasize - hdr_len); } return (error); #undef GETFWOFFSET } static int bwn_phy_init(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int error; mac->mac_phy.chan = mac->mac_phy.get_default_chan(mac); mac->mac_phy.rf_onoff(mac, 1); error = mac->mac_phy.init(mac); if (error) { device_printf(sc->sc_dev, "PHY init failed\n"); goto fail0; } error = bwn_switch_channel(mac, mac->mac_phy.get_default_chan(mac)); if (error) { device_printf(sc->sc_dev, "failed to switch default channel\n"); goto fail1; } return (0); fail1: if (mac->mac_phy.exit) mac->mac_phy.exit(mac); fail0: mac->mac_phy.rf_onoff(mac, 0); return (error); } static void bwn_set_txantenna(struct bwn_mac *mac, int antenna) { uint16_t ant; uint16_t tmp; ant = bwn_ant2phy(antenna); /* For ACK/CTS */ tmp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL); tmp = (tmp & ~BWN_TX_PHY_ANT) | ant; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_ACKCTS_PHYCTL, tmp); /* For Probe Resposes */ tmp = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL); tmp = (tmp & ~BWN_TX_PHY_ANT) | ant; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PROBE_RESP_PHYCTL, tmp); } static void bwn_set_opmode(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint32_t ctl; uint16_t cfp_pretbtt; ctl = BWN_READ_4(mac, BWN_MACCTL); ctl &= ~(BWN_MACCTL_HOSTAP | BWN_MACCTL_PASS_CTL | BWN_MACCTL_PASS_BADPLCP | BWN_MACCTL_PASS_BADFCS | BWN_MACCTL_PROMISC | BWN_MACCTL_BEACON_PROMISC); ctl |= BWN_MACCTL_STA; if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_MBSS) ctl |= BWN_MACCTL_HOSTAP; else if (ic->ic_opmode == IEEE80211_M_IBSS) ctl &= ~BWN_MACCTL_STA; ctl |= sc->sc_filters; if (siba_get_revid(sc->sc_dev) <= 4) ctl |= BWN_MACCTL_PROMISC; BWN_WRITE_4(mac, BWN_MACCTL, ctl); cfp_pretbtt = 2; if ((ctl & BWN_MACCTL_STA) && !(ctl & BWN_MACCTL_HOSTAP)) { if (siba_get_chipid(sc->sc_dev) == 0x4306 && siba_get_chiprev(sc->sc_dev) == 3) cfp_pretbtt = 100; else cfp_pretbtt = 50; } BWN_WRITE_2(mac, 0x612, cfp_pretbtt); } static int bwn_dma_gettype(struct bwn_mac *mac) { uint32_t tmp; uint16_t base; tmp = BWN_READ_4(mac, SIBA_TGSHIGH); if (tmp & SIBA_TGSHIGH_DMA64) return (BWN_DMA_64BIT); base = bwn_dma_base(0, 0); BWN_WRITE_4(mac, base + BWN_DMA32_TXCTL, BWN_DMA32_TXADDREXT_MASK); tmp = BWN_READ_4(mac, base + BWN_DMA32_TXCTL); if (tmp & BWN_DMA32_TXADDREXT_MASK) return (BWN_DMA_32BIT); return (BWN_DMA_30BIT); } static void bwn_dma_ring_addr(void *arg, bus_dma_segment_t *seg, int nseg, int error) { if (!error) { KASSERT(nseg == 1, ("too many segments(%d)\n", nseg)); *((bus_addr_t *)arg) = seg->ds_addr; } } static void bwn_phy_g_init_sub(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; uint16_t i, tmp; if (phy->rev == 1) bwn_phy_init_b5(mac); else bwn_phy_init_b6(mac); if (phy->rev >= 2 || phy->gmode) bwn_phy_init_a(mac); if (phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVER, 0); BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVERVAL, 0); } if (phy->rev == 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, 0); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xc0); } if (phy->rev > 5) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, 0x400); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xc0); } if (phy->gmode || phy->rev >= 2) { tmp = BWN_PHY_READ(mac, BWN_PHY_VERSION_OFDM); tmp &= BWN_PHYVER_VERSION; if (tmp == 3 || tmp == 5) { BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xc2), 0x1816); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xc3), 0x8006); } if (tmp == 5) { BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xcc), 0x00ff, 0x1f00); } } if ((phy->rev <= 2 && phy->gmode) || phy->rev >= 2) BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x7e), 0x78); if (phy->rf_rev == 8) { BWN_PHY_SET(mac, BWN_PHY_EXTG(0x01), 0x80); BWN_PHY_SET(mac, BWN_PHY_OFDM(0x3e), 0x4); } if (BWN_HAS_LOOPBACK(phy)) bwn_loopback_calcgain(mac); if (phy->rf_rev != 8) { if (pg->pg_initval == 0xffff) pg->pg_initval = bwn_rf_init_bcm2050(mac); else BWN_RF_WRITE(mac, 0x0078, pg->pg_initval); } bwn_lo_g_init(mac); if (BWN_HAS_TXMAG(phy)) { BWN_RF_WRITE(mac, 0x52, (BWN_RF_READ(mac, 0x52) & 0xff00) | pg->pg_loctl.tx_bias | pg->pg_loctl.tx_magn); } else { BWN_RF_SETMASK(mac, 0x52, 0xfff0, pg->pg_loctl.tx_bias); } if (phy->rev >= 6) { BWN_PHY_SETMASK(mac, BWN_PHY_CCK(0x36), 0x0fff, (pg->pg_loctl.tx_bias << 12)); } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2e), 0x8075); else BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2e), 0x807f); if (phy->rev < 2) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2f), 0x101); else BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2f), 0x202); if (phy->gmode || phy->rev >= 2) { bwn_lo_g_adjust(mac); BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0x8078); } if (!(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_RSSI)) { for (i = 0; i < 64; i++) { BWN_PHY_WRITE(mac, BWN_PHY_NRSSI_CTRL, i); BWN_PHY_WRITE(mac, BWN_PHY_NRSSI_DATA, (uint16_t)MIN(MAX(bwn_nrssi_read(mac, i) - 0xffff, -32), 31)); } bwn_nrssi_threshold(mac); } else if (phy->gmode || phy->rev >= 2) { if (pg->pg_nrssi[0] == -1000) { KASSERT(pg->pg_nrssi[1] == -1000, ("%s:%d: fail", __func__, __LINE__)); bwn_nrssi_slope_11g(mac); } else bwn_nrssi_threshold(mac); } if (phy->rf_rev == 8) BWN_PHY_WRITE(mac, BWN_PHY_EXTG(0x05), 0x3230); bwn_phy_hwpctl_init(mac); if ((siba_get_chipid(sc->sc_dev) == 0x4306 && siba_get_chippkg(sc->sc_dev) == 2) || 0) { BWN_PHY_MASK(mac, BWN_PHY_CRS0, 0xbfff); BWN_PHY_MASK(mac, BWN_PHY_OFDM(0xc3), 0x7fff); } } static uint8_t bwn_has_hwpctl(struct bwn_mac *mac) { if (mac->mac_phy.hwpctl == 0 || mac->mac_phy.use_hwpctl == NULL) return (0); return (mac->mac_phy.use_hwpctl(mac)); } static void bwn_phy_init_b5(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; uint16_t offset, value; uint8_t old_channel; if (phy->analog == 1) BWN_RF_SET(mac, 0x007a, 0x0050); if ((siba_get_pci_subvendor(sc->sc_dev) != SIBA_BOARDVENDOR_BCM) && (siba_get_pci_subdevice(sc->sc_dev) != SIBA_BOARD_BU4306)) { value = 0x2120; for (offset = 0x00a8; offset < 0x00c7; offset++) { BWN_PHY_WRITE(mac, offset, value); value += 0x202; } } BWN_PHY_SETMASK(mac, 0x0035, 0xf0ff, 0x0700); if (phy->rf_ver == 0x2050) BWN_PHY_WRITE(mac, 0x0038, 0x0667); if (phy->gmode || phy->rev >= 2) { if (phy->rf_ver == 0x2050) { BWN_RF_SET(mac, 0x007a, 0x0020); BWN_RF_SET(mac, 0x0051, 0x0004); } BWN_WRITE_2(mac, BWN_PHY_RADIO, 0x0000); BWN_PHY_SET(mac, 0x0802, 0x0100); BWN_PHY_SET(mac, 0x042b, 0x2000); BWN_PHY_WRITE(mac, 0x001c, 0x186a); BWN_PHY_SETMASK(mac, 0x0013, 0x00ff, 0x1900); BWN_PHY_SETMASK(mac, 0x0035, 0xffc0, 0x0064); BWN_PHY_SETMASK(mac, 0x005d, 0xff80, 0x000a); } if (mac->mac_flags & BWN_MAC_FLAG_BADFRAME_PREEMP) BWN_PHY_SET(mac, BWN_PHY_RADIO_BITFIELD, (1 << 11)); if (phy->analog == 1) { BWN_PHY_WRITE(mac, 0x0026, 0xce00); BWN_PHY_WRITE(mac, 0x0021, 0x3763); BWN_PHY_WRITE(mac, 0x0022, 0x1bc3); BWN_PHY_WRITE(mac, 0x0023, 0x06f9); BWN_PHY_WRITE(mac, 0x0024, 0x037e); } else BWN_PHY_WRITE(mac, 0x0026, 0xcc00); BWN_PHY_WRITE(mac, 0x0030, 0x00c6); BWN_WRITE_2(mac, 0x03ec, 0x3f22); if (phy->analog == 1) BWN_PHY_WRITE(mac, 0x0020, 0x3e1c); else BWN_PHY_WRITE(mac, 0x0020, 0x301c); if (phy->analog == 0) BWN_WRITE_2(mac, 0x03e4, 0x3000); old_channel = phy->chan; bwn_phy_g_switch_chan(mac, 7, 0); if (phy->rf_ver != 0x2050) { BWN_RF_WRITE(mac, 0x0075, 0x0080); BWN_RF_WRITE(mac, 0x0079, 0x0081); } BWN_RF_WRITE(mac, 0x0050, 0x0020); BWN_RF_WRITE(mac, 0x0050, 0x0023); if (phy->rf_ver == 0x2050) { BWN_RF_WRITE(mac, 0x0050, 0x0020); BWN_RF_WRITE(mac, 0x005a, 0x0070); } BWN_RF_WRITE(mac, 0x005b, 0x007b); BWN_RF_WRITE(mac, 0x005c, 0x00b0); BWN_RF_SET(mac, 0x007a, 0x0007); bwn_phy_g_switch_chan(mac, old_channel, 0); BWN_PHY_WRITE(mac, 0x0014, 0x0080); BWN_PHY_WRITE(mac, 0x0032, 0x00ca); BWN_PHY_WRITE(mac, 0x002a, 0x88a3); bwn_phy_g_set_txpwr_sub(mac, &pg->pg_bbatt, &pg->pg_rfatt, pg->pg_txctl); if (phy->rf_ver == 0x2050) BWN_RF_WRITE(mac, 0x005d, 0x000d); BWN_WRITE_2(mac, 0x03e4, (BWN_READ_2(mac, 0x03e4) & 0xffc0) | 0x0004); } static void bwn_loopback_calcgain(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; uint16_t backup_phy[16] = { 0 }; uint16_t backup_radio[3]; uint16_t backup_bband; uint16_t i, j, loop_i_max; uint16_t trsw_rx; uint16_t loop1_outer_done, loop1_inner_done; backup_phy[0] = BWN_PHY_READ(mac, BWN_PHY_CRS0); backup_phy[1] = BWN_PHY_READ(mac, BWN_PHY_CCKBBANDCFG); backup_phy[2] = BWN_PHY_READ(mac, BWN_PHY_RFOVER); backup_phy[3] = BWN_PHY_READ(mac, BWN_PHY_RFOVERVAL); if (phy->rev != 1) { backup_phy[4] = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVER); backup_phy[5] = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVERVAL); } backup_phy[6] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x5a)); backup_phy[7] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x59)); backup_phy[8] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x58)); backup_phy[9] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x0a)); backup_phy[10] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x03)); backup_phy[11] = BWN_PHY_READ(mac, BWN_PHY_LO_MASK); backup_phy[12] = BWN_PHY_READ(mac, BWN_PHY_LO_CTL); backup_phy[13] = BWN_PHY_READ(mac, BWN_PHY_CCK(0x2b)); backup_phy[14] = BWN_PHY_READ(mac, BWN_PHY_PGACTL); backup_phy[15] = BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE); backup_bband = pg->pg_bbatt.att; backup_radio[0] = BWN_RF_READ(mac, 0x52); backup_radio[1] = BWN_RF_READ(mac, 0x43); backup_radio[2] = BWN_RF_READ(mac, 0x7a); BWN_PHY_MASK(mac, BWN_PHY_CRS0, 0x3fff); BWN_PHY_SET(mac, BWN_PHY_CCKBBANDCFG, 0x8000); BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x0002); BWN_PHY_MASK(mac, BWN_PHY_RFOVERVAL, 0xfffd); BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x0001); BWN_PHY_MASK(mac, BWN_PHY_RFOVERVAL, 0xfffe); if (phy->rev != 1) { BWN_PHY_SET(mac, BWN_PHY_ANALOGOVER, 0x0001); BWN_PHY_MASK(mac, BWN_PHY_ANALOGOVERVAL, 0xfffe); BWN_PHY_SET(mac, BWN_PHY_ANALOGOVER, 0x0002); BWN_PHY_MASK(mac, BWN_PHY_ANALOGOVERVAL, 0xfffd); } BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x000c); BWN_PHY_SET(mac, BWN_PHY_RFOVERVAL, 0x000c); BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x0030); BWN_PHY_SETMASK(mac, BWN_PHY_RFOVERVAL, 0xffcf, 0x10); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x5a), 0x0780); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x59), 0xc810); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0x000d); BWN_PHY_SET(mac, BWN_PHY_CCK(0x0a), 0x2000); if (phy->rev != 1) { BWN_PHY_SET(mac, BWN_PHY_ANALOGOVER, 0x0004); BWN_PHY_MASK(mac, BWN_PHY_ANALOGOVERVAL, 0xfffb); } BWN_PHY_SETMASK(mac, BWN_PHY_CCK(0x03), 0xff9f, 0x40); if (phy->rf_rev == 8) BWN_RF_WRITE(mac, 0x43, 0x000f); else { BWN_RF_WRITE(mac, 0x52, 0); BWN_RF_SETMASK(mac, 0x43, 0xfff0, 0x9); } bwn_phy_g_set_bbatt(mac, 11); if (phy->rev >= 3) BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0xc020); else BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0x8020); BWN_PHY_WRITE(mac, BWN_PHY_LO_CTL, 0); BWN_PHY_SETMASK(mac, BWN_PHY_CCK(0x2b), 0xffc0, 0x01); BWN_PHY_SETMASK(mac, BWN_PHY_CCK(0x2b), 0xc0ff, 0x800); BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x0100); BWN_PHY_MASK(mac, BWN_PHY_RFOVERVAL, 0xcfff); if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA) { if (phy->rev >= 7) { BWN_PHY_SET(mac, BWN_PHY_RFOVER, 0x0800); BWN_PHY_SET(mac, BWN_PHY_RFOVERVAL, 0x8000); } } BWN_RF_MASK(mac, 0x7a, 0x00f7); j = 0; loop_i_max = (phy->rf_rev == 8) ? 15 : 9; for (i = 0; i < loop_i_max; i++) { for (j = 0; j < 16; j++) { BWN_RF_WRITE(mac, 0x43, i); BWN_PHY_SETMASK(mac, BWN_PHY_RFOVERVAL, 0xf0ff, (j << 8)); BWN_PHY_SETMASK(mac, BWN_PHY_PGACTL, 0x0fff, 0xa000); BWN_PHY_SET(mac, BWN_PHY_PGACTL, 0xf000); DELAY(20); if (BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE) >= 0xdfc) goto done0; } } done0: loop1_outer_done = i; loop1_inner_done = j; if (j >= 8) { BWN_PHY_SET(mac, BWN_PHY_RFOVERVAL, 0x30); trsw_rx = 0x1b; for (j = j - 8; j < 16; j++) { BWN_PHY_SETMASK(mac, BWN_PHY_RFOVERVAL, 0xf0ff, j << 8); BWN_PHY_SETMASK(mac, BWN_PHY_PGACTL, 0x0fff, 0xa000); BWN_PHY_SET(mac, BWN_PHY_PGACTL, 0xf000); DELAY(20); trsw_rx -= 3; if (BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE) >= 0xdfc) goto done1; } } else trsw_rx = 0x18; done1: if (phy->rev != 1) { BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVER, backup_phy[4]); BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVERVAL, backup_phy[5]); } BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x5a), backup_phy[6]); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x59), backup_phy[7]); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), backup_phy[8]); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x0a), backup_phy[9]); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x03), backup_phy[10]); BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, backup_phy[11]); BWN_PHY_WRITE(mac, BWN_PHY_LO_CTL, backup_phy[12]); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2b), backup_phy[13]); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, backup_phy[14]); bwn_phy_g_set_bbatt(mac, backup_bband); BWN_RF_WRITE(mac, 0x52, backup_radio[0]); BWN_RF_WRITE(mac, 0x43, backup_radio[1]); BWN_RF_WRITE(mac, 0x7a, backup_radio[2]); BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, backup_phy[2] | 0x0003); DELAY(10); BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, backup_phy[2]); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, backup_phy[3]); BWN_PHY_WRITE(mac, BWN_PHY_CRS0, backup_phy[0]); BWN_PHY_WRITE(mac, BWN_PHY_CCKBBANDCFG, backup_phy[1]); pg->pg_max_lb_gain = ((loop1_inner_done * 6) - (loop1_outer_done * 4)) - 11; pg->pg_trsw_rx_gain = trsw_rx * 2; } static uint16_t bwn_rf_init_bcm2050(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; uint32_t tmp1 = 0, tmp2 = 0; uint16_t rcc, i, j, pgactl, cck0, cck1, cck2, cck3, rfover, rfoverval, analogover, analogoverval, crs0, classctl, lomask, loctl, syncctl, radio0, radio1, radio2, reg0, reg1, reg2, radio78, reg, index; static const uint8_t rcc_table[] = { 0x02, 0x03, 0x01, 0x0f, 0x06, 0x07, 0x05, 0x0f, 0x0a, 0x0b, 0x09, 0x0f, 0x0e, 0x0f, 0x0d, 0x0f, }; loctl = lomask = reg0 = classctl = crs0 = analogoverval = analogover = rfoverval = rfover = cck3 = 0; radio0 = BWN_RF_READ(mac, 0x43); radio1 = BWN_RF_READ(mac, 0x51); radio2 = BWN_RF_READ(mac, 0x52); pgactl = BWN_PHY_READ(mac, BWN_PHY_PGACTL); cck0 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x5a)); cck1 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x59)); cck2 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x58)); if (phy->type == BWN_PHYTYPE_B) { cck3 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x30)); reg0 = BWN_READ_2(mac, 0x3ec); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x30), 0xff); BWN_WRITE_2(mac, 0x3ec, 0x3f3f); } else if (phy->gmode || phy->rev >= 2) { rfover = BWN_PHY_READ(mac, BWN_PHY_RFOVER); rfoverval = BWN_PHY_READ(mac, BWN_PHY_RFOVERVAL); analogover = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVER); analogoverval = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVERVAL); crs0 = BWN_PHY_READ(mac, BWN_PHY_CRS0); classctl = BWN_PHY_READ(mac, BWN_PHY_CLASSCTL); BWN_PHY_SET(mac, BWN_PHY_ANALOGOVER, 0x0003); BWN_PHY_MASK(mac, BWN_PHY_ANALOGOVERVAL, 0xfffc); BWN_PHY_MASK(mac, BWN_PHY_CRS0, 0x7fff); BWN_PHY_MASK(mac, BWN_PHY_CLASSCTL, 0xfffc); if (BWN_HAS_LOOPBACK(phy)) { lomask = BWN_PHY_READ(mac, BWN_PHY_LO_MASK); loctl = BWN_PHY_READ(mac, BWN_PHY_LO_CTL); if (phy->rev >= 3) BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0xc020); else BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0x8020); BWN_PHY_WRITE(mac, BWN_PHY_LO_CTL, 0); } BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(0, 1, 1))); BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVER, 0)); } BWN_WRITE_2(mac, 0x3e2, BWN_READ_2(mac, 0x3e2) | 0x8000); syncctl = BWN_PHY_READ(mac, BWN_PHY_SYNCCTL); BWN_PHY_MASK(mac, BWN_PHY_SYNCCTL, 0xff7f); reg1 = BWN_READ_2(mac, 0x3e6); reg2 = BWN_READ_2(mac, 0x3f4); if (phy->analog == 0) BWN_WRITE_2(mac, 0x03e6, 0x0122); else { if (phy->analog >= 2) BWN_PHY_SETMASK(mac, BWN_PHY_CCK(0x03), 0xffbf, 0x40); BWN_WRITE_2(mac, BWN_CHANNEL_EXT, (BWN_READ_2(mac, BWN_CHANNEL_EXT) | 0x2000)); } reg = BWN_RF_READ(mac, 0x60); index = (reg & 0x001e) >> 1; rcc = (((rcc_table[index] << 1) | (reg & 0x0001)) | 0x0020); if (phy->type == BWN_PHYTYPE_B) BWN_RF_WRITE(mac, 0x78, 0x26); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(0, 1, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xbfaf); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2b), 0x1403); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(0, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xbfa0); BWN_RF_SET(mac, 0x51, 0x0004); if (phy->rf_rev == 8) BWN_RF_WRITE(mac, 0x43, 0x1f); else { BWN_RF_WRITE(mac, 0x52, 0); BWN_RF_SETMASK(mac, 0x43, 0xfff0, 0x0009); } BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0); for (i = 0; i < 16; i++) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x5a), 0x0480); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x59), 0xc810); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0x000d); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xafb0); DELAY(10); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xefb0); DELAY(10); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 0))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xfff0); DELAY(20); tmp1 += BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xafb0); } DELAY(10); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0); tmp1++; tmp1 >>= 9; for (i = 0; i < 16; i++) { radio78 = (BWN_BITREV4(i) << 1) | 0x0020; BWN_RF_WRITE(mac, 0x78, radio78); DELAY(10); for (j = 0; j < 16; j++) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x5a), 0x0d80); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x59), 0xc810); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0x000d); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xafb0); DELAY(10); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xefb0); DELAY(10); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 0))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xfff0); DELAY(10); tmp2 += BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), 0); if (phy->gmode || phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, bwn_rf_2050_rfoverval(mac, BWN_PHY_RFOVERVAL, BWN_LPD(1, 0, 1))); } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xafb0); } tmp2++; tmp2 >>= 8; if (tmp1 < tmp2) break; } BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, pgactl); BWN_RF_WRITE(mac, 0x51, radio1); BWN_RF_WRITE(mac, 0x52, radio2); BWN_RF_WRITE(mac, 0x43, radio0); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x5a), cck0); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x59), cck1); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x58), cck2); BWN_WRITE_2(mac, 0x3e6, reg1); if (phy->analog != 0) BWN_WRITE_2(mac, 0x3f4, reg2); BWN_PHY_WRITE(mac, BWN_PHY_SYNCCTL, syncctl); bwn_spu_workaround(mac, phy->chan); if (phy->type == BWN_PHYTYPE_B) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x30), cck3); BWN_WRITE_2(mac, 0x3ec, reg0); } else if (phy->gmode) { BWN_WRITE_2(mac, BWN_PHY_RADIO, BWN_READ_2(mac, BWN_PHY_RADIO) & 0x7fff); BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, rfover); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, rfoverval); BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVER, analogover); BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVERVAL, analogoverval); BWN_PHY_WRITE(mac, BWN_PHY_CRS0, crs0); BWN_PHY_WRITE(mac, BWN_PHY_CLASSCTL, classctl); if (BWN_HAS_LOOPBACK(phy)) { BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, lomask); BWN_PHY_WRITE(mac, BWN_PHY_LO_CTL, loctl); } } return ((i > 15) ? radio78 : rcc); } static void bwn_phy_init_b6(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; uint16_t offset, val; uint8_t old_channel; KASSERT(!(phy->rf_rev == 6 || phy->rf_rev == 7), ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, 0x003e, 0x817a); BWN_RF_WRITE(mac, 0x007a, BWN_RF_READ(mac, 0x007a) | 0x0058); if (phy->rf_rev == 4 || phy->rf_rev == 5) { BWN_RF_WRITE(mac, 0x51, 0x37); BWN_RF_WRITE(mac, 0x52, 0x70); BWN_RF_WRITE(mac, 0x53, 0xb3); BWN_RF_WRITE(mac, 0x54, 0x9b); BWN_RF_WRITE(mac, 0x5a, 0x88); BWN_RF_WRITE(mac, 0x5b, 0x88); BWN_RF_WRITE(mac, 0x5d, 0x88); BWN_RF_WRITE(mac, 0x5e, 0x88); BWN_RF_WRITE(mac, 0x7d, 0x88); bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_TSSI_RESET_PSM_WORKAROUN); } if (phy->rf_rev == 8) { BWN_RF_WRITE(mac, 0x51, 0); BWN_RF_WRITE(mac, 0x52, 0x40); BWN_RF_WRITE(mac, 0x53, 0xb7); BWN_RF_WRITE(mac, 0x54, 0x98); BWN_RF_WRITE(mac, 0x5a, 0x88); BWN_RF_WRITE(mac, 0x5b, 0x6b); BWN_RF_WRITE(mac, 0x5c, 0x0f); if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_ALTIQ) { BWN_RF_WRITE(mac, 0x5d, 0xfa); BWN_RF_WRITE(mac, 0x5e, 0xd8); } else { BWN_RF_WRITE(mac, 0x5d, 0xf5); BWN_RF_WRITE(mac, 0x5e, 0xb8); } BWN_RF_WRITE(mac, 0x0073, 0x0003); BWN_RF_WRITE(mac, 0x007d, 0x00a8); BWN_RF_WRITE(mac, 0x007c, 0x0001); BWN_RF_WRITE(mac, 0x007e, 0x0008); } for (val = 0x1e1f, offset = 0x0088; offset < 0x0098; offset++) { BWN_PHY_WRITE(mac, offset, val); val -= 0x0202; } for (val = 0x3e3f, offset = 0x0098; offset < 0x00a8; offset++) { BWN_PHY_WRITE(mac, offset, val); val -= 0x0202; } for (val = 0x2120, offset = 0x00a8; offset < 0x00c8; offset++) { BWN_PHY_WRITE(mac, offset, (val & 0x3f3f)); val += 0x0202; } if (phy->type == BWN_PHYTYPE_G) { BWN_RF_SET(mac, 0x007a, 0x0020); BWN_RF_SET(mac, 0x0051, 0x0004); BWN_PHY_SET(mac, 0x0802, 0x0100); BWN_PHY_SET(mac, 0x042b, 0x2000); BWN_PHY_WRITE(mac, 0x5b, 0); BWN_PHY_WRITE(mac, 0x5c, 0); } old_channel = phy->chan; bwn_phy_g_switch_chan(mac, (old_channel >= 8) ? 1 : 13, 0); BWN_RF_WRITE(mac, 0x0050, 0x0020); BWN_RF_WRITE(mac, 0x0050, 0x0023); DELAY(40); if (phy->rf_rev < 6 || phy->rf_rev == 8) { BWN_RF_WRITE(mac, 0x7c, BWN_RF_READ(mac, 0x7c) | 0x0002); BWN_RF_WRITE(mac, 0x50, 0x20); } if (phy->rf_rev <= 2) { BWN_RF_WRITE(mac, 0x7c, 0x20); BWN_RF_WRITE(mac, 0x5a, 0x70); BWN_RF_WRITE(mac, 0x5b, 0x7b); BWN_RF_WRITE(mac, 0x5c, 0xb0); } BWN_RF_SETMASK(mac, 0x007a, 0x00f8, 0x0007); bwn_phy_g_switch_chan(mac, old_channel, 0); BWN_PHY_WRITE(mac, 0x0014, 0x0200); if (phy->rf_rev >= 6) BWN_PHY_WRITE(mac, 0x2a, 0x88c2); else BWN_PHY_WRITE(mac, 0x2a, 0x8ac0); BWN_PHY_WRITE(mac, 0x0038, 0x0668); bwn_phy_g_set_txpwr_sub(mac, &pg->pg_bbatt, &pg->pg_rfatt, pg->pg_txctl); if (phy->rf_rev <= 5) BWN_PHY_SETMASK(mac, 0x5d, 0xff80, 0x0003); if (phy->rf_rev <= 2) BWN_RF_WRITE(mac, 0x005d, 0x000d); if (phy->analog == 4) { BWN_WRITE_2(mac, 0x3e4, 9); BWN_PHY_MASK(mac, 0x61, 0x0fff); } else BWN_PHY_SETMASK(mac, 0x0002, 0xffc0, 0x0004); if (phy->type == BWN_PHYTYPE_B) KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); else if (phy->type == BWN_PHYTYPE_G) BWN_WRITE_2(mac, 0x03e6, 0x0); } static void bwn_phy_init_a(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; KASSERT(phy->type == BWN_PHYTYPE_A || phy->type == BWN_PHYTYPE_G, ("%s:%d: fail", __func__, __LINE__)); if (phy->rev >= 6) { if (phy->type == BWN_PHYTYPE_A) BWN_PHY_MASK(mac, BWN_PHY_OFDM(0x1b), ~0x1000); if (BWN_PHY_READ(mac, BWN_PHY_ENCORE) & BWN_PHY_ENCORE_EN) BWN_PHY_SET(mac, BWN_PHY_ENCORE, 0x0010); else BWN_PHY_MASK(mac, BWN_PHY_ENCORE, ~0x1010); } bwn_wa_init(mac); if (phy->type == BWN_PHYTYPE_G && (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_PACTRL)) BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x6e), 0xe000, 0x3cf); } static void bwn_wa_write_noisescale(struct bwn_mac *mac, const uint16_t *nst) { int i; for (i = 0; i < BWN_TAB_NOISESCALE_SIZE; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_NOISESCALE, i, nst[i]); } static void bwn_wa_agc(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; if (phy->rev == 1) { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1_R1, 0, 254); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1_R1, 1, 13); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1_R1, 2, 19); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1_R1, 3, 25); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, 0, 0x2710); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, 1, 0x9b83); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, 2, 0x9b83); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, 3, 0x0f8d); BWN_PHY_WRITE(mac, BWN_PHY_LMS, 4); } else { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1, 0, 254); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1, 1, 13); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1, 2, 19); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC1, 3, 25); } BWN_PHY_SETMASK(mac, BWN_PHY_CCKSHIFTBITS_WA, (uint16_t)~0xff00, 0x5700); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x1a), ~0x007f, 0x000f); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x1a), ~0x3f80, 0x2b80); BWN_PHY_SETMASK(mac, BWN_PHY_ANTWRSETT, 0xf0ff, 0x0300); BWN_RF_SET(mac, 0x7a, 0x0008); BWN_PHY_SETMASK(mac, BWN_PHY_N1P1GAIN, ~0x000f, 0x0008); BWN_PHY_SETMASK(mac, BWN_PHY_P1P2GAIN, ~0x0f00, 0x0600); BWN_PHY_SETMASK(mac, BWN_PHY_N1N2GAIN, ~0x0f00, 0x0700); BWN_PHY_SETMASK(mac, BWN_PHY_N1P1GAIN, ~0x0f00, 0x0100); if (phy->rev == 1) BWN_PHY_SETMASK(mac, BWN_PHY_N1N2GAIN, ~0x000f, 0x0007); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x88), ~0x00ff, 0x001c); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x88), ~0x3f00, 0x0200); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x96), ~0x00ff, 0x001c); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x89), ~0x00ff, 0x0020); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x89), ~0x3f00, 0x0200); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x82), ~0x00ff, 0x002e); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x96), (uint16_t)~0xff00, 0x1a00); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x81), ~0x00ff, 0x0028); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x81), (uint16_t)~0xff00, 0x2c00); if (phy->rev == 1) { BWN_PHY_WRITE(mac, BWN_PHY_PEAK_COUNT, 0x092b); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x1b), ~0x001e, 0x0002); } else { BWN_PHY_MASK(mac, BWN_PHY_OFDM(0x1b), ~0x001e); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x1f), 0x287a); BWN_PHY_SETMASK(mac, BWN_PHY_LPFGAINCTL, ~0x000f, 0x0004); if (phy->rev >= 6) { BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x22), 0x287a); BWN_PHY_SETMASK(mac, BWN_PHY_LPFGAINCTL, (uint16_t)~0xf000, 0x3000); } } BWN_PHY_SETMASK(mac, BWN_PHY_DIVSRCHIDX, 0x8080, 0x7874); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x8e), 0x1c00); if (phy->rev == 1) { BWN_PHY_SETMASK(mac, BWN_PHY_DIVP1P2GAIN, ~0x0f00, 0x0600); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x8b), 0x005e); BWN_PHY_SETMASK(mac, BWN_PHY_ANTWRSETT, ~0x00ff, 0x001e); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x8d), 0x0002); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3_R1, 0, 0); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3_R1, 1, 7); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3_R1, 2, 16); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3_R1, 3, 28); } else { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3, 0, 0); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3, 1, 7); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3, 2, 16); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC3, 3, 28); } if (phy->rev >= 6) { BWN_PHY_MASK(mac, BWN_PHY_OFDM(0x26), ~0x0003); BWN_PHY_MASK(mac, BWN_PHY_OFDM(0x26), ~0x1000); } BWN_PHY_READ(mac, BWN_PHY_VERSION_OFDM); } static void bwn_wa_grev1(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; int i; static const uint16_t bwn_tab_finefreqg[] = BWN_TAB_FINEFREQ_G; static const uint32_t bwn_tab_retard[] = BWN_TAB_RETARD; static const uint32_t bwn_tab_rotor[] = BWN_TAB_ROTOR; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s fail", __func__)); /* init CRSTHRES and ANTDWELL */ if (phy->rev == 1) { BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1_R1, 0x4f19); } else if (phy->rev == 2) { BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1, 0x1861); BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES2, 0x0271); BWN_PHY_SET(mac, BWN_PHY_ANTDWELL, 0x0800); } else { BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1, 0x0098); BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES2, 0x0070); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xc9), 0x0080); BWN_PHY_SET(mac, BWN_PHY_ANTDWELL, 0x0800); } BWN_PHY_SETMASK(mac, BWN_PHY_CRS0, ~0x03c0, 0xd000); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0x2c), 0x005a); BWN_PHY_WRITE(mac, BWN_PHY_CCKSHIFTBITS, 0x0026); /* XXX support PHY-A??? */ for (i = 0; i < N(bwn_tab_finefreqg); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_DACRFPABB, i, bwn_tab_finefreqg[i]); /* XXX support PHY-A??? */ if (phy->rev == 1) for (i = 0; i < N(bwn_tab_noise_g1); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, i, bwn_tab_noise_g1[i]); else for (i = 0; i < N(bwn_tab_noise_g2); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, i, bwn_tab_noise_g2[i]); for (i = 0; i < N(bwn_tab_rotor); i++) bwn_ofdmtab_write_4(mac, BWN_OFDMTAB_ROTOR, i, bwn_tab_rotor[i]); /* XXX support PHY-A??? */ if (phy->rev >= 6) { if (BWN_PHY_READ(mac, BWN_PHY_ENCORE) & BWN_PHY_ENCORE_EN) bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g3); else bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g2); } else bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g1); for (i = 0; i < N(bwn_tab_retard); i++) bwn_ofdmtab_write_4(mac, BWN_OFDMTAB_ADVRETARD, i, bwn_tab_retard[i]); if (phy->rev == 1) { for (i = 0; i < 16; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_WRSSI_R1, i, 0x0020); } else { for (i = 0; i < 32; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_WRSSI, i, 0x0820); } bwn_wa_agc(mac); } static void bwn_wa_grev26789(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; int i; static const uint16_t bwn_tab_sigmasqr2[] = BWN_TAB_SIGMASQR2; uint16_t ofdmrev; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s fail", __func__)); bwn_gtab_write(mac, BWN_GTAB_ORIGTR, 0, 0xc480); /* init CRSTHRES and ANTDWELL */ if (phy->rev == 1) BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1_R1, 0x4f19); else if (phy->rev == 2) { BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1, 0x1861); BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES2, 0x0271); BWN_PHY_SET(mac, BWN_PHY_ANTDWELL, 0x0800); } else { BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES1, 0x0098); BWN_PHY_WRITE(mac, BWN_PHY_CRSTHRES2, 0x0070); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xc9), 0x0080); BWN_PHY_SET(mac, BWN_PHY_ANTDWELL, 0x0800); } for (i = 0; i < 64; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_RSSI, i, i); /* XXX support PHY-A??? */ if (phy->rev == 1) for (i = 0; i < N(bwn_tab_noise_g1); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, i, bwn_tab_noise_g1[i]); else for (i = 0; i < N(bwn_tab_noise_g2); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_AGC2, i, bwn_tab_noise_g2[i]); /* XXX support PHY-A??? */ if (phy->rev >= 6) { if (BWN_PHY_READ(mac, BWN_PHY_ENCORE) & BWN_PHY_ENCORE_EN) bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g3); else bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g2); } else bwn_wa_write_noisescale(mac, bwn_tab_noisescale_g1); for (i = 0; i < N(bwn_tab_sigmasqr2); i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_MINSIGSQ, i, bwn_tab_sigmasqr2[i]); if (phy->rev == 1) { for (i = 0; i < 16; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_WRSSI_R1, i, 0x0020); } else { for (i = 0; i < 32; i++) bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_WRSSI, i, 0x0820); } bwn_wa_agc(mac); ofdmrev = BWN_PHY_READ(mac, BWN_PHY_VERSION_OFDM) & BWN_PHYVER_VERSION; if (ofdmrev > 2) { if (phy->type == BWN_PHYTYPE_A) BWN_PHY_WRITE(mac, BWN_PHY_PWRDOWN, 0x1808); else BWN_PHY_WRITE(mac, BWN_PHY_PWRDOWN, 0x1000); } else { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_DAC, 3, 0x1044); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_DAC, 4, 0x7201); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_DAC, 6, 0x0040); } bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_UNKNOWN_0F, 2, 15); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_UNKNOWN_0F, 3, 20); } static void bwn_wa_init(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s fail", __func__)); switch (phy->rev) { case 1: bwn_wa_grev1(mac); break; case 2: case 6: case 7: case 8: case 9: bwn_wa_grev26789(mac); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } if (siba_get_pci_subvendor(sc->sc_dev) != SIBA_BOARDVENDOR_BCM || siba_get_pci_subdevice(sc->sc_dev) != SIBA_BOARD_BU4306 || siba_get_pci_revid(sc->sc_dev) != 0x17) { if (phy->rev < 2) { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX_R1, 1, 0x0002); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX_R1, 2, 0x0001); } else { bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 1, 0x0002); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 2, 0x0001); if ((siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA) && (phy->rev >= 7)) { BWN_PHY_MASK(mac, BWN_PHY_EXTG(0x11), 0xf7ff); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0020, 0x0001); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0021, 0x0001); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0022, 0x0001); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0023, 0x0000); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0000, 0x0000); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_GAINX, 0x0003, 0x0002); } } } if (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_FEM) { BWN_PHY_WRITE(mac, BWN_PHY_GTABCTL, 0x3120); BWN_PHY_WRITE(mac, BWN_PHY_GTABDATA, 0xc480); } bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_UNKNOWN_11, 0, 0); bwn_ofdmtab_write_2(mac, BWN_OFDMTAB_UNKNOWN_11, 1, 0); } static void bwn_ofdmtab_write_2(struct bwn_mac *mac, uint16_t table, uint16_t offset, uint16_t value) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; uint16_t addr; addr = table + offset; if ((pg->pg_ofdmtab_dir != BWN_OFDMTAB_DIR_WRITE) || (addr - 1 != pg->pg_ofdmtab_addr)) { BWN_PHY_WRITE(mac, BWN_PHY_OTABLECTL, addr); pg->pg_ofdmtab_dir = BWN_OFDMTAB_DIR_WRITE; } pg->pg_ofdmtab_addr = addr; BWN_PHY_WRITE(mac, BWN_PHY_OTABLEI, value); } static void bwn_ofdmtab_write_4(struct bwn_mac *mac, uint16_t table, uint16_t offset, uint32_t value) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; uint16_t addr; addr = table + offset; if ((pg->pg_ofdmtab_dir != BWN_OFDMTAB_DIR_WRITE) || (addr - 1 != pg->pg_ofdmtab_addr)) { BWN_PHY_WRITE(mac, BWN_PHY_OTABLECTL, addr); pg->pg_ofdmtab_dir = BWN_OFDMTAB_DIR_WRITE; } pg->pg_ofdmtab_addr = addr; BWN_PHY_WRITE(mac, BWN_PHY_OTABLEI, value); BWN_PHY_WRITE(mac, BWN_PHY_OTABLEQ, (value >> 16)); } static void bwn_gtab_write(struct bwn_mac *mac, uint16_t table, uint16_t offset, uint16_t value) { BWN_PHY_WRITE(mac, BWN_PHY_GTABCTL, table + offset); BWN_PHY_WRITE(mac, BWN_PHY_GTABDATA, value); } static void bwn_dummy_transmission(struct bwn_mac *mac, int ofdm, int paon) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; unsigned int i, max_loop; uint16_t value; uint32_t buffer[5] = { 0x00000000, 0x00d40000, 0x00000000, 0x01000000, 0x00000000 }; if (ofdm) { max_loop = 0x1e; buffer[0] = 0x000201cc; } else { max_loop = 0xfa; buffer[0] = 0x000b846e; } BWN_ASSERT_LOCKED(mac->mac_sc); for (i = 0; i < 5; i++) bwn_ram_write(mac, i * 4, buffer[i]); BWN_WRITE_2(mac, 0x0568, 0x0000); BWN_WRITE_2(mac, 0x07c0, (siba_get_revid(sc->sc_dev) < 11) ? 0x0000 : 0x0100); value = ((phy->type == BWN_PHYTYPE_A) ? 0x41 : 0x40); BWN_WRITE_2(mac, 0x050c, value); if (phy->type == BWN_PHYTYPE_LP) BWN_WRITE_2(mac, 0x0514, 0x1a02); BWN_WRITE_2(mac, 0x0508, 0x0000); BWN_WRITE_2(mac, 0x050a, 0x0000); BWN_WRITE_2(mac, 0x054c, 0x0000); BWN_WRITE_2(mac, 0x056a, 0x0014); BWN_WRITE_2(mac, 0x0568, 0x0826); BWN_WRITE_2(mac, 0x0500, 0x0000); if (phy->type == BWN_PHYTYPE_LP) BWN_WRITE_2(mac, 0x0502, 0x0050); else BWN_WRITE_2(mac, 0x0502, 0x0030); if (phy->rf_ver == 0x2050 && phy->rf_rev <= 0x5) BWN_RF_WRITE(mac, 0x0051, 0x0017); for (i = 0x00; i < max_loop; i++) { value = BWN_READ_2(mac, 0x050e); if (value & 0x0080) break; DELAY(10); } for (i = 0x00; i < 0x0a; i++) { value = BWN_READ_2(mac, 0x050e); if (value & 0x0400) break; DELAY(10); } for (i = 0x00; i < 0x19; i++) { value = BWN_READ_2(mac, 0x0690); if (!(value & 0x0100)) break; DELAY(10); } if (phy->rf_ver == 0x2050 && phy->rf_rev <= 0x5) BWN_RF_WRITE(mac, 0x0051, 0x0037); } static void bwn_ram_write(struct bwn_mac *mac, uint16_t offset, uint32_t val) { uint32_t macctl; KASSERT(offset % 4 == 0, ("%s:%d: fail", __func__, __LINE__)); macctl = BWN_READ_4(mac, BWN_MACCTL); if (macctl & BWN_MACCTL_BIGENDIAN) printf("TODO: need swap\n"); BWN_WRITE_4(mac, BWN_RAM_CONTROL, offset); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); BWN_WRITE_4(mac, BWN_RAM_DATA, val); } static void bwn_lo_write(struct bwn_mac *mac, struct bwn_loctl *ctl) { uint16_t value; KASSERT(mac->mac_phy.type == BWN_PHYTYPE_G, ("%s:%d: fail", __func__, __LINE__)); value = (uint8_t) (ctl->q); value |= ((uint8_t) (ctl->i)) << 8; BWN_PHY_WRITE(mac, BWN_PHY_LO_CTL, value); } static uint16_t bwn_lo_calcfeed(struct bwn_mac *mac, uint16_t lna, uint16_t pga, uint16_t trsw_rx) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; uint16_t rfover; uint16_t feedthrough; if (phy->gmode) { lna <<= BWN_PHY_RFOVERVAL_LNA_SHIFT; pga <<= BWN_PHY_RFOVERVAL_PGA_SHIFT; KASSERT((lna & ~BWN_PHY_RFOVERVAL_LNA) == 0, ("%s:%d: fail", __func__, __LINE__)); KASSERT((pga & ~BWN_PHY_RFOVERVAL_PGA) == 0, ("%s:%d: fail", __func__, __LINE__)); trsw_rx &= (BWN_PHY_RFOVERVAL_TRSWRX | BWN_PHY_RFOVERVAL_BW); rfover = BWN_PHY_RFOVERVAL_UNK | pga | lna | trsw_rx; if ((siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA) && phy->rev > 6) rfover |= BWN_PHY_RFOVERVAL_EXTLNA; BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xe300); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, rfover); DELAY(10); rfover |= BWN_PHY_RFOVERVAL_BW_LBW; BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, rfover); DELAY(10); rfover |= BWN_PHY_RFOVERVAL_BW_LPF; BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, rfover); DELAY(10); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xf300); } else { pga |= BWN_PHY_PGACTL_UNKNOWN; BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, pga); DELAY(10); pga |= BWN_PHY_PGACTL_LOWBANDW; BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, pga); DELAY(10); pga |= BWN_PHY_PGACTL_LPF; BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, pga); } DELAY(21); feedthrough = BWN_PHY_READ(mac, BWN_PHY_LO_LEAKAGE); return (feedthrough); } static uint16_t bwn_lo_txctl_regtable(struct bwn_mac *mac, uint16_t *value, uint16_t *pad_mix_gain) { struct bwn_phy *phy = &mac->mac_phy; uint16_t reg, v, padmix; if (phy->type == BWN_PHYTYPE_B) { v = 0x30; if (phy->rf_rev <= 5) { reg = 0x43; padmix = 0; } else { reg = 0x52; padmix = 5; } } else { if (phy->rev >= 2 && phy->rf_rev == 8) { reg = 0x43; v = 0x10; padmix = 2; } else { reg = 0x52; v = 0x30; padmix = 5; } } if (value) *value = v; if (pad_mix_gain) *pad_mix_gain = padmix; return (reg); } static void bwn_lo_measure_txctl_values(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; uint16_t reg, mask; uint16_t trsw_rx, pga; uint16_t rf_pctl_reg; static const uint8_t tx_bias_values[] = { 0x09, 0x08, 0x0a, 0x01, 0x00, 0x02, 0x05, 0x04, 0x06, }; static const uint8_t tx_magn_values[] = { 0x70, 0x40, }; if (!BWN_HAS_LOOPBACK(phy)) { rf_pctl_reg = 6; trsw_rx = 2; pga = 0; } else { int lb_gain; trsw_rx = 0; lb_gain = pg->pg_max_lb_gain / 2; if (lb_gain > 10) { rf_pctl_reg = 0; pga = abs(10 - lb_gain) / 6; pga = MIN(MAX(pga, 0), 15); } else { int cmp_val; int tmp; pga = 0; cmp_val = 0x24; if ((phy->rev >= 2) && (phy->rf_ver == 0x2050) && (phy->rf_rev == 8)) cmp_val = 0x3c; tmp = lb_gain; if ((10 - lb_gain) < cmp_val) tmp = (10 - lb_gain); if (tmp < 0) tmp += 6; else tmp += 3; cmp_val /= 4; tmp /= 4; if (tmp >= cmp_val) rf_pctl_reg = cmp_val; else rf_pctl_reg = tmp; } } BWN_RF_SETMASK(mac, 0x43, 0xfff0, rf_pctl_reg); bwn_phy_g_set_bbatt(mac, 2); reg = bwn_lo_txctl_regtable(mac, &mask, NULL); mask = ~mask; BWN_RF_MASK(mac, reg, mask); if (BWN_HAS_TXMAG(phy)) { int i, j; int feedthrough; int min_feedth = 0xffff; uint8_t tx_magn, tx_bias; for (i = 0; i < N(tx_magn_values); i++) { tx_magn = tx_magn_values[i]; BWN_RF_SETMASK(mac, 0x52, 0xff0f, tx_magn); for (j = 0; j < N(tx_bias_values); j++) { tx_bias = tx_bias_values[j]; BWN_RF_SETMASK(mac, 0x52, 0xfff0, tx_bias); feedthrough = bwn_lo_calcfeed(mac, 0, pga, trsw_rx); if (feedthrough < min_feedth) { lo->tx_bias = tx_bias; lo->tx_magn = tx_magn; min_feedth = feedthrough; } if (lo->tx_bias == 0) break; } BWN_RF_WRITE(mac, 0x52, (BWN_RF_READ(mac, 0x52) & 0xff00) | lo->tx_bias | lo-> tx_magn); } } else { lo->tx_magn = 0; lo->tx_bias = 0; BWN_RF_MASK(mac, 0x52, 0xfff0); } BWN_GETTIME(lo->txctl_measured_time); } static void bwn_lo_get_powervector(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; int i; uint64_t tmp; uint64_t power_vector = 0; for (i = 0; i < 8; i += 2) { tmp = bwn_shm_read_2(mac, BWN_SHARED, 0x310 + i); power_vector |= (tmp << (i * 8)); bwn_shm_write_2(mac, BWN_SHARED, 0x310 + i, 0); } if (power_vector) lo->power_vector = power_vector; BWN_GETTIME(lo->pwr_vec_read_time); } static void bwn_lo_measure_gain_values(struct bwn_mac *mac, int16_t max_rx_gain, int use_trsw_rx) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; uint16_t tmp; if (max_rx_gain < 0) max_rx_gain = 0; if (BWN_HAS_LOOPBACK(phy)) { int trsw_rx = 0; int trsw_rx_gain; if (use_trsw_rx) { trsw_rx_gain = pg->pg_trsw_rx_gain / 2; if (max_rx_gain >= trsw_rx_gain) { trsw_rx_gain = max_rx_gain - trsw_rx_gain; trsw_rx = 0x20; } } else trsw_rx_gain = max_rx_gain; if (trsw_rx_gain < 9) { pg->pg_lna_lod_gain = 0; } else { pg->pg_lna_lod_gain = 1; trsw_rx_gain -= 8; } trsw_rx_gain = MIN(MAX(trsw_rx_gain, 0), 0x2d); pg->pg_pga_gain = trsw_rx_gain / 3; if (pg->pg_pga_gain >= 5) { pg->pg_pga_gain -= 5; pg->pg_lna_gain = 2; } else pg->pg_lna_gain = 0; } else { pg->pg_lna_gain = 0; pg->pg_trsw_rx_gain = 0x20; if (max_rx_gain >= 0x14) { pg->pg_lna_lod_gain = 1; pg->pg_pga_gain = 2; } else if (max_rx_gain >= 0x12) { pg->pg_lna_lod_gain = 1; pg->pg_pga_gain = 1; } else if (max_rx_gain >= 0xf) { pg->pg_lna_lod_gain = 1; pg->pg_pga_gain = 0; } else { pg->pg_lna_lod_gain = 0; pg->pg_pga_gain = 0; } } tmp = BWN_RF_READ(mac, 0x7a); if (pg->pg_lna_lod_gain == 0) tmp &= ~0x0008; else tmp |= 0x0008; BWN_RF_WRITE(mac, 0x7a, tmp); } static void bwn_lo_save(struct bwn_mac *mac, struct bwn_lo_g_value *sav) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; struct timespec ts; uint16_t tmp; if (bwn_has_hwpctl(mac)) { sav->phy_lomask = BWN_PHY_READ(mac, BWN_PHY_LO_MASK); sav->phy_extg = BWN_PHY_READ(mac, BWN_PHY_EXTG(0x01)); sav->phy_dacctl_hwpctl = BWN_PHY_READ(mac, BWN_PHY_DACCTL); sav->phy_cck4 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x14)); sav->phy_hpwr_tssictl = BWN_PHY_READ(mac, BWN_PHY_HPWR_TSSICTL); BWN_PHY_SET(mac, BWN_PHY_HPWR_TSSICTL, 0x100); BWN_PHY_SET(mac, BWN_PHY_EXTG(0x01), 0x40); BWN_PHY_SET(mac, BWN_PHY_DACCTL, 0x40); BWN_PHY_SET(mac, BWN_PHY_CCK(0x14), 0x200); } if (phy->type == BWN_PHYTYPE_B && phy->rf_ver == 0x2050 && phy->rf_rev < 6) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x16), 0x410); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x17), 0x820); } if (phy->rev >= 2) { sav->phy_analogover = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVER); sav->phy_analogoverval = BWN_PHY_READ(mac, BWN_PHY_ANALOGOVERVAL); sav->phy_rfover = BWN_PHY_READ(mac, BWN_PHY_RFOVER); sav->phy_rfoverval = BWN_PHY_READ(mac, BWN_PHY_RFOVERVAL); sav->phy_classctl = BWN_PHY_READ(mac, BWN_PHY_CLASSCTL); sav->phy_cck3 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x3e)); sav->phy_crs0 = BWN_PHY_READ(mac, BWN_PHY_CRS0); BWN_PHY_MASK(mac, BWN_PHY_CLASSCTL, 0xfffc); BWN_PHY_MASK(mac, BWN_PHY_CRS0, 0x7fff); BWN_PHY_SET(mac, BWN_PHY_ANALOGOVER, 0x0003); BWN_PHY_MASK(mac, BWN_PHY_ANALOGOVERVAL, 0xfffc); if (phy->type == BWN_PHYTYPE_G) { if ((phy->rev >= 7) && (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA)) { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, 0x933); } else { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, 0x133); } } else { BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, 0); } BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x3e), 0); } sav->reg0 = BWN_READ_2(mac, 0x3f4); sav->reg1 = BWN_READ_2(mac, 0x3e2); sav->rf0 = BWN_RF_READ(mac, 0x43); sav->rf1 = BWN_RF_READ(mac, 0x7a); sav->phy_pgactl = BWN_PHY_READ(mac, BWN_PHY_PGACTL); sav->phy_cck2 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x2a)); sav->phy_syncctl = BWN_PHY_READ(mac, BWN_PHY_SYNCCTL); sav->phy_dacctl = BWN_PHY_READ(mac, BWN_PHY_DACCTL); if (!BWN_HAS_TXMAG(phy)) { sav->rf2 = BWN_RF_READ(mac, 0x52); sav->rf2 &= 0x00f0; } if (phy->type == BWN_PHYTYPE_B) { sav->phy_cck0 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x30)); sav->phy_cck1 = BWN_PHY_READ(mac, BWN_PHY_CCK(0x06)); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x30), 0x00ff); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x06), 0x3f3f); } else { BWN_WRITE_2(mac, 0x3e2, BWN_READ_2(mac, 0x3e2) | 0x8000); } BWN_WRITE_2(mac, 0x3f4, BWN_READ_2(mac, 0x3f4) & 0xf000); tmp = (phy->type == BWN_PHYTYPE_G) ? BWN_PHY_LO_MASK : BWN_PHY_CCK(0x2e); BWN_PHY_WRITE(mac, tmp, 0x007f); tmp = sav->phy_syncctl; BWN_PHY_WRITE(mac, BWN_PHY_SYNCCTL, tmp & 0xff7f); tmp = sav->rf1; BWN_RF_WRITE(mac, 0x007a, tmp & 0xfff0); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2a), 0x8a3); if (phy->type == BWN_PHYTYPE_G || (phy->type == BWN_PHYTYPE_B && phy->rf_ver == 0x2050 && phy->rf_rev >= 6)) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2b), 0x1003); } else BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2b), 0x0802); if (phy->rev >= 2) bwn_dummy_transmission(mac, 0, 1); bwn_phy_g_switch_chan(mac, 6, 0); BWN_RF_READ(mac, 0x51); if (phy->type == BWN_PHYTYPE_G) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2f), 0); nanouptime(&ts); if (time_before(lo->txctl_measured_time, (ts.tv_nsec / 1000000 + ts.tv_sec * 1000) - BWN_LO_TXCTL_EXPIRE)) bwn_lo_measure_txctl_values(mac); if (phy->type == BWN_PHYTYPE_G && phy->rev >= 3) BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0xc078); else { if (phy->type == BWN_PHYTYPE_B) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2e), 0x8078); else BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, 0x8078); } } static void bwn_lo_restore(struct bwn_mac *mac, struct bwn_lo_g_value *sav) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; uint16_t tmp; if (phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, 0xe300); tmp = (pg->pg_pga_gain << 8); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, tmp | 0xa0); DELAY(5); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, tmp | 0xa2); DELAY(2); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, tmp | 0xa3); } else { tmp = (pg->pg_pga_gain | 0xefa0); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, tmp); } if (phy->type == BWN_PHYTYPE_G) { if (phy->rev >= 3) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2e), 0xc078); else BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2e), 0x8078); if (phy->rev >= 2) BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2f), 0x0202); else BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2f), 0x0101); } BWN_WRITE_2(mac, 0x3f4, sav->reg0); BWN_PHY_WRITE(mac, BWN_PHY_PGACTL, sav->phy_pgactl); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x2a), sav->phy_cck2); BWN_PHY_WRITE(mac, BWN_PHY_SYNCCTL, sav->phy_syncctl); BWN_PHY_WRITE(mac, BWN_PHY_DACCTL, sav->phy_dacctl); BWN_RF_WRITE(mac, 0x43, sav->rf0); BWN_RF_WRITE(mac, 0x7a, sav->rf1); if (!BWN_HAS_TXMAG(phy)) { tmp = sav->rf2; BWN_RF_SETMASK(mac, 0x52, 0xff0f, tmp); } BWN_WRITE_2(mac, 0x3e2, sav->reg1); if (phy->type == BWN_PHYTYPE_B && phy->rf_ver == 0x2050 && phy->rf_rev <= 5) { BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x30), sav->phy_cck0); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x06), sav->phy_cck1); } if (phy->rev >= 2) { BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVER, sav->phy_analogover); BWN_PHY_WRITE(mac, BWN_PHY_ANALOGOVERVAL, sav->phy_analogoverval); BWN_PHY_WRITE(mac, BWN_PHY_CLASSCTL, sav->phy_classctl); BWN_PHY_WRITE(mac, BWN_PHY_RFOVER, sav->phy_rfover); BWN_PHY_WRITE(mac, BWN_PHY_RFOVERVAL, sav->phy_rfoverval); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x3e), sav->phy_cck3); BWN_PHY_WRITE(mac, BWN_PHY_CRS0, sav->phy_crs0); } if (bwn_has_hwpctl(mac)) { tmp = (sav->phy_lomask & 0xbfff); BWN_PHY_WRITE(mac, BWN_PHY_LO_MASK, tmp); BWN_PHY_WRITE(mac, BWN_PHY_EXTG(0x01), sav->phy_extg); BWN_PHY_WRITE(mac, BWN_PHY_DACCTL, sav->phy_dacctl_hwpctl); BWN_PHY_WRITE(mac, BWN_PHY_CCK(0x14), sav->phy_cck4); BWN_PHY_WRITE(mac, BWN_PHY_HPWR_TSSICTL, sav->phy_hpwr_tssictl); } bwn_phy_g_switch_chan(mac, sav->old_channel, 1); } static int bwn_lo_probe_loctl(struct bwn_mac *mac, struct bwn_loctl *probe, struct bwn_lo_g_sm *d) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_loctl orig, test; struct bwn_loctl prev = { -100, -100 }; static const struct bwn_loctl modifiers[] = { { 1, 1,}, { 1, 0,}, { 1, -1,}, { 0, -1,}, { -1, -1,}, { -1, 0,}, { -1, 1,}, { 0, 1,} }; int begin, end, lower = 0, i; uint16_t feedth; if (d->curstate == 0) { begin = 1; end = 8; } else if (d->curstate % 2 == 0) { begin = d->curstate - 1; end = d->curstate + 1; } else { begin = d->curstate - 2; end = d->curstate + 2; } if (begin < 1) begin += 8; if (end > 8) end -= 8; memcpy(&orig, probe, sizeof(struct bwn_loctl)); i = begin; d->curstate = i; while (1) { KASSERT(i >= 1 && i <= 8, ("%s:%d: fail", __func__, __LINE__)); memcpy(&test, &orig, sizeof(struct bwn_loctl)); test.i += modifiers[i - 1].i * d->multipler; test.q += modifiers[i - 1].q * d->multipler; if ((test.i != prev.i || test.q != prev.q) && (abs(test.i) <= 16 && abs(test.q) <= 16)) { bwn_lo_write(mac, &test); feedth = bwn_lo_calcfeed(mac, pg->pg_lna_gain, pg->pg_pga_gain, pg->pg_trsw_rx_gain); if (feedth < d->feedth) { memcpy(probe, &test, sizeof(struct bwn_loctl)); lower = 1; d->feedth = feedth; if (d->nmeasure < 2 && !BWN_HAS_LOOPBACK(phy)) break; } } memcpy(&prev, &test, sizeof(prev)); if (i == end) break; if (i == 8) i = 1; else i++; d->curstate = i; } return (lower); } static void bwn_lo_probe_sm(struct bwn_mac *mac, struct bwn_loctl *loctl, int *rxgain) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_lo_g_sm d; struct bwn_loctl probe; int lower, repeat, cnt = 0; uint16_t feedth; d.nmeasure = 0; d.multipler = 1; if (BWN_HAS_LOOPBACK(phy)) d.multipler = 3; memcpy(&d.loctl, loctl, sizeof(struct bwn_loctl)); repeat = (BWN_HAS_LOOPBACK(phy)) ? 4 : 1; do { bwn_lo_write(mac, &d.loctl); feedth = bwn_lo_calcfeed(mac, pg->pg_lna_gain, pg->pg_pga_gain, pg->pg_trsw_rx_gain); if (feedth < 0x258) { if (feedth >= 0x12c) *rxgain += 6; else *rxgain += 3; feedth = bwn_lo_calcfeed(mac, pg->pg_lna_gain, pg->pg_pga_gain, pg->pg_trsw_rx_gain); } d.feedth = feedth; d.curstate = 0; do { KASSERT(d.curstate >= 0 && d.curstate <= 8, ("%s:%d: fail", __func__, __LINE__)); memcpy(&probe, &d.loctl, sizeof(struct bwn_loctl)); lower = bwn_lo_probe_loctl(mac, &probe, &d); if (!lower) break; if ((probe.i == d.loctl.i) && (probe.q == d.loctl.q)) break; memcpy(&d.loctl, &probe, sizeof(struct bwn_loctl)); d.nmeasure++; } while (d.nmeasure < 24); memcpy(loctl, &d.loctl, sizeof(struct bwn_loctl)); if (BWN_HAS_LOOPBACK(phy)) { if (d.feedth > 0x1194) *rxgain -= 6; else if (d.feedth < 0x5dc) *rxgain += 3; if (cnt == 0) { if (d.feedth <= 0x5dc) { d.multipler = 1; cnt++; } else d.multipler = 2; } else if (cnt == 2) d.multipler = 1; } bwn_lo_measure_gain_values(mac, *rxgain, BWN_HAS_LOOPBACK(phy)); } while (++cnt < repeat); } static struct bwn_lo_calib * bwn_lo_calibset(struct bwn_mac *mac, const struct bwn_bbatt *bbatt, const struct bwn_rfatt *rfatt) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_loctl loctl = { 0, 0 }; struct bwn_lo_calib *cal; struct bwn_lo_g_value sval = { 0 }; int rxgain; uint16_t pad, reg, value; sval.old_channel = phy->chan; bwn_mac_suspend(mac); bwn_lo_save(mac, &sval); reg = bwn_lo_txctl_regtable(mac, &value, &pad); BWN_RF_SETMASK(mac, 0x43, 0xfff0, rfatt->att); BWN_RF_SETMASK(mac, reg, ~value, (rfatt->padmix ? value :0)); rxgain = (rfatt->att * 2) + (bbatt->att / 2); if (rfatt->padmix) rxgain -= pad; if (BWN_HAS_LOOPBACK(phy)) rxgain += pg->pg_max_lb_gain; bwn_lo_measure_gain_values(mac, rxgain, BWN_HAS_LOOPBACK(phy)); bwn_phy_g_set_bbatt(mac, bbatt->att); bwn_lo_probe_sm(mac, &loctl, &rxgain); bwn_lo_restore(mac, &sval); bwn_mac_enable(mac); cal = malloc(sizeof(*cal), M_DEVBUF, M_NOWAIT | M_ZERO); if (!cal) { device_printf(mac->mac_sc->sc_dev, "out of memory\n"); return (NULL); } memcpy(&cal->bbatt, bbatt, sizeof(*bbatt)); memcpy(&cal->rfatt, rfatt, sizeof(*rfatt)); memcpy(&cal->ctl, &loctl, sizeof(loctl)); BWN_GETTIME(cal->calib_time); return (cal); } static struct bwn_lo_calib * bwn_lo_get_calib(struct bwn_mac *mac, const struct bwn_bbatt *bbatt, const struct bwn_rfatt *rfatt) { struct bwn_txpwr_loctl *lo = &mac->mac_phy.phy_g.pg_loctl; struct bwn_lo_calib *c; TAILQ_FOREACH(c, &lo->calib_list, list) { if (!BWN_BBATTCMP(&c->bbatt, bbatt)) continue; if (!BWN_RFATTCMP(&c->rfatt, rfatt)) continue; return (c); } c = bwn_lo_calibset(mac, bbatt, rfatt); if (!c) return (NULL); TAILQ_INSERT_TAIL(&lo->calib_list, c, list); return (c); } static void bwn_phy_g_dc_lookup_init(struct bwn_mac *mac, uint8_t update) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; const struct bwn_rfatt *rfatt; const struct bwn_bbatt *bbatt; uint64_t pvector; int i; int rf_offset, bb_offset; uint8_t changed = 0; KASSERT(BWN_DC_LT_SIZE == 32, ("%s:%d: fail", __func__, __LINE__)); KASSERT(lo->rfatt.len * lo->bbatt.len <= 64, ("%s:%d: fail", __func__, __LINE__)); pvector = lo->power_vector; if (!update && !pvector) return; bwn_mac_suspend(mac); for (i = 0; i < BWN_DC_LT_SIZE * 2; i++) { struct bwn_lo_calib *cal; int idx; uint16_t val; if (!update && !(pvector & (((uint64_t)1ULL) << i))) continue; bb_offset = i / lo->rfatt.len; rf_offset = i % lo->rfatt.len; bbatt = &(lo->bbatt.array[bb_offset]); rfatt = &(lo->rfatt.array[rf_offset]); cal = bwn_lo_calibset(mac, bbatt, rfatt); if (!cal) { device_printf(sc->sc_dev, "LO: Could not " "calibrate DC table entry\n"); continue; } val = (uint8_t)(cal->ctl.q); val |= ((uint8_t)(cal->ctl.i)) << 4; free(cal, M_DEVBUF); idx = i / 2; if (i % 2) lo->dc_lt[idx] = (lo->dc_lt[idx] & 0x00ff) | ((val & 0x00ff) << 8); else lo->dc_lt[idx] = (lo->dc_lt[idx] & 0xff00) | (val & 0x00ff); changed = 1; } if (changed) { for (i = 0; i < BWN_DC_LT_SIZE; i++) BWN_PHY_WRITE(mac, 0x3a0 + i, lo->dc_lt[i]); } bwn_mac_enable(mac); } static void bwn_lo_fixup_rfatt(struct bwn_rfatt *rf) { if (!rf->padmix) return; if ((rf->att != 1) && (rf->att != 2) && (rf->att != 3)) rf->att = 4; } static void bwn_lo_g_adjust(struct bwn_mac *mac) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; struct bwn_lo_calib *cal; struct bwn_rfatt rf; memcpy(&rf, &pg->pg_rfatt, sizeof(rf)); bwn_lo_fixup_rfatt(&rf); cal = bwn_lo_get_calib(mac, &pg->pg_bbatt, &rf); if (!cal) return; bwn_lo_write(mac, &cal->ctl); } static void bwn_lo_g_init(struct bwn_mac *mac) { if (!bwn_has_hwpctl(mac)) return; bwn_lo_get_powervector(mac); bwn_phy_g_dc_lookup_init(mac, 1); } static void bwn_mac_suspend(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int i; uint32_t tmp; KASSERT(mac->mac_suspended >= 0, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_suspended == 0) { bwn_psctl(mac, BWN_PS_AWAKE); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_ON); BWN_READ_4(mac, BWN_MACCTL); for (i = 35; i; i--) { tmp = BWN_READ_4(mac, BWN_INTR_REASON); if (tmp & BWN_INTR_MAC_SUSPENDED) goto out; DELAY(10); } for (i = 40; i; i--) { tmp = BWN_READ_4(mac, BWN_INTR_REASON); if (tmp & BWN_INTR_MAC_SUSPENDED) goto out; DELAY(1000); } device_printf(sc->sc_dev, "MAC suspend failed\n"); } out: mac->mac_suspended++; } static void bwn_mac_enable(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t state; state = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODESTAT); if (state != BWN_SHARED_UCODESTAT_SUSPEND && state != BWN_SHARED_UCODESTAT_SLEEP) device_printf(sc->sc_dev, "warn: firmware state (%d)\n", state); mac->mac_suspended--; KASSERT(mac->mac_suspended >= 0, ("%s:%d: fail", __func__, __LINE__)); if (mac->mac_suspended == 0) { BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_ON); BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_MAC_SUSPENDED); BWN_READ_4(mac, BWN_MACCTL); BWN_READ_4(mac, BWN_INTR_REASON); bwn_psctl(mac, 0); } } static void bwn_psctl(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; int i; uint16_t ucstat; KASSERT(!((flags & BWN_PS_ON) && (flags & BWN_PS_OFF)), ("%s:%d: fail", __func__, __LINE__)); KASSERT(!((flags & BWN_PS_AWAKE) && (flags & BWN_PS_ASLEEP)), ("%s:%d: fail", __func__, __LINE__)); /* XXX forcibly awake and hwps-off */ BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_AWAKE) & ~BWN_MACCTL_HWPS); BWN_READ_4(mac, BWN_MACCTL); if (siba_get_revid(sc->sc_dev) >= 5) { for (i = 0; i < 100; i++) { ucstat = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODESTAT); if (ucstat != BWN_SHARED_UCODESTAT_SLEEP) break; DELAY(10); } } } static int16_t bwn_nrssi_read(struct bwn_mac *mac, uint16_t offset) { BWN_PHY_WRITE(mac, BWN_PHY_NRSSI_CTRL, offset); return ((int16_t)BWN_PHY_READ(mac, BWN_PHY_NRSSI_DATA)); } static void bwn_nrssi_threshold(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; int32_t a, b; int16_t tmp16; uint16_t tmpu16; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s: fail", __func__)); if (phy->gmode && (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_RSSI)) { if (!pg->pg_aci_wlan_automatic && pg->pg_aci_enable) { a = 0x13; b = 0x12; } else { a = 0xe; b = 0x11; } a = a * (pg->pg_nrssi[1] - pg->pg_nrssi[0]); a += (pg->pg_nrssi[0] << 6); a += (a < 32) ? 31 : 32; a = a >> 6; a = MIN(MAX(a, -31), 31); b = b * (pg->pg_nrssi[1] - pg->pg_nrssi[0]); b += (pg->pg_nrssi[0] << 6); if (b < 32) b += 31; else b += 32; b = b >> 6; b = MIN(MAX(b, -31), 31); tmpu16 = BWN_PHY_READ(mac, 0x048a) & 0xf000; tmpu16 |= ((uint32_t)b & 0x0000003f); tmpu16 |= (((uint32_t)a & 0x0000003f) << 6); BWN_PHY_WRITE(mac, 0x048a, tmpu16); return; } tmp16 = bwn_nrssi_read(mac, 0x20); if (tmp16 >= 0x20) tmp16 -= 0x40; BWN_PHY_SETMASK(mac, 0x048a, 0xf000, (tmp16 < 3) ? 0x09eb : 0x0aed); } static void bwn_nrssi_slope_11g(struct bwn_mac *mac) { #define SAVE_RF_MAX 3 #define SAVE_PHY_COMM_MAX 4 #define SAVE_PHY3_MAX 8 static const uint16_t save_rf_regs[SAVE_RF_MAX] = { 0x7a, 0x52, 0x43 }; static const uint16_t save_phy_comm_regs[SAVE_PHY_COMM_MAX] = { 0x15, 0x5a, 0x59, 0x58 }; static const uint16_t save_phy3_regs[SAVE_PHY3_MAX] = { 0x002e, 0x002f, 0x080f, BWN_PHY_G_LOCTL, 0x0801, 0x0060, 0x0014, 0x0478 }; struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; int32_t i, tmp32, phy3_idx = 0; uint16_t delta, tmp; uint16_t save_rf[SAVE_RF_MAX]; uint16_t save_phy_comm[SAVE_PHY_COMM_MAX]; uint16_t save_phy3[SAVE_PHY3_MAX]; uint16_t ant_div, phy0, chan_ex; int16_t nrssi0, nrssi1; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s:%d: fail", __func__, __LINE__)); if (phy->rf_rev >= 9) return; if (phy->rf_rev == 8) bwn_nrssi_offset(mac); BWN_PHY_MASK(mac, BWN_PHY_G_CRS, 0x7fff); BWN_PHY_MASK(mac, 0x0802, 0xfffc); /* * Save RF/PHY registers for later restoration */ ant_div = BWN_READ_2(mac, 0x03e2); BWN_WRITE_2(mac, 0x03e2, BWN_READ_2(mac, 0x03e2) | 0x8000); for (i = 0; i < SAVE_RF_MAX; ++i) save_rf[i] = BWN_RF_READ(mac, save_rf_regs[i]); for (i = 0; i < SAVE_PHY_COMM_MAX; ++i) save_phy_comm[i] = BWN_PHY_READ(mac, save_phy_comm_regs[i]); phy0 = BWN_READ_2(mac, BWN_PHY0); chan_ex = BWN_READ_2(mac, BWN_CHANNEL_EXT); if (phy->rev >= 3) { for (i = 0; i < SAVE_PHY3_MAX; ++i) save_phy3[i] = BWN_PHY_READ(mac, save_phy3_regs[i]); BWN_PHY_WRITE(mac, 0x002e, 0); BWN_PHY_WRITE(mac, BWN_PHY_G_LOCTL, 0); switch (phy->rev) { case 4: case 6: case 7: BWN_PHY_SET(mac, 0x0478, 0x0100); BWN_PHY_SET(mac, 0x0801, 0x0040); break; case 3: case 5: BWN_PHY_MASK(mac, 0x0801, 0xffbf); break; } BWN_PHY_SET(mac, 0x0060, 0x0040); BWN_PHY_SET(mac, 0x0014, 0x0200); } /* * Calculate nrssi0 */ BWN_RF_SET(mac, 0x007a, 0x0070); bwn_set_all_gains(mac, 0, 8, 0); BWN_RF_MASK(mac, 0x007a, 0x00f7); if (phy->rev >= 2) { BWN_PHY_SETMASK(mac, 0x0811, 0xffcf, 0x0030); BWN_PHY_SETMASK(mac, 0x0812, 0xffcf, 0x0010); } BWN_RF_SET(mac, 0x007a, 0x0080); DELAY(20); nrssi0 = (int16_t) ((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); if (nrssi0 >= 0x0020) nrssi0 -= 0x0040; /* * Calculate nrssi1 */ BWN_RF_MASK(mac, 0x007a, 0x007f); if (phy->rev >= 2) BWN_PHY_SETMASK(mac, 0x0003, 0xff9f, 0x0040); BWN_WRITE_2(mac, BWN_CHANNEL_EXT, BWN_READ_2(mac, BWN_CHANNEL_EXT) | 0x2000); BWN_RF_SET(mac, 0x007a, 0x000f); BWN_PHY_WRITE(mac, 0x0015, 0xf330); if (phy->rev >= 2) { BWN_PHY_SETMASK(mac, 0x0812, 0xffcf, 0x0020); BWN_PHY_SETMASK(mac, 0x0811, 0xffcf, 0x0020); } bwn_set_all_gains(mac, 3, 0, 1); if (phy->rf_rev == 8) { BWN_RF_WRITE(mac, 0x0043, 0x001f); } else { tmp = BWN_RF_READ(mac, 0x0052) & 0xff0f; BWN_RF_WRITE(mac, 0x0052, tmp | 0x0060); tmp = BWN_RF_READ(mac, 0x0043) & 0xfff0; BWN_RF_WRITE(mac, 0x0043, tmp | 0x0009); } BWN_PHY_WRITE(mac, 0x005a, 0x0480); BWN_PHY_WRITE(mac, 0x0059, 0x0810); BWN_PHY_WRITE(mac, 0x0058, 0x000d); DELAY(20); nrssi1 = (int16_t) ((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); /* * Install calculated narrow RSSI values */ if (nrssi1 >= 0x0020) nrssi1 -= 0x0040; if (nrssi0 == nrssi1) pg->pg_nrssi_slope = 0x00010000; else pg->pg_nrssi_slope = 0x00400000 / (nrssi0 - nrssi1); if (nrssi0 >= -4) { pg->pg_nrssi[0] = nrssi1; pg->pg_nrssi[1] = nrssi0; } /* * Restore saved RF/PHY registers */ if (phy->rev >= 3) { for (phy3_idx = 0; phy3_idx < 4; ++phy3_idx) { BWN_PHY_WRITE(mac, save_phy3_regs[phy3_idx], save_phy3[phy3_idx]); } } if (phy->rev >= 2) { BWN_PHY_MASK(mac, 0x0812, 0xffcf); BWN_PHY_MASK(mac, 0x0811, 0xffcf); } for (i = 0; i < SAVE_RF_MAX; ++i) BWN_RF_WRITE(mac, save_rf_regs[i], save_rf[i]); BWN_WRITE_2(mac, 0x03e2, ant_div); BWN_WRITE_2(mac, 0x03e6, phy0); BWN_WRITE_2(mac, BWN_CHANNEL_EXT, chan_ex); for (i = 0; i < SAVE_PHY_COMM_MAX; ++i) BWN_PHY_WRITE(mac, save_phy_comm_regs[i], save_phy_comm[i]); bwn_spu_workaround(mac, phy->chan); BWN_PHY_SET(mac, 0x0802, (0x0001 | 0x0002)); bwn_set_original_gains(mac); BWN_PHY_SET(mac, BWN_PHY_G_CRS, 0x8000); if (phy->rev >= 3) { for (; phy3_idx < SAVE_PHY3_MAX; ++phy3_idx) { BWN_PHY_WRITE(mac, save_phy3_regs[phy3_idx], save_phy3[phy3_idx]); } } delta = 0x1f - pg->pg_nrssi[0]; for (i = 0; i < 64; i++) { tmp32 = (((i - delta) * pg->pg_nrssi_slope) / 0x10000) + 0x3a; tmp32 = MIN(MAX(tmp32, 0), 0x3f); pg->pg_nrssi_lt[i] = tmp32; } bwn_nrssi_threshold(mac); #undef SAVE_RF_MAX #undef SAVE_PHY_COMM_MAX #undef SAVE_PHY3_MAX } static void bwn_nrssi_offset(struct bwn_mac *mac) { #define SAVE_RF_MAX 2 #define SAVE_PHY_COMM_MAX 10 #define SAVE_PHY6_MAX 8 static const uint16_t save_rf_regs[SAVE_RF_MAX] = { 0x7a, 0x43 }; static const uint16_t save_phy_comm_regs[SAVE_PHY_COMM_MAX] = { 0x0001, 0x0811, 0x0812, 0x0814, 0x0815, 0x005a, 0x0059, 0x0058, 0x000a, 0x0003 }; static const uint16_t save_phy6_regs[SAVE_PHY6_MAX] = { 0x002e, 0x002f, 0x080f, 0x0810, 0x0801, 0x0060, 0x0014, 0x0478 }; struct bwn_phy *phy = &mac->mac_phy; int i, phy6_idx = 0; uint16_t save_rf[SAVE_RF_MAX]; uint16_t save_phy_comm[SAVE_PHY_COMM_MAX]; uint16_t save_phy6[SAVE_PHY6_MAX]; int16_t nrssi; uint16_t saved = 0xffff; for (i = 0; i < SAVE_PHY_COMM_MAX; ++i) save_phy_comm[i] = BWN_PHY_READ(mac, save_phy_comm_regs[i]); for (i = 0; i < SAVE_RF_MAX; ++i) save_rf[i] = BWN_RF_READ(mac, save_rf_regs[i]); BWN_PHY_MASK(mac, 0x0429, 0x7fff); BWN_PHY_SETMASK(mac, 0x0001, 0x3fff, 0x4000); BWN_PHY_SET(mac, 0x0811, 0x000c); BWN_PHY_SETMASK(mac, 0x0812, 0xfff3, 0x0004); BWN_PHY_MASK(mac, 0x0802, ~(0x1 | 0x2)); if (phy->rev >= 6) { for (i = 0; i < SAVE_PHY6_MAX; ++i) save_phy6[i] = BWN_PHY_READ(mac, save_phy6_regs[i]); BWN_PHY_WRITE(mac, 0x002e, 0); BWN_PHY_WRITE(mac, 0x002f, 0); BWN_PHY_WRITE(mac, 0x080f, 0); BWN_PHY_WRITE(mac, 0x0810, 0); BWN_PHY_SET(mac, 0x0478, 0x0100); BWN_PHY_SET(mac, 0x0801, 0x0040); BWN_PHY_SET(mac, 0x0060, 0x0040); BWN_PHY_SET(mac, 0x0014, 0x0200); } BWN_RF_SET(mac, 0x007a, 0x0070); BWN_RF_SET(mac, 0x007a, 0x0080); DELAY(30); nrssi = (int16_t) ((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); if (nrssi >= 0x20) nrssi -= 0x40; if (nrssi == 31) { for (i = 7; i >= 4; i--) { BWN_RF_WRITE(mac, 0x007b, i); DELAY(20); nrssi = (int16_t) ((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); if (nrssi >= 0x20) nrssi -= 0x40; if (nrssi < 31 && saved == 0xffff) saved = i; } if (saved == 0xffff) saved = 4; } else { BWN_RF_MASK(mac, 0x007a, 0x007f); if (phy->rev != 1) { BWN_PHY_SET(mac, 0x0814, 0x0001); BWN_PHY_MASK(mac, 0x0815, 0xfffe); } BWN_PHY_SET(mac, 0x0811, 0x000c); BWN_PHY_SET(mac, 0x0812, 0x000c); BWN_PHY_SET(mac, 0x0811, 0x0030); BWN_PHY_SET(mac, 0x0812, 0x0030); BWN_PHY_WRITE(mac, 0x005a, 0x0480); BWN_PHY_WRITE(mac, 0x0059, 0x0810); BWN_PHY_WRITE(mac, 0x0058, 0x000d); if (phy->rev == 0) BWN_PHY_WRITE(mac, 0x0003, 0x0122); else BWN_PHY_SET(mac, 0x000a, 0x2000); if (phy->rev != 1) { BWN_PHY_SET(mac, 0x0814, 0x0004); BWN_PHY_MASK(mac, 0x0815, 0xfffb); } BWN_PHY_SETMASK(mac, 0x0003, 0xff9f, 0x0040); BWN_RF_SET(mac, 0x007a, 0x000f); bwn_set_all_gains(mac, 3, 0, 1); BWN_RF_SETMASK(mac, 0x0043, 0x00f0, 0x000f); DELAY(30); nrssi = (int16_t) ((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); if (nrssi >= 0x20) nrssi -= 0x40; if (nrssi == -32) { for (i = 0; i < 4; i++) { BWN_RF_WRITE(mac, 0x007b, i); DELAY(20); nrssi = (int16_t)((BWN_PHY_READ(mac, 0x047f) >> 8) & 0x003f); if (nrssi >= 0x20) nrssi -= 0x40; if (nrssi > -31 && saved == 0xffff) saved = i; } if (saved == 0xffff) saved = 3; } else saved = 0; } BWN_RF_WRITE(mac, 0x007b, saved); /* * Restore saved RF/PHY registers */ if (phy->rev >= 6) { for (phy6_idx = 0; phy6_idx < 4; ++phy6_idx) { BWN_PHY_WRITE(mac, save_phy6_regs[phy6_idx], save_phy6[phy6_idx]); } } if (phy->rev != 1) { for (i = 3; i < 5; i++) BWN_PHY_WRITE(mac, save_phy_comm_regs[i], save_phy_comm[i]); } for (i = 5; i < SAVE_PHY_COMM_MAX; i++) BWN_PHY_WRITE(mac, save_phy_comm_regs[i], save_phy_comm[i]); for (i = SAVE_RF_MAX - 1; i >= 0; --i) BWN_RF_WRITE(mac, save_rf_regs[i], save_rf[i]); BWN_PHY_WRITE(mac, 0x0802, BWN_PHY_READ(mac, 0x0802) | 0x1 | 0x2); BWN_PHY_SET(mac, 0x0429, 0x8000); bwn_set_original_gains(mac); if (phy->rev >= 6) { for (; phy6_idx < SAVE_PHY6_MAX; ++phy6_idx) { BWN_PHY_WRITE(mac, save_phy6_regs[phy6_idx], save_phy6[phy6_idx]); } } BWN_PHY_WRITE(mac, save_phy_comm_regs[0], save_phy_comm[0]); BWN_PHY_WRITE(mac, save_phy_comm_regs[2], save_phy_comm[2]); BWN_PHY_WRITE(mac, save_phy_comm_regs[1], save_phy_comm[1]); } static void bwn_set_all_gains(struct bwn_mac *mac, int16_t first, int16_t second, int16_t third) { struct bwn_phy *phy = &mac->mac_phy; uint16_t i; uint16_t start = 0x08, end = 0x18; uint16_t tmp; uint16_t table; if (phy->rev <= 1) { start = 0x10; end = 0x20; } table = BWN_OFDMTAB_GAINX; if (phy->rev <= 1) table = BWN_OFDMTAB_GAINX_R1; for (i = 0; i < 4; i++) bwn_ofdmtab_write_2(mac, table, i, first); for (i = start; i < end; i++) bwn_ofdmtab_write_2(mac, table, i, second); if (third != -1) { tmp = ((uint16_t) third << 14) | ((uint16_t) third << 6); BWN_PHY_SETMASK(mac, 0x04a0, 0xbfbf, tmp); BWN_PHY_SETMASK(mac, 0x04a1, 0xbfbf, tmp); BWN_PHY_SETMASK(mac, 0x04a2, 0xbfbf, tmp); } bwn_dummy_transmission(mac, 0, 1); } static void bwn_set_original_gains(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; uint16_t i, tmp; uint16_t table; uint16_t start = 0x0008, end = 0x0018; if (phy->rev <= 1) { start = 0x0010; end = 0x0020; } table = BWN_OFDMTAB_GAINX; if (phy->rev <= 1) table = BWN_OFDMTAB_GAINX_R1; for (i = 0; i < 4; i++) { tmp = (i & 0xfffc); tmp |= (i & 0x0001) << 1; tmp |= (i & 0x0002) >> 1; bwn_ofdmtab_write_2(mac, table, i, tmp); } for (i = start; i < end; i++) bwn_ofdmtab_write_2(mac, table, i, i - start); BWN_PHY_SETMASK(mac, 0x04a0, 0xbfbf, 0x4040); BWN_PHY_SETMASK(mac, 0x04a1, 0xbfbf, 0x4040); BWN_PHY_SETMASK(mac, 0x04a2, 0xbfbf, 0x4000); bwn_dummy_transmission(mac, 0, 1); } static void bwn_phy_hwpctl_init(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_rfatt old_rfatt, rfatt; struct bwn_bbatt old_bbatt, bbatt; struct bwn_softc *sc = mac->mac_sc; uint8_t old_txctl = 0; KASSERT(phy->type == BWN_PHYTYPE_G, ("%s:%d: fail", __func__, __LINE__)); if ((siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM) && (siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BU4306)) return; BWN_PHY_WRITE(mac, 0x0028, 0x8018); BWN_WRITE_2(mac, BWN_PHY0, BWN_READ_2(mac, BWN_PHY0) & 0xffdf); if (!phy->gmode) return; bwn_hwpctl_early_init(mac); if (pg->pg_curtssi == 0) { if (phy->rf_ver == 0x2050 && phy->analog == 0) { BWN_RF_SETMASK(mac, 0x0076, 0x00f7, 0x0084); } else { memcpy(&old_rfatt, &pg->pg_rfatt, sizeof(old_rfatt)); memcpy(&old_bbatt, &pg->pg_bbatt, sizeof(old_bbatt)); old_txctl = pg->pg_txctl; bbatt.att = 11; if (phy->rf_rev == 8) { rfatt.att = 15; rfatt.padmix = 1; } else { rfatt.att = 9; rfatt.padmix = 0; } bwn_phy_g_set_txpwr_sub(mac, &bbatt, &rfatt, 0); } bwn_dummy_transmission(mac, 0, 1); pg->pg_curtssi = BWN_PHY_READ(mac, BWN_PHY_TSSI); if (phy->rf_ver == 0x2050 && phy->analog == 0) BWN_RF_MASK(mac, 0x0076, 0xff7b); else bwn_phy_g_set_txpwr_sub(mac, &old_bbatt, &old_rfatt, old_txctl); } bwn_hwpctl_init_gphy(mac); /* clear TSSI */ bwn_shm_write_2(mac, BWN_SHARED, 0x0058, 0x7f7f); bwn_shm_write_2(mac, BWN_SHARED, 0x005a, 0x7f7f); bwn_shm_write_2(mac, BWN_SHARED, 0x0070, 0x7f7f); bwn_shm_write_2(mac, BWN_SHARED, 0x0072, 0x7f7f); } static void bwn_hwpctl_early_init(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; if (!bwn_has_hwpctl(mac)) { BWN_PHY_WRITE(mac, 0x047a, 0xc111); return; } BWN_PHY_MASK(mac, 0x0036, 0xfeff); BWN_PHY_WRITE(mac, 0x002f, 0x0202); BWN_PHY_SET(mac, 0x047c, 0x0002); BWN_PHY_SET(mac, 0x047a, 0xf000); if (phy->rf_ver == 0x2050 && phy->rf_rev == 8) { BWN_PHY_SETMASK(mac, 0x047a, 0xff0f, 0x0010); BWN_PHY_SET(mac, 0x005d, 0x8000); BWN_PHY_SETMASK(mac, 0x004e, 0xffc0, 0x0010); BWN_PHY_WRITE(mac, 0x002e, 0xc07f); BWN_PHY_SET(mac, 0x0036, 0x0400); } else { BWN_PHY_SET(mac, 0x0036, 0x0200); BWN_PHY_SET(mac, 0x0036, 0x0400); BWN_PHY_MASK(mac, 0x005d, 0x7fff); BWN_PHY_MASK(mac, 0x004f, 0xfffe); BWN_PHY_SETMASK(mac, 0x004e, 0xffc0, 0x0010); BWN_PHY_WRITE(mac, 0x002e, 0xc07f); BWN_PHY_SETMASK(mac, 0x047a, 0xff0f, 0x0010); } } static void bwn_hwpctl_init_gphy(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; int i; uint16_t nr_written = 0, tmp, value; uint8_t rf, bb; if (!bwn_has_hwpctl(mac)) { bwn_hf_write(mac, bwn_hf_read(mac) & ~BWN_HF_HW_POWERCTL); return; } BWN_PHY_SETMASK(mac, 0x0036, 0xffc0, (pg->pg_idletssi - pg->pg_curtssi)); BWN_PHY_SETMASK(mac, 0x0478, 0xff00, (pg->pg_idletssi - pg->pg_curtssi)); for (i = 0; i < 32; i++) bwn_ofdmtab_write_2(mac, 0x3c20, i, pg->pg_tssi2dbm[i]); for (i = 32; i < 64; i++) bwn_ofdmtab_write_2(mac, 0x3c00, i - 32, pg->pg_tssi2dbm[i]); for (i = 0; i < 64; i += 2) { value = (uint16_t) pg->pg_tssi2dbm[i]; value |= ((uint16_t) pg->pg_tssi2dbm[i + 1]) << 8; BWN_PHY_WRITE(mac, 0x380 + (i / 2), value); } for (rf = 0; rf < lo->rfatt.len; rf++) { for (bb = 0; bb < lo->bbatt.len; bb++) { if (nr_written >= 0x40) return; tmp = lo->bbatt.array[bb].att; tmp <<= 8; if (phy->rf_rev == 8) tmp |= 0x50; else tmp |= 0x40; tmp |= lo->rfatt.array[rf].att; BWN_PHY_WRITE(mac, 0x3c0 + nr_written, tmp); nr_written++; } } BWN_PHY_MASK(mac, 0x0060, 0xffbf); BWN_PHY_WRITE(mac, 0x0014, 0x0000); KASSERT(phy->rev >= 6, ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_SET(mac, 0x0478, 0x0800); BWN_PHY_MASK(mac, 0x0478, 0xfeff); BWN_PHY_MASK(mac, 0x0801, 0xffbf); bwn_phy_g_dc_lookup_init(mac, 1); bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_HW_POWERCTL); } static void bwn_phy_g_switch_chan(struct bwn_mac *mac, int channel, uint8_t spu) { struct bwn_softc *sc = mac->mac_sc; if (spu != 0) bwn_spu_workaround(mac, channel); BWN_WRITE_2(mac, BWN_CHANNEL, bwn_phy_g_chan2freq(channel)); if (channel == 14) { if (siba_sprom_get_ccode(sc->sc_dev) == SIBA_CCODE_JAPAN) bwn_hf_write(mac, bwn_hf_read(mac) & ~BWN_HF_JAPAN_CHAN14_OFF); else bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_JAPAN_CHAN14_OFF); BWN_WRITE_2(mac, BWN_CHANNEL_EXT, BWN_READ_2(mac, BWN_CHANNEL_EXT) | (1 << 11)); return; } BWN_WRITE_2(mac, BWN_CHANNEL_EXT, BWN_READ_2(mac, BWN_CHANNEL_EXT) & 0xf7bf); } static uint16_t bwn_phy_g_chan2freq(uint8_t channel) { static const uint8_t bwn_phy_g_rf_channels[] = BWN_PHY_G_RF_CHANNELS; KASSERT(channel >= 1 && channel <= 14, ("%s:%d: fail", __func__, __LINE__)); return (bwn_phy_g_rf_channels[channel - 1]); } static void bwn_phy_g_set_txpwr_sub(struct bwn_mac *mac, const struct bwn_bbatt *bbatt, const struct bwn_rfatt *rfatt, uint8_t txctl) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_txpwr_loctl *lo = &pg->pg_loctl; uint16_t bb, rf; uint16_t tx_bias, tx_magn; bb = bbatt->att; rf = rfatt->att; tx_bias = lo->tx_bias; tx_magn = lo->tx_magn; if (tx_bias == 0xff) tx_bias = 0; pg->pg_txctl = txctl; memmove(&pg->pg_rfatt, rfatt, sizeof(*rfatt)); pg->pg_rfatt.padmix = (txctl & BWN_TXCTL_TXMIX) ? 1 : 0; memmove(&pg->pg_bbatt, bbatt, sizeof(*bbatt)); bwn_phy_g_set_bbatt(mac, bb); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_RADIO_ATT, rf); if (phy->rf_ver == 0x2050 && phy->rf_rev == 8) BWN_RF_WRITE(mac, 0x43, (rf & 0x000f) | (txctl & 0x0070)); else { BWN_RF_SETMASK(mac, 0x43, 0xfff0, (rf & 0x000f)); BWN_RF_SETMASK(mac, 0x52, ~0x0070, (txctl & 0x0070)); } if (BWN_HAS_TXMAG(phy)) BWN_RF_WRITE(mac, 0x52, tx_magn | tx_bias); else BWN_RF_SETMASK(mac, 0x52, 0xfff0, (tx_bias & 0x000f)); bwn_lo_g_adjust(mac); } static void bwn_phy_g_set_bbatt(struct bwn_mac *mac, uint16_t bbatt) { struct bwn_phy *phy = &mac->mac_phy; if (phy->analog == 0) { BWN_WRITE_2(mac, BWN_PHY0, (BWN_READ_2(mac, BWN_PHY0) & 0xfff0) | bbatt); return; } if (phy->analog > 1) { BWN_PHY_SETMASK(mac, BWN_PHY_DACCTL, 0xffc3, bbatt << 2); return; } BWN_PHY_SETMASK(mac, BWN_PHY_DACCTL, 0xff87, bbatt << 3); } static uint16_t bwn_rf_2050_rfoverval(struct bwn_mac *mac, uint16_t reg, uint32_t lpd) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_g *pg = &phy->phy_g; struct bwn_softc *sc = mac->mac_sc; int max_lb_gain; uint16_t extlna; uint16_t i; if (phy->gmode == 0) return (0); if (BWN_HAS_LOOPBACK(phy)) { max_lb_gain = pg->pg_max_lb_gain; max_lb_gain += (phy->rf_rev == 8) ? 0x3e : 0x26; if (max_lb_gain >= 0x46) { extlna = 0x3000; max_lb_gain -= 0x46; } else if (max_lb_gain >= 0x3a) { extlna = 0x1000; max_lb_gain -= 0x3a; } else if (max_lb_gain >= 0x2e) { extlna = 0x2000; max_lb_gain -= 0x2e; } else { extlna = 0; max_lb_gain -= 0x10; } for (i = 0; i < 16; i++) { max_lb_gain -= (i * 6); if (max_lb_gain < 6) break; } if ((phy->rev < 7) || !(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA)) { if (reg == BWN_PHY_RFOVER) { return (0x1b3); } else if (reg == BWN_PHY_RFOVERVAL) { extlna |= (i << 8); switch (lpd) { case BWN_LPD(0, 1, 1): return (0x0f92); case BWN_LPD(0, 0, 1): case BWN_LPD(1, 0, 1): return (0x0092 | extlna); case BWN_LPD(1, 0, 0): return (0x0093 | extlna); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } else { if (reg == BWN_PHY_RFOVER) return (0x9b3); if (reg == BWN_PHY_RFOVERVAL) { if (extlna) extlna |= 0x8000; extlna |= (i << 8); switch (lpd) { case BWN_LPD(0, 1, 1): return (0x8f92); case BWN_LPD(0, 0, 1): return (0x8092 | extlna); case BWN_LPD(1, 0, 1): return (0x2092 | extlna); case BWN_LPD(1, 0, 0): return (0x2093 | extlna); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } return (0); } if ((phy->rev < 7) || !(siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_EXTLNA)) { if (reg == BWN_PHY_RFOVER) { return (0x1b3); } else if (reg == BWN_PHY_RFOVERVAL) { switch (lpd) { case BWN_LPD(0, 1, 1): return (0x0fb2); case BWN_LPD(0, 0, 1): return (0x00b2); case BWN_LPD(1, 0, 1): return (0x30b2); case BWN_LPD(1, 0, 0): return (0x30b3); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } else { if (reg == BWN_PHY_RFOVER) { return (0x9b3); } else if (reg == BWN_PHY_RFOVERVAL) { switch (lpd) { case BWN_LPD(0, 1, 1): return (0x8fb2); case BWN_LPD(0, 0, 1): return (0x80b2); case BWN_LPD(1, 0, 1): return (0x20b2); case BWN_LPD(1, 0, 0): return (0x20b3); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } return (0); } static void bwn_spu_workaround(struct bwn_mac *mac, uint8_t channel) { if (mac->mac_phy.rf_ver != 0x2050 || mac->mac_phy.rf_rev >= 6) return; BWN_WRITE_2(mac, BWN_CHANNEL, (channel <= 10) ? bwn_phy_g_chan2freq(channel + 4) : bwn_phy_g_chan2freq(1)); DELAY(1000); BWN_WRITE_2(mac, BWN_CHANNEL, bwn_phy_g_chan2freq(channel)); } static int bwn_fw_gets(struct bwn_mac *mac, enum bwn_fwtype type) { struct bwn_softc *sc = mac->mac_sc; struct bwn_fw *fw = &mac->mac_fw; const uint8_t rev = siba_get_revid(sc->sc_dev); const char *filename; uint32_t high; int error; /* microcode */ if (rev >= 5 && rev <= 10) filename = "ucode5"; else if (rev >= 11 && rev <= 12) filename = "ucode11"; else if (rev == 13) filename = "ucode13"; else if (rev == 14) filename = "ucode14"; else if (rev >= 15) filename = "ucode15"; else { device_printf(sc->sc_dev, "no ucode for rev %d\n", rev); bwn_release_firmware(mac); return (EOPNOTSUPP); } error = bwn_fw_get(mac, type, filename, &fw->ucode); if (error) { bwn_release_firmware(mac); return (error); } /* PCM */ KASSERT(fw->no_pcmfile == 0, ("%s:%d fail", __func__, __LINE__)); if (rev >= 5 && rev <= 10) { error = bwn_fw_get(mac, type, "pcm5", &fw->pcm); if (error == ENOENT) fw->no_pcmfile = 1; else if (error) { bwn_release_firmware(mac); return (error); } } else if (rev < 11) { device_printf(sc->sc_dev, "no PCM for rev %d\n", rev); return (EOPNOTSUPP); } /* initvals */ high = siba_read_4(sc->sc_dev, SIBA_TGSHIGH); switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: if (rev < 5 || rev > 10) goto fail1; if (high & BWN_TGSHIGH_HAVE_2GHZ) filename = "a0g1initvals5"; else filename = "a0g0initvals5"; break; case BWN_PHYTYPE_G: if (rev >= 5 && rev <= 10) filename = "b0g0initvals5"; else if (rev >= 13) filename = "b0g0initvals13"; else goto fail1; break; case BWN_PHYTYPE_LP: if (rev == 13) filename = "lp0initvals13"; else if (rev == 14) filename = "lp0initvals14"; else if (rev >= 15) filename = "lp0initvals15"; else goto fail1; break; case BWN_PHYTYPE_N: if (rev >= 11 && rev <= 12) filename = "n0initvals11"; else goto fail1; break; default: goto fail1; } error = bwn_fw_get(mac, type, filename, &fw->initvals); if (error) { bwn_release_firmware(mac); return (error); } /* bandswitch initvals */ switch (mac->mac_phy.type) { case BWN_PHYTYPE_A: if (rev >= 5 && rev <= 10) { if (high & BWN_TGSHIGH_HAVE_2GHZ) filename = "a0g1bsinitvals5"; else filename = "a0g0bsinitvals5"; } else if (rev >= 11) filename = NULL; else goto fail1; break; case BWN_PHYTYPE_G: if (rev >= 5 && rev <= 10) filename = "b0g0bsinitvals5"; else if (rev >= 11) filename = NULL; else goto fail1; break; case BWN_PHYTYPE_LP: if (rev == 13) filename = "lp0bsinitvals13"; else if (rev == 14) filename = "lp0bsinitvals14"; else if (rev >= 15) filename = "lp0bsinitvals15"; else goto fail1; break; case BWN_PHYTYPE_N: if (rev >= 11 && rev <= 12) filename = "n0bsinitvals11"; else goto fail1; break; default: goto fail1; } error = bwn_fw_get(mac, type, filename, &fw->initvals_band); if (error) { bwn_release_firmware(mac); return (error); } return (0); fail1: device_printf(sc->sc_dev, "no INITVALS for rev %d\n", rev); bwn_release_firmware(mac); return (EOPNOTSUPP); } static int bwn_fw_get(struct bwn_mac *mac, enum bwn_fwtype type, const char *name, struct bwn_fwfile *bfw) { const struct bwn_fwhdr *hdr; struct bwn_softc *sc = mac->mac_sc; const struct firmware *fw; char namebuf[64]; if (name == NULL) { bwn_do_release_fw(bfw); return (0); } if (bfw->filename != NULL) { if (bfw->type == type && (strcmp(bfw->filename, name) == 0)) return (0); bwn_do_release_fw(bfw); } snprintf(namebuf, sizeof(namebuf), "bwn%s_v4_%s%s", (type == BWN_FWTYPE_OPENSOURCE) ? "-open" : "", (mac->mac_phy.type == BWN_PHYTYPE_LP) ? "lp_" : "", name); /* XXX Sleeping on "fwload" with the non-sleepable locks held */ fw = firmware_get(namebuf); if (fw == NULL) { device_printf(sc->sc_dev, "the fw file(%s) not found\n", namebuf); return (ENOENT); } if (fw->datasize < sizeof(struct bwn_fwhdr)) goto fail; hdr = (const struct bwn_fwhdr *)(fw->data); switch (hdr->type) { case BWN_FWTYPE_UCODE: case BWN_FWTYPE_PCM: if (be32toh(hdr->size) != (fw->datasize - sizeof(struct bwn_fwhdr))) goto fail; /* FALLTHROUGH */ case BWN_FWTYPE_IV: if (hdr->ver != 1) goto fail; break; default: goto fail; } bfw->filename = name; bfw->fw = fw; bfw->type = type; return (0); fail: device_printf(sc->sc_dev, "the fw file(%s) format error\n", namebuf); if (fw != NULL) firmware_put(fw, FIRMWARE_UNLOAD); return (EPROTO); } static void bwn_release_firmware(struct bwn_mac *mac) { bwn_do_release_fw(&mac->mac_fw.ucode); bwn_do_release_fw(&mac->mac_fw.pcm); bwn_do_release_fw(&mac->mac_fw.initvals); bwn_do_release_fw(&mac->mac_fw.initvals_band); } static void bwn_do_release_fw(struct bwn_fwfile *bfw) { if (bfw->fw != NULL) firmware_put(bfw->fw, FIRMWARE_UNLOAD); bfw->fw = NULL; bfw->filename = NULL; } static int bwn_fw_loaducode(struct bwn_mac *mac) { #define GETFWOFFSET(fwp, offset) \ ((const uint32_t *)((const char *)fwp.fw->data + offset)) #define GETFWSIZE(fwp, offset) \ ((fwp.fw->datasize - offset) / sizeof(uint32_t)) struct bwn_softc *sc = mac->mac_sc; const uint32_t *data; unsigned int i; uint32_t ctl; uint16_t date, fwcaps, time; int error = 0; ctl = BWN_READ_4(mac, BWN_MACCTL); ctl |= BWN_MACCTL_MCODE_JMP0; KASSERT(!(ctl & BWN_MACCTL_MCODE_RUN), ("%s:%d: fail", __func__, __LINE__)); BWN_WRITE_4(mac, BWN_MACCTL, ctl); for (i = 0; i < 64; i++) bwn_shm_write_2(mac, BWN_SCRATCH, i, 0); for (i = 0; i < 4096; i += 2) bwn_shm_write_2(mac, BWN_SHARED, i, 0); data = GETFWOFFSET(mac->mac_fw.ucode, sizeof(struct bwn_fwhdr)); bwn_shm_ctlword(mac, BWN_UCODE | BWN_SHARED_AUTOINC, 0x0000); for (i = 0; i < GETFWSIZE(mac->mac_fw.ucode, sizeof(struct bwn_fwhdr)); i++) { BWN_WRITE_4(mac, BWN_SHM_DATA, be32toh(data[i])); DELAY(10); } if (mac->mac_fw.pcm.fw) { data = GETFWOFFSET(mac->mac_fw.pcm, sizeof(struct bwn_fwhdr)); bwn_shm_ctlword(mac, BWN_HW, 0x01ea); BWN_WRITE_4(mac, BWN_SHM_DATA, 0x00004000); bwn_shm_ctlword(mac, BWN_HW, 0x01eb); for (i = 0; i < GETFWSIZE(mac->mac_fw.pcm, sizeof(struct bwn_fwhdr)); i++) { BWN_WRITE_4(mac, BWN_SHM_DATA, be32toh(data[i])); DELAY(10); } } BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_ALL); BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_MCODE_JMP0) | BWN_MACCTL_MCODE_RUN); for (i = 0; i < 21; i++) { if (BWN_READ_4(mac, BWN_INTR_REASON) == BWN_INTR_MAC_SUSPENDED) break; if (i >= 20) { device_printf(sc->sc_dev, "ucode timeout\n"); error = ENXIO; goto error; } DELAY(50000); } BWN_READ_4(mac, BWN_INTR_REASON); mac->mac_fw.rev = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_REV); if (mac->mac_fw.rev <= 0x128) { device_printf(sc->sc_dev, "the firmware is too old\n"); error = EOPNOTSUPP; goto error; } mac->mac_fw.patch = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_PATCH); date = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_DATE); mac->mac_fw.opensource = (date == 0xffff); if (bwn_wme != 0) mac->mac_flags |= BWN_MAC_FLAG_WME; mac->mac_flags |= BWN_MAC_FLAG_HWCRYPTO; time = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_UCODE_TIME); if (mac->mac_fw.opensource == 0) { device_printf(sc->sc_dev, "firmware version (rev %u patch %u date %#x time %#x)\n", mac->mac_fw.rev, mac->mac_fw.patch, date, time); if (mac->mac_fw.no_pcmfile) device_printf(sc->sc_dev, "no HW crypto acceleration due to pcm5\n"); } else { mac->mac_fw.patch = time; fwcaps = bwn_fwcaps_read(mac); if (!(fwcaps & BWN_FWCAPS_HWCRYPTO) || mac->mac_fw.no_pcmfile) { device_printf(sc->sc_dev, "disabling HW crypto acceleration\n"); mac->mac_flags &= ~BWN_MAC_FLAG_HWCRYPTO; } if (!(fwcaps & BWN_FWCAPS_WME)) { device_printf(sc->sc_dev, "disabling WME support\n"); mac->mac_flags &= ~BWN_MAC_FLAG_WME; } } if (BWN_ISOLDFMT(mac)) device_printf(sc->sc_dev, "using old firmware image\n"); return (0); error: BWN_WRITE_4(mac, BWN_MACCTL, (BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_MCODE_RUN) | BWN_MACCTL_MCODE_JMP0); return (error); #undef GETFWSIZE #undef GETFWOFFSET } /* OpenFirmware only */ static uint16_t bwn_fwcaps_read(struct bwn_mac *mac) { KASSERT(mac->mac_fw.opensource == 1, ("%s:%d: fail", __func__, __LINE__)); return (bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_FWCAPS)); } static int bwn_fwinitvals_write(struct bwn_mac *mac, const struct bwn_fwinitvals *ivals, size_t count, size_t array_size) { #define GET_NEXTIV16(iv) \ ((const struct bwn_fwinitvals *)((const uint8_t *)(iv) + \ sizeof(uint16_t) + sizeof(uint16_t))) #define GET_NEXTIV32(iv) \ ((const struct bwn_fwinitvals *)((const uint8_t *)(iv) + \ sizeof(uint16_t) + sizeof(uint32_t))) struct bwn_softc *sc = mac->mac_sc; const struct bwn_fwinitvals *iv; uint16_t offset; size_t i; uint8_t bit32; KASSERT(sizeof(struct bwn_fwinitvals) == 6, ("%s:%d: fail", __func__, __LINE__)); iv = ivals; for (i = 0; i < count; i++) { if (array_size < sizeof(iv->offset_size)) goto fail; array_size -= sizeof(iv->offset_size); offset = be16toh(iv->offset_size); bit32 = (offset & BWN_FWINITVALS_32BIT) ? 1 : 0; offset &= BWN_FWINITVALS_OFFSET_MASK; if (offset >= 0x1000) goto fail; if (bit32) { if (array_size < sizeof(iv->data.d32)) goto fail; array_size -= sizeof(iv->data.d32); BWN_WRITE_4(mac, offset, be32toh(iv->data.d32)); iv = GET_NEXTIV32(iv); } else { if (array_size < sizeof(iv->data.d16)) goto fail; array_size -= sizeof(iv->data.d16); BWN_WRITE_2(mac, offset, be16toh(iv->data.d16)); iv = GET_NEXTIV16(iv); } } if (array_size != 0) goto fail; return (0); fail: device_printf(sc->sc_dev, "initvals: invalid format\n"); return (EPROTO); #undef GET_NEXTIV16 #undef GET_NEXTIV32 } static int bwn_switch_channel(struct bwn_mac *mac, int chan) { struct bwn_phy *phy = &(mac->mac_phy); struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint16_t channelcookie, savedcookie; int error; if (chan == 0xffff) chan = phy->get_default_chan(mac); channelcookie = chan; if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) channelcookie |= 0x100; savedcookie = bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_CHAN); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_CHAN, channelcookie); error = phy->switch_channel(mac, chan); if (error) goto fail; mac->mac_phy.chan = chan; DELAY(8000); return (0); fail: device_printf(sc->sc_dev, "failed to switch channel\n"); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_CHAN, savedcookie); return (error); } static uint16_t bwn_ant2phy(int antenna) { switch (antenna) { case BWN_ANT0: return (BWN_TX_PHY_ANT0); case BWN_ANT1: return (BWN_TX_PHY_ANT1); case BWN_ANT2: return (BWN_TX_PHY_ANT2); case BWN_ANT3: return (BWN_TX_PHY_ANT3); case BWN_ANTAUTO: return (BWN_TX_PHY_ANT01AUTO); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static void bwn_wme_load(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int i; KASSERT(N(bwn_wme_shm_offsets) == N(sc->sc_wmeParams), ("%s:%d: fail", __func__, __LINE__)); bwn_mac_suspend(mac); for (i = 0; i < N(sc->sc_wmeParams); i++) bwn_wme_loadparams(mac, &(sc->sc_wmeParams[i]), bwn_wme_shm_offsets[i]); bwn_mac_enable(mac); } static void bwn_wme_loadparams(struct bwn_mac *mac, const struct wmeParams *p, uint16_t shm_offset) { #define SM(_v, _f) (((_v) << _f##_S) & _f) struct bwn_softc *sc = mac->mac_sc; uint16_t params[BWN_NR_WMEPARAMS]; int slot, tmp; unsigned int i; slot = BWN_READ_2(mac, BWN_RNG) & SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); memset(¶ms, 0, sizeof(params)); DPRINTF(sc, BWN_DEBUG_WME, "wmep_txopLimit %d wmep_logcwmin %d " "wmep_logcwmax %d wmep_aifsn %d\n", p->wmep_txopLimit, p->wmep_logcwmin, p->wmep_logcwmax, p->wmep_aifsn); params[BWN_WMEPARAM_TXOP] = p->wmep_txopLimit * 32; params[BWN_WMEPARAM_CWMIN] = SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); params[BWN_WMEPARAM_CWMAX] = SM(p->wmep_logcwmax, WME_PARAM_LOGCWMAX); params[BWN_WMEPARAM_CWCUR] = SM(p->wmep_logcwmin, WME_PARAM_LOGCWMIN); params[BWN_WMEPARAM_AIFS] = p->wmep_aifsn; params[BWN_WMEPARAM_BSLOTS] = slot; params[BWN_WMEPARAM_REGGAP] = slot + p->wmep_aifsn; for (i = 0; i < N(params); i++) { if (i == BWN_WMEPARAM_STATUS) { tmp = bwn_shm_read_2(mac, BWN_SHARED, shm_offset + (i * 2)); tmp |= 0x100; bwn_shm_write_2(mac, BWN_SHARED, shm_offset + (i * 2), tmp); } else { bwn_shm_write_2(mac, BWN_SHARED, shm_offset + (i * 2), params[i]); } } } static void bwn_mac_write_bssid(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint32_t tmp; int i; uint8_t mac_bssid[IEEE80211_ADDR_LEN * 2]; bwn_mac_setfilter(mac, BWN_MACFILTER_BSSID, sc->sc_bssid); memcpy(mac_bssid, sc->sc_macaddr, IEEE80211_ADDR_LEN); memcpy(mac_bssid + IEEE80211_ADDR_LEN, sc->sc_bssid, IEEE80211_ADDR_LEN); for (i = 0; i < N(mac_bssid); i += sizeof(uint32_t)) { tmp = (uint32_t) (mac_bssid[i + 0]); tmp |= (uint32_t) (mac_bssid[i + 1]) << 8; tmp |= (uint32_t) (mac_bssid[i + 2]) << 16; tmp |= (uint32_t) (mac_bssid[i + 3]) << 24; bwn_ram_write(mac, 0x20 + i, tmp); } } static void bwn_mac_setfilter(struct bwn_mac *mac, uint16_t offset, const uint8_t *macaddr) { static const uint8_t zero[IEEE80211_ADDR_LEN] = { 0 }; uint16_t data; if (!mac) macaddr = zero; offset |= 0x0020; BWN_WRITE_2(mac, BWN_MACFILTER_CONTROL, offset); data = macaddr[0]; data |= macaddr[1] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); data = macaddr[2]; data |= macaddr[3] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); data = macaddr[4]; data |= macaddr[5] << 8; BWN_WRITE_2(mac, BWN_MACFILTER_DATA, data); } static void bwn_key_dowrite(struct bwn_mac *mac, uint8_t index, uint8_t algorithm, const uint8_t *key, size_t key_len, const uint8_t *mac_addr) { uint8_t buf[BWN_SEC_KEYSIZE] = { 0, }; uint8_t per_sta_keys_start = 8; if (BWN_SEC_NEWAPI(mac)) per_sta_keys_start = 4; KASSERT(index < mac->mac_max_nr_keys, ("%s:%d: fail", __func__, __LINE__)); KASSERT(key_len <= BWN_SEC_KEYSIZE, ("%s:%d: fail", __func__, __LINE__)); if (index >= per_sta_keys_start) bwn_key_macwrite(mac, index, NULL); if (key) memcpy(buf, key, key_len); bwn_key_write(mac, index, algorithm, buf); if (index >= per_sta_keys_start) bwn_key_macwrite(mac, index, mac_addr); mac->mac_key[index].algorithm = algorithm; } static void bwn_key_macwrite(struct bwn_mac *mac, uint8_t index, const uint8_t *addr) { struct bwn_softc *sc = mac->mac_sc; uint32_t addrtmp[2] = { 0, 0 }; uint8_t start = 8; if (BWN_SEC_NEWAPI(mac)) start = 4; KASSERT(index >= start, ("%s:%d: fail", __func__, __LINE__)); index -= start; if (addr) { addrtmp[0] = addr[0]; addrtmp[0] |= ((uint32_t) (addr[1]) << 8); addrtmp[0] |= ((uint32_t) (addr[2]) << 16); addrtmp[0] |= ((uint32_t) (addr[3]) << 24); addrtmp[1] = addr[4]; addrtmp[1] |= ((uint32_t) (addr[5]) << 8); } if (siba_get_revid(sc->sc_dev) >= 5) { bwn_shm_write_4(mac, BWN_RCMTA, (index * 2) + 0, addrtmp[0]); bwn_shm_write_2(mac, BWN_RCMTA, (index * 2) + 1, addrtmp[1]); } else { if (index >= 8) { bwn_shm_write_4(mac, BWN_SHARED, BWN_SHARED_PSM + (index * 6) + 0, addrtmp[0]); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PSM + (index * 6) + 4, addrtmp[1]); } } } static void bwn_key_write(struct bwn_mac *mac, uint8_t index, uint8_t algorithm, const uint8_t *key) { unsigned int i; uint32_t offset; uint16_t kidx, value; kidx = BWN_SEC_KEY2FW(mac, index); bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_KEYIDX_BLOCK + (kidx * 2), (kidx << 4) | algorithm); offset = mac->mac_ktp + (index * BWN_SEC_KEYSIZE); for (i = 0; i < BWN_SEC_KEYSIZE; i += 2) { value = key[i]; value |= (uint16_t)(key[i + 1]) << 8; bwn_shm_write_2(mac, BWN_SHARED, offset + i, value); } } static void bwn_phy_exit(struct bwn_mac *mac) { mac->mac_phy.rf_onoff(mac, 0); if (mac->mac_phy.exit != NULL) mac->mac_phy.exit(mac); } static void bwn_dma_free(struct bwn_mac *mac) { struct bwn_dma *dma; if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) return; dma = &mac->mac_method.dma; bwn_dma_ringfree(&dma->rx); bwn_dma_ringfree(&dma->wme[WME_AC_BK]); bwn_dma_ringfree(&dma->wme[WME_AC_BE]); bwn_dma_ringfree(&dma->wme[WME_AC_VI]); bwn_dma_ringfree(&dma->wme[WME_AC_VO]); bwn_dma_ringfree(&dma->mcast); } static void bwn_core_stop(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED) return; callout_stop(&sc->sc_rfswitch_ch); callout_stop(&sc->sc_task_ch); callout_stop(&sc->sc_watchdog_ch); sc->sc_watchdog_timer = 0; BWN_WRITE_4(mac, BWN_INTR_MASK, 0); BWN_READ_4(mac, BWN_INTR_MASK); bwn_mac_suspend(mac); mac->mac_status = BWN_MAC_STATUS_INITED; } static int bwn_switch_band(struct bwn_softc *sc, struct ieee80211_channel *chan) { struct bwn_mac *up_dev = NULL; struct bwn_mac *down_dev; struct bwn_mac *mac; int err, status; uint8_t gmode; BWN_ASSERT_LOCKED(sc); TAILQ_FOREACH(mac, &sc->sc_maclist, mac_list) { if (IEEE80211_IS_CHAN_2GHZ(chan) && mac->mac_phy.supports_2ghz) { up_dev = mac; gmode = 1; } else if (IEEE80211_IS_CHAN_5GHZ(chan) && mac->mac_phy.supports_5ghz) { up_dev = mac; gmode = 0; } else { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (EINVAL); } if (up_dev != NULL) break; } if (up_dev == NULL) { device_printf(sc->sc_dev, "Could not find a device\n"); return (ENODEV); } if (up_dev == sc->sc_curmac && sc->sc_curmac->mac_phy.gmode == gmode) return (0); device_printf(sc->sc_dev, "switching to %s-GHz band\n", IEEE80211_IS_CHAN_2GHZ(chan) ? "2" : "5"); down_dev = sc->sc_curmac; status = down_dev->mac_status; if (status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(down_dev); if (status >= BWN_MAC_STATUS_INITED) bwn_core_exit(down_dev); if (down_dev != up_dev) bwn_phy_reset(down_dev); up_dev->mac_phy.gmode = gmode; if (status >= BWN_MAC_STATUS_INITED) { err = bwn_core_init(up_dev); if (err) { device_printf(sc->sc_dev, "fatal: failed to initialize for %s-GHz\n", IEEE80211_IS_CHAN_2GHZ(chan) ? "2" : "5"); goto fail; } } if (status >= BWN_MAC_STATUS_STARTED) bwn_core_start(up_dev); KASSERT(up_dev->mac_status == status, ("%s: fail", __func__)); sc->sc_curmac = up_dev; return (0); fail: sc->sc_curmac = NULL; return (err); } static void bwn_rf_turnon(struct bwn_mac *mac) { bwn_mac_suspend(mac); mac->mac_phy.rf_onoff(mac, 1); mac->mac_phy.rf_on = 1; bwn_mac_enable(mac); } static void bwn_rf_turnoff(struct bwn_mac *mac) { bwn_mac_suspend(mac); mac->mac_phy.rf_onoff(mac, 0); mac->mac_phy.rf_on = 0; bwn_mac_enable(mac); } static void bwn_phy_reset(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; siba_write_4(sc->sc_dev, SIBA_TGSLOW, ((siba_read_4(sc->sc_dev, SIBA_TGSLOW) & ~BWN_TGSLOW_SUPPORT_G) | BWN_TGSLOW_PHYRESET) | SIBA_TGSLOW_FGC); DELAY(1000); siba_write_4(sc->sc_dev, SIBA_TGSLOW, (siba_read_4(sc->sc_dev, SIBA_TGSLOW) & ~SIBA_TGSLOW_FGC) | BWN_TGSLOW_PHYRESET); DELAY(1000); } static int bwn_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct bwn_vap *bvp = BWN_VAP(vap); struct ieee80211com *ic= vap->iv_ic; struct ifnet *ifp = ic->ic_ifp; enum ieee80211_state ostate = vap->iv_state; struct bwn_softc *sc = ifp->if_softc; struct bwn_mac *mac = sc->sc_curmac; int error; DPRINTF(sc, BWN_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); error = bvp->bv_newstate(vap, nstate, arg); if (error != 0) return (error); BWN_LOCK(sc); bwn_led_newstate(mac, nstate); /* * Clear the BSSID when we stop a STA */ if (vap->iv_opmode == IEEE80211_M_STA) { if (ostate == IEEE80211_S_RUN && nstate != IEEE80211_S_RUN) { /* * Clear out the BSSID. If we reassociate to * the same AP, this will reinialize things * correctly... */ if (ic->ic_opmode == IEEE80211_M_STA && (sc->sc_flags & BWN_FLAG_INVALID) == 0) { memset(sc->sc_bssid, 0, IEEE80211_ADDR_LEN); bwn_set_macaddr(mac); } } } if (vap->iv_opmode == IEEE80211_M_MONITOR || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* XXX nothing to do? */ } else if (nstate == IEEE80211_S_RUN) { memcpy(sc->sc_bssid, vap->iv_bss->ni_bssid, IEEE80211_ADDR_LEN); memcpy(sc->sc_macaddr, IF_LLADDR(ifp), IEEE80211_ADDR_LEN); bwn_set_opmode(mac); bwn_set_pretbtt(mac); bwn_spu_setdelay(mac, 0); bwn_set_macaddr(mac); } BWN_UNLOCK(sc); return (error); } static void bwn_set_pretbtt(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; uint16_t pretbtt; if (ic->ic_opmode == IEEE80211_M_IBSS) pretbtt = 2; else pretbtt = (mac->mac_phy.type == BWN_PHYTYPE_A) ? 120 : 250; bwn_shm_write_2(mac, BWN_SHARED, BWN_SHARED_PRETBTT, pretbtt); BWN_WRITE_2(mac, BWN_TSF_CFP_PRETBTT, pretbtt); } static int bwn_intr(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; uint32_t reason; if (mac->mac_status < BWN_MAC_STATUS_STARTED || (sc->sc_flags & BWN_FLAG_INVALID)) return (FILTER_STRAY); reason = BWN_READ_4(mac, BWN_INTR_REASON); if (reason == 0xffffffff) /* shared IRQ */ return (FILTER_STRAY); reason &= mac->mac_intr_mask; if (reason == 0) return (FILTER_HANDLED); mac->mac_reason[0] = BWN_READ_4(mac, BWN_DMA0_REASON) & 0x0001dc00; mac->mac_reason[1] = BWN_READ_4(mac, BWN_DMA1_REASON) & 0x0000dc00; mac->mac_reason[2] = BWN_READ_4(mac, BWN_DMA2_REASON) & 0x0000dc00; mac->mac_reason[3] = BWN_READ_4(mac, BWN_DMA3_REASON) & 0x0001dc00; mac->mac_reason[4] = BWN_READ_4(mac, BWN_DMA4_REASON) & 0x0000dc00; BWN_WRITE_4(mac, BWN_INTR_REASON, reason); BWN_WRITE_4(mac, BWN_DMA0_REASON, mac->mac_reason[0]); BWN_WRITE_4(mac, BWN_DMA1_REASON, mac->mac_reason[1]); BWN_WRITE_4(mac, BWN_DMA2_REASON, mac->mac_reason[2]); BWN_WRITE_4(mac, BWN_DMA3_REASON, mac->mac_reason[3]); BWN_WRITE_4(mac, BWN_DMA4_REASON, mac->mac_reason[4]); /* Disable interrupts. */ BWN_WRITE_4(mac, BWN_INTR_MASK, 0); mac->mac_reason_intr = reason; BWN_BARRIER(mac, BUS_SPACE_BARRIER_READ); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); taskqueue_enqueue_fast(sc->sc_tq, &mac->mac_intrtask); return (FILTER_HANDLED); } static void bwn_intrtask(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; uint32_t merged = 0; int i, tx = 0, rx = 0; BWN_LOCK(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED || (sc->sc_flags & BWN_FLAG_INVALID)) { BWN_UNLOCK(sc); return; } for (i = 0; i < N(mac->mac_reason); i++) merged |= mac->mac_reason[i]; if (mac->mac_reason_intr & BWN_INTR_MAC_TXERR) device_printf(sc->sc_dev, "MAC trans error\n"); if (mac->mac_reason_intr & BWN_INTR_PHY_TXERR) { DPRINTF(sc, BWN_DEBUG_INTR, "%s: PHY trans error\n", __func__); mac->mac_phy.txerrors--; if (mac->mac_phy.txerrors == 0) { mac->mac_phy.txerrors = BWN_TXERROR_MAX; bwn_restart(mac, "PHY TX errors"); } } if (merged & (BWN_DMAINTR_FATALMASK | BWN_DMAINTR_NONFATALMASK)) { if (merged & BWN_DMAINTR_FATALMASK) { device_printf(sc->sc_dev, "Fatal DMA error: %#x %#x %#x %#x %#x %#x\n", mac->mac_reason[0], mac->mac_reason[1], mac->mac_reason[2], mac->mac_reason[3], mac->mac_reason[4], mac->mac_reason[5]); bwn_restart(mac, "DMA error"); BWN_UNLOCK(sc); return; } if (merged & BWN_DMAINTR_NONFATALMASK) { device_printf(sc->sc_dev, "DMA error: %#x %#x %#x %#x %#x %#x\n", mac->mac_reason[0], mac->mac_reason[1], mac->mac_reason[2], mac->mac_reason[3], mac->mac_reason[4], mac->mac_reason[5]); } } if (mac->mac_reason_intr & BWN_INTR_UCODE_DEBUG) bwn_intr_ucode_debug(mac); if (mac->mac_reason_intr & BWN_INTR_TBTT_INDI) bwn_intr_tbtt_indication(mac); if (mac->mac_reason_intr & BWN_INTR_ATIM_END) bwn_intr_atim_end(mac); if (mac->mac_reason_intr & BWN_INTR_BEACON) bwn_intr_beacon(mac); if (mac->mac_reason_intr & BWN_INTR_PMQ) bwn_intr_pmq(mac); if (mac->mac_reason_intr & BWN_INTR_NOISESAMPLE_OK) bwn_intr_noise(mac); if (mac->mac_flags & BWN_MAC_FLAG_DMA) { if (mac->mac_reason[0] & BWN_DMAINTR_RX_DONE) { bwn_dma_rx(mac->mac_method.dma.rx); rx = 1; } } else rx = bwn_pio_rx(&mac->mac_method.pio.rx); KASSERT(!(mac->mac_reason[1] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[2] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[3] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[4] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); KASSERT(!(mac->mac_reason[5] & BWN_DMAINTR_RX_DONE), ("%s", __func__)); if (mac->mac_reason_intr & BWN_INTR_TX_OK) { bwn_intr_txeof(mac); tx = 1; } BWN_WRITE_4(mac, BWN_INTR_MASK, mac->mac_intr_mask); if (sc->sc_blink_led != NULL && sc->sc_led_blink) { int evt = BWN_LED_EVENT_NONE; if (tx && rx) { if (sc->sc_rx_rate > sc->sc_tx_rate) evt = BWN_LED_EVENT_RX; else evt = BWN_LED_EVENT_TX; } else if (tx) { evt = BWN_LED_EVENT_TX; } else if (rx) { evt = BWN_LED_EVENT_RX; } else if (rx == 0) { evt = BWN_LED_EVENT_POLL; } if (evt != BWN_LED_EVENT_NONE) bwn_led_event(mac, evt); } if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0) { if (!IFQ_IS_EMPTY(&ifp->if_snd)) bwn_start_locked(ifp); } BWN_BARRIER(mac, BUS_SPACE_BARRIER_READ); BWN_BARRIER(mac, BUS_SPACE_BARRIER_WRITE); BWN_UNLOCK(sc); } static void bwn_restart(struct bwn_mac *mac, const char *msg) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; if (mac->mac_status < BWN_MAC_STATUS_INITED) return; device_printf(sc->sc_dev, "HW reset: %s\n", msg); ieee80211_runtask(ic, &mac->mac_hwreset); } static void bwn_intr_ucode_debug(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t reason; if (mac->mac_fw.opensource == 0) return; reason = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_DEBUGINTR_REASON_REG); switch (reason) { case BWN_DEBUGINTR_PANIC: bwn_handle_fwpanic(mac); break; case BWN_DEBUGINTR_DUMP_SHM: device_printf(sc->sc_dev, "BWN_DEBUGINTR_DUMP_SHM\n"); break; case BWN_DEBUGINTR_DUMP_REGS: device_printf(sc->sc_dev, "BWN_DEBUGINTR_DUMP_REGS\n"); break; case BWN_DEBUGINTR_MARKER: device_printf(sc->sc_dev, "BWN_DEBUGINTR_MARKER\n"); break; default: device_printf(sc->sc_dev, "ucode debug unknown reason: %#x\n", reason); } bwn_shm_write_2(mac, BWN_SCRATCH, BWN_DEBUGINTR_REASON_REG, BWN_DEBUGINTR_ACK); } static void bwn_intr_tbtt_indication(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; if (ic->ic_opmode != IEEE80211_M_HOSTAP) bwn_psctl(mac, 0); if (ic->ic_opmode == IEEE80211_M_IBSS) mac->mac_flags |= BWN_MAC_FLAG_DFQVALID; } static void bwn_intr_atim_end(struct bwn_mac *mac) { if (mac->mac_flags & BWN_MAC_FLAG_DFQVALID) { BWN_WRITE_4(mac, BWN_MACCMD, BWN_READ_4(mac, BWN_MACCMD) | BWN_MACCMD_DFQ_VALID); mac->mac_flags &= ~BWN_MAC_FLAG_DFQVALID; } } static void bwn_intr_beacon(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; uint32_t cmd, beacon0, beacon1; if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_MBSS) return; mac->mac_intr_mask &= ~BWN_INTR_BEACON; cmd = BWN_READ_4(mac, BWN_MACCMD); beacon0 = (cmd & BWN_MACCMD_BEACON0_VALID); beacon1 = (cmd & BWN_MACCMD_BEACON1_VALID); if (beacon0 && beacon1) { BWN_WRITE_4(mac, BWN_INTR_REASON, BWN_INTR_BEACON); mac->mac_intr_mask |= BWN_INTR_BEACON; return; } if (sc->sc_flags & BWN_FLAG_NEED_BEACON_TP) { sc->sc_flags &= ~BWN_FLAG_NEED_BEACON_TP; bwn_load_beacon0(mac); bwn_load_beacon1(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON0_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } else { if (!beacon0) { bwn_load_beacon0(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON0_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } else if (!beacon1) { bwn_load_beacon1(mac); cmd = BWN_READ_4(mac, BWN_MACCMD); cmd |= BWN_MACCMD_BEACON1_VALID; BWN_WRITE_4(mac, BWN_MACCMD, cmd); } } } static void bwn_intr_pmq(struct bwn_mac *mac) { uint32_t tmp; while (1) { tmp = BWN_READ_4(mac, BWN_PS_STATUS); if (!(tmp & 0x00000008)) break; } BWN_WRITE_2(mac, BWN_PS_STATUS, 0x0002); } static void bwn_intr_noise(struct bwn_mac *mac) { struct bwn_phy_g *pg = &mac->mac_phy.phy_g; uint16_t tmp; uint8_t noise[4]; uint8_t i, j; int32_t average; if (mac->mac_phy.type != BWN_PHYTYPE_G) return; KASSERT(mac->mac_noise.noi_running, ("%s: fail", __func__)); *((uint32_t *)noise) = htole32(bwn_jssi_read(mac)); if (noise[0] == 0x7f || noise[1] == 0x7f || noise[2] == 0x7f || noise[3] == 0x7f) goto new; KASSERT(mac->mac_noise.noi_nsamples < 8, ("%s:%d: fail", __func__, __LINE__)); i = mac->mac_noise.noi_nsamples; noise[0] = MIN(MAX(noise[0], 0), N(pg->pg_nrssi_lt) - 1); noise[1] = MIN(MAX(noise[1], 0), N(pg->pg_nrssi_lt) - 1); noise[2] = MIN(MAX(noise[2], 0), N(pg->pg_nrssi_lt) - 1); noise[3] = MIN(MAX(noise[3], 0), N(pg->pg_nrssi_lt) - 1); mac->mac_noise.noi_samples[i][0] = pg->pg_nrssi_lt[noise[0]]; mac->mac_noise.noi_samples[i][1] = pg->pg_nrssi_lt[noise[1]]; mac->mac_noise.noi_samples[i][2] = pg->pg_nrssi_lt[noise[2]]; mac->mac_noise.noi_samples[i][3] = pg->pg_nrssi_lt[noise[3]]; mac->mac_noise.noi_nsamples++; if (mac->mac_noise.noi_nsamples == 8) { average = 0; for (i = 0; i < 8; i++) { for (j = 0; j < 4; j++) average += mac->mac_noise.noi_samples[i][j]; } average = (((average / 32) * 125) + 64) / 128; tmp = (bwn_shm_read_2(mac, BWN_SHARED, 0x40c) / 128) & 0x1f; if (tmp >= 8) average += 2; else average -= 25; average -= (tmp == 8) ? 72 : 48; mac->mac_stats.link_noise = average; mac->mac_noise.noi_running = 0; return; } new: bwn_noise_gensample(mac); } static int bwn_pio_rx(struct bwn_pio_rxqueue *prq) { struct bwn_mac *mac = prq->prq_mac; struct bwn_softc *sc = mac->mac_sc; unsigned int i; BWN_ASSERT_LOCKED(sc); if (mac->mac_status < BWN_MAC_STATUS_STARTED) return (0); for (i = 0; i < 5000; i++) { if (bwn_pio_rxeof(prq) == 0) break; } if (i >= 5000) device_printf(sc->sc_dev, "too many RX frames in PIO mode\n"); return ((i > 0) ? 1 : 0); } static void bwn_dma_rx(struct bwn_dma_ring *dr) { int slot, curslot; KASSERT(!dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); curslot = dr->get_curslot(dr); KASSERT(curslot >= 0 && curslot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); slot = dr->dr_curslot; for (; slot != curslot; slot = bwn_dma_nextslot(dr, slot)) bwn_dma_rxeof(dr, &slot); bus_dmamap_sync(dr->dr_ring_dtag, dr->dr_ring_dmap, BUS_DMASYNC_PREWRITE); dr->set_curslot(dr, slot); dr->dr_curslot = slot; } static void bwn_intr_txeof(struct bwn_mac *mac) { struct bwn_txstatus stat; uint32_t stat0, stat1; uint16_t tmp; BWN_ASSERT_LOCKED(mac->mac_sc); while (1) { stat0 = BWN_READ_4(mac, BWN_XMITSTAT_0); if (!(stat0 & 0x00000001)) break; stat1 = BWN_READ_4(mac, BWN_XMITSTAT_1); stat.cookie = (stat0 >> 16); stat.seq = (stat1 & 0x0000ffff); stat.phy_stat = ((stat1 & 0x00ff0000) >> 16); tmp = (stat0 & 0x0000ffff); stat.framecnt = ((tmp & 0xf000) >> 12); stat.rtscnt = ((tmp & 0x0f00) >> 8); stat.sreason = ((tmp & 0x001c) >> 2); stat.pm = (tmp & 0x0080) ? 1 : 0; stat.im = (tmp & 0x0040) ? 1 : 0; stat.ampdu = (tmp & 0x0020) ? 1 : 0; stat.ack = (tmp & 0x0002) ? 1 : 0; bwn_handle_txeof(mac, &stat); } } static void bwn_hwreset(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; int error = 0; int prev_status; BWN_LOCK(sc); prev_status = mac->mac_status; if (prev_status >= BWN_MAC_STATUS_STARTED) bwn_core_stop(mac); if (prev_status >= BWN_MAC_STATUS_INITED) bwn_core_exit(mac); if (prev_status >= BWN_MAC_STATUS_INITED) { error = bwn_core_init(mac); if (error) goto out; } if (prev_status >= BWN_MAC_STATUS_STARTED) bwn_core_start(mac); out: if (error) { device_printf(sc->sc_dev, "%s: failed (%d)\n", __func__, error); sc->sc_curmac = NULL; } BWN_UNLOCK(sc); } static void bwn_handle_fwpanic(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; uint16_t reason; reason = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_FWPANIC_REASON_REG); device_printf(sc->sc_dev,"fw panic (%u)\n", reason); if (reason == BWN_FWPANIC_RESTART) bwn_restart(mac, "ucode panic"); } static void bwn_load_beacon0(struct bwn_mac *mac) { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } static void bwn_load_beacon1(struct bwn_mac *mac) { KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } static uint32_t bwn_jssi_read(struct bwn_mac *mac) { uint32_t val = 0; val = bwn_shm_read_2(mac, BWN_SHARED, 0x08a); val <<= 16; val |= bwn_shm_read_2(mac, BWN_SHARED, 0x088); return (val); } static void bwn_noise_gensample(struct bwn_mac *mac) { uint32_t jssi = 0x7f7f7f7f; bwn_shm_write_2(mac, BWN_SHARED, 0x088, (jssi & 0x0000ffff)); bwn_shm_write_2(mac, BWN_SHARED, 0x08a, (jssi & 0xffff0000) >> 16); BWN_WRITE_4(mac, BWN_MACCMD, BWN_READ_4(mac, BWN_MACCMD) | BWN_MACCMD_BGNOISE); } static int bwn_dma_freeslot(struct bwn_dma_ring *dr) { BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); return (dr->dr_numslots - dr->dr_usedslot); } static int bwn_dma_nextslot(struct bwn_dma_ring *dr, int slot) { BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); KASSERT(slot >= -1 && slot <= dr->dr_numslots - 1, ("%s:%d: fail", __func__, __LINE__)); if (slot == dr->dr_numslots - 1) return (0); return (slot + 1); } static void bwn_dma_rxeof(struct bwn_dma_ring *dr, int *slot) { struct bwn_mac *mac = dr->dr_mac; struct bwn_softc *sc = mac->mac_sc; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_rxhdr4 *rxhdr; struct ifnet *ifp = sc->sc_ifp; struct mbuf *m; uint32_t macstat; int32_t tmp; int cnt = 0; uint16_t len; dr->getdesc(dr, *slot, &desc, &meta); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_POSTREAD); m = meta->mt_m; if (bwn_dma_newbuf(dr, desc, meta, 0)) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } rxhdr = mtod(m, struct bwn_rxhdr4 *); len = le16toh(rxhdr->frame_len); if (len <= 0) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); return; } if (bwn_dma_check_redzone(dr, m)) { device_printf(sc->sc_dev, "redzone error.\n"); bwn_dma_set_redzone(dr, m); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); return; } if (len > dr->dr_rx_bufsize) { tmp = len; while (1) { dr->getdesc(dr, *slot, &desc, &meta); bwn_dma_set_redzone(dr, meta->mt_m); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); *slot = bwn_dma_nextslot(dr, *slot); cnt++; tmp -= dr->dr_rx_bufsize; if (tmp <= 0) break; } device_printf(sc->sc_dev, "too small buffer " "(len %u buffer %u dropped %d)\n", len, dr->dr_rx_bufsize, cnt); return; } macstat = le32toh(rxhdr->mac_status); if (macstat & BWN_RX_MAC_FCSERR) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADFCS)) { device_printf(sc->sc_dev, "RX drop\n"); return; } } m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = len + dr->dr_frameoffset; m_adj(m, dr->dr_frameoffset); bwn_rxeof(dr->dr_mac, m, rxhdr); } static void bwn_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_dma_ring *dr; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_pio_txqueue *tq; struct bwn_pio_txpkt *tp = NULL; struct bwn_softc *sc = mac->mac_sc; struct bwn_stats *stats = &mac->mac_stats; struct ieee80211_node *ni; struct ieee80211vap *vap; int retrycnt = 0, slot; BWN_ASSERT_LOCKED(mac->mac_sc); if (status->im) device_printf(sc->sc_dev, "TODO: STATUS IM\n"); if (status->ampdu) device_printf(sc->sc_dev, "TODO: STATUS AMPDU\n"); if (status->rtscnt) { if (status->rtscnt == 0xf) stats->rtsfail++; else stats->rts++; } if (mac->mac_flags & BWN_MAC_FLAG_DMA) { if (status->ack) { dr = bwn_dma_parse_cookie(mac, status, status->cookie, &slot); if (dr == NULL) { device_printf(sc->sc_dev, "failed to parse cookie\n"); return; } while (1) { dr->getdesc(dr, slot, &desc, &meta); if (meta->mt_islast) { ni = meta->mt_ni; vap = ni->ni_vap; ieee80211_ratectl_tx_complete(vap, ni, status->ack ? IEEE80211_RATECTL_TX_SUCCESS : IEEE80211_RATECTL_TX_FAILURE, &retrycnt, 0); break; } slot = bwn_dma_nextslot(dr, slot); } } bwn_dma_handle_txeof(mac, status); } else { if (status->ack) { tq = bwn_pio_parse_cookie(mac, status->cookie, &tp); if (tq == NULL) { device_printf(sc->sc_dev, "failed to parse cookie\n"); return; } ni = tp->tp_ni; vap = ni->ni_vap; ieee80211_ratectl_tx_complete(vap, ni, status->ack ? IEEE80211_RATECTL_TX_SUCCESS : IEEE80211_RATECTL_TX_FAILURE, &retrycnt, 0); } bwn_pio_handle_txeof(mac, status); } bwn_phy_txpower_check(mac, 0); } static uint8_t bwn_pio_rxeof(struct bwn_pio_rxqueue *prq) { struct bwn_mac *mac = prq->prq_mac; struct bwn_softc *sc = mac->mac_sc; struct bwn_rxhdr4 rxhdr; struct ifnet *ifp = sc->sc_ifp; struct mbuf *m; uint32_t ctl32, macstat, v32; unsigned int i, padding; uint16_t ctl16, len, totlen, v16; unsigned char *mp; char *data; memset(&rxhdr, 0, sizeof(rxhdr)); if (prq->prq_rev >= 8) { ctl32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXCTL); if (!(ctl32 & BWN_PIO8_RXCTL_FRAMEREADY)) return (0); bwn_pio_rx_write_4(prq, BWN_PIO8_RXCTL, BWN_PIO8_RXCTL_FRAMEREADY); for (i = 0; i < 10; i++) { ctl32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXCTL); if (ctl32 & BWN_PIO8_RXCTL_DATAREADY) goto ready; DELAY(10); } } else { ctl16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXCTL); if (!(ctl16 & BWN_PIO_RXCTL_FRAMEREADY)) return (0); bwn_pio_rx_write_2(prq, BWN_PIO_RXCTL, BWN_PIO_RXCTL_FRAMEREADY); for (i = 0; i < 10; i++) { ctl16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXCTL); if (ctl16 & BWN_PIO_RXCTL_DATAREADY) goto ready; DELAY(10); } } device_printf(sc->sc_dev, "%s: timed out\n", __func__); return (1); ready: if (prq->prq_rev >= 8) siba_read_multi_4(sc->sc_dev, &rxhdr, sizeof(rxhdr), prq->prq_base + BWN_PIO8_RXDATA); else siba_read_multi_2(sc->sc_dev, &rxhdr, sizeof(rxhdr), prq->prq_base + BWN_PIO_RXDATA); len = le16toh(rxhdr.frame_len); if (len > 0x700) { device_printf(sc->sc_dev, "%s: len is too big\n", __func__); goto error; } if (len == 0) { device_printf(sc->sc_dev, "%s: len is 0\n", __func__); goto error; } macstat = le32toh(rxhdr.mac_status); if (macstat & BWN_RX_MAC_FCSERR) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADFCS)) { device_printf(sc->sc_dev, "%s: FCS error", __func__); goto error; } } padding = (macstat & BWN_RX_MAC_PADDING) ? 2 : 0; totlen = len + padding; KASSERT(totlen <= MCLBYTES, ("too big..\n")); m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { device_printf(sc->sc_dev, "%s: out of memory", __func__); goto error; } mp = mtod(m, unsigned char *); if (prq->prq_rev >= 8) { siba_read_multi_4(sc->sc_dev, mp, (totlen & ~3), prq->prq_base + BWN_PIO8_RXDATA); if (totlen & 3) { v32 = bwn_pio_rx_read_4(prq, BWN_PIO8_RXDATA); data = &(mp[totlen - 1]); switch (totlen & 3) { case 3: *data = (v32 >> 16); data--; case 2: *data = (v32 >> 8); data--; case 1: *data = v32; } } } else { siba_read_multi_2(sc->sc_dev, mp, (totlen & ~1), prq->prq_base + BWN_PIO_RXDATA); if (totlen & 1) { v16 = bwn_pio_rx_read_2(prq, BWN_PIO_RXDATA); mp[totlen - 1] = v16; } } m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = totlen; bwn_rxeof(prq->prq_mac, m, &rxhdr); return (1); error: if (prq->prq_rev >= 8) bwn_pio_rx_write_4(prq, BWN_PIO8_RXCTL, BWN_PIO8_RXCTL_DATAREADY); else bwn_pio_rx_write_2(prq, BWN_PIO_RXCTL, BWN_PIO_RXCTL_DATAREADY); return (1); } static int bwn_dma_newbuf(struct bwn_dma_ring *dr, struct bwn_dmadesc_generic *desc, struct bwn_dmadesc_meta *meta, int init) { struct bwn_mac *mac = dr->dr_mac; struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_rxhdr4 *hdr; bus_dmamap_t map; bus_addr_t paddr; struct mbuf *m; int error; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { error = ENOBUFS; /* * If the NIC is up and running, we need to: * - Clear RX buffer's header. * - Restore RX descriptor settings. */ if (init) return (error); else goto back; } m->m_len = m->m_pkthdr.len = MCLBYTES; bwn_dma_set_redzone(dr, m); /* * Try to load RX buf into temporary DMA map */ error = bus_dmamap_load_mbuf(dma->rxbuf_dtag, dr->dr_spare_dmap, m, bwn_dma_buf_addr, &paddr, BUS_DMA_NOWAIT); if (error) { m_freem(m); /* * See the comment above */ if (init) return (error); else goto back; } if (!init) bus_dmamap_unload(dma->rxbuf_dtag, meta->mt_dmap); meta->mt_m = m; meta->mt_paddr = paddr; /* * Swap RX buf's DMA map with the loaded temporary one */ map = meta->mt_dmap; meta->mt_dmap = dr->dr_spare_dmap; dr->dr_spare_dmap = map; back: /* * Clear RX buf header */ hdr = mtod(meta->mt_m, struct bwn_rxhdr4 *); bzero(hdr, sizeof(*hdr)); bus_dmamap_sync(dma->rxbuf_dtag, meta->mt_dmap, BUS_DMASYNC_PREWRITE); /* * Setup RX buf descriptor */ dr->setdesc(dr, desc, meta->mt_paddr, meta->mt_m->m_len - sizeof(*hdr), 0, 0, 0); return (error); } static void bwn_dma_buf_addr(void *arg, bus_dma_segment_t *seg, int nseg, bus_size_t mapsz __unused, int error) { if (!error) { KASSERT(nseg == 1, ("too many segments(%d)\n", nseg)); *((bus_addr_t *)arg) = seg->ds_addr; } } static int bwn_hwrate2ieeerate(int rate) { switch (rate) { case BWN_CCK_RATE_1MB: return (2); case BWN_CCK_RATE_2MB: return (4); case BWN_CCK_RATE_5MB: return (11); case BWN_CCK_RATE_11MB: return (22); case BWN_OFDM_RATE_6MB: return (12); case BWN_OFDM_RATE_9MB: return (18); case BWN_OFDM_RATE_12MB: return (24); case BWN_OFDM_RATE_18MB: return (36); case BWN_OFDM_RATE_24MB: return (48); case BWN_OFDM_RATE_36MB: return (72); case BWN_OFDM_RATE_48MB: return (96); case BWN_OFDM_RATE_54MB: return (108); default: printf("Ooops\n"); return (0); } } static void bwn_rxeof(struct bwn_mac *mac, struct mbuf *m, const void *_rxhdr) { const struct bwn_rxhdr4 *rxhdr = _rxhdr; struct bwn_plcp6 *plcp; struct bwn_softc *sc = mac->mac_sc; struct ieee80211_frame_min *wh; struct ieee80211_node *ni; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint32_t macstat; int padding, rate, rssi = 0, noise = 0, type; uint16_t phytype, phystat0, phystat3, chanstat; unsigned char *mp = mtod(m, unsigned char *); static int rx_mac_dec_rpt = 0; BWN_ASSERT_LOCKED(sc); phystat0 = le16toh(rxhdr->phy_status0); phystat3 = le16toh(rxhdr->phy_status3); macstat = le32toh(rxhdr->mac_status); chanstat = le16toh(rxhdr->channel); phytype = chanstat & BWN_RX_CHAN_PHYTYPE; if (macstat & BWN_RX_MAC_FCSERR) device_printf(sc->sc_dev, "TODO RX: RX_FLAG_FAILED_FCS_CRC\n"); if (phystat0 & (BWN_RX_PHYST0_PLCPHCF | BWN_RX_PHYST0_PLCPFV)) device_printf(sc->sc_dev, "TODO RX: RX_FLAG_FAILED_PLCP_CRC\n"); if (macstat & BWN_RX_MAC_DECERR) goto drop; padding = (macstat & BWN_RX_MAC_PADDING) ? 2 : 0; if (m->m_pkthdr.len < (sizeof(struct bwn_plcp6) + padding)) { device_printf(sc->sc_dev, "frame too short (length=%d)\n", m->m_pkthdr.len); goto drop; } plcp = (struct bwn_plcp6 *)(mp + padding); m_adj(m, sizeof(struct bwn_plcp6) + padding); if (m->m_pkthdr.len < IEEE80211_MIN_LEN) { device_printf(sc->sc_dev, "frame too short (length=%d)\n", m->m_pkthdr.len); goto drop; } wh = mtod(m, struct ieee80211_frame_min *); if (macstat & BWN_RX_MAC_DEC && rx_mac_dec_rpt++ < 50) device_printf(sc->sc_dev, "RX decryption attempted (old %d keyidx %#x)\n", BWN_ISOLDFMT(mac), (macstat & BWN_RX_MAC_KEYIDX) >> BWN_RX_MAC_KEYIDX_SHIFT); /* XXX calculating RSSI & noise & antenna */ if (phystat0 & BWN_RX_PHYST0_OFDM) rate = bwn_plcp_get_ofdmrate(mac, plcp, phytype == BWN_PHYTYPE_A); else rate = bwn_plcp_get_cckrate(mac, plcp); if (rate == -1) { if (!(mac->mac_sc->sc_filters & BWN_MACCTL_PASS_BADPLCP)) goto drop; } sc->sc_rx_rate = bwn_hwrate2ieeerate(rate); /* RX radio tap */ if (ieee80211_radiotap_active(ic)) bwn_rx_radiotap(mac, m, rxhdr, plcp, rate, rssi, noise); m_adj(m, -IEEE80211_CRC_LEN); rssi = rxhdr->phy.abg.rssi; /* XXX incorrect RSSI calculation? */ noise = mac->mac_stats.link_noise; if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); BWN_UNLOCK(sc); ni = ieee80211_find_rxnode(ic, wh); if (ni != NULL) { type = ieee80211_input(ni, m, rssi, noise); ieee80211_free_node(ni); } else type = ieee80211_input_all(ic, m, rssi, noise); BWN_LOCK(sc); return; drop: device_printf(sc->sc_dev, "%s: dropped\n", __func__); } static void bwn_dma_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_dmadesc_generic *desc; struct bwn_dmadesc_meta *meta; struct bwn_softc *sc = mac->mac_sc; struct ieee80211_node *ni; struct ifnet *ifp = sc->sc_ifp; struct mbuf *m; int slot; BWN_ASSERT_LOCKED(sc); dr = bwn_dma_parse_cookie(mac, status, status->cookie, &slot); if (dr == NULL) { device_printf(sc->sc_dev, "failed to parse cookie\n"); return; } KASSERT(dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); while (1) { KASSERT(slot >= 0 && slot < dr->dr_numslots, ("%s:%d: fail", __func__, __LINE__)); dr->getdesc(dr, slot, &desc, &meta); if (meta->mt_txtype == BWN_DMADESC_METATYPE_HEADER) bus_dmamap_unload(dr->dr_txring_dtag, meta->mt_dmap); else if (meta->mt_txtype == BWN_DMADESC_METATYPE_BODY) bus_dmamap_unload(dma->txbuf_dtag, meta->mt_dmap); if (meta->mt_islast) { KASSERT(meta->mt_m != NULL, ("%s:%d: fail", __func__, __LINE__)); ni = meta->mt_ni; m = meta->mt_m; if (ni != NULL) { /* * Do any tx complete callback. Note this must * be done before releasing the node reference. */ if (m->m_flags & M_TXCB) ieee80211_process_callback(ni, m, 0); ieee80211_free_node(ni); meta->mt_ni = NULL; } m_freem(m); meta->mt_m = NULL; } else { KASSERT(meta->mt_m == NULL, ("%s:%d: fail", __func__, __LINE__)); } dr->dr_usedslot--; if (meta->mt_islast) { if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); break; } slot = bwn_dma_nextslot(dr, slot); } sc->sc_watchdog_timer = 0; if (dr->dr_stop) { KASSERT(bwn_dma_freeslot(dr) >= BWN_TX_SLOTS_PER_FRAME, ("%s:%d: fail", __func__, __LINE__)); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; dr->dr_stop = 0; } } static void bwn_pio_handle_txeof(struct bwn_mac *mac, const struct bwn_txstatus *status) { struct bwn_pio_txqueue *tq; struct bwn_pio_txpkt *tp = NULL; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; BWN_ASSERT_LOCKED(sc); tq = bwn_pio_parse_cookie(mac, status->cookie, &tp); if (tq == NULL) return; tq->tq_used -= roundup(tp->tp_m->m_pkthdr.len + BWN_HDRSIZE(mac), 4); tq->tq_free++; if (tp->tp_ni != NULL) { /* * Do any tx complete callback. Note this must * be done before releasing the node reference. */ if (tp->tp_m->m_flags & M_TXCB) ieee80211_process_callback(tp->tp_ni, tp->tp_m, 0); ieee80211_free_node(tp->tp_ni); tp->tp_ni = NULL; } m_freem(tp->tp_m); tp->tp_m = NULL; TAILQ_INSERT_TAIL(&tq->tq_pktlist, tp, tp_list); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); sc->sc_watchdog_timer = 0; if (tq->tq_stop) { ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; tq->tq_stop = 0; } } static void bwn_phy_txpower_check(struct bwn_mac *mac, uint32_t flags) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy *phy = &mac->mac_phy; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; unsigned long now; int result; BWN_GETTIME(now); if (!(flags & BWN_TXPWR_IGNORE_TIME) && time_before(now, phy->nexttime)) return; phy->nexttime = now + 2 * 1000; if (siba_get_pci_subvendor(sc->sc_dev) == SIBA_BOARDVENDOR_BCM && siba_get_pci_subdevice(sc->sc_dev) == SIBA_BOARD_BU4306) return; if (phy->recalc_txpwr != NULL) { result = phy->recalc_txpwr(mac, (flags & BWN_TXPWR_IGNORE_TSSI) ? 1 : 0); if (result == BWN_TXPWR_RES_DONE) return; KASSERT(result == BWN_TXPWR_RES_NEED_ADJUST, ("%s: fail", __func__)); KASSERT(phy->set_txpwr != NULL, ("%s: fail", __func__)); ieee80211_runtask(ic, &mac->mac_txpower); } } static uint16_t bwn_pio_rx_read_2(struct bwn_pio_rxqueue *prq, uint16_t offset) { return (BWN_READ_2(prq->prq_mac, prq->prq_base + offset)); } static uint32_t bwn_pio_rx_read_4(struct bwn_pio_rxqueue *prq, uint16_t offset) { return (BWN_READ_4(prq->prq_mac, prq->prq_base + offset)); } static void bwn_pio_rx_write_2(struct bwn_pio_rxqueue *prq, uint16_t offset, uint16_t value) { BWN_WRITE_2(prq->prq_mac, prq->prq_base + offset, value); } static void bwn_pio_rx_write_4(struct bwn_pio_rxqueue *prq, uint16_t offset, uint32_t value) { BWN_WRITE_4(prq->prq_mac, prq->prq_base + offset, value); } static int bwn_ieeerate2hwrate(struct bwn_softc *sc, int rate) { switch (rate) { /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return (BWN_OFDM_RATE_6MB); case 18: return (BWN_OFDM_RATE_9MB); case 24: return (BWN_OFDM_RATE_12MB); case 36: return (BWN_OFDM_RATE_18MB); case 48: return (BWN_OFDM_RATE_24MB); case 72: return (BWN_OFDM_RATE_36MB); case 96: return (BWN_OFDM_RATE_48MB); case 108: return (BWN_OFDM_RATE_54MB); /* CCK rates (NB: not IEEE std, device-specific) */ case 2: return (BWN_CCK_RATE_1MB); case 4: return (BWN_CCK_RATE_2MB); case 11: return (BWN_CCK_RATE_5MB); case 22: return (BWN_CCK_RATE_11MB); } device_printf(sc->sc_dev, "unsupported rate %d\n", rate); return (BWN_CCK_RATE_1MB); } static int bwn_set_txhdr(struct bwn_mac *mac, struct ieee80211_node *ni, struct mbuf *m, struct bwn_txhdr *txhdr, uint16_t cookie) { const struct bwn_phy *phy = &mac->mac_phy; struct bwn_softc *sc = mac->mac_sc; struct ieee80211_frame *wh; struct ieee80211_frame *protwh; struct ieee80211_frame_cts *cts; struct ieee80211_frame_rts *rts; const struct ieee80211_txparam *tp; struct ieee80211vap *vap = ni->ni_vap; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; struct mbuf *mprot; unsigned int len; uint32_t macctl = 0; int protdur, rts_rate, rts_rate_fb, ismcast, isshort, rix, type; uint16_t phyctl = 0; uint8_t rate, rate_fb; wh = mtod(m, struct ieee80211_frame *); memset(txhdr, 0, sizeof(*txhdr)); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; /* * Find TX rate */ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; if (type != IEEE80211_FC0_TYPE_DATA || (m->m_flags & M_EAPOL)) rate = rate_fb = tp->mgmtrate; else if (ismcast) rate = rate_fb = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = rate_fb = tp->ucastrate; else { rix = ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; if (rix > 0) rate_fb = ni->ni_rates.rs_rates[rix - 1] & IEEE80211_RATE_VAL; else rate_fb = rate; } sc->sc_tx_rate = rate; rate = bwn_ieeerate2hwrate(sc, rate); rate_fb = bwn_ieeerate2hwrate(sc, rate_fb); txhdr->phyrate = (BWN_ISOFDMRATE(rate)) ? bwn_plcp_getofdm(rate) : bwn_plcp_getcck(rate); bcopy(wh->i_fc, txhdr->macfc, sizeof(txhdr->macfc)); bcopy(wh->i_addr1, txhdr->addr1, IEEE80211_ADDR_LEN); if ((rate_fb == rate) || (*(u_int16_t *)wh->i_dur & htole16(0x8000)) || (*(u_int16_t *)wh->i_dur == htole16(0))) txhdr->dur_fb = *(u_int16_t *)wh->i_dur; else txhdr->dur_fb = ieee80211_compute_duration(ic->ic_rt, m->m_pkthdr.len, rate, isshort); /* XXX TX encryption */ bwn_plcp_genhdr(BWN_ISOLDFMT(mac) ? (struct bwn_plcp4 *)(&txhdr->body.old.plcp) : (struct bwn_plcp4 *)(&txhdr->body.new.plcp), m->m_pkthdr.len + IEEE80211_CRC_LEN, rate); bwn_plcp_genhdr((struct bwn_plcp4 *)(&txhdr->plcp_fb), m->m_pkthdr.len + IEEE80211_CRC_LEN, rate_fb); txhdr->eftypes |= (BWN_ISOFDMRATE(rate_fb)) ? BWN_TX_EFT_FB_OFDM : BWN_TX_EFT_FB_CCK; txhdr->chan = phy->chan; phyctl |= (BWN_ISOFDMRATE(rate)) ? BWN_TX_PHY_ENC_OFDM : BWN_TX_PHY_ENC_CCK; if (isshort && (rate == BWN_CCK_RATE_2MB || rate == BWN_CCK_RATE_5MB || rate == BWN_CCK_RATE_11MB)) phyctl |= BWN_TX_PHY_SHORTPRMBL; /* XXX TX antenna selection */ switch (bwn_antenna_sanitize(mac, 0)) { case 0: phyctl |= BWN_TX_PHY_ANT01AUTO; break; case 1: phyctl |= BWN_TX_PHY_ANT0; break; case 2: phyctl |= BWN_TX_PHY_ANT1; break; case 3: phyctl |= BWN_TX_PHY_ANT2; break; case 4: phyctl |= BWN_TX_PHY_ANT3; break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } if (!ismcast) macctl |= BWN_TX_MAC_ACK; macctl |= (BWN_TX_MAC_HWSEQ | BWN_TX_MAC_START_MSDU); if (!IEEE80211_IS_MULTICAST(wh->i_addr1) && m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) macctl |= BWN_TX_MAC_LONGFRAME; if (ic->ic_flags & IEEE80211_F_USEPROT) { /* XXX RTS rate is always 1MB??? */ rts_rate = BWN_CCK_RATE_1MB; rts_rate_fb = bwn_get_fbrate(rts_rate); protdur = ieee80211_compute_duration(ic->ic_rt, m->m_pkthdr.len, rate, isshort) + + ieee80211_ack_duration(ic->ic_rt, rate, isshort); if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) { cts = (struct ieee80211_frame_cts *)(BWN_ISOLDFMT(mac) ? (txhdr->body.old.rts_frame) : (txhdr->body.new.rts_frame)); mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, protdur); KASSERT(mprot != NULL, ("failed to alloc mbuf\n")); bcopy(mtod(mprot, uint8_t *), (uint8_t *)cts, mprot->m_pkthdr.len); m_freem(mprot); macctl |= BWN_TX_MAC_SEND_CTSTOSELF; len = sizeof(struct ieee80211_frame_cts); } else { rts = (struct ieee80211_frame_rts *)(BWN_ISOLDFMT(mac) ? (txhdr->body.old.rts_frame) : (txhdr->body.new.rts_frame)); protdur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, protdur); KASSERT(mprot != NULL, ("failed to alloc mbuf\n")); bcopy(mtod(mprot, uint8_t *), (uint8_t *)rts, mprot->m_pkthdr.len); m_freem(mprot); macctl |= BWN_TX_MAC_SEND_RTSCTS; len = sizeof(struct ieee80211_frame_rts); } len += IEEE80211_CRC_LEN; bwn_plcp_genhdr((struct bwn_plcp4 *)((BWN_ISOLDFMT(mac)) ? &txhdr->body.old.rts_plcp : &txhdr->body.new.rts_plcp), len, rts_rate); bwn_plcp_genhdr((struct bwn_plcp4 *)&txhdr->rts_plcp_fb, len, rts_rate_fb); protwh = (struct ieee80211_frame *)(BWN_ISOLDFMT(mac) ? (&txhdr->body.old.rts_frame) : (&txhdr->body.new.rts_frame)); txhdr->rts_dur_fb = *(u_int16_t *)protwh->i_dur; if (BWN_ISOFDMRATE(rts_rate)) { txhdr->eftypes |= BWN_TX_EFT_RTS_OFDM; txhdr->phyrate_rts = bwn_plcp_getofdm(rts_rate); } else { txhdr->eftypes |= BWN_TX_EFT_RTS_CCK; txhdr->phyrate_rts = bwn_plcp_getcck(rts_rate); } txhdr->eftypes |= (BWN_ISOFDMRATE(rts_rate_fb)) ? BWN_TX_EFT_RTS_FBOFDM : BWN_TX_EFT_RTS_FBCCK; } if (BWN_ISOLDFMT(mac)) txhdr->body.old.cookie = htole16(cookie); else txhdr->body.new.cookie = htole16(cookie); txhdr->macctl = htole32(macctl); txhdr->phyctl = htole16(phyctl); /* * TX radio tap */ if (ieee80211_radiotap_active_vap(vap)) { sc->sc_tx_th.wt_flags = 0; if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (isshort && (rate == BWN_CCK_RATE_2MB || rate == BWN_CCK_RATE_5MB || rate == BWN_CCK_RATE_11MB)) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; sc->sc_tx_th.wt_rate = rate; ieee80211_radiotap_tx(vap, m); } return (0); } static void bwn_plcp_genhdr(struct bwn_plcp4 *plcp, const uint16_t octets, const uint8_t rate) { uint32_t d, plen; uint8_t *raw = plcp->o.raw; if (BWN_ISOFDMRATE(rate)) { d = bwn_plcp_getofdm(rate); KASSERT(!(octets & 0xf000), ("%s:%d: fail", __func__, __LINE__)); d |= (octets << 5); plcp->o.data = htole32(d); } else { plen = octets * 16 / rate; if ((octets * 16 % rate) > 0) { plen++; if ((rate == BWN_CCK_RATE_11MB) && ((octets * 8 % 11) < 4)) { raw[1] = 0x84; } else raw[1] = 0x04; } else raw[1] = 0x04; plcp->o.data |= htole32(plen << 16); raw[0] = bwn_plcp_getcck(rate); } } static uint8_t bwn_antenna_sanitize(struct bwn_mac *mac, uint8_t n) { struct bwn_softc *sc = mac->mac_sc; uint8_t mask; if (n == 0) return (0); if (mac->mac_phy.gmode) mask = siba_sprom_get_ant_bg(sc->sc_dev); else mask = siba_sprom_get_ant_a(sc->sc_dev); if (!(mask & (1 << (n - 1)))) return (0); return (n); } static uint8_t bwn_get_fbrate(uint8_t bitrate) { switch (bitrate) { case BWN_CCK_RATE_1MB: return (BWN_CCK_RATE_1MB); case BWN_CCK_RATE_2MB: return (BWN_CCK_RATE_1MB); case BWN_CCK_RATE_5MB: return (BWN_CCK_RATE_2MB); case BWN_CCK_RATE_11MB: return (BWN_CCK_RATE_5MB); case BWN_OFDM_RATE_6MB: return (BWN_CCK_RATE_5MB); case BWN_OFDM_RATE_9MB: return (BWN_OFDM_RATE_6MB); case BWN_OFDM_RATE_12MB: return (BWN_OFDM_RATE_9MB); case BWN_OFDM_RATE_18MB: return (BWN_OFDM_RATE_12MB); case BWN_OFDM_RATE_24MB: return (BWN_OFDM_RATE_18MB); case BWN_OFDM_RATE_36MB: return (BWN_OFDM_RATE_24MB); case BWN_OFDM_RATE_48MB: return (BWN_OFDM_RATE_36MB); case BWN_OFDM_RATE_54MB: return (BWN_OFDM_RATE_48MB); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (0); } static uint32_t bwn_pio_write_multi_4(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint32_t ctl, const void *_data, int len) { struct bwn_softc *sc = mac->mac_sc; uint32_t value = 0; const uint8_t *data = _data; ctl |= BWN_PIO8_TXCTL_0_7 | BWN_PIO8_TXCTL_8_15 | BWN_PIO8_TXCTL_16_23 | BWN_PIO8_TXCTL_24_31; bwn_pio_write_4(mac, tq, BWN_PIO8_TXCTL, ctl); siba_write_multi_4(sc->sc_dev, data, (len & ~3), tq->tq_base + BWN_PIO8_TXDATA); if (len & 3) { ctl &= ~(BWN_PIO8_TXCTL_8_15 | BWN_PIO8_TXCTL_16_23 | BWN_PIO8_TXCTL_24_31); data = &(data[len - 1]); switch (len & 3) { case 3: ctl |= BWN_PIO8_TXCTL_16_23; value |= (uint32_t)(*data) << 16; data--; case 2: ctl |= BWN_PIO8_TXCTL_8_15; value |= (uint32_t)(*data) << 8; data--; case 1: value |= (uint32_t)(*data); } bwn_pio_write_4(mac, tq, BWN_PIO8_TXCTL, ctl); bwn_pio_write_4(mac, tq, BWN_PIO8_TXDATA, value); } return (ctl); } static void bwn_pio_write_4(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t offset, uint32_t value) { BWN_WRITE_4(mac, tq->tq_base + offset, value); } static uint16_t bwn_pio_write_multi_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t ctl, const void *_data, int len) { struct bwn_softc *sc = mac->mac_sc; const uint8_t *data = _data; ctl |= BWN_PIO_TXCTL_WRITELO | BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); siba_write_multi_2(sc->sc_dev, data, (len & ~1), tq->tq_base + BWN_PIO_TXDATA); if (len & 1) { ctl &= ~BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data[len - 1]); } return (ctl); } static uint16_t bwn_pio_write_mbuf_2(struct bwn_mac *mac, struct bwn_pio_txqueue *tq, uint16_t ctl, struct mbuf *m0) { int i, j = 0; uint16_t data = 0; const uint8_t *buf; struct mbuf *m = m0; ctl |= BWN_PIO_TXCTL_WRITELO | BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); for (; m != NULL; m = m->m_next) { buf = mtod(m, const uint8_t *); for (i = 0; i < m->m_len; i++) { if (!((j++) % 2)) data |= buf[i]; else { data |= (buf[i] << 8); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data); data = 0; } } } if (m0->m_pkthdr.len % 2) { ctl &= ~BWN_PIO_TXCTL_WRITEHI; BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXCTL, ctl); BWN_PIO_WRITE_2(mac, tq, BWN_PIO_TXDATA, data); } return (ctl); } static void bwn_set_slot_time(struct bwn_mac *mac, uint16_t time) { if (mac->mac_phy.type != BWN_PHYTYPE_G) return; BWN_WRITE_2(mac, 0x684, 510 + time); bwn_shm_write_2(mac, BWN_SHARED, 0x0010, time); } static struct bwn_dma_ring * bwn_dma_select(struct bwn_mac *mac, uint8_t prio) { if ((mac->mac_flags & BWN_MAC_FLAG_WME) == 0) return (mac->mac_method.dma.wme[WME_AC_BE]); switch (prio) { case 3: return (mac->mac_method.dma.wme[WME_AC_VO]); case 2: return (mac->mac_method.dma.wme[WME_AC_VI]); case 0: return (mac->mac_method.dma.wme[WME_AC_BE]); case 1: return (mac->mac_method.dma.wme[WME_AC_BK]); } KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); return (NULL); } static int bwn_dma_getslot(struct bwn_dma_ring *dr) { int slot; BWN_ASSERT_LOCKED(dr->dr_mac->mac_sc); KASSERT(dr->dr_tx, ("%s:%d: fail", __func__, __LINE__)); KASSERT(!(dr->dr_stop), ("%s:%d: fail", __func__, __LINE__)); KASSERT(bwn_dma_freeslot(dr) != 0, ("%s:%d: fail", __func__, __LINE__)); slot = bwn_dma_nextslot(dr, dr->dr_curslot); KASSERT(!(slot & ~0x0fff), ("%s:%d: fail", __func__, __LINE__)); dr->dr_curslot = slot; dr->dr_usedslot++; return (slot); } static int bwn_phy_shm_tssi_read(struct bwn_mac *mac, uint16_t shm_offset) { const uint8_t ofdm = (shm_offset != BWN_SHARED_TSSI_CCK); unsigned int a, b, c, d; unsigned int avg; uint32_t tmp; tmp = bwn_shm_read_4(mac, BWN_SHARED, shm_offset); a = tmp & 0xff; b = (tmp >> 8) & 0xff; c = (tmp >> 16) & 0xff; d = (tmp >> 24) & 0xff; if (a == 0 || a == BWN_TSSI_MAX || b == 0 || b == BWN_TSSI_MAX || c == 0 || c == BWN_TSSI_MAX || d == 0 || d == BWN_TSSI_MAX) return (ENOENT); bwn_shm_write_4(mac, BWN_SHARED, shm_offset, BWN_TSSI_MAX | (BWN_TSSI_MAX << 8) | (BWN_TSSI_MAX << 16) | (BWN_TSSI_MAX << 24)); if (ofdm) { a = (a + 32) & 0x3f; b = (b + 32) & 0x3f; c = (c + 32) & 0x3f; d = (d + 32) & 0x3f; } avg = (a + b + c + d + 2) / 4; if (ofdm) { if (bwn_shm_read_2(mac, BWN_SHARED, BWN_SHARED_HFLO) & BWN_HF_4DB_CCK_POWERBOOST) avg = (avg >= 13) ? (avg - 13) : 0; } return (avg); } static void bwn_phy_g_setatt(struct bwn_mac *mac, int *bbattp, int *rfattp) { struct bwn_txpwr_loctl *lo = &mac->mac_phy.phy_g.pg_loctl; int rfatt = *rfattp; int bbatt = *bbattp; while (1) { if (rfatt > lo->rfatt.max && bbatt > lo->bbatt.max - 4) break; if (rfatt < lo->rfatt.min && bbatt < lo->bbatt.min + 4) break; if (bbatt > lo->bbatt.max && rfatt > lo->rfatt.max - 1) break; if (bbatt < lo->bbatt.min && rfatt < lo->rfatt.min + 1) break; if (bbatt > lo->bbatt.max) { bbatt -= 4; rfatt += 1; continue; } if (bbatt < lo->bbatt.min) { bbatt += 4; rfatt -= 1; continue; } if (rfatt > lo->rfatt.max) { rfatt -= 1; bbatt += 4; continue; } if (rfatt < lo->rfatt.min) { rfatt += 1; bbatt -= 4; continue; } break; } *rfattp = MIN(MAX(rfatt, lo->rfatt.min), lo->rfatt.max); *bbattp = MIN(MAX(bbatt, lo->bbatt.min), lo->bbatt.max); } static void bwn_phy_lock(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; KASSERT(siba_get_revid(sc->sc_dev) >= 3, ("%s: unsupported rev %d", __func__, siba_get_revid(sc->sc_dev))); if (ic->ic_opmode != IEEE80211_M_HOSTAP) bwn_psctl(mac, BWN_PS_AWAKE); } static void bwn_phy_unlock(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ieee80211com *ic = sc->sc_ifp->if_l2com; KASSERT(siba_get_revid(sc->sc_dev) >= 3, ("%s: unsupported rev %d", __func__, siba_get_revid(sc->sc_dev))); if (ic->ic_opmode != IEEE80211_M_HOSTAP) bwn_psctl(mac, 0); } static void bwn_rf_lock(struct bwn_mac *mac) { BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) | BWN_MACCTL_RADIO_LOCK); BWN_READ_4(mac, BWN_MACCTL); DELAY(10); } static void bwn_rf_unlock(struct bwn_mac *mac) { BWN_READ_2(mac, BWN_PHYVER); BWN_WRITE_4(mac, BWN_MACCTL, BWN_READ_4(mac, BWN_MACCTL) & ~BWN_MACCTL_RADIO_LOCK); } static struct bwn_pio_txqueue * bwn_pio_parse_cookie(struct bwn_mac *mac, uint16_t cookie, struct bwn_pio_txpkt **pack) { struct bwn_pio *pio = &mac->mac_method.pio; struct bwn_pio_txqueue *tq = NULL; unsigned int index; switch (cookie & 0xf000) { case 0x1000: tq = &pio->wme[WME_AC_BK]; break; case 0x2000: tq = &pio->wme[WME_AC_BE]; break; case 0x3000: tq = &pio->wme[WME_AC_VI]; break; case 0x4000: tq = &pio->wme[WME_AC_VO]; break; case 0x5000: tq = &pio->mcast; break; } KASSERT(tq != NULL, ("%s:%d: fail", __func__, __LINE__)); if (tq == NULL) return (NULL); index = (cookie & 0x0fff); KASSERT(index < N(tq->tq_pkts), ("%s:%d: fail", __func__, __LINE__)); if (index >= N(tq->tq_pkts)) return (NULL); *pack = &tq->tq_pkts[index]; KASSERT(*pack != NULL, ("%s:%d: fail", __func__, __LINE__)); return (tq); } static void bwn_txpwr(void *arg, int npending) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; BWN_LOCK(sc); if (mac && mac->mac_status >= BWN_MAC_STATUS_STARTED && mac->mac_phy.set_txpwr != NULL) mac->mac_phy.set_txpwr(mac); BWN_UNLOCK(sc); } static void bwn_task_15s(struct bwn_mac *mac) { uint16_t reg; if (mac->mac_fw.opensource) { reg = bwn_shm_read_2(mac, BWN_SCRATCH, BWN_WATCHDOG_REG); if (reg) { bwn_restart(mac, "fw watchdog"); return; } bwn_shm_write_2(mac, BWN_SCRATCH, BWN_WATCHDOG_REG, 1); } if (mac->mac_phy.task_15s) mac->mac_phy.task_15s(mac); mac->mac_phy.txerrors = BWN_TXERROR_MAX; } static void bwn_task_30s(struct bwn_mac *mac) { if (mac->mac_phy.type != BWN_PHYTYPE_G || mac->mac_noise.noi_running) return; mac->mac_noise.noi_running = 1; mac->mac_noise.noi_nsamples = 0; bwn_noise_gensample(mac); } static void bwn_task_60s(struct bwn_mac *mac) { if (mac->mac_phy.task_60s) mac->mac_phy.task_60s(mac); bwn_phy_txpower_check(mac, BWN_TXPWR_IGNORE_TIME); } static void bwn_tasks(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(sc); if (mac->mac_status != BWN_MAC_STATUS_STARTED) return; if (mac->mac_task_state % 4 == 0) bwn_task_60s(mac); if (mac->mac_task_state % 2 == 0) bwn_task_30s(mac); bwn_task_15s(mac); mac->mac_task_state++; callout_reset(&sc->sc_task_ch, hz * 15, bwn_tasks, mac); } static int bwn_plcp_get_ofdmrate(struct bwn_mac *mac, struct bwn_plcp6 *plcp, uint8_t a) { struct bwn_softc *sc = mac->mac_sc; KASSERT(a == 0, ("not support APHY\n")); switch (plcp->o.raw[0] & 0xf) { case 0xb: return (BWN_OFDM_RATE_6MB); case 0xf: return (BWN_OFDM_RATE_9MB); case 0xa: return (BWN_OFDM_RATE_12MB); case 0xe: return (BWN_OFDM_RATE_18MB); case 0x9: return (BWN_OFDM_RATE_24MB); case 0xd: return (BWN_OFDM_RATE_36MB); case 0x8: return (BWN_OFDM_RATE_48MB); case 0xc: return (BWN_OFDM_RATE_54MB); } device_printf(sc->sc_dev, "incorrect OFDM rate %d\n", plcp->o.raw[0] & 0xf); return (-1); } static int bwn_plcp_get_cckrate(struct bwn_mac *mac, struct bwn_plcp6 *plcp) { struct bwn_softc *sc = mac->mac_sc; switch (plcp->o.raw[0]) { case 0x0a: return (BWN_CCK_RATE_1MB); case 0x14: return (BWN_CCK_RATE_2MB); case 0x37: return (BWN_CCK_RATE_5MB); case 0x6e: return (BWN_CCK_RATE_11MB); } device_printf(sc->sc_dev, "incorrect CCK rate %d\n", plcp->o.raw[0]); return (-1); } static void bwn_rx_radiotap(struct bwn_mac *mac, struct mbuf *m, const struct bwn_rxhdr4 *rxhdr, struct bwn_plcp6 *plcp, int rate, int rssi, int noise) { struct bwn_softc *sc = mac->mac_sc; const struct ieee80211_frame_min *wh; uint64_t tsf; uint16_t low_mactime_now; if (htole16(rxhdr->phy_status0) & BWN_RX_PHYST0_SHORTPRMBL) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; wh = mtod(m, const struct ieee80211_frame_min *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) sc->sc_rx_th.wr_flags |= IEEE80211_RADIOTAP_F_WEP; bwn_tsf_read(mac, &tsf); low_mactime_now = tsf; tsf = tsf & ~0xffffULL; tsf += le16toh(rxhdr->mac_time); if (low_mactime_now < le16toh(rxhdr->mac_time)) tsf -= 0x10000; sc->sc_rx_th.wr_tsf = tsf; sc->sc_rx_th.wr_rate = rate; sc->sc_rx_th.wr_antsignal = rssi; sc->sc_rx_th.wr_antnoise = noise; } static void bwn_tsf_read(struct bwn_mac *mac, uint64_t *tsf) { uint32_t low, high; KASSERT(siba_get_revid(mac->mac_sc->sc_dev) >= 3, ("%s:%d: fail", __func__, __LINE__)); low = BWN_READ_4(mac, BWN_REV3PLUS_TSF_LOW); high = BWN_READ_4(mac, BWN_REV3PLUS_TSF_HIGH); *tsf = high; *tsf <<= 32; *tsf |= low; } static int bwn_dma_attach(struct bwn_mac *mac) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_softc *sc = mac->mac_sc; bus_addr_t lowaddr = 0; int error; if (siba_get_type(sc->sc_dev) == SIBA_TYPE_PCMCIA || bwn_usedma == 0) return (0); KASSERT(siba_get_revid(sc->sc_dev) >= 5, ("%s: fail", __func__)); mac->mac_flags |= BWN_MAC_FLAG_DMA; dma->dmatype = bwn_dma_gettype(mac); if (dma->dmatype == BWN_DMA_30BIT) lowaddr = BWN_BUS_SPACE_MAXADDR_30BIT; else if (dma->dmatype == BWN_DMA_32BIT) lowaddr = BUS_SPACE_MAXADDR_32BIT; else lowaddr = BUS_SPACE_MAXADDR; /* * Create top level DMA tag */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), /* parent */ BWN_ALIGN, 0, /* alignment, bounds */ lowaddr, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, /* maxsize */ + BUS_SPACE_MAXSIZE, /* maxsize */ BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &dma->parent_dtag); if (error) { device_printf(sc->sc_dev, "can't create parent DMA tag\n"); return (error); } /* * Create TX/RX mbuf DMA tag */ error = bus_dma_tag_create(dma->parent_dtag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dma->rxbuf_dtag); if (error) { device_printf(sc->sc_dev, "can't create mbuf DMA tag\n"); goto fail0; } error = bus_dma_tag_create(dma->parent_dtag, 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, BUS_SPACE_MAXSIZE_32BIT, 0, NULL, NULL, &dma->txbuf_dtag); if (error) { device_printf(sc->sc_dev, "can't create mbuf DMA tag\n"); goto fail1; } dma->wme[WME_AC_BK] = bwn_dma_ringsetup(mac, 0, 1, dma->dmatype); if (!dma->wme[WME_AC_BK]) goto fail2; dma->wme[WME_AC_BE] = bwn_dma_ringsetup(mac, 1, 1, dma->dmatype); if (!dma->wme[WME_AC_BE]) goto fail3; dma->wme[WME_AC_VI] = bwn_dma_ringsetup(mac, 2, 1, dma->dmatype); if (!dma->wme[WME_AC_VI]) goto fail4; dma->wme[WME_AC_VO] = bwn_dma_ringsetup(mac, 3, 1, dma->dmatype); if (!dma->wme[WME_AC_VO]) goto fail5; dma->mcast = bwn_dma_ringsetup(mac, 4, 1, dma->dmatype); if (!dma->mcast) goto fail6; dma->rx = bwn_dma_ringsetup(mac, 0, 0, dma->dmatype); if (!dma->rx) goto fail7; return (error); fail7: bwn_dma_ringfree(&dma->mcast); fail6: bwn_dma_ringfree(&dma->wme[WME_AC_VO]); fail5: bwn_dma_ringfree(&dma->wme[WME_AC_VI]); fail4: bwn_dma_ringfree(&dma->wme[WME_AC_BE]); fail3: bwn_dma_ringfree(&dma->wme[WME_AC_BK]); fail2: bus_dma_tag_destroy(dma->txbuf_dtag); fail1: bus_dma_tag_destroy(dma->rxbuf_dtag); fail0: bus_dma_tag_destroy(dma->parent_dtag); return (error); } static struct bwn_dma_ring * bwn_dma_parse_cookie(struct bwn_mac *mac, const struct bwn_txstatus *status, uint16_t cookie, int *slot) { struct bwn_dma *dma = &mac->mac_method.dma; struct bwn_dma_ring *dr; struct bwn_softc *sc = mac->mac_sc; BWN_ASSERT_LOCKED(mac->mac_sc); switch (cookie & 0xf000) { case 0x1000: dr = dma->wme[WME_AC_BK]; break; case 0x2000: dr = dma->wme[WME_AC_BE]; break; case 0x3000: dr = dma->wme[WME_AC_VI]; break; case 0x4000: dr = dma->wme[WME_AC_VO]; break; case 0x5000: dr = dma->mcast; break; default: dr = NULL; KASSERT(0 == 1, ("invalid cookie value %d", cookie & 0xf000)); } *slot = (cookie & 0x0fff); if (*slot < 0 || *slot >= dr->dr_numslots) { /* * XXX FIXME: sometimes H/W returns TX DONE events duplicately * that it occurs events which have same H/W sequence numbers. * When it's occurred just prints a WARNING msgs and ignores. */ KASSERT(status->seq == dma->lastseq, ("%s:%d: fail", __func__, __LINE__)); device_printf(sc->sc_dev, "out of slot ranges (0 < %d < %d)\n", *slot, dr->dr_numslots); return (NULL); } dma->lastseq = status->seq; return (dr); } static void bwn_dma_stop(struct bwn_mac *mac) { struct bwn_dma *dma; if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) return; dma = &mac->mac_method.dma; bwn_dma_ringstop(&dma->rx); bwn_dma_ringstop(&dma->wme[WME_AC_BK]); bwn_dma_ringstop(&dma->wme[WME_AC_BE]); bwn_dma_ringstop(&dma->wme[WME_AC_VI]); bwn_dma_ringstop(&dma->wme[WME_AC_VO]); bwn_dma_ringstop(&dma->mcast); } static void bwn_dma_ringstop(struct bwn_dma_ring **dr) { if (dr == NULL) return; bwn_dma_cleanup(*dr); } static void bwn_pio_stop(struct bwn_mac *mac) { struct bwn_pio *pio; if (mac->mac_flags & BWN_MAC_FLAG_DMA) return; pio = &mac->mac_method.pio; bwn_destroy_queue_tx(&pio->mcast); bwn_destroy_queue_tx(&pio->wme[WME_AC_VO]); bwn_destroy_queue_tx(&pio->wme[WME_AC_VI]); bwn_destroy_queue_tx(&pio->wme[WME_AC_BE]); bwn_destroy_queue_tx(&pio->wme[WME_AC_BK]); } static void bwn_led_attach(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; const uint8_t *led_act = NULL; uint16_t val[BWN_LED_MAX]; int i; sc->sc_led_idle = (2350 * hz) / 1000; sc->sc_led_blink = 1; for (i = 0; i < N(bwn_vendor_led_act); ++i) { if (siba_get_pci_subvendor(sc->sc_dev) == bwn_vendor_led_act[i].vid) { led_act = bwn_vendor_led_act[i].led_act; break; } } if (led_act == NULL) led_act = bwn_default_led_act; val[0] = siba_sprom_get_gpio0(sc->sc_dev); val[1] = siba_sprom_get_gpio1(sc->sc_dev); val[2] = siba_sprom_get_gpio2(sc->sc_dev); val[3] = siba_sprom_get_gpio3(sc->sc_dev); for (i = 0; i < BWN_LED_MAX; ++i) { struct bwn_led *led = &sc->sc_leds[i]; if (val[i] == 0xff) { led->led_act = led_act[i]; } else { if (val[i] & BWN_LED_ACT_LOW) led->led_flags |= BWN_LED_F_ACTLOW; led->led_act = val[i] & BWN_LED_ACT_MASK; } led->led_mask = (1 << i); if (led->led_act == BWN_LED_ACT_BLINK_SLOW || led->led_act == BWN_LED_ACT_BLINK_POLL || led->led_act == BWN_LED_ACT_BLINK) { led->led_flags |= BWN_LED_F_BLINK; if (led->led_act == BWN_LED_ACT_BLINK_POLL) led->led_flags |= BWN_LED_F_POLLABLE; else if (led->led_act == BWN_LED_ACT_BLINK_SLOW) led->led_flags |= BWN_LED_F_SLOW; if (sc->sc_blink_led == NULL) { sc->sc_blink_led = led; if (led->led_flags & BWN_LED_F_SLOW) BWN_LED_SLOWDOWN(sc->sc_led_idle); } } DPRINTF(sc, BWN_DEBUG_LED, "%dth led, act %d, lowact %d\n", i, led->led_act, led->led_flags & BWN_LED_F_ACTLOW); } callout_init_mtx(&sc->sc_led_blink_ch, &sc->sc_mtx, 0); } static __inline uint16_t bwn_led_onoff(const struct bwn_led *led, uint16_t val, int on) { if (led->led_flags & BWN_LED_F_ACTLOW) on = !on; if (on) val |= led->led_mask; else val &= ~led->led_mask; return val; } static void bwn_led_newstate(struct bwn_mac *mac, enum ieee80211_state nstate) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint16_t val; int i; if (nstate == IEEE80211_S_INIT) { callout_stop(&sc->sc_led_blink_ch); sc->sc_led_blinking = 0; } if ((ic->ic_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); for (i = 0; i < BWN_LED_MAX; ++i) { struct bwn_led *led = &sc->sc_leds[i]; int on; if (led->led_act == BWN_LED_ACT_UNKN || led->led_act == BWN_LED_ACT_NULL) continue; if ((led->led_flags & BWN_LED_F_BLINK) && nstate != IEEE80211_S_INIT) continue; switch (led->led_act) { case BWN_LED_ACT_ON: /* Always on */ on = 1; break; case BWN_LED_ACT_OFF: /* Always off */ case BWN_LED_ACT_5GHZ: /* TODO: 11A */ on = 0; break; default: on = 1; switch (nstate) { case IEEE80211_S_INIT: on = 0; break; case IEEE80211_S_RUN: if (led->led_act == BWN_LED_ACT_11G && ic->ic_curmode != IEEE80211_MODE_11G) on = 0; break; default: if (led->led_act == BWN_LED_ACT_ASSOC) on = 0; break; } break; } val = bwn_led_onoff(led, val, on); } BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); } static void bwn_led_event(struct bwn_mac *mac, int event) { struct bwn_softc *sc = mac->mac_sc; struct bwn_led *led = sc->sc_blink_led; int rate; if (event == BWN_LED_EVENT_POLL) { if ((led->led_flags & BWN_LED_F_POLLABLE) == 0) return; if (ticks - sc->sc_led_ticks < sc->sc_led_idle) return; } sc->sc_led_ticks = ticks; if (sc->sc_led_blinking) return; switch (event) { case BWN_LED_EVENT_RX: rate = sc->sc_rx_rate; break; case BWN_LED_EVENT_TX: rate = sc->sc_tx_rate; break; case BWN_LED_EVENT_POLL: rate = 0; break; default: panic("unknown LED event %d\n", event); break; } bwn_led_blink_start(mac, bwn_led_duration[rate].on_dur, bwn_led_duration[rate].off_dur); } static void bwn_led_blink_start(struct bwn_mac *mac, int on_dur, int off_dur) { struct bwn_softc *sc = mac->mac_sc; struct bwn_led *led = sc->sc_blink_led; uint16_t val; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); val = bwn_led_onoff(led, val, 1); BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); if (led->led_flags & BWN_LED_F_SLOW) { BWN_LED_SLOWDOWN(on_dur); BWN_LED_SLOWDOWN(off_dur); } sc->sc_led_blinking = 1; sc->sc_led_blink_offdur = off_dur; callout_reset(&sc->sc_led_blink_ch, on_dur, bwn_led_blink_next, mac); } static void bwn_led_blink_next(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; uint16_t val; val = BWN_READ_2(mac, BWN_GPIO_CONTROL); val = bwn_led_onoff(sc->sc_blink_led, val, 0); BWN_WRITE_2(mac, BWN_GPIO_CONTROL, val); callout_reset(&sc->sc_led_blink_ch, sc->sc_led_blink_offdur, bwn_led_blink_end, mac); } static void bwn_led_blink_end(void *arg) { struct bwn_mac *mac = arg; struct bwn_softc *sc = mac->mac_sc; sc->sc_led_blinking = 0; } static int bwn_suspend(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); bwn_stop(sc, 1); return (0); } static int bwn_resume(device_t dev) { struct bwn_softc *sc = device_get_softc(dev); struct ifnet *ifp = sc->sc_ifp; if (ifp->if_flags & IFF_UP) bwn_init(sc); return (0); } static void bwn_rfswitch(void *arg) { struct bwn_softc *sc = arg; struct bwn_mac *mac = sc->sc_curmac; int cur = 0, prev = 0; KASSERT(mac->mac_status >= BWN_MAC_STATUS_STARTED, ("%s: invalid MAC status %d", __func__, mac->mac_status)); if (mac->mac_phy.rf_rev >= 3 || mac->mac_phy.type == BWN_PHYTYPE_LP) { if (!(BWN_READ_4(mac, BWN_RF_HWENABLED_HI) & BWN_RF_HWENABLED_HI_MASK)) cur = 1; } else { if (BWN_READ_2(mac, BWN_RF_HWENABLED_LO) & BWN_RF_HWENABLED_LO_MASK) cur = 1; } if (mac->mac_flags & BWN_MAC_FLAG_RADIO_ON) prev = 1; if (cur != prev) { if (cur) mac->mac_flags |= BWN_MAC_FLAG_RADIO_ON; else mac->mac_flags &= ~BWN_MAC_FLAG_RADIO_ON; device_printf(sc->sc_dev, "status of RF switch is changed to %s\n", cur ? "ON" : "OFF"); if (cur != mac->mac_phy.rf_on) { if (cur) bwn_rf_turnon(mac); else bwn_rf_turnoff(mac); } } callout_schedule(&sc->sc_rfswitch_ch, hz); } static void bwn_phy_lp_init_pre(struct bwn_mac *mac) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_lp *plp = &phy->phy_lp; plp->plp_antenna = BWN_ANT_DEFAULT; } static int bwn_phy_lp_init(struct bwn_mac *mac) { static const struct bwn_stxtable tables[] = { { 2, 6, 0x3d, 3, 0x01 }, { 1, 12, 0x4c, 1, 0x01 }, { 1, 8, 0x50, 0, 0x7f }, { 0, 8, 0x44, 0, 0xff }, { 1, 0, 0x4a, 0, 0xff }, { 0, 4, 0x4d, 0, 0xff }, { 1, 4, 0x4e, 0, 0xff }, { 0, 12, 0x4f, 0, 0x0f }, { 1, 0, 0x4f, 4, 0x0f }, { 3, 0, 0x49, 0, 0x0f }, { 4, 3, 0x46, 4, 0x07 }, { 3, 15, 0x46, 0, 0x01 }, { 4, 0, 0x46, 1, 0x07 }, { 3, 8, 0x48, 4, 0x07 }, { 3, 11, 0x48, 0, 0x0f }, { 3, 4, 0x49, 4, 0x0f }, { 2, 15, 0x45, 0, 0x01 }, { 5, 13, 0x52, 4, 0x07 }, { 6, 0, 0x52, 7, 0x01 }, { 5, 3, 0x41, 5, 0x07 }, { 5, 6, 0x41, 0, 0x0f }, { 5, 10, 0x42, 5, 0x07 }, { 4, 15, 0x42, 0, 0x01 }, { 5, 0, 0x42, 1, 0x07 }, { 4, 11, 0x43, 4, 0x0f }, { 4, 7, 0x43, 0, 0x0f }, { 4, 6, 0x45, 1, 0x01 }, { 2, 7, 0x40, 4, 0x0f }, { 2, 11, 0x40, 0, 0x0f } }; struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; const struct bwn_stxtable *st; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; int i, error; uint16_t tmp; bwn_phy_lp_readsprom(mac); /* XXX bad place */ bwn_phy_lp_bbinit(mac); /* initialize RF */ BWN_PHY_SET(mac, BWN_PHY_4WIRECTL, 0x2); DELAY(1); BWN_PHY_MASK(mac, BWN_PHY_4WIRECTL, 0xfffd); DELAY(1); if (mac->mac_phy.rf_ver == 0x2062) bwn_phy_lp_b2062_init(mac); else { bwn_phy_lp_b2063_init(mac); /* synchronize stx table. */ for (i = 0; i < N(tables); i++) { st = &tables[i]; tmp = BWN_RF_READ(mac, st->st_rfaddr); tmp >>= st->st_rfshift; tmp <<= st->st_physhift; BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xf2 + st->st_phyoffset), ~(st->st_mask << st->st_physhift), tmp); } BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xf0), 0x5f80); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xf1), 0); } /* calibrate RC */ if (mac->mac_phy.rev >= 2) bwn_phy_lp_rxcal_r2(mac); else if (!plp->plp_rccap) { if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) bwn_phy_lp_rccal_r12(mac); } else bwn_phy_lp_set_rccap(mac); error = bwn_phy_lp_switch_channel(mac, 7); if (error) device_printf(sc->sc_dev, "failed to change channel 7 (%d)\n", error); bwn_phy_lp_txpctl_init(mac); bwn_phy_lp_calib(mac); return (0); } static uint16_t bwn_phy_lp_read(struct bwn_mac *mac, uint16_t reg) { BWN_WRITE_2(mac, BWN_PHYCTL, reg); return (BWN_READ_2(mac, BWN_PHYDATA)); } static void bwn_phy_lp_write(struct bwn_mac *mac, uint16_t reg, uint16_t value) { BWN_WRITE_2(mac, BWN_PHYCTL, reg); BWN_WRITE_2(mac, BWN_PHYDATA, value); } static void bwn_phy_lp_maskset(struct bwn_mac *mac, uint16_t reg, uint16_t mask, uint16_t set) { BWN_WRITE_2(mac, BWN_PHYCTL, reg); BWN_WRITE_2(mac, BWN_PHYDATA, (BWN_READ_2(mac, BWN_PHYDATA) & mask) | set); } static uint16_t bwn_phy_lp_rf_read(struct bwn_mac *mac, uint16_t reg) { KASSERT(reg != 1, ("unaccessible register %d", reg)); if (mac->mac_phy.rev < 2 && reg != 0x4001) reg |= 0x100; if (mac->mac_phy.rev >= 2) reg |= 0x200; BWN_WRITE_2(mac, BWN_RFCTL, reg); return BWN_READ_2(mac, BWN_RFDATALO); } static void bwn_phy_lp_rf_write(struct bwn_mac *mac, uint16_t reg, uint16_t value) { KASSERT(reg != 1, ("unaccessible register %d", reg)); BWN_WRITE_2(mac, BWN_RFCTL, reg); BWN_WRITE_2(mac, BWN_RFDATALO, value); } static void bwn_phy_lp_rf_onoff(struct bwn_mac *mac, int on) { if (on) { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xe0ff); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, (mac->mac_phy.rev >= 2) ? 0xf7f7 : 0xffe7); return; } if (mac->mac_phy.rev >= 2) { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x83ff); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x1f00); BWN_PHY_MASK(mac, BWN_PHY_AFE_DDFS, 0x80ff); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xdfff); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x0808); return; } BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xe0ff); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x1f00); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfcff); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x0018); } static int bwn_phy_lp_switch_channel(struct bwn_mac *mac, uint32_t chan) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_lp *plp = &phy->phy_lp; int error; if (phy->rf_ver == 0x2063) { error = bwn_phy_lp_b2063_switch_channel(mac, chan); if (error) return (error); } else { error = bwn_phy_lp_b2062_switch_channel(mac, chan); if (error) return (error); bwn_phy_lp_set_anafilter(mac, chan); bwn_phy_lp_set_gaintbl(mac, ieee80211_ieee2mhz(chan, 0)); } plp->plp_chan = chan; BWN_WRITE_2(mac, BWN_CHANNEL, chan); return (0); } static uint32_t bwn_phy_lp_get_default_chan(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; return (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan) ? 1 : 36); } static void bwn_phy_lp_set_antenna(struct bwn_mac *mac, int antenna) { struct bwn_phy *phy = &mac->mac_phy; struct bwn_phy_lp *plp = &phy->phy_lp; if (phy->rev >= 2 || antenna > BWN_ANTAUTO1) return; bwn_hf_write(mac, bwn_hf_read(mac) & ~BWN_HF_UCODE_ANTDIV_HELPER); BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xfffd, antenna & 0x2); BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xfffe, antenna & 0x1); bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_UCODE_ANTDIV_HELPER); plp->plp_antenna = antenna; } static void bwn_phy_lp_task_60s(struct bwn_mac *mac) { bwn_phy_lp_calib(mac); } static void bwn_phy_lp_readsprom(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { plp->plp_txisoband_m = siba_sprom_get_tri2g(sc->sc_dev); plp->plp_bxarch = siba_sprom_get_bxa2g(sc->sc_dev); plp->plp_rxpwroffset = siba_sprom_get_rxpo2g(sc->sc_dev); plp->plp_rssivf = siba_sprom_get_rssismf2g(sc->sc_dev); plp->plp_rssivc = siba_sprom_get_rssismc2g(sc->sc_dev); plp->plp_rssigs = siba_sprom_get_rssisav2g(sc->sc_dev); return; } plp->plp_txisoband_l = siba_sprom_get_tri5gl(sc->sc_dev); plp->plp_txisoband_m = siba_sprom_get_tri5g(sc->sc_dev); plp->plp_txisoband_h = siba_sprom_get_tri5gh(sc->sc_dev); plp->plp_bxarch = siba_sprom_get_bxa5g(sc->sc_dev); plp->plp_rxpwroffset = siba_sprom_get_rxpo5g(sc->sc_dev); plp->plp_rssivf = siba_sprom_get_rssismf5g(sc->sc_dev); plp->plp_rssivc = siba_sprom_get_rssismc5g(sc->sc_dev); plp->plp_rssigs = siba_sprom_get_rssisav5g(sc->sc_dev); } static void bwn_phy_lp_bbinit(struct bwn_mac *mac) { bwn_phy_lp_tblinit(mac); if (mac->mac_phy.rev >= 2) bwn_phy_lp_bbinit_r2(mac); else bwn_phy_lp_bbinit_r01(mac); } static void bwn_phy_lp_txpctl_init(struct bwn_mac *mac) { struct bwn_txgain gain_2ghz = { 4, 12, 12, 0 }; struct bwn_txgain gain_5ghz = { 7, 15, 14, 0 }; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; bwn_phy_lp_set_txgain(mac, IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan) ? &gain_2ghz : &gain_5ghz); bwn_phy_lp_set_bbmult(mac, 150); } static void bwn_phy_lp_calib(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; const struct bwn_rxcompco *rc = NULL; struct bwn_txgain ogain; int i, omode, oafeovr, orf, obbmult; uint8_t mode, fc = 0; if (plp->plp_chanfullcal != plp->plp_chan) { plp->plp_chanfullcal = plp->plp_chan; fc = 1; } bwn_mac_suspend(mac); /* BlueTooth Coexistance Override */ BWN_WRITE_2(mac, BWN_BTCOEX_CTL, 0x3); BWN_WRITE_2(mac, BWN_BTCOEX_TXCTL, 0xff); if (mac->mac_phy.rev >= 2) bwn_phy_lp_digflt_save(mac); bwn_phy_lp_get_txpctlmode(mac); mode = plp->plp_txpctlmode; bwn_phy_lp_set_txpctlmode(mac, BWN_PHYLP_TXPCTL_OFF); if (mac->mac_phy.rev == 0 && mode != BWN_PHYLP_TXPCTL_OFF) bwn_phy_lp_bugfix(mac); if (mac->mac_phy.rev >= 2 && fc == 1) { bwn_phy_lp_get_txpctlmode(mac); omode = plp->plp_txpctlmode; oafeovr = BWN_PHY_READ(mac, BWN_PHY_AFE_CTL_OVR) & 0x40; if (oafeovr) ogain = bwn_phy_lp_get_txgain(mac); orf = BWN_PHY_READ(mac, BWN_PHY_RF_PWR_OVERRIDE) & 0xff; obbmult = bwn_phy_lp_get_bbmult(mac); bwn_phy_lp_set_txpctlmode(mac, BWN_PHYLP_TXPCTL_OFF); if (oafeovr) bwn_phy_lp_set_txgain(mac, &ogain); bwn_phy_lp_set_bbmult(mac, obbmult); bwn_phy_lp_set_txpctlmode(mac, omode); BWN_PHY_SETMASK(mac, BWN_PHY_RF_PWR_OVERRIDE, 0xff00, orf); } bwn_phy_lp_set_txpctlmode(mac, mode); if (mac->mac_phy.rev >= 2) bwn_phy_lp_digflt_restore(mac); /* do RX IQ Calculation; assumes that noise is true. */ if (siba_get_chipid(sc->sc_dev) == 0x5354) { for (i = 0; i < N(bwn_rxcompco_5354); i++) { if (bwn_rxcompco_5354[i].rc_chan == plp->plp_chan) rc = &bwn_rxcompco_5354[i]; } } else if (mac->mac_phy.rev >= 2) rc = &bwn_rxcompco_r2; else { for (i = 0; i < N(bwn_rxcompco_r12); i++) { if (bwn_rxcompco_r12[i].rc_chan == plp->plp_chan) rc = &bwn_rxcompco_r12[i]; } } if (rc == NULL) goto fail; BWN_PHY_SETMASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0xff00, rc->rc_c1); BWN_PHY_SETMASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0x00ff, rc->rc_c0 << 8); bwn_phy_lp_set_trsw_over(mac, 1 /* TX */, 0 /* RX */); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x8); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfff7, 0); } else { BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x20); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xffdf, 0); } bwn_phy_lp_set_rxgain(mac, 0x2d5d); BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVR, 0xfffe); BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVRVAL, 0xfffe); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x800); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x800); bwn_phy_lp_set_deaf(mac, 0); /* XXX no checking return value? */ (void)bwn_phy_lp_calc_rx_iq_comp(mac, 0xfff0); bwn_phy_lp_clear_deaf(mac, 0); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xfffc); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xfff7); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xffdf); /* disable RX GAIN override. */ BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xfffe); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xffef); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xffbf); if (mac->mac_phy.rev >= 2) { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xfeff); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xfbff); BWN_PHY_MASK(mac, BWN_PHY_OFDM(0xe5), 0xfff7); } } else { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xfdff); } BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVR, 0xfffe); BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVRVAL, 0xf7ff); fail: bwn_mac_enable(mac); } static void bwn_phy_lp_switch_analog(struct bwn_mac *mac, int on) { if (on) { BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVR, 0xfff8); return; } BWN_PHY_SET(mac, BWN_PHY_AFE_CTL_OVRVAL, 0x0007); BWN_PHY_SET(mac, BWN_PHY_AFE_CTL_OVR, 0x0007); } static int bwn_phy_lp_b2063_switch_channel(struct bwn_mac *mac, uint8_t chan) { static const struct bwn_b206x_chan *bc = NULL; struct bwn_softc *sc = mac->mac_sc; uint32_t count, freqref, freqvco, freqxtal, val[3], timeout, timeoutref, tmp[6]; uint16_t old, scale, tmp16; int i, div; for (i = 0; i < N(bwn_b2063_chantable); i++) { if (bwn_b2063_chantable[i].bc_chan == chan) { bc = &bwn_b2063_chantable[i]; break; } } if (bc == NULL) return (EINVAL); BWN_RF_WRITE(mac, BWN_B2063_LOGEN_VCOBUF1, bc->bc_data[0]); BWN_RF_WRITE(mac, BWN_B2063_LOGEN_MIXER2, bc->bc_data[1]); BWN_RF_WRITE(mac, BWN_B2063_LOGEN_BUF2, bc->bc_data[2]); BWN_RF_WRITE(mac, BWN_B2063_LOGEN_RCCR1, bc->bc_data[3]); BWN_RF_WRITE(mac, BWN_B2063_A_RX_1ST3, bc->bc_data[4]); BWN_RF_WRITE(mac, BWN_B2063_A_RX_2ND1, bc->bc_data[5]); BWN_RF_WRITE(mac, BWN_B2063_A_RX_2ND4, bc->bc_data[6]); BWN_RF_WRITE(mac, BWN_B2063_A_RX_2ND7, bc->bc_data[7]); BWN_RF_WRITE(mac, BWN_B2063_A_RX_PS6, bc->bc_data[8]); BWN_RF_WRITE(mac, BWN_B2063_TX_RF_CTL2, bc->bc_data[9]); BWN_RF_WRITE(mac, BWN_B2063_TX_RF_CTL5, bc->bc_data[10]); BWN_RF_WRITE(mac, BWN_B2063_PA_CTL11, bc->bc_data[11]); old = BWN_RF_READ(mac, BWN_B2063_COM15); BWN_RF_SET(mac, BWN_B2063_COM15, 0x1e); freqxtal = siba_get_cc_pmufreq(sc->sc_dev) * 1000; freqvco = bc->bc_freq << ((bc->bc_freq > 4000) ? 1 : 2); freqref = freqxtal * 3; div = (freqxtal <= 26000000 ? 1 : 2); timeout = ((((8 * freqxtal) / (div * 5000000)) + 1) >> 1) - 1; timeoutref = ((((8 * freqxtal) / (div * (timeout + 1))) + 999999) / 1000000) + 1; BWN_RF_WRITE(mac, BWN_B2063_JTAG_VCO_CALIB3, 0x2); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_VCO_CALIB6, 0xfff8, timeout >> 2); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_VCO_CALIB7, 0xff9f,timeout << 5); BWN_RF_WRITE(mac, BWN_B2063_JTAG_VCO_CALIB5, timeoutref); val[0] = bwn_phy_lp_roundup(freqxtal, 1000000, 16); val[1] = bwn_phy_lp_roundup(freqxtal, 1000000 * div, 16); val[2] = bwn_phy_lp_roundup(freqvco, 3, 16); count = (bwn_phy_lp_roundup(val[2], val[1] + 16, 16) * (timeout + 1) * (timeoutref + 1)) - 1; BWN_RF_SETMASK(mac, BWN_B2063_JTAG_VCO_CALIB7, 0xf0, count >> 8); BWN_RF_WRITE(mac, BWN_B2063_JTAG_VCO_CALIB8, count & 0xff); tmp[0] = ((val[2] * 62500) / freqref) << 4; tmp[1] = ((val[2] * 62500) % freqref) << 4; while (tmp[1] >= freqref) { tmp[0]++; tmp[1] -= freqref; } BWN_RF_SETMASK(mac, BWN_B2063_JTAG_SG1, 0xffe0, tmp[0] >> 4); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_SG2, 0xfe0f, tmp[0] << 4); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_SG2, 0xfff0, tmp[0] >> 16); BWN_RF_WRITE(mac, BWN_B2063_JTAG_SG3, (tmp[1] >> 8) & 0xff); BWN_RF_WRITE(mac, BWN_B2063_JTAG_SG4, tmp[1] & 0xff); BWN_RF_WRITE(mac, BWN_B2063_JTAG_LF1, 0xb9); BWN_RF_WRITE(mac, BWN_B2063_JTAG_LF2, 0x88); BWN_RF_WRITE(mac, BWN_B2063_JTAG_LF3, 0x28); BWN_RF_WRITE(mac, BWN_B2063_JTAG_LF4, 0x63); tmp[2] = ((41 * (val[2] - 3000)) /1200) + 27; tmp[3] = bwn_phy_lp_roundup(132000 * tmp[0], 8451, 16); if ((tmp[3] + tmp[2] - 1) / tmp[2] > 60) { scale = 1; tmp[4] = ((tmp[3] + tmp[2]) / (tmp[2] << 1)) - 8; } else { scale = 0; tmp[4] = ((tmp[3] + (tmp[2] >> 1)) / tmp[2]) - 8; } BWN_RF_SETMASK(mac, BWN_B2063_JTAG_CP2, 0xffc0, tmp[4]); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_CP2, 0xffbf, scale << 6); tmp[5] = bwn_phy_lp_roundup(100 * val[0], val[2], 16) * (tmp[4] * 8) * (scale + 1); if (tmp[5] > 150) tmp[5] = 0; BWN_RF_SETMASK(mac, BWN_B2063_JTAG_CP3, 0xffe0, tmp[5]); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_CP3, 0xffdf, scale << 5); BWN_RF_SETMASK(mac, BWN_B2063_JTAG_XTAL_12, 0xfffb, 0x4); if (freqxtal > 26000000) BWN_RF_SET(mac, BWN_B2063_JTAG_XTAL_12, 0x2); else BWN_RF_MASK(mac, BWN_B2063_JTAG_XTAL_12, 0xfd); if (val[0] == 45) BWN_RF_SET(mac, BWN_B2063_JTAG_VCO1, 0x2); else BWN_RF_MASK(mac, BWN_B2063_JTAG_VCO1, 0xfd); BWN_RF_SET(mac, BWN_B2063_PLL_SP2, 0x3); DELAY(1); BWN_RF_MASK(mac, BWN_B2063_PLL_SP2, 0xfffc); /* VCO Calibration */ BWN_RF_MASK(mac, BWN_B2063_PLL_SP1, ~0x40); tmp16 = BWN_RF_READ(mac, BWN_B2063_JTAG_CALNRST) & 0xf8; BWN_RF_WRITE(mac, BWN_B2063_JTAG_CALNRST, tmp16); DELAY(1); BWN_RF_WRITE(mac, BWN_B2063_JTAG_CALNRST, tmp16 | 0x4); DELAY(1); BWN_RF_WRITE(mac, BWN_B2063_JTAG_CALNRST, tmp16 | 0x6); DELAY(1); BWN_RF_WRITE(mac, BWN_B2063_JTAG_CALNRST, tmp16 | 0x7); DELAY(300); BWN_RF_SET(mac, BWN_B2063_PLL_SP1, 0x40); BWN_RF_WRITE(mac, BWN_B2063_COM15, old); return (0); } static int bwn_phy_lp_b2062_switch_channel(struct bwn_mac *mac, uint8_t chan) { struct bwn_softc *sc = mac->mac_sc; struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; const struct bwn_b206x_chan *bc = NULL; uint32_t freqxtal = siba_get_cc_pmufreq(sc->sc_dev) * 1000; uint32_t tmp[9]; int i; for (i = 0; i < N(bwn_b2062_chantable); i++) { if (bwn_b2062_chantable[i].bc_chan == chan) { bc = &bwn_b2062_chantable[i]; break; } } if (bc == NULL) return (EINVAL); BWN_RF_SET(mac, BWN_B2062_S_RFPLLCTL14, 0x04); BWN_RF_WRITE(mac, BWN_B2062_N_LGENATUNE0, bc->bc_data[0]); BWN_RF_WRITE(mac, BWN_B2062_N_LGENATUNE2, bc->bc_data[1]); BWN_RF_WRITE(mac, BWN_B2062_N_LGENATUNE3, bc->bc_data[2]); BWN_RF_WRITE(mac, BWN_B2062_N_TX_TUNE, bc->bc_data[3]); BWN_RF_WRITE(mac, BWN_B2062_S_LGENG_CTL1, bc->bc_data[4]); BWN_RF_WRITE(mac, BWN_B2062_N_LGENACTL5, bc->bc_data[5]); BWN_RF_WRITE(mac, BWN_B2062_N_LGENACTL6, bc->bc_data[6]); BWN_RF_WRITE(mac, BWN_B2062_N_TX_PGA, bc->bc_data[7]); BWN_RF_WRITE(mac, BWN_B2062_N_TX_PAD, bc->bc_data[8]); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL33, 0xcc); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL34, 0x07); bwn_phy_lp_b2062_reset_pllbias(mac); tmp[0] = freqxtal / 1000; tmp[1] = plp->plp_div * 1000; tmp[2] = tmp[1] * ieee80211_ieee2mhz(chan, 0); if (ieee80211_ieee2mhz(chan, 0) < 4000) tmp[2] *= 2; tmp[3] = 48 * tmp[0]; tmp[5] = tmp[2] / tmp[3]; tmp[6] = tmp[2] % tmp[3]; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL26, tmp[5]); tmp[4] = tmp[6] * 0x100; tmp[5] = tmp[4] / tmp[3]; tmp[6] = tmp[4] % tmp[3]; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL27, tmp[5]); tmp[4] = tmp[6] * 0x100; tmp[5] = tmp[4] / tmp[3]; tmp[6] = tmp[4] % tmp[3]; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL28, tmp[5]); tmp[4] = tmp[6] * 0x100; tmp[5] = tmp[4] / tmp[3]; tmp[6] = tmp[4] % tmp[3]; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL29, tmp[5] + ((2 * tmp[6]) / tmp[3])); tmp[7] = BWN_RF_READ(mac, BWN_B2062_S_RFPLLCTL19); tmp[8] = ((2 * tmp[2] * (tmp[7] + 1)) + (3 * tmp[0])) / (6 * tmp[0]); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL23, (tmp[8] >> 8) + 16); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL24, tmp[8] & 0xff); bwn_phy_lp_b2062_vco_calib(mac); if (BWN_RF_READ(mac, BWN_B2062_S_RFPLLCTL3) & 0x10) { BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL33, 0xfc); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL34, 0); bwn_phy_lp_b2062_reset_pllbias(mac); bwn_phy_lp_b2062_vco_calib(mac); if (BWN_RF_READ(mac, BWN_B2062_S_RFPLLCTL3) & 0x10) { BWN_RF_MASK(mac, BWN_B2062_S_RFPLLCTL14, ~0x04); return (EIO); } } BWN_RF_MASK(mac, BWN_B2062_S_RFPLLCTL14, ~0x04); return (0); } static void bwn_phy_lp_set_anafilter(struct bwn_mac *mac, uint8_t channel) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; uint16_t tmp = (channel == 14); if (mac->mac_phy.rev < 2) { BWN_PHY_SETMASK(mac, BWN_PHY_LP_PHY_CTL, 0xfcff, tmp << 9); if ((mac->mac_phy.rev == 1) && (plp->plp_rccap)) bwn_phy_lp_set_rccap(mac); return; } BWN_RF_WRITE(mac, BWN_B2063_TX_BB_SP3, 0x3f); } static void bwn_phy_lp_set_gaintbl(struct bwn_mac *mac, uint32_t freq) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint16_t iso, tmp[3]; KASSERT(mac->mac_phy.rev < 2, ("%s:%d: fail", __func__, __LINE__)); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) iso = plp->plp_txisoband_m; else if (freq <= 5320) iso = plp->plp_txisoband_l; else if (freq <= 5700) iso = plp->plp_txisoband_m; else iso = plp->plp_txisoband_h; tmp[0] = ((iso - 26) / 12) << 12; tmp[1] = tmp[0] + 0x1000; tmp[2] = tmp[0] + 0x2000; bwn_tab_write_multi(mac, BWN_TAB_2(13, 0), 3, tmp); bwn_tab_write_multi(mac, BWN_TAB_2(12, 0), 3, tmp); } static void bwn_phy_lp_digflt_save(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; int i; static const uint16_t addr[] = { BWN_PHY_OFDM(0xc1), BWN_PHY_OFDM(0xc2), BWN_PHY_OFDM(0xc3), BWN_PHY_OFDM(0xc4), BWN_PHY_OFDM(0xc5), BWN_PHY_OFDM(0xc6), BWN_PHY_OFDM(0xc7), BWN_PHY_OFDM(0xc8), BWN_PHY_OFDM(0xcf), }; static const uint16_t val[] = { 0xde5e, 0xe832, 0xe331, 0x4d26, 0x0026, 0x1420, 0x0020, 0xfe08, 0x0008, }; for (i = 0; i < N(addr); i++) { plp->plp_digfilt[i] = BWN_PHY_READ(mac, addr[i]); BWN_PHY_WRITE(mac, addr[i], val[i]); } } static void bwn_phy_lp_get_txpctlmode(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; uint16_t ctl; ctl = BWN_PHY_READ(mac, BWN_PHY_TX_PWR_CTL_CMD); switch (ctl & BWN_PHY_TX_PWR_CTL_CMD_MODE) { case BWN_PHY_TX_PWR_CTL_CMD_MODE_OFF: plp->plp_txpctlmode = BWN_PHYLP_TXPCTL_OFF; break; case BWN_PHY_TX_PWR_CTL_CMD_MODE_SW: plp->plp_txpctlmode = BWN_PHYLP_TXPCTL_ON_SW; break; case BWN_PHY_TX_PWR_CTL_CMD_MODE_HW: plp->plp_txpctlmode = BWN_PHYLP_TXPCTL_ON_HW; break; default: plp->plp_txpctlmode = BWN_PHYLP_TXPCTL_UNKNOWN; device_printf(sc->sc_dev, "unknown command mode\n"); break; } } static void bwn_phy_lp_set_txpctlmode(struct bwn_mac *mac, uint8_t mode) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; uint16_t ctl; uint8_t old; bwn_phy_lp_get_txpctlmode(mac); old = plp->plp_txpctlmode; if (old == mode) return; plp->plp_txpctlmode = mode; if (old != BWN_PHYLP_TXPCTL_ON_HW && mode == BWN_PHYLP_TXPCTL_ON_HW) { BWN_PHY_SETMASK(mac, BWN_PHY_TX_PWR_CTL_CMD, 0xff80, plp->plp_tssiidx); BWN_PHY_SETMASK(mac, BWN_PHY_TX_PWR_CTL_NNUM, 0x8fff, ((uint16_t)plp->plp_tssinpt << 16)); /* disable TX GAIN override */ if (mac->mac_phy.rev < 2) BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xfeff); else { BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xff7f); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xbfff); } BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVR, 0xffbf); plp->plp_txpwridx = -1; } if (mac->mac_phy.rev >= 2) { if (mode == BWN_PHYLP_TXPCTL_ON_HW) BWN_PHY_SET(mac, BWN_PHY_OFDM(0xd0), 0x2); else BWN_PHY_MASK(mac, BWN_PHY_OFDM(0xd0), 0xfffd); } /* writes TX Power Control mode */ switch (plp->plp_txpctlmode) { case BWN_PHYLP_TXPCTL_OFF: ctl = BWN_PHY_TX_PWR_CTL_CMD_MODE_OFF; break; case BWN_PHYLP_TXPCTL_ON_HW: ctl = BWN_PHY_TX_PWR_CTL_CMD_MODE_HW; break; case BWN_PHYLP_TXPCTL_ON_SW: ctl = BWN_PHY_TX_PWR_CTL_CMD_MODE_SW; break; default: ctl = 0; KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } BWN_PHY_SETMASK(mac, BWN_PHY_TX_PWR_CTL_CMD, (uint16_t)~BWN_PHY_TX_PWR_CTL_CMD_MODE, ctl); } static void bwn_phy_lp_bugfix(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; const unsigned int size = 256; struct bwn_txgain tg; uint32_t rxcomp, txgain, coeff, rfpwr, *tabs; uint16_t tssinpt, tssiidx, value[2]; uint8_t mode; int8_t txpwridx; tabs = (uint32_t *)malloc(sizeof(uint32_t) * size, M_DEVBUF, M_NOWAIT | M_ZERO); if (tabs == NULL) { device_printf(sc->sc_dev, "failed to allocate buffer.\n"); return; } bwn_phy_lp_get_txpctlmode(mac); mode = plp->plp_txpctlmode; txpwridx = plp->plp_txpwridx; tssinpt = plp->plp_tssinpt; tssiidx = plp->plp_tssiidx; bwn_tab_read_multi(mac, (mac->mac_phy.rev < 2) ? BWN_TAB_4(10, 0x140) : BWN_TAB_4(7, 0x140), size, tabs); bwn_phy_lp_tblinit(mac); bwn_phy_lp_bbinit(mac); bwn_phy_lp_txpctl_init(mac); bwn_phy_lp_rf_onoff(mac, 1); bwn_phy_lp_set_txpctlmode(mac, BWN_PHYLP_TXPCTL_OFF); bwn_tab_write_multi(mac, (mac->mac_phy.rev < 2) ? BWN_TAB_4(10, 0x140) : BWN_TAB_4(7, 0x140), size, tabs); BWN_WRITE_2(mac, BWN_CHANNEL, plp->plp_chan); plp->plp_tssinpt = tssinpt; plp->plp_tssiidx = tssiidx; bwn_phy_lp_set_anafilter(mac, plp->plp_chan); if (txpwridx != -1) { /* set TX power by index */ plp->plp_txpwridx = txpwridx; bwn_phy_lp_get_txpctlmode(mac); if (plp->plp_txpctlmode != BWN_PHYLP_TXPCTL_OFF) bwn_phy_lp_set_txpctlmode(mac, BWN_PHYLP_TXPCTL_ON_SW); if (mac->mac_phy.rev >= 2) { rxcomp = bwn_tab_read(mac, BWN_TAB_4(7, txpwridx + 320)); txgain = bwn_tab_read(mac, BWN_TAB_4(7, txpwridx + 192)); tg.tg_pad = (txgain >> 16) & 0xff; tg.tg_gm = txgain & 0xff; tg.tg_pga = (txgain >> 8) & 0xff; tg.tg_dac = (rxcomp >> 28) & 0xff; bwn_phy_lp_set_txgain(mac, &tg); } else { rxcomp = bwn_tab_read(mac, BWN_TAB_4(10, txpwridx + 320)); txgain = bwn_tab_read(mac, BWN_TAB_4(10, txpwridx + 192)); BWN_PHY_SETMASK(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL, 0xf800, (txgain >> 4) & 0x7fff); bwn_phy_lp_set_txgain_dac(mac, txgain & 0x7); bwn_phy_lp_set_txgain_pa(mac, (txgain >> 24) & 0x7f); } bwn_phy_lp_set_bbmult(mac, (rxcomp >> 20) & 0xff); /* set TX IQCC */ value[0] = (rxcomp >> 10) & 0x3ff; value[1] = rxcomp & 0x3ff; bwn_tab_write_multi(mac, BWN_TAB_2(0, 80), 2, value); coeff = bwn_tab_read(mac, (mac->mac_phy.rev >= 2) ? BWN_TAB_4(7, txpwridx + 448) : BWN_TAB_4(10, txpwridx + 448)); bwn_tab_write(mac, BWN_TAB_2(0, 85), coeff & 0xffff); if (mac->mac_phy.rev >= 2) { rfpwr = bwn_tab_read(mac, BWN_TAB_4(7, txpwridx + 576)); BWN_PHY_SETMASK(mac, BWN_PHY_RF_PWR_OVERRIDE, 0xff00, rfpwr & 0xffff); } bwn_phy_lp_set_txgain_override(mac); } if (plp->plp_rccap) bwn_phy_lp_set_rccap(mac); bwn_phy_lp_set_antenna(mac, plp->plp_antenna); bwn_phy_lp_set_txpctlmode(mac, mode); free(tabs, M_DEVBUF); } static void bwn_phy_lp_digflt_restore(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; int i; static const uint16_t addr[] = { BWN_PHY_OFDM(0xc1), BWN_PHY_OFDM(0xc2), BWN_PHY_OFDM(0xc3), BWN_PHY_OFDM(0xc4), BWN_PHY_OFDM(0xc5), BWN_PHY_OFDM(0xc6), BWN_PHY_OFDM(0xc7), BWN_PHY_OFDM(0xc8), BWN_PHY_OFDM(0xcf), }; for (i = 0; i < N(addr); i++) BWN_PHY_WRITE(mac, addr[i], plp->plp_digfilt[i]); } static void bwn_phy_lp_tblinit(struct bwn_mac *mac) { uint32_t freq = ieee80211_ieee2mhz(bwn_phy_lp_get_default_chan(mac), 0); if (mac->mac_phy.rev < 2) { bwn_phy_lp_tblinit_r01(mac); bwn_phy_lp_tblinit_txgain(mac); bwn_phy_lp_set_gaintbl(mac, freq); return; } bwn_phy_lp_tblinit_r2(mac); bwn_phy_lp_tblinit_txgain(mac); } struct bwn_wpair { uint16_t reg; uint16_t value; }; struct bwn_smpair { uint16_t offset; uint16_t mask; uint16_t set; }; static void bwn_phy_lp_bbinit_r2(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static const struct bwn_wpair v1[] = { { BWN_PHY_AFE_DAC_CTL, 0x50 }, { BWN_PHY_AFE_CTL, 0x8800 }, { BWN_PHY_AFE_CTL_OVR, 0 }, { BWN_PHY_AFE_CTL_OVRVAL, 0 }, { BWN_PHY_RF_OVERRIDE_0, 0 }, { BWN_PHY_RF_OVERRIDE_2, 0 }, { BWN_PHY_OFDM(0xf9), 0 }, { BWN_PHY_TR_LOOKUP_1, 0 } }; static const struct bwn_smpair v2[] = { { BWN_PHY_OFDMSYNCTHRESH0, 0xff00, 0xb4 }, { BWN_PHY_DCOFFSETTRANSIENT, 0xf8ff, 0x200 }, { BWN_PHY_DCOFFSETTRANSIENT, 0xff00, 0x7f }, { BWN_PHY_GAINDIRECTMISMATCH, 0xff0f, 0x40 }, { BWN_PHY_PREAMBLECONFIRMTO, 0xff00, 0x2 } }; static const struct bwn_smpair v3[] = { { BWN_PHY_OFDM(0xfe), 0xffe0, 0x1f }, { BWN_PHY_OFDM(0xff), 0xffe0, 0xc }, { BWN_PHY_OFDM(0x100), 0xff00, 0x19 }, { BWN_PHY_OFDM(0xff), 0x03ff, 0x3c00 }, { BWN_PHY_OFDM(0xfe), 0xfc1f, 0x3e0 }, { BWN_PHY_OFDM(0xff), 0xffe0, 0xc }, { BWN_PHY_OFDM(0x100), 0x00ff, 0x1900 }, { BWN_PHY_CLIPCTRTHRESH, 0x83ff, 0x5800 }, { BWN_PHY_CLIPCTRTHRESH, 0xffe0, 0x12 }, { BWN_PHY_GAINMISMATCH, 0x0fff, 0x9000 }, }; int i; for (i = 0; i < N(v1); i++) BWN_PHY_WRITE(mac, v1[i].reg, v1[i].value); BWN_PHY_SET(mac, BWN_PHY_ADC_COMPENSATION_CTL, 0x10); for (i = 0; i < N(v2); i++) BWN_PHY_SETMASK(mac, v2[i].offset, v2[i].mask, v2[i].set); BWN_PHY_MASK(mac, BWN_PHY_CRSGAIN_CTL, ~0x4000); BWN_PHY_MASK(mac, BWN_PHY_CRSGAIN_CTL, ~0x2000); BWN_PHY_SET(mac, BWN_PHY_OFDM(0x10a), 0x1); if (siba_get_pci_revid(sc->sc_dev) >= 0x18) { bwn_tab_write(mac, BWN_TAB_4(17, 65), 0xec); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x10a), 0xff01, 0x14); } else { BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0x10a), 0xff01, 0x10); } BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xdf), 0xff00, 0xf4); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xdf), 0x00ff, 0xf100); BWN_PHY_WRITE(mac, BWN_PHY_CLIPTHRESH, 0x48); BWN_PHY_SETMASK(mac, BWN_PHY_HIGAINDB, 0xff00, 0x46); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xe4), 0xff00, 0x10); BWN_PHY_SETMASK(mac, BWN_PHY_PWR_THRESH1, 0xfff0, 0x9); BWN_PHY_MASK(mac, BWN_PHY_GAINDIRECTMISMATCH, ~0xf); BWN_PHY_SETMASK(mac, BWN_PHY_VERYLOWGAINDB, 0x00ff, 0x5500); BWN_PHY_SETMASK(mac, BWN_PHY_CLIPCTRTHRESH, 0xfc1f, 0xa0); BWN_PHY_SETMASK(mac, BWN_PHY_GAINDIRECTMISMATCH, 0xe0ff, 0x300); BWN_PHY_SETMASK(mac, BWN_PHY_HIGAINDB, 0x00ff, 0x2a00); if ((siba_get_chipid(sc->sc_dev) == 0x4325) && (siba_get_chiprev(sc->sc_dev) == 0)) { BWN_PHY_SETMASK(mac, BWN_PHY_LOWGAINDB, 0x00ff, 0x2100); BWN_PHY_SETMASK(mac, BWN_PHY_VERYLOWGAINDB, 0xff00, 0xa); } else { BWN_PHY_SETMASK(mac, BWN_PHY_LOWGAINDB, 0x00ff, 0x1e00); BWN_PHY_SETMASK(mac, BWN_PHY_VERYLOWGAINDB, 0xff00, 0xd); } for (i = 0; i < N(v3); i++) BWN_PHY_SETMASK(mac, v3[i].offset, v3[i].mask, v3[i].set); if ((siba_get_chipid(sc->sc_dev) == 0x4325) && (siba_get_chiprev(sc->sc_dev) == 0)) { bwn_tab_write(mac, BWN_TAB_2(0x08, 0x14), 0); bwn_tab_write(mac, BWN_TAB_2(0x08, 0x12), 0x40); } if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { BWN_PHY_SET(mac, BWN_PHY_CRSGAIN_CTL, 0x40); BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xf0ff, 0xb00); BWN_PHY_SETMASK(mac, BWN_PHY_SYNCPEAKCNT, 0xfff8, 0x6); BWN_PHY_SETMASK(mac, BWN_PHY_MINPWR_LEVEL, 0x00ff, 0x9d00); BWN_PHY_SETMASK(mac, BWN_PHY_MINPWR_LEVEL, 0xff00, 0xa1); BWN_PHY_MASK(mac, BWN_PHY_IDLEAFTERPKTRXTO, 0x00ff); } else BWN_PHY_MASK(mac, BWN_PHY_CRSGAIN_CTL, ~0x40); BWN_PHY_SETMASK(mac, BWN_PHY_CRS_ED_THRESH, 0xff00, 0xb3); BWN_PHY_SETMASK(mac, BWN_PHY_CRS_ED_THRESH, 0x00ff, 0xad00); BWN_PHY_SETMASK(mac, BWN_PHY_INPUT_PWRDB, 0xff00, plp->plp_rxpwroffset); BWN_PHY_SET(mac, BWN_PHY_RESET_CTL, 0x44); BWN_PHY_WRITE(mac, BWN_PHY_RESET_CTL, 0x80); BWN_PHY_WRITE(mac, BWN_PHY_AFE_RSSI_CTL_0, 0xa954); BWN_PHY_WRITE(mac, BWN_PHY_AFE_RSSI_CTL_1, 0x2000 | ((uint16_t)plp->plp_rssigs << 10) | ((uint16_t)plp->plp_rssivc << 4) | plp->plp_rssivf); if ((siba_get_chipid(sc->sc_dev) == 0x4325) && (siba_get_chiprev(sc->sc_dev) == 0)) { BWN_PHY_SET(mac, BWN_PHY_AFE_ADC_CTL_0, 0x1c); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_CTL, 0x00ff, 0x8800); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_ADC_CTL_1, 0xfc3c, 0x0400); } bwn_phy_lp_digflt_save(mac); } static void bwn_phy_lp_bbinit_r01(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static const struct bwn_smpair v1[] = { { BWN_PHY_CLIPCTRTHRESH, 0xffe0, 0x0005 }, { BWN_PHY_CLIPCTRTHRESH, 0xfc1f, 0x0180 }, { BWN_PHY_CLIPCTRTHRESH, 0x83ff, 0x3c00 }, { BWN_PHY_GAINDIRECTMISMATCH, 0xfff0, 0x0005 }, { BWN_PHY_GAIN_MISMATCH_LIMIT, 0xffc0, 0x001a }, { BWN_PHY_CRS_ED_THRESH, 0xff00, 0x00b3 }, { BWN_PHY_CRS_ED_THRESH, 0x00ff, 0xad00 } }; static const struct bwn_smpair v2[] = { { BWN_PHY_TR_LOOKUP_1, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_1, 0x3f00, 0x0900 }, { BWN_PHY_TR_LOOKUP_2, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_2, 0xc0ff, 0x0b00 }, { BWN_PHY_TR_LOOKUP_3, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_3, 0xc0ff, 0x0400 }, { BWN_PHY_TR_LOOKUP_4, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_4, 0xc0ff, 0x0b00 }, { BWN_PHY_TR_LOOKUP_5, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_5, 0xc0ff, 0x0900 }, { BWN_PHY_TR_LOOKUP_6, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_6, 0xc0ff, 0x0b00 }, { BWN_PHY_TR_LOOKUP_7, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_7, 0xc0ff, 0x0900 }, { BWN_PHY_TR_LOOKUP_8, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_8, 0xc0ff, 0x0b00 } }; static const struct bwn_smpair v3[] = { { BWN_PHY_TR_LOOKUP_1, 0xffc0, 0x0001 }, { BWN_PHY_TR_LOOKUP_1, 0xc0ff, 0x0400 }, { BWN_PHY_TR_LOOKUP_2, 0xffc0, 0x0001 }, { BWN_PHY_TR_LOOKUP_2, 0xc0ff, 0x0500 }, { BWN_PHY_TR_LOOKUP_3, 0xffc0, 0x0002 }, { BWN_PHY_TR_LOOKUP_3, 0xc0ff, 0x0800 }, { BWN_PHY_TR_LOOKUP_4, 0xffc0, 0x0002 }, { BWN_PHY_TR_LOOKUP_4, 0xc0ff, 0x0a00 } }; static const struct bwn_smpair v4[] = { { BWN_PHY_TR_LOOKUP_1, 0xffc0, 0x0004 }, { BWN_PHY_TR_LOOKUP_1, 0xc0ff, 0x0800 }, { BWN_PHY_TR_LOOKUP_2, 0xffc0, 0x0004 }, { BWN_PHY_TR_LOOKUP_2, 0xc0ff, 0x0c00 }, { BWN_PHY_TR_LOOKUP_3, 0xffc0, 0x0002 }, { BWN_PHY_TR_LOOKUP_3, 0xc0ff, 0x0100 }, { BWN_PHY_TR_LOOKUP_4, 0xffc0, 0x0002 }, { BWN_PHY_TR_LOOKUP_4, 0xc0ff, 0x0300 } }; static const struct bwn_smpair v5[] = { { BWN_PHY_TR_LOOKUP_1, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_1, 0xc0ff, 0x0900 }, { BWN_PHY_TR_LOOKUP_2, 0xffc0, 0x000a }, { BWN_PHY_TR_LOOKUP_2, 0xc0ff, 0x0b00 }, { BWN_PHY_TR_LOOKUP_3, 0xffc0, 0x0006 }, { BWN_PHY_TR_LOOKUP_3, 0xc0ff, 0x0500 }, { BWN_PHY_TR_LOOKUP_4, 0xffc0, 0x0006 }, { BWN_PHY_TR_LOOKUP_4, 0xc0ff, 0x0700 } }; int i; uint16_t tmp, tmp2; BWN_PHY_MASK(mac, BWN_PHY_AFE_DAC_CTL, 0xf7ff); BWN_PHY_WRITE(mac, BWN_PHY_AFE_CTL, 0); BWN_PHY_WRITE(mac, BWN_PHY_AFE_CTL_OVR, 0); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_0, 0); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_2, 0); BWN_PHY_SET(mac, BWN_PHY_AFE_DAC_CTL, 0x0004); BWN_PHY_SETMASK(mac, BWN_PHY_OFDMSYNCTHRESH0, 0xff00, 0x0078); BWN_PHY_SETMASK(mac, BWN_PHY_CLIPCTRTHRESH, 0x83ff, 0x5800); BWN_PHY_WRITE(mac, BWN_PHY_ADC_COMPENSATION_CTL, 0x0016); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_ADC_CTL_0, 0xfff8, 0x0004); BWN_PHY_SETMASK(mac, BWN_PHY_VERYLOWGAINDB, 0x00ff, 0x5400); BWN_PHY_SETMASK(mac, BWN_PHY_HIGAINDB, 0x00ff, 0x2400); BWN_PHY_SETMASK(mac, BWN_PHY_LOWGAINDB, 0x00ff, 0x2100); BWN_PHY_SETMASK(mac, BWN_PHY_VERYLOWGAINDB, 0xff00, 0x0006); BWN_PHY_MASK(mac, BWN_PHY_RX_RADIO_CTL, 0xfffe); for (i = 0; i < N(v1); i++) BWN_PHY_SETMASK(mac, v1[i].offset, v1[i].mask, v1[i].set); BWN_PHY_SETMASK(mac, BWN_PHY_INPUT_PWRDB, 0xff00, plp->plp_rxpwroffset); if ((siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_FEM) && ((IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) || (siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_LDO_PAREF))) { siba_cc_pmu_set_ldovolt(sc->sc_dev, SIBA_LDO_PAREF, 0x28); siba_cc_pmu_set_ldoparef(sc->sc_dev, 1); if (mac->mac_phy.rev == 0) BWN_PHY_SETMASK(mac, BWN_PHY_LP_RF_SIGNAL_LUT, 0xffcf, 0x0010); bwn_tab_write(mac, BWN_TAB_2(11, 7), 60); } else { siba_cc_pmu_set_ldoparef(sc->sc_dev, 0); BWN_PHY_SETMASK(mac, BWN_PHY_LP_RF_SIGNAL_LUT, 0xffcf, 0x0020); bwn_tab_write(mac, BWN_TAB_2(11, 7), 100); } tmp = plp->plp_rssivf | plp->plp_rssivc << 4 | 0xa000; BWN_PHY_WRITE(mac, BWN_PHY_AFE_RSSI_CTL_0, tmp); if (siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_RSSIINV) BWN_PHY_SETMASK(mac, BWN_PHY_AFE_RSSI_CTL_1, 0xf000, 0x0aaa); else BWN_PHY_SETMASK(mac, BWN_PHY_AFE_RSSI_CTL_1, 0xf000, 0x02aa); bwn_tab_write(mac, BWN_TAB_2(11, 1), 24); BWN_PHY_SETMASK(mac, BWN_PHY_RX_RADIO_CTL, 0xfff9, (plp->plp_bxarch << 1)); if (mac->mac_phy.rev == 1 && (siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_FEM_BT)) { for (i = 0; i < N(v2); i++) BWN_PHY_SETMASK(mac, v2[i].offset, v2[i].mask, v2[i].set); } else if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan) || (siba_get_pci_subdevice(sc->sc_dev) == 0x048a) || ((mac->mac_phy.rev == 0) && (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_FEM))) { for (i = 0; i < N(v3); i++) BWN_PHY_SETMASK(mac, v3[i].offset, v3[i].mask, v3[i].set); } else if (mac->mac_phy.rev == 1 || (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_FEM)) { for (i = 0; i < N(v4); i++) BWN_PHY_SETMASK(mac, v4[i].offset, v4[i].mask, v4[i].set); } else { for (i = 0; i < N(v5); i++) BWN_PHY_SETMASK(mac, v5[i].offset, v5[i].mask, v5[i].set); } if (mac->mac_phy.rev == 1 && (siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_LDO_PAREF)) { BWN_PHY_COPY(mac, BWN_PHY_TR_LOOKUP_5, BWN_PHY_TR_LOOKUP_1); BWN_PHY_COPY(mac, BWN_PHY_TR_LOOKUP_6, BWN_PHY_TR_LOOKUP_2); BWN_PHY_COPY(mac, BWN_PHY_TR_LOOKUP_7, BWN_PHY_TR_LOOKUP_3); BWN_PHY_COPY(mac, BWN_PHY_TR_LOOKUP_8, BWN_PHY_TR_LOOKUP_4); } if ((siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_FEM_BT) && (siba_get_chipid(sc->sc_dev) == 0x5354) && (siba_get_chippkg(sc->sc_dev) == SIBA_CHIPPACK_BCM4712S)) { BWN_PHY_SET(mac, BWN_PHY_CRSGAIN_CTL, 0x0006); BWN_PHY_WRITE(mac, BWN_PHY_GPIO_SELECT, 0x0005); BWN_PHY_WRITE(mac, BWN_PHY_GPIO_OUTEN, 0xffff); bwn_hf_write(mac, bwn_hf_read(mac) | BWN_HF_PR45960W); } if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { BWN_PHY_SET(mac, BWN_PHY_LP_PHY_CTL, 0x8000); BWN_PHY_SET(mac, BWN_PHY_CRSGAIN_CTL, 0x0040); BWN_PHY_SETMASK(mac, BWN_PHY_MINPWR_LEVEL, 0x00ff, 0xa400); BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xf0ff, 0x0b00); BWN_PHY_SETMASK(mac, BWN_PHY_SYNCPEAKCNT, 0xfff8, 0x0007); BWN_PHY_SETMASK(mac, BWN_PHY_DSSS_CONFIRM_CNT, 0xfff8, 0x0003); BWN_PHY_SETMASK(mac, BWN_PHY_DSSS_CONFIRM_CNT, 0xffc7, 0x0020); BWN_PHY_MASK(mac, BWN_PHY_IDLEAFTERPKTRXTO, 0x00ff); } else { BWN_PHY_MASK(mac, BWN_PHY_LP_PHY_CTL, 0x7fff); BWN_PHY_MASK(mac, BWN_PHY_CRSGAIN_CTL, 0xffbf); } if (mac->mac_phy.rev == 1) { tmp = BWN_PHY_READ(mac, BWN_PHY_CLIPCTRTHRESH); tmp2 = (tmp & 0x03e0) >> 5; tmp2 |= tmp2 << 5; BWN_PHY_WRITE(mac, BWN_PHY_4C3, tmp2); tmp = BWN_PHY_READ(mac, BWN_PHY_GAINDIRECTMISMATCH); tmp2 = (tmp & 0x1f00) >> 8; tmp2 |= tmp2 << 5; BWN_PHY_WRITE(mac, BWN_PHY_4C4, tmp2); tmp = BWN_PHY_READ(mac, BWN_PHY_VERYLOWGAINDB); tmp2 = tmp & 0x00ff; tmp2 |= tmp << 8; BWN_PHY_WRITE(mac, BWN_PHY_4C5, tmp2); } } struct bwn_b2062_freq { uint16_t freq; uint8_t value[6]; }; static void bwn_phy_lp_b2062_init(struct bwn_mac *mac) { #define CALC_CTL7(freq, div) \ (((800000000 * (div) + (freq)) / (2 * (freq)) - 8) & 0xff) #define CALC_CTL18(freq, div) \ ((((100 * (freq) + 16000000 * (div)) / (32000000 * (div))) - 1) & 0xff) #define CALC_CTL19(freq, div) \ ((((2 * (freq) + 1000000 * (div)) / (2000000 * (div))) - 1) & 0xff) struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static const struct bwn_b2062_freq freqdata_tab[] = { { 12000, { 6, 6, 6, 6, 10, 6 } }, { 13000, { 4, 4, 4, 4, 11, 7 } }, { 14400, { 3, 3, 3, 3, 12, 7 } }, { 16200, { 3, 3, 3, 3, 13, 8 } }, { 18000, { 2, 2, 2, 2, 14, 8 } }, { 19200, { 1, 1, 1, 1, 14, 9 } } }; static const struct bwn_wpair v1[] = { { BWN_B2062_N_TXCTL3, 0 }, { BWN_B2062_N_TXCTL4, 0 }, { BWN_B2062_N_TXCTL5, 0 }, { BWN_B2062_N_TXCTL6, 0 }, { BWN_B2062_N_PDNCTL0, 0x40 }, { BWN_B2062_N_PDNCTL0, 0 }, { BWN_B2062_N_CALIB_TS, 0x10 }, { BWN_B2062_N_CALIB_TS, 0 } }; const struct bwn_b2062_freq *f = NULL; uint32_t xtalfreq, ref; unsigned int i; bwn_phy_lp_b2062_tblinit(mac); for (i = 0; i < N(v1); i++) BWN_RF_WRITE(mac, v1[i].reg, v1[i].value); if (mac->mac_phy.rev > 0) BWN_RF_WRITE(mac, BWN_B2062_S_BG_CTL1, (BWN_RF_READ(mac, BWN_B2062_N_COM2) >> 1) | 0x80); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) BWN_RF_SET(mac, BWN_B2062_N_TSSI_CTL0, 0x1); else BWN_RF_MASK(mac, BWN_B2062_N_TSSI_CTL0, ~0x1); KASSERT(siba_get_cc_caps(sc->sc_dev) & SIBA_CC_CAPS_PMU, ("%s:%d: fail", __func__, __LINE__)); xtalfreq = siba_get_cc_pmufreq(sc->sc_dev) * 1000; KASSERT(xtalfreq != 0, ("%s:%d: fail", __func__, __LINE__)); if (xtalfreq <= 30000000) { plp->plp_div = 1; BWN_RF_MASK(mac, BWN_B2062_S_RFPLLCTL1, 0xfffb); } else { plp->plp_div = 2; BWN_RF_SET(mac, BWN_B2062_S_RFPLLCTL1, 0x4); } BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL7, CALC_CTL7(xtalfreq, plp->plp_div)); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL18, CALC_CTL18(xtalfreq, plp->plp_div)); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL19, CALC_CTL19(xtalfreq, plp->plp_div)); ref = (1000 * plp->plp_div + 2 * xtalfreq) / (2000 * plp->plp_div); ref &= 0xffff; for (i = 0; i < N(freqdata_tab); i++) { if (ref < freqdata_tab[i].freq) { f = &freqdata_tab[i]; break; } } if (f == NULL) f = &freqdata_tab[N(freqdata_tab) - 1]; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL8, ((uint16_t)(f->value[1]) << 4) | f->value[0]); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL9, ((uint16_t)(f->value[3]) << 4) | f->value[2]); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL10, f->value[4]); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL11, f->value[5]); #undef CALC_CTL7 #undef CALC_CTL18 #undef CALC_CTL19 } static void bwn_phy_lp_b2063_init(struct bwn_mac *mac) { bwn_phy_lp_b2063_tblinit(mac); BWN_RF_WRITE(mac, BWN_B2063_LOGEN_SP5, 0); BWN_RF_SET(mac, BWN_B2063_COM8, 0x38); BWN_RF_WRITE(mac, BWN_B2063_REG_SP1, 0x56); BWN_RF_MASK(mac, BWN_B2063_RX_BB_CTL2, ~0x2); BWN_RF_WRITE(mac, BWN_B2063_PA_SP7, 0); BWN_RF_WRITE(mac, BWN_B2063_TX_RF_SP6, 0x20); BWN_RF_WRITE(mac, BWN_B2063_TX_RF_SP9, 0x40); if (mac->mac_phy.rev == 2) { BWN_RF_WRITE(mac, BWN_B2063_PA_SP3, 0xa0); BWN_RF_WRITE(mac, BWN_B2063_PA_SP4, 0xa0); BWN_RF_WRITE(mac, BWN_B2063_PA_SP2, 0x18); } else { BWN_RF_WRITE(mac, BWN_B2063_PA_SP3, 0x20); BWN_RF_WRITE(mac, BWN_B2063_PA_SP2, 0x20); } } static void bwn_phy_lp_rxcal_r2(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; static const struct bwn_wpair v1[] = { { BWN_B2063_RX_BB_SP8, 0x0 }, { BWN_B2063_RC_CALIB_CTL1, 0x7e }, { BWN_B2063_RC_CALIB_CTL1, 0x7c }, { BWN_B2063_RC_CALIB_CTL2, 0x15 }, { BWN_B2063_RC_CALIB_CTL3, 0x70 }, { BWN_B2063_RC_CALIB_CTL4, 0x52 }, { BWN_B2063_RC_CALIB_CTL5, 0x1 }, { BWN_B2063_RC_CALIB_CTL1, 0x7d } }; static const struct bwn_wpair v2[] = { { BWN_B2063_TX_BB_SP3, 0x0 }, { BWN_B2063_RC_CALIB_CTL1, 0x7e }, { BWN_B2063_RC_CALIB_CTL1, 0x7c }, { BWN_B2063_RC_CALIB_CTL2, 0x55 }, { BWN_B2063_RC_CALIB_CTL3, 0x76 } }; uint32_t freqxtal = siba_get_cc_pmufreq(sc->sc_dev) * 1000; int i; uint8_t tmp; tmp = BWN_RF_READ(mac, BWN_B2063_RX_BB_SP8) & 0xff; for (i = 0; i < 2; i++) BWN_RF_WRITE(mac, v1[i].reg, v1[i].value); BWN_RF_MASK(mac, BWN_B2063_PLL_SP1, 0xf7); for (i = 2; i < N(v1); i++) BWN_RF_WRITE(mac, v1[i].reg, v1[i].value); for (i = 0; i < 10000; i++) { if (BWN_RF_READ(mac, BWN_B2063_RC_CALIB_CTL6) & 0x2) break; DELAY(1000); } if (!(BWN_RF_READ(mac, BWN_B2063_RC_CALIB_CTL6) & 0x2)) BWN_RF_WRITE(mac, BWN_B2063_RX_BB_SP8, tmp); tmp = BWN_RF_READ(mac, BWN_B2063_TX_BB_SP3) & 0xff; for (i = 0; i < N(v2); i++) BWN_RF_WRITE(mac, v2[i].reg, v2[i].value); if (freqxtal == 24000000) { BWN_RF_WRITE(mac, BWN_B2063_RC_CALIB_CTL4, 0xfc); BWN_RF_WRITE(mac, BWN_B2063_RC_CALIB_CTL5, 0x0); } else { BWN_RF_WRITE(mac, BWN_B2063_RC_CALIB_CTL4, 0x13); BWN_RF_WRITE(mac, BWN_B2063_RC_CALIB_CTL5, 0x1); } BWN_RF_WRITE(mac, BWN_B2063_PA_SP7, 0x7d); for (i = 0; i < 10000; i++) { if (BWN_RF_READ(mac, BWN_B2063_RC_CALIB_CTL6) & 0x2) break; DELAY(1000); } if (!(BWN_RF_READ(mac, BWN_B2063_RC_CALIB_CTL6) & 0x2)) BWN_RF_WRITE(mac, BWN_B2063_TX_BB_SP3, tmp); BWN_RF_WRITE(mac, BWN_B2063_RC_CALIB_CTL1, 0x7e); } static void bwn_phy_lp_rccal_r12(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct bwn_phy_lp_iq_est ie; struct bwn_txgain tx_gains; static const uint32_t pwrtbl[21] = { 0x10000, 0x10557, 0x10e2d, 0x113e0, 0x10f22, 0x0ff64, 0x0eda2, 0x0e5d4, 0x0efd1, 0x0fbe8, 0x0b7b8, 0x04b35, 0x01a5e, 0x00a0b, 0x00444, 0x001fd, 0x000ff, 0x00088, 0x0004c, 0x0002c, 0x0001a, }; uint32_t npwr, ipwr, sqpwr, tmp; int loopback, i, j, sum, error; uint16_t save[7]; uint8_t txo, bbmult, txpctlmode; error = bwn_phy_lp_switch_channel(mac, 7); if (error) device_printf(sc->sc_dev, "failed to change channel to 7 (%d)\n", error); txo = (BWN_PHY_READ(mac, BWN_PHY_AFE_CTL_OVR) & 0x40) ? 1 : 0; bbmult = bwn_phy_lp_get_bbmult(mac); if (txo) tx_gains = bwn_phy_lp_get_txgain(mac); save[0] = BWN_PHY_READ(mac, BWN_PHY_RF_OVERRIDE_0); save[1] = BWN_PHY_READ(mac, BWN_PHY_RF_OVERRIDE_VAL_0); save[2] = BWN_PHY_READ(mac, BWN_PHY_AFE_CTL_OVR); save[3] = BWN_PHY_READ(mac, BWN_PHY_AFE_CTL_OVRVAL); save[4] = BWN_PHY_READ(mac, BWN_PHY_RF_OVERRIDE_2); save[5] = BWN_PHY_READ(mac, BWN_PHY_RF_OVERRIDE_2_VAL); save[6] = BWN_PHY_READ(mac, BWN_PHY_LP_PHY_CTL); bwn_phy_lp_get_txpctlmode(mac); txpctlmode = plp->plp_txpctlmode; bwn_phy_lp_set_txpctlmode(mac, BWN_PHYLP_TXPCTL_OFF); /* disable CRS */ bwn_phy_lp_set_deaf(mac, 1); bwn_phy_lp_set_trsw_over(mac, 0, 1); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfffb); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x4); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfff7); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x8); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x10); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x10); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xffdf); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x20); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xffbf); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x40); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0x7); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0x38); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xff3f); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0x100); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfdff); BWN_PHY_WRITE(mac, BWN_PHY_PS_CTL_OVERRIDE_VAL0, 0); BWN_PHY_WRITE(mac, BWN_PHY_PS_CTL_OVERRIDE_VAL1, 1); BWN_PHY_WRITE(mac, BWN_PHY_PS_CTL_OVERRIDE_VAL2, 0x20); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfbff); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xf7ff); BWN_PHY_WRITE(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL, 0); BWN_PHY_WRITE(mac, BWN_PHY_RX_GAIN_CTL_OVERRIDE_VAL, 0x45af); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_2, 0x3ff); loopback = bwn_phy_lp_loopback(mac); if (loopback == -1) goto done; bwn_phy_lp_set_rxgain_idx(mac, loopback); BWN_PHY_SETMASK(mac, BWN_PHY_LP_PHY_CTL, 0xffbf, 0x40); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfff8, 0x1); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xffc7, 0x8); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xff3f, 0xc0); tmp = 0; memset(&ie, 0, sizeof(ie)); for (i = 128; i <= 159; i++) { BWN_RF_WRITE(mac, BWN_B2062_N_RXBB_CALIB2, i); sum = 0; for (j = 5; j <= 25; j++) { bwn_phy_lp_ddfs_turnon(mac, 1, 1, j, j, 0); if (!(bwn_phy_lp_rx_iq_est(mac, 1000, 32, &ie))) goto done; sqpwr = ie.ie_ipwr + ie.ie_qpwr; ipwr = ((pwrtbl[j - 5] >> 3) + 1) >> 1; npwr = bwn_phy_lp_roundup(sqpwr, (j == 5) ? sqpwr : 0, 12); sum += ((ipwr - npwr) * (ipwr - npwr)); if ((i == 128) || (sum < tmp)) { plp->plp_rccap = i; tmp = sum; } } } bwn_phy_lp_ddfs_turnoff(mac); done: /* restore CRS */ bwn_phy_lp_clear_deaf(mac, 1); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_0, 0xff80); BWN_PHY_MASK(mac, BWN_PHY_RF_OVERRIDE_2, 0xfc00); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_VAL_0, save[1]); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_0, save[0]); BWN_PHY_WRITE(mac, BWN_PHY_AFE_CTL_OVRVAL, save[3]); BWN_PHY_WRITE(mac, BWN_PHY_AFE_CTL_OVR, save[2]); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_2_VAL, save[5]); BWN_PHY_WRITE(mac, BWN_PHY_RF_OVERRIDE_2, save[4]); BWN_PHY_WRITE(mac, BWN_PHY_LP_PHY_CTL, save[6]); bwn_phy_lp_set_bbmult(mac, bbmult); if (txo) bwn_phy_lp_set_txgain(mac, &tx_gains); bwn_phy_lp_set_txpctlmode(mac, txpctlmode); if (plp->plp_rccap) bwn_phy_lp_set_rccap(mac); } static void bwn_phy_lp_set_rccap(struct bwn_mac *mac) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; uint8_t rc_cap = (plp->plp_rccap & 0x1f) >> 1; if (mac->mac_phy.rev == 1) rc_cap = MIN(rc_cap + 5, 15); BWN_RF_WRITE(mac, BWN_B2062_N_RXBB_CALIB2, MAX(plp->plp_rccap - 4, 0x80)); BWN_RF_WRITE(mac, BWN_B2062_N_TXCTL_A, rc_cap | 0x80); BWN_RF_WRITE(mac, BWN_B2062_S_RXG_CNT16, ((plp->plp_rccap & 0x1f) >> 2) | 0x80); } static uint32_t bwn_phy_lp_roundup(uint32_t value, uint32_t div, uint8_t pre) { uint32_t i, q, r; if (div == 0) return (0); for (i = 0, q = value / div, r = value % div; i < pre; i++) { q <<= 1; if (r << 1 >= div) { q++; r = (r << 1) - div; } } if (r << 1 >= div) q++; return (q); } static void bwn_phy_lp_b2062_reset_pllbias(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL2, 0xff); DELAY(20); if (siba_get_chipid(sc->sc_dev) == 0x5354) { BWN_RF_WRITE(mac, BWN_B2062_N_COM1, 4); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL2, 4); } else { BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL2, 0); } DELAY(5); } static void bwn_phy_lp_b2062_vco_calib(struct bwn_mac *mac) { BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL21, 0x42); BWN_RF_WRITE(mac, BWN_B2062_S_RFPLLCTL21, 0x62); DELAY(200); } static void bwn_phy_lp_b2062_tblinit(struct bwn_mac *mac) { #define FLAG_A 0x01 #define FLAG_G 0x02 struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static const struct bwn_b206x_rfinit_entry bwn_b2062_init_tab[] = { { BWN_B2062_N_COM4, 0x1, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_N_PDNCTL1, 0x0, 0xca, FLAG_G, }, { BWN_B2062_N_PDNCTL3, 0x0, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_N_PDNCTL4, 0x15, 0x2a, FLAG_A | FLAG_G, }, { BWN_B2062_N_LGENC, 0xDB, 0xff, FLAG_A, }, { BWN_B2062_N_LGENATUNE0, 0xdd, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_N_LGENATUNE2, 0xdd, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_N_LGENATUNE3, 0x77, 0xB5, FLAG_A | FLAG_G, }, { BWN_B2062_N_LGENACTL3, 0x0, 0xff, FLAG_A | FLAG_G, }, { BWN_B2062_N_LGENACTL7, 0x33, 0x33, FLAG_A | FLAG_G, }, { BWN_B2062_N_RXA_CTL1, 0x0, 0x0, FLAG_G, }, { BWN_B2062_N_RXBB_CTL0, 0x82, 0x80, FLAG_A | FLAG_G, }, { BWN_B2062_N_RXBB_GAIN1, 0x4, 0x4, FLAG_A | FLAG_G, }, { BWN_B2062_N_RXBB_GAIN2, 0x0, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_N_TXCTL4, 0x3, 0x3, FLAG_A | FLAG_G, }, { BWN_B2062_N_TXCTL5, 0x2, 0x2, FLAG_A | FLAG_G, }, { BWN_B2062_N_TX_TUNE, 0x88, 0x1b, FLAG_A | FLAG_G, }, { BWN_B2062_S_COM4, 0x1, 0x0, FLAG_A | FLAG_G, }, { BWN_B2062_S_PDS_CTL0, 0xff, 0xff, FLAG_A | FLAG_G, }, { BWN_B2062_S_LGENG_CTL0, 0xf8, 0xd8, FLAG_A | FLAG_G, }, { BWN_B2062_S_LGENG_CTL1, 0x3c, 0x24, FLAG_A | FLAG_G, }, { BWN_B2062_S_LGENG_CTL8, 0x88, 0x80, FLAG_A | FLAG_G, }, { BWN_B2062_S_LGENG_CTL10, 0x88, 0x80, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL0, 0x98, 0x98, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL1, 0x10, 0x10, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL5, 0x43, 0x43, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL6, 0x47, 0x47, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL7, 0xc, 0xc, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL8, 0x11, 0x11, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL9, 0x11, 0x11, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL10, 0xe, 0xe, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL11, 0x8, 0x8, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL12, 0x33, 0x33, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL13, 0xa, 0xa, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL14, 0x6, 0x6, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL18, 0x3e, 0x3e, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL19, 0x13, 0x13, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL21, 0x62, 0x62, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL22, 0x7, 0x7, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL23, 0x16, 0x16, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL24, 0x5c, 0x5c, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL25, 0x95, 0x95, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL30, 0xa0, 0xa0, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL31, 0x4, 0x4, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL33, 0xcc, 0xcc, FLAG_A | FLAG_G, }, { BWN_B2062_S_RFPLLCTL34, 0x7, 0x7, FLAG_A | FLAG_G, }, { BWN_B2062_S_RXG_CNT8, 0xf, 0xf, FLAG_A, }, }; const struct bwn_b206x_rfinit_entry *br; unsigned int i; for (i = 0; i < N(bwn_b2062_init_tab); i++) { br = &bwn_b2062_init_tab[i]; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { if (br->br_flags & FLAG_G) BWN_RF_WRITE(mac, br->br_offset, br->br_valueg); } else { if (br->br_flags & FLAG_A) BWN_RF_WRITE(mac, br->br_offset, br->br_valuea); } } #undef FLAG_A #undef FLAG_B } static void bwn_phy_lp_b2063_tblinit(struct bwn_mac *mac) { #define FLAG_A 0x01 #define FLAG_G 0x02 struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static const struct bwn_b206x_rfinit_entry bwn_b2063_init_tab[] = { { BWN_B2063_COM1, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM10, 0x1, 0x0, FLAG_A, }, { BWN_B2063_COM16, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM17, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM18, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM19, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM20, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM21, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM22, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM23, 0x0, 0x0, FLAG_G, }, { BWN_B2063_COM24, 0x0, 0x0, FLAG_G, }, { BWN_B2063_LOGEN_SP1, 0xe8, 0xd4, FLAG_A | FLAG_G, }, { BWN_B2063_LOGEN_SP2, 0xa7, 0x53, FLAG_A | FLAG_G, }, { BWN_B2063_LOGEN_SP4, 0xf0, 0xf, FLAG_A | FLAG_G, }, { BWN_B2063_G_RX_SP1, 0x1f, 0x5e, FLAG_G, }, { BWN_B2063_G_RX_SP2, 0x7f, 0x7e, FLAG_G, }, { BWN_B2063_G_RX_SP3, 0x30, 0xf0, FLAG_G, }, { BWN_B2063_G_RX_SP7, 0x7f, 0x7f, FLAG_A | FLAG_G, }, { BWN_B2063_G_RX_SP10, 0xc, 0xc, FLAG_A | FLAG_G, }, { BWN_B2063_A_RX_SP1, 0x3c, 0x3f, FLAG_A, }, { BWN_B2063_A_RX_SP2, 0xfc, 0xfe, FLAG_A, }, { BWN_B2063_A_RX_SP7, 0x8, 0x8, FLAG_A | FLAG_G, }, { BWN_B2063_RX_BB_SP4, 0x60, 0x60, FLAG_A | FLAG_G, }, { BWN_B2063_RX_BB_SP8, 0x30, 0x30, FLAG_A | FLAG_G, }, { BWN_B2063_TX_RF_SP3, 0xc, 0xb, FLAG_A | FLAG_G, }, { BWN_B2063_TX_RF_SP4, 0x10, 0xf, FLAG_A | FLAG_G, }, { BWN_B2063_PA_SP1, 0x3d, 0xfd, FLAG_A | FLAG_G, }, { BWN_B2063_TX_BB_SP1, 0x2, 0x2, FLAG_A | FLAG_G, }, { BWN_B2063_BANDGAP_CTL1, 0x56, 0x56, FLAG_A | FLAG_G, }, { BWN_B2063_JTAG_VCO2, 0xF7, 0xF7, FLAG_A | FLAG_G, }, { BWN_B2063_G_RX_MIX3, 0x71, 0x71, FLAG_A | FLAG_G, }, { BWN_B2063_G_RX_MIX4, 0x71, 0x71, FLAG_A | FLAG_G, }, { BWN_B2063_A_RX_1ST2, 0xf0, 0x30, FLAG_A, }, { BWN_B2063_A_RX_PS6, 0x77, 0x77, FLAG_A | FLAG_G, }, { BWN_B2063_A_RX_MIX4, 0x3, 0x3, FLAG_A | FLAG_G, }, { BWN_B2063_A_RX_MIX5, 0xf, 0xf, FLAG_A | FLAG_G, }, { BWN_B2063_A_RX_MIX6, 0xf, 0xf, FLAG_A | FLAG_G, }, { BWN_B2063_RX_TIA_CTL1, 0x77, 0x77, FLAG_A | FLAG_G, }, { BWN_B2063_RX_TIA_CTL3, 0x77, 0x77, FLAG_A | FLAG_G, }, { BWN_B2063_RX_BB_CTL2, 0x4, 0x4, FLAG_A | FLAG_G, }, { BWN_B2063_PA_CTL1, 0x0, 0x4, FLAG_A, }, { BWN_B2063_VREG_CTL1, 0x3, 0x3, FLAG_A | FLAG_G, }, }; const struct bwn_b206x_rfinit_entry *br; unsigned int i; for (i = 0; i < N(bwn_b2063_init_tab); i++) { br = &bwn_b2063_init_tab[i]; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { if (br->br_flags & FLAG_G) BWN_RF_WRITE(mac, br->br_offset, br->br_valueg); } else { if (br->br_flags & FLAG_A) BWN_RF_WRITE(mac, br->br_offset, br->br_valuea); } } #undef FLAG_A #undef FLAG_B } static void bwn_tab_read_multi(struct bwn_mac *mac, uint32_t typenoffset, int count, void *_data) { unsigned int i; uint32_t offset, type; uint8_t *data = _data; type = BWN_TAB_GETTYPE(typenoffset); offset = BWN_TAB_GETOFFSET(typenoffset); KASSERT(offset <= 0xffff, ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); for (i = 0; i < count; i++) { switch (type) { case BWN_TAB_8BIT: *data = BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO) & 0xff; data++; break; case BWN_TAB_16BIT: *((uint16_t *)data) = BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO); data += 2; break; case BWN_TAB_32BIT: *((uint32_t *)data) = BWN_PHY_READ(mac, BWN_PHY_TABLEDATAHI); *((uint32_t *)data) <<= 16; *((uint32_t *)data) |= BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO); data += 4; break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } } static void bwn_tab_write_multi(struct bwn_mac *mac, uint32_t typenoffset, int count, const void *_data) { uint32_t offset, type, value; const uint8_t *data = _data; unsigned int i; type = BWN_TAB_GETTYPE(typenoffset); offset = BWN_TAB_GETOFFSET(typenoffset); KASSERT(offset <= 0xffff, ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); for (i = 0; i < count; i++) { switch (type) { case BWN_TAB_8BIT: value = *data; data++; KASSERT(!(value & ~0xff), ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; case BWN_TAB_16BIT: value = *((const uint16_t *)data); data += 2; KASSERT(!(value & ~0xffff), ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; case BWN_TAB_32BIT: value = *((const uint32_t *)data); data += 4; BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATAHI, value >> 16); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } } static struct bwn_txgain bwn_phy_lp_get_txgain(struct bwn_mac *mac) { struct bwn_txgain tg; uint16_t tmp; tg.tg_dac = (BWN_PHY_READ(mac, BWN_PHY_AFE_DAC_CTL) & 0x380) >> 7; if (mac->mac_phy.rev < 2) { tmp = BWN_PHY_READ(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL) & 0x7ff; tg.tg_gm = tmp & 0x0007; tg.tg_pga = (tmp & 0x0078) >> 3; tg.tg_pad = (tmp & 0x780) >> 7; return (tg); } tmp = BWN_PHY_READ(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL); tg.tg_pad = BWN_PHY_READ(mac, BWN_PHY_OFDM(0xfb)) & 0xff; tg.tg_gm = tmp & 0xff; tg.tg_pga = (tmp >> 8) & 0xff; return (tg); } static uint8_t bwn_phy_lp_get_bbmult(struct bwn_mac *mac) { return (bwn_tab_read(mac, BWN_TAB_2(0, 87)) & 0xff00) >> 8; } static void bwn_phy_lp_set_txgain(struct bwn_mac *mac, struct bwn_txgain *tg) { uint16_t pa; if (mac->mac_phy.rev < 2) { BWN_PHY_SETMASK(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL, 0xf800, (tg->tg_pad << 7) | (tg->tg_pga << 3) | tg->tg_gm); bwn_phy_lp_set_txgain_dac(mac, tg->tg_dac); bwn_phy_lp_set_txgain_override(mac); return; } pa = bwn_phy_lp_get_pa_gain(mac); BWN_PHY_WRITE(mac, BWN_PHY_TX_GAIN_CTL_OVERRIDE_VAL, (tg->tg_pga << 8) | tg->tg_gm); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xfb), 0x8000, tg->tg_pad | (pa << 6)); BWN_PHY_WRITE(mac, BWN_PHY_OFDM(0xfc), (tg->tg_pga << 8) | tg->tg_gm); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xfd), 0x8000, tg->tg_pad | (pa << 8)); bwn_phy_lp_set_txgain_dac(mac, tg->tg_dac); bwn_phy_lp_set_txgain_override(mac); } static void bwn_phy_lp_set_bbmult(struct bwn_mac *mac, uint8_t bbmult) { bwn_tab_write(mac, BWN_TAB_2(0, 87), (uint16_t)bbmult << 8); } static void bwn_phy_lp_set_trsw_over(struct bwn_mac *mac, uint8_t tx, uint8_t rx) { uint16_t trsw = (tx << 1) | rx; BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfffc, trsw); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x3); } static void bwn_phy_lp_set_rxgain(struct bwn_mac *mac, uint32_t gain) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint16_t ext_lna, high_gain, lna, low_gain, trsw, tmp; if (mac->mac_phy.rev < 2) { trsw = gain & 0x1; lna = (gain & 0xfffc) | ((gain & 0xc) >> 2); ext_lna = (gain & 2) >> 1; BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfffe, trsw); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfbff, ext_lna << 10); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xf7ff, ext_lna << 11); BWN_PHY_WRITE(mac, BWN_PHY_RX_GAIN_CTL_OVERRIDE_VAL, lna); } else { low_gain = gain & 0xffff; high_gain = (gain >> 16) & 0xf; ext_lna = (gain >> 21) & 0x1; trsw = ~(gain >> 20) & 0x1; BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0xfffe, trsw); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfdff, ext_lna << 9); BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xfbff, ext_lna << 10); BWN_PHY_WRITE(mac, BWN_PHY_RX_GAIN_CTL_OVERRIDE_VAL, low_gain); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS, 0xfff0, high_gain); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { tmp = (gain >> 2) & 0x3; BWN_PHY_SETMASK(mac, BWN_PHY_RF_OVERRIDE_2_VAL, 0xe7ff, tmp<<11); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xe6), 0xffe7, tmp << 3); } } BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x1); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x10); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x40); if (mac->mac_phy.rev >= 2) { BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x100); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x400); BWN_PHY_SET(mac, BWN_PHY_OFDM(0xe5), 0x8); } return; } BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x200); } static void bwn_phy_lp_set_deaf(struct bwn_mac *mac, uint8_t user) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; if (user) plp->plp_crsusr_off = 1; else plp->plp_crssys_off = 1; BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xff1f, 0x80); } static void bwn_phy_lp_clear_deaf(struct bwn_mac *mac, uint8_t user) { struct bwn_phy_lp *plp = &mac->mac_phy.phy_lp; struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; if (user) plp->plp_crsusr_off = 0; else plp->plp_crssys_off = 0; if (plp->plp_crsusr_off || plp->plp_crssys_off) return; if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xff1f, 0x60); else BWN_PHY_SETMASK(mac, BWN_PHY_CRSGAIN_CTL, 0xff1f, 0x20); } static unsigned int bwn_sqrt(struct bwn_mac *mac, unsigned int x) { /* Table holding (10 * sqrt(x)) for x between 1 and 256. */ static uint8_t sqrt_table[256] = { 10, 14, 17, 20, 22, 24, 26, 28, 30, 31, 33, 34, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 60, 61, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 70, 70, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 80, 80, 81, 81, 82, 83, 83, 84, 84, 85, 86, 86, 87, 87, 88, 88, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 100, 100, 100, 101, 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 110, 111, 111, 112, 112, 113, 113, 114, 114, 114, 115, 115, 116, 116, 117, 117, 117, 118, 118, 119, 119, 120, 120, 120, 121, 121, 122, 122, 122, 123, 123, 124, 124, 124, 125, 125, 126, 126, 126, 127, 127, 128, 128, 128, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 150, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160 }; if (x == 0) return (0); if (x >= 256) { unsigned int tmp; for (tmp = 0; x >= (2 * tmp) + 1; x -= (2 * tmp++) + 1) /* do nothing */ ; return (tmp); } return (sqrt_table[x - 1] / 10); } static int bwn_phy_lp_calc_rx_iq_comp(struct bwn_mac *mac, uint16_t sample) { #define CALC_COEFF(_v, _x, _y, _z) do { \ int _t; \ _t = _x - 20; \ if (_t >= 0) { \ _v = ((_y << (30 - _x)) + (_z >> (1 + _t))) / (_z >> _t); \ } else { \ _v = ((_y << (30 - _x)) + (_z << (-1 - _t))) / (_z << -_t); \ } \ } while (0) #define CALC_COEFF2(_v, _x, _y, _z) do { \ int _t; \ _t = _x - 11; \ if (_t >= 0) \ _v = (_y << (31 - _x)) / (_z >> _t); \ else \ _v = (_y << (31 - _x)) / (_z << -_t); \ } while (0) struct bwn_phy_lp_iq_est ie; uint16_t v0, v1; int tmp[2], ret; v1 = BWN_PHY_READ(mac, BWN_PHY_RX_COMP_COEFF_S); v0 = v1 >> 8; v1 |= 0xff; BWN_PHY_SETMASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0xff00, 0x00c0); BWN_PHY_MASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0x00ff); ret = bwn_phy_lp_rx_iq_est(mac, sample, 32, &ie); if (ret == 0) goto done; if (ie.ie_ipwr + ie.ie_qpwr < 2) { ret = 0; goto done; } CALC_COEFF(tmp[0], bwn_nbits(ie.ie_iqprod), ie.ie_iqprod, ie.ie_ipwr); CALC_COEFF2(tmp[1], bwn_nbits(ie.ie_qpwr), ie.ie_qpwr, ie.ie_ipwr); tmp[1] = -bwn_sqrt(mac, tmp[1] - (tmp[0] * tmp[0])); v0 = tmp[0] >> 3; v1 = tmp[1] >> 4; done: BWN_PHY_SETMASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0xff00, v1); BWN_PHY_SETMASK(mac, BWN_PHY_RX_COMP_COEFF_S, 0x00ff, v0 << 8); return ret; #undef CALC_COEFF #undef CALC_COEFF2 } static void bwn_phy_lp_tblinit_r01(struct bwn_mac *mac) { static const uint16_t noisescale[] = { 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa400, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0x00a4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4c00, 0x2d36, 0x0000, 0x0000, 0x4c00, 0x2d36, }; static const uint16_t crsgainnft[] = { 0x0366, 0x036a, 0x036f, 0x0364, 0x0367, 0x036d, 0x0374, 0x037f, 0x036f, 0x037b, 0x038a, 0x0378, 0x0367, 0x036d, 0x0375, 0x0381, 0x0374, 0x0381, 0x0392, 0x03a9, 0x03c4, 0x03e1, 0x0001, 0x001f, 0x0040, 0x005e, 0x007f, 0x009e, 0x00bd, 0x00dd, 0x00fd, 0x011d, 0x013d, }; static const uint16_t filterctl[] = { 0xa0fc, 0x10fc, 0x10db, 0x20b7, 0xff93, 0x10bf, 0x109b, 0x2077, 0xff53, 0x0127, }; static const uint32_t psctl[] = { 0x00010000, 0x000000a0, 0x00040000, 0x00000048, 0x08080101, 0x00000080, 0x08080101, 0x00000040, 0x08080101, 0x000000c0, 0x08a81501, 0x000000c0, 0x0fe8fd01, 0x000000c0, 0x08300105, 0x000000c0, 0x08080201, 0x000000c0, 0x08280205, 0x000000c0, 0xe80802fe, 0x000000c7, 0x28080206, 0x000000c0, 0x08080202, 0x000000c0, 0x0ba87602, 0x000000c0, 0x1068013d, 0x000000c0, 0x10280105, 0x000000c0, 0x08880102, 0x000000c0, 0x08280106, 0x000000c0, 0xe80801fd, 0x000000c7, 0xa8080115, 0x000000c0, }; static const uint16_t ofdmcckgain_r0[] = { 0x0001, 0x0001, 0x0001, 0x0001, 0x1001, 0x2001, 0x3001, 0x4001, 0x5001, 0x6001, 0x7001, 0x7011, 0x7021, 0x2035, 0x2045, 0x2055, 0x2065, 0x2075, 0x006d, 0x007d, 0x014d, 0x015d, 0x115d, 0x035d, 0x135d, 0x055d, 0x155d, 0x0d5d, 0x1d5d, 0x2d5d, 0x555d, 0x655d, 0x755d, }; static const uint16_t ofdmcckgain_r1[] = { 0x5000, 0x6000, 0x7000, 0x0001, 0x1001, 0x2001, 0x3001, 0x4001, 0x5001, 0x6001, 0x7001, 0x7011, 0x7021, 0x2035, 0x2045, 0x2055, 0x2065, 0x2075, 0x006d, 0x007d, 0x014d, 0x015d, 0x115d, 0x035d, 0x135d, 0x055d, 0x155d, 0x0d5d, 0x1d5d, 0x2d5d, 0x555d, 0x655d, 0x755d, }; static const uint16_t gaindelta[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, }; static const uint32_t txpwrctl[] = { 0x00000050, 0x0000004f, 0x0000004e, 0x0000004d, 0x0000004c, 0x0000004b, 0x0000004a, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, 0x00000044, 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003f, 0x0000003e, 0x0000003d, 0x0000003c, 0x0000003b, 0x0000003a, 0x00000039, 0x00000038, 0x00000037, 0x00000036, 0x00000035, 0x00000034, 0x00000033, 0x00000032, 0x00000031, 0x00000030, 0x0000002f, 0x0000002e, 0x0000002d, 0x0000002c, 0x0000002b, 0x0000002a, 0x00000029, 0x00000028, 0x00000027, 0x00000026, 0x00000025, 0x00000024, 0x00000023, 0x00000022, 0x00000021, 0x00000020, 0x0000001f, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001b, 0x0000001a, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, 0x00000014, 0x00000013, 0x00000012, 0x00000011, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000075a0, 0x000075a0, 0x000075a1, 0x000075a1, 0x000075a2, 0x000075a2, 0x000075a3, 0x000075a3, 0x000074b0, 0x000074b0, 0x000074b1, 0x000074b1, 0x000074b2, 0x000074b2, 0x000074b3, 0x000074b3, 0x00006d20, 0x00006d20, 0x00006d21, 0x00006d21, 0x00006d22, 0x00006d22, 0x00006d23, 0x00006d23, 0x00004660, 0x00004660, 0x00004661, 0x00004661, 0x00004662, 0x00004662, 0x00004663, 0x00004663, 0x00003e60, 0x00003e60, 0x00003e61, 0x00003e61, 0x00003e62, 0x00003e62, 0x00003e63, 0x00003e63, 0x00003660, 0x00003660, 0x00003661, 0x00003661, 0x00003662, 0x00003662, 0x00003663, 0x00003663, 0x00002e60, 0x00002e60, 0x00002e61, 0x00002e61, 0x00002e62, 0x00002e62, 0x00002e63, 0x00002e63, 0x00002660, 0x00002660, 0x00002661, 0x00002661, 0x00002662, 0x00002662, 0x00002663, 0x00002663, 0x000025e0, 0x000025e0, 0x000025e1, 0x000025e1, 0x000025e2, 0x000025e2, 0x000025e3, 0x000025e3, 0x00001de0, 0x00001de0, 0x00001de1, 0x00001de1, 0x00001de2, 0x00001de2, 0x00001de3, 0x00001de3, 0x00001d60, 0x00001d60, 0x00001d61, 0x00001d61, 0x00001d62, 0x00001d62, 0x00001d63, 0x00001d63, 0x00001560, 0x00001560, 0x00001561, 0x00001561, 0x00001562, 0x00001562, 0x00001563, 0x00001563, 0x00000d60, 0x00000d60, 0x00000d61, 0x00000d61, 0x00000d62, 0x00000d62, 0x00000d63, 0x00000d63, 0x00000ce0, 0x00000ce0, 0x00000ce1, 0x00000ce1, 0x00000ce2, 0x00000ce2, 0x00000ce3, 0x00000ce3, 0x00000e10, 0x00000e10, 0x00000e11, 0x00000e11, 0x00000e12, 0x00000e12, 0x00000e13, 0x00000e13, 0x00000bf0, 0x00000bf0, 0x00000bf1, 0x00000bf1, 0x00000bf2, 0x00000bf2, 0x00000bf3, 0x00000bf3, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x000000ff, 0x000002fc, 0x0000fa08, 0x00000305, 0x00000206, 0x00000304, 0x0000fb04, 0x0000fcff, 0x000005fb, 0x0000fd01, 0x00000401, 0x00000006, 0x0000ff03, 0x000007fc, 0x0000fc08, 0x00000203, 0x0000fffb, 0x00000600, 0x0000fa01, 0x0000fc03, 0x0000fe06, 0x0000fe00, 0x00000102, 0x000007fd, 0x000004fb, 0x000006ff, 0x000004fd, 0x0000fdfa, 0x000007fb, 0x0000fdfa, 0x0000fa06, 0x00000500, 0x0000f902, 0x000007fa, 0x0000fafa, 0x00000500, 0x000007fa, 0x00000700, 0x00000305, 0x000004ff, 0x00000801, 0x00000503, 0x000005f9, 0x00000404, 0x0000fb08, 0x000005fd, 0x00000501, 0x00000405, 0x0000fb03, 0x000007fc, 0x00000403, 0x00000303, 0x00000402, 0x0000faff, 0x0000fe05, 0x000005fd, 0x0000fe01, 0x000007fa, 0x00000202, 0x00000504, 0x00000102, 0x000008fe, 0x0000fa04, 0x0000fafc, 0x0000fe08, 0x000000f9, 0x000002fa, 0x000003fe, 0x00000304, 0x000004f9, 0x00000100, 0x0000fd06, 0x000008fc, 0x00000701, 0x00000504, 0x0000fdfe, 0x0000fdfc, 0x000003fe, 0x00000704, 0x000002fc, 0x000004f9, 0x0000fdfd, 0x0000fa07, 0x00000205, 0x000003fd, 0x000005fb, 0x000004f9, 0x00000804, 0x0000fc06, 0x0000fcf9, 0x00000100, 0x0000fe05, 0x00000408, 0x0000fb02, 0x00000304, 0x000006fe, 0x000004fa, 0x00000305, 0x000008fc, 0x00000102, 0x000001fd, 0x000004fc, 0x0000fe03, 0x00000701, 0x000001fb, 0x000001f9, 0x00000206, 0x000006fd, 0x00000508, 0x00000700, 0x00000304, 0x000005fe, 0x000005ff, 0x0000fa04, 0x00000303, 0x0000fefb, 0x000007f9, 0x0000fefc, 0x000004fd, 0x000005fc, 0x0000fffd, 0x0000fc08, 0x0000fbf9, 0x0000fd07, 0x000008fb, 0x0000fe02, 0x000006fb, 0x00000702, }; KASSERT(mac->mac_phy.rev < 2, ("%s:%d: fail", __func__, __LINE__)); bwn_tab_write_multi(mac, BWN_TAB_1(2, 0), N(bwn_tab_sigsq_tbl), bwn_tab_sigsq_tbl); bwn_tab_write_multi(mac, BWN_TAB_2(1, 0), N(noisescale), noisescale); bwn_tab_write_multi(mac, BWN_TAB_2(14, 0), N(crsgainnft), crsgainnft); bwn_tab_write_multi(mac, BWN_TAB_2(8, 0), N(filterctl), filterctl); bwn_tab_write_multi(mac, BWN_TAB_4(9, 0), N(psctl), psctl); bwn_tab_write_multi(mac, BWN_TAB_1(6, 0), N(bwn_tab_pllfrac_tbl), bwn_tab_pllfrac_tbl); bwn_tab_write_multi(mac, BWN_TAB_2(0, 0), N(bwn_tabl_iqlocal_tbl), bwn_tabl_iqlocal_tbl); if (mac->mac_phy.rev == 0) { bwn_tab_write_multi(mac, BWN_TAB_2(13, 0), N(ofdmcckgain_r0), ofdmcckgain_r0); bwn_tab_write_multi(mac, BWN_TAB_2(12, 0), N(ofdmcckgain_r0), ofdmcckgain_r0); } else { bwn_tab_write_multi(mac, BWN_TAB_2(13, 0), N(ofdmcckgain_r1), ofdmcckgain_r1); bwn_tab_write_multi(mac, BWN_TAB_2(12, 0), N(ofdmcckgain_r1), ofdmcckgain_r1); } bwn_tab_write_multi(mac, BWN_TAB_2(15, 0), N(gaindelta), gaindelta); bwn_tab_write_multi(mac, BWN_TAB_4(10, 0), N(txpwrctl), txpwrctl); } static void bwn_phy_lp_tblinit_r2(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; int i; static const uint16_t noisescale[] = { 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x0000, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4 }; static const uint32_t filterctl[] = { 0x000141fc, 0x000021fc, 0x000021b7, 0x0000416f, 0x0001ff27, 0x0000217f, 0x00002137, 0x000040ef, 0x0001fea7, 0x0000024f }; static const uint32_t psctl[] = { 0x00e38e08, 0x00e08e38, 0x00000000, 0x00000000, 0x00000000, 0x00002080, 0x00006180, 0x00003002, 0x00000040, 0x00002042, 0x00180047, 0x00080043, 0x00000041, 0x000020c1, 0x00046006, 0x00042002, 0x00040000, 0x00002003, 0x00180006, 0x00080002 }; static const uint32_t gainidx[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10000001, 0x00000000, 0x20000082, 0x00000000, 0x40000104, 0x00000000, 0x60004207, 0x00000001, 0x7000838a, 0x00000001, 0xd021050d, 0x00000001, 0xe041c683, 0x00000001, 0x50828805, 0x00000000, 0x80e34288, 0x00000000, 0xb144040b, 0x00000000, 0xe1a6058e, 0x00000000, 0x12064711, 0x00000001, 0xb0a18612, 0x00000010, 0xe1024794, 0x00000010, 0x11630915, 0x00000011, 0x31c3ca1b, 0x00000011, 0xc1848a9c, 0x00000018, 0xf1e50da0, 0x00000018, 0x22468e21, 0x00000019, 0x4286d023, 0x00000019, 0xa347d0a4, 0x00000019, 0xb36811a6, 0x00000019, 0xf3e89227, 0x00000019, 0x0408d329, 0x0000001a, 0x244953aa, 0x0000001a, 0x346994ab, 0x0000001a, 0x54aa152c, 0x0000001a, 0x64ca55ad, 0x0000001a, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10000001, 0x00000000, 0x20000082, 0x00000000, 0x40000104, 0x00000000, 0x60004207, 0x00000001, 0x7000838a, 0x00000001, 0xd021050d, 0x00000001, 0xe041c683, 0x00000001, 0x50828805, 0x00000000, 0x80e34288, 0x00000000, 0xb144040b, 0x00000000, 0xe1a6058e, 0x00000000, 0x12064711, 0x00000001, 0xb0a18612, 0x00000010, 0xe1024794, 0x00000010, 0x11630915, 0x00000011, 0x31c3ca1b, 0x00000011, 0xc1848a9c, 0x00000018, 0xf1e50da0, 0x00000018, 0x22468e21, 0x00000019, 0x4286d023, 0x00000019, 0xa347d0a4, 0x00000019, 0xb36811a6, 0x00000019, 0xf3e89227, 0x00000019, 0x0408d329, 0x0000001a, 0x244953aa, 0x0000001a, 0x346994ab, 0x0000001a, 0x54aa152c, 0x0000001a, 0x64ca55ad, 0x0000001a }; static const uint16_t auxgainidx[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0016, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0016 }; static const uint16_t swctl[] = { 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018 }; static const uint8_t hf[] = { 0x4b, 0x36, 0x24, 0x18, 0x49, 0x34, 0x23, 0x17, 0x48, 0x33, 0x23, 0x17, 0x48, 0x33, 0x23, 0x17 }; static const uint32_t gainval[] = { 0x00000008, 0x0000000e, 0x00000014, 0x0000001a, 0x000000fb, 0x00000004, 0x00000008, 0x0000000d, 0x00000001, 0x00000004, 0x00000007, 0x0000000a, 0x0000000d, 0x00000010, 0x00000012, 0x00000015, 0x00000000, 0x00000006, 0x0000000c, 0x00000000, 0x00000000, 0x00000000, 0x00000012, 0x00000000, 0x00000000, 0x00000000, 0x00000018, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000003, 0x00000006, 0x00000009, 0x0000000c, 0x0000000f, 0x00000012, 0x00000015, 0x00000018, 0x0000001b, 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000009, 0x000000f1, 0x00000000, 0x00000000 }; static const uint16_t gain[] = { 0x0000, 0x0400, 0x0800, 0x0802, 0x0804, 0x0806, 0x0807, 0x0808, 0x080a, 0x080b, 0x080c, 0x080e, 0x080f, 0x0810, 0x0812, 0x0813, 0x0814, 0x0816, 0x0817, 0x081a, 0x081b, 0x081f, 0x0820, 0x0824, 0x0830, 0x0834, 0x0837, 0x083b, 0x083f, 0x0840, 0x0844, 0x0857, 0x085b, 0x085f, 0x08d7, 0x08db, 0x08df, 0x0957, 0x095b, 0x095f, 0x0b57, 0x0b5b, 0x0b5f, 0x0f5f, 0x135f, 0x175f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; static const uint32_t papdeps[] = { 0x00000000, 0x00013ffc, 0x0001dff3, 0x0001bff0, 0x00023fe9, 0x00021fdf, 0x00028fdf, 0x00033fd2, 0x00039fcb, 0x00043fc7, 0x0004efc2, 0x00055fb5, 0x0005cfb0, 0x00063fa8, 0x00068fa3, 0x00071f98, 0x0007ef92, 0x00084f8b, 0x0008df82, 0x00097f77, 0x0009df69, 0x000a3f62, 0x000adf57, 0x000b6f4c, 0x000bff41, 0x000c9f39, 0x000cff30, 0x000dbf27, 0x000e4f1e, 0x000edf16, 0x000f7f13, 0x00102f11, 0x00110f10, 0x0011df11, 0x0012ef15, 0x00143f1c, 0x00158f27, 0x00172f35, 0x00193f47, 0x001baf5f, 0x001e6f7e, 0x0021cfa4, 0x0025bfd2, 0x002a2008, 0x002fb047, 0x00360090, 0x003d40e0, 0x0045c135, 0x004fb189, 0x005ae1d7, 0x0067221d, 0x0075025a, 0x007ff291, 0x007ff2bf, 0x007ff2e3, 0x007ff2ff, 0x007ff315, 0x007ff329, 0x007ff33f, 0x007ff356, 0x007ff36e, 0x007ff39c, 0x007ff441, 0x007ff506 }; static const uint32_t papdmult[] = { 0x001111e0, 0x00652051, 0x00606055, 0x005b005a, 0x00555060, 0x00511065, 0x004c806b, 0x0047d072, 0x00444078, 0x00400080, 0x003ca087, 0x0039408f, 0x0035e098, 0x0032e0a1, 0x003030aa, 0x002d80b4, 0x002ae0bf, 0x002880ca, 0x002640d6, 0x002410e3, 0x002220f0, 0x002020ff, 0x001e510e, 0x001ca11e, 0x001b012f, 0x00199140, 0x00182153, 0x0016c168, 0x0015817d, 0x00145193, 0x001321ab, 0x001211c5, 0x001111e0, 0x001021fc, 0x000f321a, 0x000e523a, 0x000d925c, 0x000cd27f, 0x000c12a5, 0x000b62cd, 0x000ac2f8, 0x000a2325, 0x00099355, 0x00091387, 0x000883bd, 0x000813f5, 0x0007a432, 0x00073471, 0x0006c4b5, 0x000664fc, 0x00061547, 0x0005b598, 0x000565ec, 0x00051646, 0x0004d6a5, 0x0004870a, 0x00044775, 0x000407e6, 0x0003d85e, 0x000398dd, 0x00036963, 0x000339f2, 0x00030a89, 0x0002db28 }; static const uint32_t gainidx_a0[] = { 0x001111e0, 0x00652051, 0x00606055, 0x005b005a, 0x00555060, 0x00511065, 0x004c806b, 0x0047d072, 0x00444078, 0x00400080, 0x003ca087, 0x0039408f, 0x0035e098, 0x0032e0a1, 0x003030aa, 0x002d80b4, 0x002ae0bf, 0x002880ca, 0x002640d6, 0x002410e3, 0x002220f0, 0x002020ff, 0x001e510e, 0x001ca11e, 0x001b012f, 0x00199140, 0x00182153, 0x0016c168, 0x0015817d, 0x00145193, 0x001321ab, 0x001211c5, 0x001111e0, 0x001021fc, 0x000f321a, 0x000e523a, 0x000d925c, 0x000cd27f, 0x000c12a5, 0x000b62cd, 0x000ac2f8, 0x000a2325, 0x00099355, 0x00091387, 0x000883bd, 0x000813f5, 0x0007a432, 0x00073471, 0x0006c4b5, 0x000664fc, 0x00061547, 0x0005b598, 0x000565ec, 0x00051646, 0x0004d6a5, 0x0004870a, 0x00044775, 0x000407e6, 0x0003d85e, 0x000398dd, 0x00036963, 0x000339f2, 0x00030a89, 0x0002db28 }; static const uint16_t auxgainidx_a0[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0014, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0014 }; static const uint32_t gainval_a0[] = { 0x00000008, 0x0000000e, 0x00000014, 0x0000001a, 0x000000fb, 0x00000004, 0x00000008, 0x0000000d, 0x00000001, 0x00000004, 0x00000007, 0x0000000a, 0x0000000d, 0x00000010, 0x00000012, 0x00000015, 0x00000000, 0x00000006, 0x0000000c, 0x00000000, 0x00000000, 0x00000000, 0x00000012, 0x00000000, 0x00000000, 0x00000000, 0x00000018, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000003, 0x00000006, 0x00000009, 0x0000000c, 0x0000000f, 0x00000012, 0x00000015, 0x00000018, 0x0000001b, 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000000f, 0x000000f7, 0x00000000, 0x00000000 }; static const uint16_t gain_a0[] = { 0x0000, 0x0002, 0x0004, 0x0006, 0x0007, 0x0008, 0x000a, 0x000b, 0x000c, 0x000e, 0x000f, 0x0010, 0x0012, 0x0013, 0x0014, 0x0016, 0x0017, 0x001a, 0x001b, 0x001f, 0x0020, 0x0024, 0x0030, 0x0034, 0x0037, 0x003b, 0x003f, 0x0040, 0x0044, 0x0057, 0x005b, 0x005f, 0x00d7, 0x00db, 0x00df, 0x0157, 0x015b, 0x015f, 0x0357, 0x035b, 0x035f, 0x075f, 0x0b5f, 0x0f5f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; KASSERT(mac->mac_phy.rev < 2, ("%s:%d: fail", __func__, __LINE__)); for (i = 0; i < 704; i++) bwn_tab_write(mac, BWN_TAB_4(7, i), 0); bwn_tab_write_multi(mac, BWN_TAB_1(2, 0), N(bwn_tab_sigsq_tbl), bwn_tab_sigsq_tbl); bwn_tab_write_multi(mac, BWN_TAB_2(1, 0), N(noisescale), noisescale); bwn_tab_write_multi(mac, BWN_TAB_4(11, 0), N(filterctl), filterctl); bwn_tab_write_multi(mac, BWN_TAB_4(12, 0), N(psctl), psctl); bwn_tab_write_multi(mac, BWN_TAB_4(13, 0), N(gainidx), gainidx); bwn_tab_write_multi(mac, BWN_TAB_2(14, 0), N(auxgainidx), auxgainidx); bwn_tab_write_multi(mac, BWN_TAB_2(15, 0), N(swctl), swctl); bwn_tab_write_multi(mac, BWN_TAB_1(16, 0), N(hf), hf); bwn_tab_write_multi(mac, BWN_TAB_4(17, 0), N(gainval), gainval); bwn_tab_write_multi(mac, BWN_TAB_2(18, 0), N(gain), gain); bwn_tab_write_multi(mac, BWN_TAB_1(6, 0), N(bwn_tab_pllfrac_tbl), bwn_tab_pllfrac_tbl); bwn_tab_write_multi(mac, BWN_TAB_2(0, 0), N(bwn_tabl_iqlocal_tbl), bwn_tabl_iqlocal_tbl); bwn_tab_write_multi(mac, BWN_TAB_4(9, 0), N(papdeps), papdeps); bwn_tab_write_multi(mac, BWN_TAB_4(10, 0), N(papdmult), papdmult); if ((siba_get_chipid(sc->sc_dev) == 0x4325) && (siba_get_chiprev(sc->sc_dev) == 0)) { bwn_tab_write_multi(mac, BWN_TAB_4(13, 0), N(gainidx_a0), gainidx_a0); bwn_tab_write_multi(mac, BWN_TAB_2(14, 0), N(auxgainidx_a0), auxgainidx_a0); bwn_tab_write_multi(mac, BWN_TAB_4(17, 0), N(gainval_a0), gainval_a0); bwn_tab_write_multi(mac, BWN_TAB_2(18, 0), N(gain_a0), gain_a0); } } static void bwn_phy_lp_tblinit_txgain(struct bwn_mac *mac) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; static struct bwn_txgain_entry txgain_r2[] = { { 255, 255, 203, 0, 152 }, { 255, 255, 203, 0, 147 }, { 255, 255, 203, 0, 143 }, { 255, 255, 203, 0, 139 }, { 255, 255, 203, 0, 135 }, { 255, 255, 203, 0, 131 }, { 255, 255, 203, 0, 128 }, { 255, 255, 203, 0, 124 }, { 255, 255, 203, 0, 121 }, { 255, 255, 203, 0, 117 }, { 255, 255, 203, 0, 114 }, { 255, 255, 203, 0, 111 }, { 255, 255, 203, 0, 107 }, { 255, 255, 203, 0, 104 }, { 255, 255, 203, 0, 101 }, { 255, 255, 203, 0, 99 }, { 255, 255, 203, 0, 96 }, { 255, 255, 203, 0, 93 }, { 255, 255, 203, 0, 90 }, { 255, 255, 203, 0, 88 }, { 255, 255, 203, 0, 85 }, { 255, 255, 203, 0, 83 }, { 255, 255, 203, 0, 81 }, { 255, 255, 203, 0, 78 }, { 255, 255, 203, 0, 76 }, { 255, 255, 203, 0, 74 }, { 255, 255, 203, 0, 72 }, { 255, 255, 203, 0, 70 }, { 255, 255, 203, 0, 68 }, { 255, 255, 203, 0, 66 }, { 255, 255, 203, 0, 64 }, { 255, 255, 197, 0, 64 }, { 255, 255, 192, 0, 64 }, { 255, 255, 186, 0, 64 }, { 255, 255, 181, 0, 64 }, { 255, 255, 176, 0, 64 }, { 255, 255, 171, 0, 64 }, { 255, 255, 166, 0, 64 }, { 255, 255, 161, 0, 64 }, { 255, 255, 157, 0, 64 }, { 255, 255, 152, 0, 64 }, { 255, 255, 148, 0, 64 }, { 255, 255, 144, 0, 64 }, { 255, 255, 140, 0, 64 }, { 255, 255, 136, 0, 64 }, { 255, 255, 132, 0, 64 }, { 255, 255, 128, 0, 64 }, { 255, 255, 124, 0, 64 }, { 255, 255, 121, 0, 64 }, { 255, 255, 117, 0, 64 }, { 255, 255, 114, 0, 64 }, { 255, 255, 111, 0, 64 }, { 255, 255, 108, 0, 64 }, { 255, 255, 105, 0, 64 }, { 255, 255, 102, 0, 64 }, { 255, 255, 99, 0, 64 }, { 255, 255, 96, 0, 64 }, { 255, 255, 93, 0, 64 }, { 255, 255, 91, 0, 64 }, { 255, 255, 88, 0, 64 }, { 255, 255, 86, 0, 64 }, { 255, 255, 83, 0, 64 }, { 255, 255, 81, 0, 64 }, { 255, 255, 79, 0, 64 }, { 255, 255, 76, 0, 64 }, { 255, 255, 74, 0, 64 }, { 255, 255, 72, 0, 64 }, { 255, 255, 70, 0, 64 }, { 255, 255, 68, 0, 64 }, { 255, 255, 66, 0, 64 }, { 255, 255, 64, 0, 64 }, { 255, 248, 64, 0, 64 }, { 255, 248, 62, 0, 64 }, { 255, 241, 62, 0, 64 }, { 255, 241, 60, 0, 64 }, { 255, 234, 60, 0, 64 }, { 255, 234, 59, 0, 64 }, { 255, 227, 59, 0, 64 }, { 255, 227, 57, 0, 64 }, { 255, 221, 57, 0, 64 }, { 255, 221, 55, 0, 64 }, { 255, 215, 55, 0, 64 }, { 255, 215, 54, 0, 64 }, { 255, 208, 54, 0, 64 }, { 255, 208, 52, 0, 64 }, { 255, 203, 52, 0, 64 }, { 255, 203, 51, 0, 64 }, { 255, 197, 51, 0, 64 }, { 255, 197, 49, 0, 64 }, { 255, 191, 49, 0, 64 }, { 255, 191, 48, 0, 64 }, { 255, 186, 48, 0, 64 }, { 255, 186, 47, 0, 64 }, { 255, 181, 47, 0, 64 }, { 255, 181, 45, 0, 64 }, { 255, 175, 45, 0, 64 }, { 255, 175, 44, 0, 64 }, { 255, 170, 44, 0, 64 }, { 255, 170, 43, 0, 64 }, { 255, 166, 43, 0, 64 }, { 255, 166, 42, 0, 64 }, { 255, 161, 42, 0, 64 }, { 255, 161, 40, 0, 64 }, { 255, 156, 40, 0, 64 }, { 255, 156, 39, 0, 64 }, { 255, 152, 39, 0, 64 }, { 255, 152, 38, 0, 64 }, { 255, 148, 38, 0, 64 }, { 255, 148, 37, 0, 64 }, { 255, 143, 37, 0, 64 }, { 255, 143, 36, 0, 64 }, { 255, 139, 36, 0, 64 }, { 255, 139, 35, 0, 64 }, { 255, 135, 35, 0, 64 }, { 255, 135, 34, 0, 64 }, { 255, 132, 34, 0, 64 }, { 255, 132, 33, 0, 64 }, { 255, 128, 33, 0, 64 }, { 255, 128, 32, 0, 64 }, { 255, 124, 32, 0, 64 }, { 255, 124, 31, 0, 64 }, { 255, 121, 31, 0, 64 }, { 255, 121, 30, 0, 64 }, { 255, 117, 30, 0, 64 }, { 255, 117, 29, 0, 64 }, { 255, 114, 29, 0, 64 }, { 255, 114, 29, 0, 64 }, { 255, 111, 29, 0, 64 }, }; static struct bwn_txgain_entry txgain_2ghz_r2[] = { { 7, 99, 255, 0, 64 }, { 7, 96, 255, 0, 64 }, { 7, 93, 255, 0, 64 }, { 7, 90, 255, 0, 64 }, { 7, 88, 255, 0, 64 }, { 7, 85, 255, 0, 64 }, { 7, 83, 255, 0, 64 }, { 7, 81, 255, 0, 64 }, { 7, 78, 255, 0, 64 }, { 7, 76, 255, 0, 64 }, { 7, 74, 255, 0, 64 }, { 7, 72, 255, 0, 64 }, { 7, 70, 255, 0, 64 }, { 7, 68, 255, 0, 64 }, { 7, 66, 255, 0, 64 }, { 7, 64, 255, 0, 64 }, { 7, 64, 255, 0, 64 }, { 7, 62, 255, 0, 64 }, { 7, 62, 248, 0, 64 }, { 7, 60, 248, 0, 64 }, { 7, 60, 241, 0, 64 }, { 7, 59, 241, 0, 64 }, { 7, 59, 234, 0, 64 }, { 7, 57, 234, 0, 64 }, { 7, 57, 227, 0, 64 }, { 7, 55, 227, 0, 64 }, { 7, 55, 221, 0, 64 }, { 7, 54, 221, 0, 64 }, { 7, 54, 215, 0, 64 }, { 7, 52, 215, 0, 64 }, { 7, 52, 208, 0, 64 }, { 7, 51, 208, 0, 64 }, { 7, 51, 203, 0, 64 }, { 7, 49, 203, 0, 64 }, { 7, 49, 197, 0, 64 }, { 7, 48, 197, 0, 64 }, { 7, 48, 191, 0, 64 }, { 7, 47, 191, 0, 64 }, { 7, 47, 186, 0, 64 }, { 7, 45, 186, 0, 64 }, { 7, 45, 181, 0, 64 }, { 7, 44, 181, 0, 64 }, { 7, 44, 175, 0, 64 }, { 7, 43, 175, 0, 64 }, { 7, 43, 170, 0, 64 }, { 7, 42, 170, 0, 64 }, { 7, 42, 166, 0, 64 }, { 7, 40, 166, 0, 64 }, { 7, 40, 161, 0, 64 }, { 7, 39, 161, 0, 64 }, { 7, 39, 156, 0, 64 }, { 7, 38, 156, 0, 64 }, { 7, 38, 152, 0, 64 }, { 7, 37, 152, 0, 64 }, { 7, 37, 148, 0, 64 }, { 7, 36, 148, 0, 64 }, { 7, 36, 143, 0, 64 }, { 7, 35, 143, 0, 64 }, { 7, 35, 139, 0, 64 }, { 7, 34, 139, 0, 64 }, { 7, 34, 135, 0, 64 }, { 7, 33, 135, 0, 64 }, { 7, 33, 132, 0, 64 }, { 7, 32, 132, 0, 64 }, { 7, 32, 128, 0, 64 }, { 7, 31, 128, 0, 64 }, { 7, 31, 124, 0, 64 }, { 7, 30, 124, 0, 64 }, { 7, 30, 121, 0, 64 }, { 7, 29, 121, 0, 64 }, { 7, 29, 117, 0, 64 }, { 7, 29, 117, 0, 64 }, { 7, 29, 114, 0, 64 }, { 7, 28, 114, 0, 64 }, { 7, 28, 111, 0, 64 }, { 7, 27, 111, 0, 64 }, { 7, 27, 108, 0, 64 }, { 7, 26, 108, 0, 64 }, { 7, 26, 104, 0, 64 }, { 7, 25, 104, 0, 64 }, { 7, 25, 102, 0, 64 }, { 7, 25, 102, 0, 64 }, { 7, 25, 99, 0, 64 }, { 7, 24, 99, 0, 64 }, { 7, 24, 96, 0, 64 }, { 7, 23, 96, 0, 64 }, { 7, 23, 93, 0, 64 }, { 7, 23, 93, 0, 64 }, { 7, 23, 90, 0, 64 }, { 7, 22, 90, 0, 64 }, { 7, 22, 88, 0, 64 }, { 7, 21, 88, 0, 64 }, { 7, 21, 85, 0, 64 }, { 7, 21, 85, 0, 64 }, { 7, 21, 83, 0, 64 }, { 7, 20, 83, 0, 64 }, { 7, 20, 81, 0, 64 }, { 7, 20, 81, 0, 64 }, { 7, 20, 78, 0, 64 }, { 7, 19, 78, 0, 64 }, { 7, 19, 76, 0, 64 }, { 7, 19, 76, 0, 64 }, { 7, 19, 74, 0, 64 }, { 7, 18, 74, 0, 64 }, { 7, 18, 72, 0, 64 }, { 7, 18, 72, 0, 64 }, { 7, 18, 70, 0, 64 }, { 7, 17, 70, 0, 64 }, { 7, 17, 68, 0, 64 }, { 7, 17, 68, 0, 64 }, { 7, 17, 66, 0, 64 }, { 7, 16, 66, 0, 64 }, { 7, 16, 64, 0, 64 }, { 7, 16, 64, 0, 64 }, { 7, 16, 62, 0, 64 }, { 7, 15, 62, 0, 64 }, { 7, 15, 60, 0, 64 }, { 7, 15, 60, 0, 64 }, { 7, 15, 59, 0, 64 }, { 7, 14, 59, 0, 64 }, { 7, 14, 57, 0, 64 }, { 7, 14, 57, 0, 64 }, { 7, 14, 55, 0, 64 }, { 7, 14, 55, 0, 64 }, { 7, 14, 54, 0, 64 }, { 7, 13, 54, 0, 64 }, { 7, 13, 52, 0, 64 }, { 7, 13, 52, 0, 64 }, }; static struct bwn_txgain_entry txgain_5ghz_r2[] = { { 255, 255, 255, 0, 152 }, { 255, 255, 255, 0, 147 }, { 255, 255, 255, 0, 143 }, { 255, 255, 255, 0, 139 }, { 255, 255, 255, 0, 135 }, { 255, 255, 255, 0, 131 }, { 255, 255, 255, 0, 128 }, { 255, 255, 255, 0, 124 }, { 255, 255, 255, 0, 121 }, { 255, 255, 255, 0, 117 }, { 255, 255, 255, 0, 114 }, { 255, 255, 255, 0, 111 }, { 255, 255, 255, 0, 107 }, { 255, 255, 255, 0, 104 }, { 255, 255, 255, 0, 101 }, { 255, 255, 255, 0, 99 }, { 255, 255, 255, 0, 96 }, { 255, 255, 255, 0, 93 }, { 255, 255, 255, 0, 90 }, { 255, 255, 255, 0, 88 }, { 255, 255, 255, 0, 85 }, { 255, 255, 255, 0, 83 }, { 255, 255, 255, 0, 81 }, { 255, 255, 255, 0, 78 }, { 255, 255, 255, 0, 76 }, { 255, 255, 255, 0, 74 }, { 255, 255, 255, 0, 72 }, { 255, 255, 255, 0, 70 }, { 255, 255, 255, 0, 68 }, { 255, 255, 255, 0, 66 }, { 255, 255, 255, 0, 64 }, { 255, 255, 248, 0, 64 }, { 255, 255, 241, 0, 64 }, { 255, 255, 234, 0, 64 }, { 255, 255, 227, 0, 64 }, { 255, 255, 221, 0, 64 }, { 255, 255, 215, 0, 64 }, { 255, 255, 208, 0, 64 }, { 255, 255, 203, 0, 64 }, { 255, 255, 197, 0, 64 }, { 255, 255, 191, 0, 64 }, { 255, 255, 186, 0, 64 }, { 255, 255, 181, 0, 64 }, { 255, 255, 175, 0, 64 }, { 255, 255, 170, 0, 64 }, { 255, 255, 166, 0, 64 }, { 255, 255, 161, 0, 64 }, { 255, 255, 156, 0, 64 }, { 255, 255, 152, 0, 64 }, { 255, 255, 148, 0, 64 }, { 255, 255, 143, 0, 64 }, { 255, 255, 139, 0, 64 }, { 255, 255, 135, 0, 64 }, { 255, 255, 132, 0, 64 }, { 255, 255, 128, 0, 64 }, { 255, 255, 124, 0, 64 }, { 255, 255, 121, 0, 64 }, { 255, 255, 117, 0, 64 }, { 255, 255, 114, 0, 64 }, { 255, 255, 111, 0, 64 }, { 255, 255, 108, 0, 64 }, { 255, 255, 104, 0, 64 }, { 255, 255, 102, 0, 64 }, { 255, 255, 99, 0, 64 }, { 255, 255, 96, 0, 64 }, { 255, 255, 93, 0, 64 }, { 255, 255, 90, 0, 64 }, { 255, 255, 88, 0, 64 }, { 255, 255, 85, 0, 64 }, { 255, 255, 83, 0, 64 }, { 255, 255, 81, 0, 64 }, { 255, 255, 78, 0, 64 }, { 255, 255, 76, 0, 64 }, { 255, 255, 74, 0, 64 }, { 255, 255, 72, 0, 64 }, { 255, 255, 70, 0, 64 }, { 255, 255, 68, 0, 64 }, { 255, 255, 66, 0, 64 }, { 255, 255, 64, 0, 64 }, { 255, 255, 64, 0, 64 }, { 255, 255, 62, 0, 64 }, { 255, 248, 62, 0, 64 }, { 255, 248, 60, 0, 64 }, { 255, 241, 60, 0, 64 }, { 255, 241, 59, 0, 64 }, { 255, 234, 59, 0, 64 }, { 255, 234, 57, 0, 64 }, { 255, 227, 57, 0, 64 }, { 255, 227, 55, 0, 64 }, { 255, 221, 55, 0, 64 }, { 255, 221, 54, 0, 64 }, { 255, 215, 54, 0, 64 }, { 255, 215, 52, 0, 64 }, { 255, 208, 52, 0, 64 }, { 255, 208, 51, 0, 64 }, { 255, 203, 51, 0, 64 }, { 255, 203, 49, 0, 64 }, { 255, 197, 49, 0, 64 }, { 255, 197, 48, 0, 64 }, { 255, 191, 48, 0, 64 }, { 255, 191, 47, 0, 64 }, { 255, 186, 47, 0, 64 }, { 255, 186, 45, 0, 64 }, { 255, 181, 45, 0, 64 }, { 255, 181, 44, 0, 64 }, { 255, 175, 44, 0, 64 }, { 255, 175, 43, 0, 64 }, { 255, 170, 43, 0, 64 }, { 255, 170, 42, 0, 64 }, { 255, 166, 42, 0, 64 }, { 255, 166, 40, 0, 64 }, { 255, 161, 40, 0, 64 }, { 255, 161, 39, 0, 64 }, { 255, 156, 39, 0, 64 }, { 255, 156, 38, 0, 64 }, { 255, 152, 38, 0, 64 }, { 255, 152, 37, 0, 64 }, { 255, 148, 37, 0, 64 }, { 255, 148, 36, 0, 64 }, { 255, 143, 36, 0, 64 }, { 255, 143, 35, 0, 64 }, { 255, 139, 35, 0, 64 }, { 255, 139, 34, 0, 64 }, { 255, 135, 34, 0, 64 }, { 255, 135, 33, 0, 64 }, { 255, 132, 33, 0, 64 }, { 255, 132, 32, 0, 64 }, { 255, 128, 32, 0, 64 } }; static struct bwn_txgain_entry txgain_r0[] = { { 7, 15, 14, 0, 152 }, { 7, 15, 14, 0, 147 }, { 7, 15, 14, 0, 143 }, { 7, 15, 14, 0, 139 }, { 7, 15, 14, 0, 135 }, { 7, 15, 14, 0, 131 }, { 7, 15, 14, 0, 128 }, { 7, 15, 14, 0, 124 }, { 7, 15, 14, 0, 121 }, { 7, 15, 14, 0, 117 }, { 7, 15, 14, 0, 114 }, { 7, 15, 14, 0, 111 }, { 7, 15, 14, 0, 107 }, { 7, 15, 14, 0, 104 }, { 7, 15, 14, 0, 101 }, { 7, 15, 14, 0, 99 }, { 7, 15, 14, 0, 96 }, { 7, 15, 14, 0, 93 }, { 7, 15, 14, 0, 90 }, { 7, 15, 14, 0, 88 }, { 7, 15, 14, 0, 85 }, { 7, 15, 14, 0, 83 }, { 7, 15, 14, 0, 81 }, { 7, 15, 14, 0, 78 }, { 7, 15, 14, 0, 76 }, { 7, 15, 14, 0, 74 }, { 7, 15, 14, 0, 72 }, { 7, 15, 14, 0, 70 }, { 7, 15, 14, 0, 68 }, { 7, 15, 14, 0, 66 }, { 7, 15, 14, 0, 64 }, { 7, 15, 14, 0, 62 }, { 7, 15, 14, 0, 60 }, { 7, 15, 14, 0, 59 }, { 7, 15, 14, 0, 57 }, { 7, 15, 13, 0, 72 }, { 7, 15, 13, 0, 70 }, { 7, 15, 13, 0, 68 }, { 7, 15, 13, 0, 66 }, { 7, 15, 13, 0, 64 }, { 7, 15, 13, 0, 62 }, { 7, 15, 13, 0, 60 }, { 7, 15, 13, 0, 59 }, { 7, 15, 13, 0, 57 }, { 7, 15, 12, 0, 71 }, { 7, 15, 12, 0, 69 }, { 7, 15, 12, 0, 67 }, { 7, 15, 12, 0, 65 }, { 7, 15, 12, 0, 63 }, { 7, 15, 12, 0, 62 }, { 7, 15, 12, 0, 60 }, { 7, 15, 12, 0, 58 }, { 7, 15, 12, 0, 57 }, { 7, 15, 11, 0, 70 }, { 7, 15, 11, 0, 68 }, { 7, 15, 11, 0, 66 }, { 7, 15, 11, 0, 65 }, { 7, 15, 11, 0, 63 }, { 7, 15, 11, 0, 61 }, { 7, 15, 11, 0, 59 }, { 7, 15, 11, 0, 58 }, { 7, 15, 10, 0, 71 }, { 7, 15, 10, 0, 69 }, { 7, 15, 10, 0, 67 }, { 7, 15, 10, 0, 65 }, { 7, 15, 10, 0, 63 }, { 7, 15, 10, 0, 61 }, { 7, 15, 10, 0, 60 }, { 7, 15, 10, 0, 58 }, { 7, 15, 10, 0, 56 }, { 7, 15, 9, 0, 70 }, { 7, 15, 9, 0, 68 }, { 7, 15, 9, 0, 66 }, { 7, 15, 9, 0, 64 }, { 7, 15, 9, 0, 62 }, { 7, 15, 9, 0, 60 }, { 7, 15, 9, 0, 59 }, { 7, 14, 9, 0, 72 }, { 7, 14, 9, 0, 70 }, { 7, 14, 9, 0, 68 }, { 7, 14, 9, 0, 66 }, { 7, 14, 9, 0, 64 }, { 7, 14, 9, 0, 62 }, { 7, 14, 9, 0, 60 }, { 7, 14, 9, 0, 59 }, { 7, 13, 9, 0, 72 }, { 7, 13, 9, 0, 70 }, { 7, 13, 9, 0, 68 }, { 7, 13, 9, 0, 66 }, { 7, 13, 9, 0, 64 }, { 7, 13, 9, 0, 63 }, { 7, 13, 9, 0, 61 }, { 7, 13, 9, 0, 59 }, { 7, 13, 9, 0, 57 }, { 7, 13, 8, 0, 72 }, { 7, 13, 8, 0, 70 }, { 7, 13, 8, 0, 68 }, { 7, 13, 8, 0, 66 }, { 7, 13, 8, 0, 64 }, { 7, 13, 8, 0, 62 }, { 7, 13, 8, 0, 60 }, { 7, 13, 8, 0, 59 }, { 7, 12, 8, 0, 72 }, { 7, 12, 8, 0, 70 }, { 7, 12, 8, 0, 68 }, { 7, 12, 8, 0, 66 }, { 7, 12, 8, 0, 64 }, { 7, 12, 8, 0, 62 }, { 7, 12, 8, 0, 61 }, { 7, 12, 8, 0, 59 }, { 7, 12, 7, 0, 73 }, { 7, 12, 7, 0, 71 }, { 7, 12, 7, 0, 69 }, { 7, 12, 7, 0, 67 }, { 7, 12, 7, 0, 65 }, { 7, 12, 7, 0, 63 }, { 7, 12, 7, 0, 61 }, { 7, 12, 7, 0, 59 }, { 7, 11, 7, 0, 72 }, { 7, 11, 7, 0, 70 }, { 7, 11, 7, 0, 68 }, { 7, 11, 7, 0, 66 }, { 7, 11, 7, 0, 65 }, { 7, 11, 7, 0, 63 }, { 7, 11, 7, 0, 61 }, { 7, 11, 7, 0, 59 }, { 7, 11, 6, 0, 73 }, { 7, 11, 6, 0, 71 } }; static struct bwn_txgain_entry txgain_2ghz_r0[] = { { 4, 15, 9, 0, 64 }, { 4, 15, 9, 0, 62 }, { 4, 15, 9, 0, 60 }, { 4, 15, 9, 0, 59 }, { 4, 14, 9, 0, 72 }, { 4, 14, 9, 0, 70 }, { 4, 14, 9, 0, 68 }, { 4, 14, 9, 0, 66 }, { 4, 14, 9, 0, 64 }, { 4, 14, 9, 0, 62 }, { 4, 14, 9, 0, 60 }, { 4, 14, 9, 0, 59 }, { 4, 13, 9, 0, 72 }, { 4, 13, 9, 0, 70 }, { 4, 13, 9, 0, 68 }, { 4, 13, 9, 0, 66 }, { 4, 13, 9, 0, 64 }, { 4, 13, 9, 0, 63 }, { 4, 13, 9, 0, 61 }, { 4, 13, 9, 0, 59 }, { 4, 13, 9, 0, 57 }, { 4, 13, 8, 0, 72 }, { 4, 13, 8, 0, 70 }, { 4, 13, 8, 0, 68 }, { 4, 13, 8, 0, 66 }, { 4, 13, 8, 0, 64 }, { 4, 13, 8, 0, 62 }, { 4, 13, 8, 0, 60 }, { 4, 13, 8, 0, 59 }, { 4, 12, 8, 0, 72 }, { 4, 12, 8, 0, 70 }, { 4, 12, 8, 0, 68 }, { 4, 12, 8, 0, 66 }, { 4, 12, 8, 0, 64 }, { 4, 12, 8, 0, 62 }, { 4, 12, 8, 0, 61 }, { 4, 12, 8, 0, 59 }, { 4, 12, 7, 0, 73 }, { 4, 12, 7, 0, 71 }, { 4, 12, 7, 0, 69 }, { 4, 12, 7, 0, 67 }, { 4, 12, 7, 0, 65 }, { 4, 12, 7, 0, 63 }, { 4, 12, 7, 0, 61 }, { 4, 12, 7, 0, 59 }, { 4, 11, 7, 0, 72 }, { 4, 11, 7, 0, 70 }, { 4, 11, 7, 0, 68 }, { 4, 11, 7, 0, 66 }, { 4, 11, 7, 0, 65 }, { 4, 11, 7, 0, 63 }, { 4, 11, 7, 0, 61 }, { 4, 11, 7, 0, 59 }, { 4, 11, 6, 0, 73 }, { 4, 11, 6, 0, 71 }, { 4, 11, 6, 0, 69 }, { 4, 11, 6, 0, 67 }, { 4, 11, 6, 0, 65 }, { 4, 11, 6, 0, 63 }, { 4, 11, 6, 0, 61 }, { 4, 11, 6, 0, 60 }, { 4, 10, 6, 0, 72 }, { 4, 10, 6, 0, 70 }, { 4, 10, 6, 0, 68 }, { 4, 10, 6, 0, 66 }, { 4, 10, 6, 0, 64 }, { 4, 10, 6, 0, 62 }, { 4, 10, 6, 0, 60 }, { 4, 10, 6, 0, 59 }, { 4, 10, 5, 0, 72 }, { 4, 10, 5, 0, 70 }, { 4, 10, 5, 0, 68 }, { 4, 10, 5, 0, 66 }, { 4, 10, 5, 0, 64 }, { 4, 10, 5, 0, 62 }, { 4, 10, 5, 0, 60 }, { 4, 10, 5, 0, 59 }, { 4, 9, 5, 0, 70 }, { 4, 9, 5, 0, 68 }, { 4, 9, 5, 0, 66 }, { 4, 9, 5, 0, 64 }, { 4, 9, 5, 0, 63 }, { 4, 9, 5, 0, 61 }, { 4, 9, 5, 0, 59 }, { 4, 9, 4, 0, 71 }, { 4, 9, 4, 0, 69 }, { 4, 9, 4, 0, 67 }, { 4, 9, 4, 0, 65 }, { 4, 9, 4, 0, 63 }, { 4, 9, 4, 0, 62 }, { 4, 9, 4, 0, 60 }, { 4, 9, 4, 0, 58 }, { 4, 8, 4, 0, 70 }, { 4, 8, 4, 0, 68 }, { 4, 8, 4, 0, 66 }, { 4, 8, 4, 0, 65 }, { 4, 8, 4, 0, 63 }, { 4, 8, 4, 0, 61 }, { 4, 8, 4, 0, 59 }, { 4, 7, 4, 0, 68 }, { 4, 7, 4, 0, 66 }, { 4, 7, 4, 0, 64 }, { 4, 7, 4, 0, 62 }, { 4, 7, 4, 0, 61 }, { 4, 7, 4, 0, 59 }, { 4, 7, 3, 0, 67 }, { 4, 7, 3, 0, 65 }, { 4, 7, 3, 0, 63 }, { 4, 7, 3, 0, 62 }, { 4, 7, 3, 0, 60 }, { 4, 6, 3, 0, 65 }, { 4, 6, 3, 0, 63 }, { 4, 6, 3, 0, 61 }, { 4, 6, 3, 0, 60 }, { 4, 6, 3, 0, 58 }, { 4, 5, 3, 0, 68 }, { 4, 5, 3, 0, 66 }, { 4, 5, 3, 0, 64 }, { 4, 5, 3, 0, 62 }, { 4, 5, 3, 0, 60 }, { 4, 5, 3, 0, 59 }, { 4, 5, 3, 0, 57 }, { 4, 4, 2, 0, 83 }, { 4, 4, 2, 0, 81 }, { 4, 4, 2, 0, 78 }, { 4, 4, 2, 0, 76 }, { 4, 4, 2, 0, 74 }, { 4, 4, 2, 0, 72 } }; static struct bwn_txgain_entry txgain_5ghz_r0[] = { { 7, 15, 15, 0, 99 }, { 7, 15, 15, 0, 96 }, { 7, 15, 15, 0, 93 }, { 7, 15, 15, 0, 90 }, { 7, 15, 15, 0, 88 }, { 7, 15, 15, 0, 85 }, { 7, 15, 15, 0, 83 }, { 7, 15, 15, 0, 81 }, { 7, 15, 15, 0, 78 }, { 7, 15, 15, 0, 76 }, { 7, 15, 15, 0, 74 }, { 7, 15, 15, 0, 72 }, { 7, 15, 15, 0, 70 }, { 7, 15, 15, 0, 68 }, { 7, 15, 15, 0, 66 }, { 7, 15, 15, 0, 64 }, { 7, 15, 15, 0, 62 }, { 7, 15, 15, 0, 60 }, { 7, 15, 15, 0, 59 }, { 7, 15, 15, 0, 57 }, { 7, 15, 15, 0, 55 }, { 7, 15, 14, 0, 72 }, { 7, 15, 14, 0, 70 }, { 7, 15, 14, 0, 68 }, { 7, 15, 14, 0, 66 }, { 7, 15, 14, 0, 64 }, { 7, 15, 14, 0, 62 }, { 7, 15, 14, 0, 60 }, { 7, 15, 14, 0, 58 }, { 7, 15, 14, 0, 56 }, { 7, 15, 14, 0, 55 }, { 7, 15, 13, 0, 71 }, { 7, 15, 13, 0, 69 }, { 7, 15, 13, 0, 67 }, { 7, 15, 13, 0, 65 }, { 7, 15, 13, 0, 63 }, { 7, 15, 13, 0, 62 }, { 7, 15, 13, 0, 60 }, { 7, 15, 13, 0, 58 }, { 7, 15, 13, 0, 56 }, { 7, 15, 12, 0, 72 }, { 7, 15, 12, 0, 70 }, { 7, 15, 12, 0, 68 }, { 7, 15, 12, 0, 66 }, { 7, 15, 12, 0, 64 }, { 7, 15, 12, 0, 62 }, { 7, 15, 12, 0, 60 }, { 7, 15, 12, 0, 59 }, { 7, 15, 12, 0, 57 }, { 7, 15, 11, 0, 73 }, { 7, 15, 11, 0, 71 }, { 7, 15, 11, 0, 69 }, { 7, 15, 11, 0, 67 }, { 7, 15, 11, 0, 65 }, { 7, 15, 11, 0, 63 }, { 7, 15, 11, 0, 61 }, { 7, 15, 11, 0, 60 }, { 7, 15, 11, 0, 58 }, { 7, 15, 10, 0, 71 }, { 7, 15, 10, 0, 69 }, { 7, 15, 10, 0, 67 }, { 7, 15, 10, 0, 65 }, { 7, 15, 10, 0, 63 }, { 7, 15, 10, 0, 61 }, { 7, 15, 10, 0, 60 }, { 7, 15, 10, 0, 58 }, { 7, 15, 9, 0, 70 }, { 7, 15, 9, 0, 68 }, { 7, 15, 9, 0, 66 }, { 7, 15, 9, 0, 64 }, { 7, 15, 9, 0, 62 }, { 7, 15, 9, 0, 61 }, { 7, 15, 9, 0, 59 }, { 7, 15, 9, 0, 57 }, { 7, 15, 9, 0, 56 }, { 7, 14, 9, 0, 68 }, { 7, 14, 9, 0, 66 }, { 7, 14, 9, 0, 65 }, { 7, 14, 9, 0, 63 }, { 7, 14, 9, 0, 61 }, { 7, 14, 9, 0, 59 }, { 7, 14, 9, 0, 58 }, { 7, 13, 9, 0, 70 }, { 7, 13, 9, 0, 68 }, { 7, 13, 9, 0, 66 }, { 7, 13, 9, 0, 64 }, { 7, 13, 9, 0, 63 }, { 7, 13, 9, 0, 61 }, { 7, 13, 9, 0, 59 }, { 7, 13, 9, 0, 57 }, { 7, 13, 8, 0, 70 }, { 7, 13, 8, 0, 68 }, { 7, 13, 8, 0, 66 }, { 7, 13, 8, 0, 64 }, { 7, 13, 8, 0, 62 }, { 7, 13, 8, 0, 60 }, { 7, 13, 8, 0, 59 }, { 7, 13, 8, 0, 57 }, { 7, 12, 8, 0, 70 }, { 7, 12, 8, 0, 68 }, { 7, 12, 8, 0, 66 }, { 7, 12, 8, 0, 64 }, { 7, 12, 8, 0, 62 }, { 7, 12, 8, 0, 61 }, { 7, 12, 8, 0, 59 }, { 7, 12, 8, 0, 57 }, { 7, 12, 7, 0, 70 }, { 7, 12, 7, 0, 68 }, { 7, 12, 7, 0, 66 }, { 7, 12, 7, 0, 64 }, { 7, 12, 7, 0, 62 }, { 7, 12, 7, 0, 61 }, { 7, 12, 7, 0, 59 }, { 7, 12, 7, 0, 57 }, { 7, 11, 7, 0, 70 }, { 7, 11, 7, 0, 68 }, { 7, 11, 7, 0, 66 }, { 7, 11, 7, 0, 64 }, { 7, 11, 7, 0, 62 }, { 7, 11, 7, 0, 61 }, { 7, 11, 7, 0, 59 }, { 7, 11, 7, 0, 57 }, { 7, 11, 6, 0, 69 }, { 7, 11, 6, 0, 67 }, { 7, 11, 6, 0, 65 }, { 7, 11, 6, 0, 63 }, { 7, 11, 6, 0, 62 }, { 7, 11, 6, 0, 60 } }; static struct bwn_txgain_entry txgain_r1[] = { { 7, 15, 14, 0, 152 }, { 7, 15, 14, 0, 147 }, { 7, 15, 14, 0, 143 }, { 7, 15, 14, 0, 139 }, { 7, 15, 14, 0, 135 }, { 7, 15, 14, 0, 131 }, { 7, 15, 14, 0, 128 }, { 7, 15, 14, 0, 124 }, { 7, 15, 14, 0, 121 }, { 7, 15, 14, 0, 117 }, { 7, 15, 14, 0, 114 }, { 7, 15, 14, 0, 111 }, { 7, 15, 14, 0, 107 }, { 7, 15, 14, 0, 104 }, { 7, 15, 14, 0, 101 }, { 7, 15, 14, 0, 99 }, { 7, 15, 14, 0, 96 }, { 7, 15, 14, 0, 93 }, { 7, 15, 14, 0, 90 }, { 7, 15, 14, 0, 88 }, { 7, 15, 14, 0, 85 }, { 7, 15, 14, 0, 83 }, { 7, 15, 14, 0, 81 }, { 7, 15, 14, 0, 78 }, { 7, 15, 14, 0, 76 }, { 7, 15, 14, 0, 74 }, { 7, 15, 14, 0, 72 }, { 7, 15, 14, 0, 70 }, { 7, 15, 14, 0, 68 }, { 7, 15, 14, 0, 66 }, { 7, 15, 14, 0, 64 }, { 7, 15, 14, 0, 62 }, { 7, 15, 14, 0, 60 }, { 7, 15, 14, 0, 59 }, { 7, 15, 14, 0, 57 }, { 7, 15, 13, 0, 72 }, { 7, 15, 13, 0, 70 }, { 7, 15, 14, 0, 68 }, { 7, 15, 14, 0, 66 }, { 7, 15, 14, 0, 64 }, { 7, 15, 14, 0, 62 }, { 7, 15, 14, 0, 60 }, { 7, 15, 14, 0, 59 }, { 7, 15, 14, 0, 57 }, { 7, 15, 13, 0, 72 }, { 7, 15, 13, 0, 70 }, { 7, 15, 13, 0, 68 }, { 7, 15, 13, 0, 66 }, { 7, 15, 13, 0, 64 }, { 7, 15, 13, 0, 62 }, { 7, 15, 13, 0, 60 }, { 7, 15, 13, 0, 59 }, { 7, 15, 13, 0, 57 }, { 7, 15, 12, 0, 71 }, { 7, 15, 12, 0, 69 }, { 7, 15, 12, 0, 67 }, { 7, 15, 12, 0, 65 }, { 7, 15, 12, 0, 63 }, { 7, 15, 12, 0, 62 }, { 7, 15, 12, 0, 60 }, { 7, 15, 12, 0, 58 }, { 7, 15, 12, 0, 57 }, { 7, 15, 11, 0, 70 }, { 7, 15, 11, 0, 68 }, { 7, 15, 11, 0, 66 }, { 7, 15, 11, 0, 65 }, { 7, 15, 11, 0, 63 }, { 7, 15, 11, 0, 61 }, { 7, 15, 11, 0, 59 }, { 7, 15, 11, 0, 58 }, { 7, 15, 10, 0, 71 }, { 7, 15, 10, 0, 69 }, { 7, 15, 10, 0, 67 }, { 7, 15, 10, 0, 65 }, { 7, 15, 10, 0, 63 }, { 7, 15, 10, 0, 61 }, { 7, 15, 10, 0, 60 }, { 7, 15, 10, 0, 58 }, { 7, 15, 10, 0, 56 }, { 7, 15, 9, 0, 70 }, { 7, 15, 9, 0, 68 }, { 7, 15, 9, 0, 66 }, { 7, 15, 9, 0, 64 }, { 7, 15, 9, 0, 62 }, { 7, 15, 9, 0, 60 }, { 7, 15, 9, 0, 59 }, { 7, 14, 9, 0, 72 }, { 7, 14, 9, 0, 70 }, { 7, 14, 9, 0, 68 }, { 7, 14, 9, 0, 66 }, { 7, 14, 9, 0, 64 }, { 7, 14, 9, 0, 62 }, { 7, 14, 9, 0, 60 }, { 7, 14, 9, 0, 59 }, { 7, 13, 9, 0, 72 }, { 7, 13, 9, 0, 70 }, { 7, 13, 9, 0, 68 }, { 7, 13, 9, 0, 66 }, { 7, 13, 9, 0, 64 }, { 7, 13, 9, 0, 63 }, { 7, 13, 9, 0, 61 }, { 7, 13, 9, 0, 59 }, { 7, 13, 9, 0, 57 }, { 7, 13, 8, 0, 72 }, { 7, 13, 8, 0, 70 }, { 7, 13, 8, 0, 68 }, { 7, 13, 8, 0, 66 }, { 7, 13, 8, 0, 64 }, { 7, 13, 8, 0, 62 }, { 7, 13, 8, 0, 60 }, { 7, 13, 8, 0, 59 }, { 7, 12, 8, 0, 72 }, { 7, 12, 8, 0, 70 }, { 7, 12, 8, 0, 68 }, { 7, 12, 8, 0, 66 }, { 7, 12, 8, 0, 64 }, { 7, 12, 8, 0, 62 }, { 7, 12, 8, 0, 61 }, { 7, 12, 8, 0, 59 }, { 7, 12, 7, 0, 73 }, { 7, 12, 7, 0, 71 }, { 7, 12, 7, 0, 69 }, { 7, 12, 7, 0, 67 }, { 7, 12, 7, 0, 65 }, { 7, 12, 7, 0, 63 }, { 7, 12, 7, 0, 61 }, { 7, 12, 7, 0, 59 }, { 7, 11, 7, 0, 72 }, { 7, 11, 7, 0, 70 }, { 7, 11, 7, 0, 68 }, { 7, 11, 7, 0, 66 }, { 7, 11, 7, 0, 65 }, { 7, 11, 7, 0, 63 }, { 7, 11, 7, 0, 61 }, { 7, 11, 7, 0, 59 }, { 7, 11, 6, 0, 73 }, { 7, 11, 6, 0, 71 } }; static struct bwn_txgain_entry txgain_2ghz_r1[] = { { 4, 15, 15, 0, 90 }, { 4, 15, 15, 0, 88 }, { 4, 15, 15, 0, 85 }, { 4, 15, 15, 0, 83 }, { 4, 15, 15, 0, 81 }, { 4, 15, 15, 0, 78 }, { 4, 15, 15, 0, 76 }, { 4, 15, 15, 0, 74 }, { 4, 15, 15, 0, 72 }, { 4, 15, 15, 0, 70 }, { 4, 15, 15, 0, 68 }, { 4, 15, 15, 0, 66 }, { 4, 15, 15, 0, 64 }, { 4, 15, 15, 0, 62 }, { 4, 15, 15, 0, 60 }, { 4, 15, 15, 0, 59 }, { 4, 15, 14, 0, 72 }, { 4, 15, 14, 0, 70 }, { 4, 15, 14, 0, 68 }, { 4, 15, 14, 0, 66 }, { 4, 15, 14, 0, 64 }, { 4, 15, 14, 0, 62 }, { 4, 15, 14, 0, 60 }, { 4, 15, 14, 0, 59 }, { 4, 15, 13, 0, 72 }, { 4, 15, 13, 0, 70 }, { 4, 15, 13, 0, 68 }, { 4, 15, 13, 0, 66 }, { 4, 15, 13, 0, 64 }, { 4, 15, 13, 0, 62 }, { 4, 15, 13, 0, 60 }, { 4, 15, 13, 0, 59 }, { 4, 15, 12, 0, 72 }, { 4, 15, 12, 0, 70 }, { 4, 15, 12, 0, 68 }, { 4, 15, 12, 0, 66 }, { 4, 15, 12, 0, 64 }, { 4, 15, 12, 0, 62 }, { 4, 15, 12, 0, 60 }, { 4, 15, 12, 0, 59 }, { 4, 15, 11, 0, 72 }, { 4, 15, 11, 0, 70 }, { 4, 15, 11, 0, 68 }, { 4, 15, 11, 0, 66 }, { 4, 15, 11, 0, 64 }, { 4, 15, 11, 0, 62 }, { 4, 15, 11, 0, 60 }, { 4, 15, 11, 0, 59 }, { 4, 15, 10, 0, 72 }, { 4, 15, 10, 0, 70 }, { 4, 15, 10, 0, 68 }, { 4, 15, 10, 0, 66 }, { 4, 15, 10, 0, 64 }, { 4, 15, 10, 0, 62 }, { 4, 15, 10, 0, 60 }, { 4, 15, 10, 0, 59 }, { 4, 15, 9, 0, 72 }, { 4, 15, 9, 0, 70 }, { 4, 15, 9, 0, 68 }, { 4, 15, 9, 0, 66 }, { 4, 15, 9, 0, 64 }, { 4, 15, 9, 0, 62 }, { 4, 15, 9, 0, 60 }, { 4, 15, 9, 0, 59 }, { 4, 14, 9, 0, 72 }, { 4, 14, 9, 0, 70 }, { 4, 14, 9, 0, 68 }, { 4, 14, 9, 0, 66 }, { 4, 14, 9, 0, 64 }, { 4, 14, 9, 0, 62 }, { 4, 14, 9, 0, 60 }, { 4, 14, 9, 0, 59 }, { 4, 13, 9, 0, 72 }, { 4, 13, 9, 0, 70 }, { 4, 13, 9, 0, 68 }, { 4, 13, 9, 0, 66 }, { 4, 13, 9, 0, 64 }, { 4, 13, 9, 0, 63 }, { 4, 13, 9, 0, 61 }, { 4, 13, 9, 0, 59 }, { 4, 13, 9, 0, 57 }, { 4, 13, 8, 0, 72 }, { 4, 13, 8, 0, 70 }, { 4, 13, 8, 0, 68 }, { 4, 13, 8, 0, 66 }, { 4, 13, 8, 0, 64 }, { 4, 13, 8, 0, 62 }, { 4, 13, 8, 0, 60 }, { 4, 13, 8, 0, 59 }, { 4, 12, 8, 0, 72 }, { 4, 12, 8, 0, 70 }, { 4, 12, 8, 0, 68 }, { 4, 12, 8, 0, 66 }, { 4, 12, 8, 0, 64 }, { 4, 12, 8, 0, 62 }, { 4, 12, 8, 0, 61 }, { 4, 12, 8, 0, 59 }, { 4, 12, 7, 0, 73 }, { 4, 12, 7, 0, 71 }, { 4, 12, 7, 0, 69 }, { 4, 12, 7, 0, 67 }, { 4, 12, 7, 0, 65 }, { 4, 12, 7, 0, 63 }, { 4, 12, 7, 0, 61 }, { 4, 12, 7, 0, 59 }, { 4, 11, 7, 0, 72 }, { 4, 11, 7, 0, 70 }, { 4, 11, 7, 0, 68 }, { 4, 11, 7, 0, 66 }, { 4, 11, 7, 0, 65 }, { 4, 11, 7, 0, 63 }, { 4, 11, 7, 0, 61 }, { 4, 11, 7, 0, 59 }, { 4, 11, 6, 0, 73 }, { 4, 11, 6, 0, 71 }, { 4, 11, 6, 0, 69 }, { 4, 11, 6, 0, 67 }, { 4, 11, 6, 0, 65 }, { 4, 11, 6, 0, 63 }, { 4, 11, 6, 0, 61 }, { 4, 11, 6, 0, 60 }, { 4, 10, 6, 0, 72 }, { 4, 10, 6, 0, 70 }, { 4, 10, 6, 0, 68 }, { 4, 10, 6, 0, 66 }, { 4, 10, 6, 0, 64 }, { 4, 10, 6, 0, 62 }, { 4, 10, 6, 0, 60 } }; static struct bwn_txgain_entry txgain_5ghz_r1[] = { { 7, 15, 15, 0, 99 }, { 7, 15, 15, 0, 96 }, { 7, 15, 15, 0, 93 }, { 7, 15, 15, 0, 90 }, { 7, 15, 15, 0, 88 }, { 7, 15, 15, 0, 85 }, { 7, 15, 15, 0, 83 }, { 7, 15, 15, 0, 81 }, { 7, 15, 15, 0, 78 }, { 7, 15, 15, 0, 76 }, { 7, 15, 15, 0, 74 }, { 7, 15, 15, 0, 72 }, { 7, 15, 15, 0, 70 }, { 7, 15, 15, 0, 68 }, { 7, 15, 15, 0, 66 }, { 7, 15, 15, 0, 64 }, { 7, 15, 15, 0, 62 }, { 7, 15, 15, 0, 60 }, { 7, 15, 15, 0, 59 }, { 7, 15, 15, 0, 57 }, { 7, 15, 15, 0, 55 }, { 7, 15, 14, 0, 72 }, { 7, 15, 14, 0, 70 }, { 7, 15, 14, 0, 68 }, { 7, 15, 14, 0, 66 }, { 7, 15, 14, 0, 64 }, { 7, 15, 14, 0, 62 }, { 7, 15, 14, 0, 60 }, { 7, 15, 14, 0, 58 }, { 7, 15, 14, 0, 56 }, { 7, 15, 14, 0, 55 }, { 7, 15, 13, 0, 71 }, { 7, 15, 13, 0, 69 }, { 7, 15, 13, 0, 67 }, { 7, 15, 13, 0, 65 }, { 7, 15, 13, 0, 63 }, { 7, 15, 13, 0, 62 }, { 7, 15, 13, 0, 60 }, { 7, 15, 13, 0, 58 }, { 7, 15, 13, 0, 56 }, { 7, 15, 12, 0, 72 }, { 7, 15, 12, 0, 70 }, { 7, 15, 12, 0, 68 }, { 7, 15, 12, 0, 66 }, { 7, 15, 12, 0, 64 }, { 7, 15, 12, 0, 62 }, { 7, 15, 12, 0, 60 }, { 7, 15, 12, 0, 59 }, { 7, 15, 12, 0, 57 }, { 7, 15, 11, 0, 73 }, { 7, 15, 11, 0, 71 }, { 7, 15, 11, 0, 69 }, { 7, 15, 11, 0, 67 }, { 7, 15, 11, 0, 65 }, { 7, 15, 11, 0, 63 }, { 7, 15, 11, 0, 61 }, { 7, 15, 11, 0, 60 }, { 7, 15, 11, 0, 58 }, { 7, 15, 10, 0, 71 }, { 7, 15, 10, 0, 69 }, { 7, 15, 10, 0, 67 }, { 7, 15, 10, 0, 65 }, { 7, 15, 10, 0, 63 }, { 7, 15, 10, 0, 61 }, { 7, 15, 10, 0, 60 }, { 7, 15, 10, 0, 58 }, { 7, 15, 9, 0, 70 }, { 7, 15, 9, 0, 68 }, { 7, 15, 9, 0, 66 }, { 7, 15, 9, 0, 64 }, { 7, 15, 9, 0, 62 }, { 7, 15, 9, 0, 61 }, { 7, 15, 9, 0, 59 }, { 7, 15, 9, 0, 57 }, { 7, 15, 9, 0, 56 }, { 7, 14, 9, 0, 68 }, { 7, 14, 9, 0, 66 }, { 7, 14, 9, 0, 65 }, { 7, 14, 9, 0, 63 }, { 7, 14, 9, 0, 61 }, { 7, 14, 9, 0, 59 }, { 7, 14, 9, 0, 58 }, { 7, 13, 9, 0, 70 }, { 7, 13, 9, 0, 68 }, { 7, 13, 9, 0, 66 }, { 7, 13, 9, 0, 64 }, { 7, 13, 9, 0, 63 }, { 7, 13, 9, 0, 61 }, { 7, 13, 9, 0, 59 }, { 7, 13, 9, 0, 57 }, { 7, 13, 8, 0, 70 }, { 7, 13, 8, 0, 68 }, { 7, 13, 8, 0, 66 }, { 7, 13, 8, 0, 64 }, { 7, 13, 8, 0, 62 }, { 7, 13, 8, 0, 60 }, { 7, 13, 8, 0, 59 }, { 7, 13, 8, 0, 57 }, { 7, 12, 8, 0, 70 }, { 7, 12, 8, 0, 68 }, { 7, 12, 8, 0, 66 }, { 7, 12, 8, 0, 64 }, { 7, 12, 8, 0, 62 }, { 7, 12, 8, 0, 61 }, { 7, 12, 8, 0, 59 }, { 7, 12, 8, 0, 57 }, { 7, 12, 7, 0, 70 }, { 7, 12, 7, 0, 68 }, { 7, 12, 7, 0, 66 }, { 7, 12, 7, 0, 64 }, { 7, 12, 7, 0, 62 }, { 7, 12, 7, 0, 61 }, { 7, 12, 7, 0, 59 }, { 7, 12, 7, 0, 57 }, { 7, 11, 7, 0, 70 }, { 7, 11, 7, 0, 68 }, { 7, 11, 7, 0, 66 }, { 7, 11, 7, 0, 64 }, { 7, 11, 7, 0, 62 }, { 7, 11, 7, 0, 61 }, { 7, 11, 7, 0, 59 }, { 7, 11, 7, 0, 57 }, { 7, 11, 6, 0, 69 }, { 7, 11, 6, 0, 67 }, { 7, 11, 6, 0, 65 }, { 7, 11, 6, 0, 63 }, { 7, 11, 6, 0, 62 }, { 7, 11, 6, 0, 60 } }; if (mac->mac_phy.rev != 0 && mac->mac_phy.rev != 1) { if (siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_NOPA) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_r2); else if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_2ghz_r2); else bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_5ghz_r2); return; } if (mac->mac_phy.rev == 0) { if ((siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_NOPA) || (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_HGPA)) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_r0); else if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_2ghz_r0); else bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_5ghz_r0); return; } if ((siba_sprom_get_bf_hi(sc->sc_dev) & BWN_BFH_NOPA) || (siba_sprom_get_bf_lo(sc->sc_dev) & BWN_BFL_HGPA)) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_r1); else if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_2ghz_r1); else bwn_phy_lp_gaintbl_write_multi(mac, 0, 128, txgain_5ghz_r1); } static void bwn_tab_write(struct bwn_mac *mac, uint32_t typeoffset, uint32_t value) { uint32_t offset, type; type = BWN_TAB_GETTYPE(typeoffset); offset = BWN_TAB_GETOFFSET(typeoffset); KASSERT(offset <= 0xffff, ("%s:%d: fail", __func__, __LINE__)); switch (type) { case BWN_TAB_8BIT: KASSERT(!(value & ~0xff), ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; case BWN_TAB_16BIT: KASSERT(!(value & ~0xffff), ("%s:%d: fail", __func__, __LINE__)); BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; case BWN_TAB_32BIT: BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATAHI, value >> 16); BWN_PHY_WRITE(mac, BWN_PHY_TABLEDATALO, value); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); } } static int bwn_phy_lp_loopback(struct bwn_mac *mac) { struct bwn_phy_lp_iq_est ie; int i, index = -1; uint32_t tmp; memset(&ie, 0, sizeof(ie)); bwn_phy_lp_set_trsw_over(mac, 1, 1); BWN_PHY_SET(mac, BWN_PHY_AFE_CTL_OVR, 1); BWN_PHY_MASK(mac, BWN_PHY_AFE_CTL_OVRVAL, 0xfffe); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x800); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x800); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x8); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x8); BWN_RF_WRITE(mac, BWN_B2062_N_TXCTL_A, 0x80); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_0, 0x80); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_VAL_0, 0x80); for (i = 0; i < 32; i++) { bwn_phy_lp_set_rxgain_idx(mac, i); bwn_phy_lp_ddfs_turnon(mac, 1, 1, 5, 5, 0); if (!(bwn_phy_lp_rx_iq_est(mac, 1000, 32, &ie))) continue; tmp = (ie.ie_ipwr + ie.ie_qpwr) / 1000; if ((tmp > 4000) && (tmp < 10000)) { index = i; break; } } bwn_phy_lp_ddfs_turnoff(mac); return (index); } static void bwn_phy_lp_set_rxgain_idx(struct bwn_mac *mac, uint16_t idx) { bwn_phy_lp_set_rxgain(mac, bwn_tab_read(mac, BWN_TAB_2(12, idx))); } static void bwn_phy_lp_ddfs_turnon(struct bwn_mac *mac, int i_on, int q_on, int incr1, int incr2, int scale_idx) { bwn_phy_lp_ddfs_turnoff(mac); BWN_PHY_MASK(mac, BWN_PHY_AFE_DDFS_POINTER_INIT, 0xff80); BWN_PHY_MASK(mac, BWN_PHY_AFE_DDFS_POINTER_INIT, 0x80ff); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS_INCR_INIT, 0xff80, incr1); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS_INCR_INIT, 0x80ff, incr2 << 8); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS, 0xfff7, i_on << 3); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS, 0xffef, q_on << 4); BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DDFS, 0xff9f, scale_idx << 5); BWN_PHY_MASK(mac, BWN_PHY_AFE_DDFS, 0xfffb); BWN_PHY_SET(mac, BWN_PHY_AFE_DDFS, 0x2); BWN_PHY_SET(mac, BWN_PHY_LP_PHY_CTL, 0x20); } static uint8_t bwn_phy_lp_rx_iq_est(struct bwn_mac *mac, uint16_t sample, uint8_t time, struct bwn_phy_lp_iq_est *ie) { int i; BWN_PHY_MASK(mac, BWN_PHY_CRSGAIN_CTL, 0xfff7); BWN_PHY_WRITE(mac, BWN_PHY_IQ_NUM_SMPLS_ADDR, sample); BWN_PHY_SETMASK(mac, BWN_PHY_IQ_ENABLE_WAIT_TIME_ADDR, 0xff00, time); BWN_PHY_MASK(mac, BWN_PHY_IQ_ENABLE_WAIT_TIME_ADDR, 0xfeff); BWN_PHY_SET(mac, BWN_PHY_IQ_ENABLE_WAIT_TIME_ADDR, 0x200); for (i = 0; i < 500; i++) { if (!(BWN_PHY_READ(mac, BWN_PHY_IQ_ENABLE_WAIT_TIME_ADDR) & 0x200)) break; DELAY(1000); } if ((BWN_PHY_READ(mac, BWN_PHY_IQ_ENABLE_WAIT_TIME_ADDR) & 0x200)) { BWN_PHY_SET(mac, BWN_PHY_CRSGAIN_CTL, 0x8); return 0; } ie->ie_iqprod = BWN_PHY_READ(mac, BWN_PHY_IQ_ACC_HI_ADDR); ie->ie_iqprod <<= 16; ie->ie_iqprod |= BWN_PHY_READ(mac, BWN_PHY_IQ_ACC_LO_ADDR); ie->ie_ipwr = BWN_PHY_READ(mac, BWN_PHY_IQ_I_PWR_ACC_HI_ADDR); ie->ie_ipwr <<= 16; ie->ie_ipwr |= BWN_PHY_READ(mac, BWN_PHY_IQ_I_PWR_ACC_LO_ADDR); ie->ie_qpwr = BWN_PHY_READ(mac, BWN_PHY_IQ_Q_PWR_ACC_HI_ADDR); ie->ie_qpwr <<= 16; ie->ie_qpwr |= BWN_PHY_READ(mac, BWN_PHY_IQ_Q_PWR_ACC_LO_ADDR); BWN_PHY_SET(mac, BWN_PHY_CRSGAIN_CTL, 0x8); return 1; } static uint32_t bwn_tab_read(struct bwn_mac *mac, uint32_t typeoffset) { uint32_t offset, type, value; type = BWN_TAB_GETTYPE(typeoffset); offset = BWN_TAB_GETOFFSET(typeoffset); KASSERT(offset <= 0xffff, ("%s:%d: fail", __func__, __LINE__)); switch (type) { case BWN_TAB_8BIT: BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); value = BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO) & 0xff; break; case BWN_TAB_16BIT: BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); value = BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO); break; case BWN_TAB_32BIT: BWN_PHY_WRITE(mac, BWN_PHY_TABLE_ADDR, offset); value = BWN_PHY_READ(mac, BWN_PHY_TABLEDATAHI); value <<= 16; value |= BWN_PHY_READ(mac, BWN_PHY_TABLEDATALO); break; default: KASSERT(0 == 1, ("%s:%d: fail", __func__, __LINE__)); value = 0; } return (value); } static void bwn_phy_lp_ddfs_turnoff(struct bwn_mac *mac) { BWN_PHY_MASK(mac, BWN_PHY_AFE_DDFS, 0xfffd); BWN_PHY_MASK(mac, BWN_PHY_LP_PHY_CTL, 0xffdf); } static void bwn_phy_lp_set_txgain_dac(struct bwn_mac *mac, uint16_t dac) { uint16_t ctl; ctl = BWN_PHY_READ(mac, BWN_PHY_AFE_DAC_CTL) & 0xc7f; ctl |= dac << 7; BWN_PHY_SETMASK(mac, BWN_PHY_AFE_DAC_CTL, 0xf000, ctl); } static void bwn_phy_lp_set_txgain_pa(struct bwn_mac *mac, uint16_t gain) { BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xfb), 0xe03f, gain << 6); BWN_PHY_SETMASK(mac, BWN_PHY_OFDM(0xfd), 0x80ff, gain << 8); } static void bwn_phy_lp_set_txgain_override(struct bwn_mac *mac) { if (mac->mac_phy.rev < 2) BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x100); else { BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x80); BWN_PHY_SET(mac, BWN_PHY_RF_OVERRIDE_2, 0x4000); } BWN_PHY_SET(mac, BWN_PHY_AFE_CTL_OVR, 0x40); } static uint16_t bwn_phy_lp_get_pa_gain(struct bwn_mac *mac) { return BWN_PHY_READ(mac, BWN_PHY_OFDM(0xfb)) & 0x7f; } static uint8_t bwn_nbits(int32_t val) { uint32_t tmp; uint8_t nbits = 0; for (tmp = abs(val); tmp != 0; tmp >>= 1) nbits++; return (nbits); } static void bwn_phy_lp_gaintbl_write_multi(struct bwn_mac *mac, int offset, int count, struct bwn_txgain_entry *table) { int i; for (i = offset; i < count; i++) bwn_phy_lp_gaintbl_write(mac, i, table[i]); } static void bwn_phy_lp_gaintbl_write(struct bwn_mac *mac, int offset, struct bwn_txgain_entry data) { if (mac->mac_phy.rev >= 2) bwn_phy_lp_gaintbl_write_r2(mac, offset, data); else bwn_phy_lp_gaintbl_write_r01(mac, offset, data); } static void bwn_phy_lp_gaintbl_write_r2(struct bwn_mac *mac, int offset, struct bwn_txgain_entry te) { struct bwn_softc *sc = mac->mac_sc; struct ifnet *ifp = sc->sc_ifp; struct ieee80211com *ic = ifp->if_l2com; uint32_t tmp; KASSERT(mac->mac_phy.rev >= 2, ("%s:%d: fail", __func__, __LINE__)); tmp = (te.te_pad << 16) | (te.te_pga << 8) | te.te_gm; if (mac->mac_phy.rev >= 3) { tmp |= ((IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) ? (0x10 << 24) : (0x70 << 24)); } else { tmp |= ((IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) ? (0x14 << 24) : (0x7f << 24)); } bwn_tab_write(mac, BWN_TAB_4(7, 0xc0 + offset), tmp); bwn_tab_write(mac, BWN_TAB_4(7, 0x140 + offset), te.te_bbmult << 20 | te.te_dac << 28); } static void bwn_phy_lp_gaintbl_write_r01(struct bwn_mac *mac, int offset, struct bwn_txgain_entry te) { KASSERT(mac->mac_phy.rev < 2, ("%s:%d: fail", __func__, __LINE__)); bwn_tab_write(mac, BWN_TAB_4(10, 0xc0 + offset), (te.te_pad << 11) | (te.te_pga << 7) | (te.te_gm << 4) | te.te_dac); bwn_tab_write(mac, BWN_TAB_4(10, 0x140 + offset), te.te_bbmult << 20); } static void bwn_sysctl_node(struct bwn_softc *sc) { device_t dev = sc->sc_dev; struct bwn_mac *mac; struct bwn_stats *stats; /* XXX assume that count of MAC is only 1. */ if ((mac = sc->sc_curmac) == NULL) return; stats = &mac->mac_stats; SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "linknoise", CTLFLAG_RW, &stats->rts, 0, "Noise level"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rts", CTLFLAG_RW, &stats->rts, 0, "RTS"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rtsfail", CTLFLAG_RW, &stats->rtsfail, 0, "RTS failed to send"); #ifdef BWN_DEBUG SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "Debug flags"); #endif } static device_method_t bwn_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bwn_probe), DEVMETHOD(device_attach, bwn_attach), DEVMETHOD(device_detach, bwn_detach), DEVMETHOD(device_suspend, bwn_suspend), DEVMETHOD(device_resume, bwn_resume), DEVMETHOD_END }; static driver_t bwn_driver = { "bwn", bwn_methods, sizeof(struct bwn_softc) }; static devclass_t bwn_devclass; DRIVER_MODULE(bwn, siba_bwn, bwn_driver, bwn_devclass, 0, 0); MODULE_DEPEND(bwn, siba_bwn, 1, 1, 1); MODULE_DEPEND(bwn, wlan, 1, 1, 1); /* 802.11 media layer */ MODULE_DEPEND(bwn, firmware, 1, 1, 1); /* firmware support */ MODULE_DEPEND(bwn, wlan_amrr, 1, 1, 1); Index: head/sys/dev/ciss/ciss.c =================================================================== --- head/sys/dev/ciss/ciss.c (revision 280346) +++ head/sys/dev/ciss/ciss.c (revision 280347) @@ -1,4733 +1,4734 @@ /*- * Copyright (c) 2001 Michael Smith * Copyright (c) 2004 Paul Saab * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Common Interface for SCSI-3 Support driver. * * CISS claims to provide a common interface between a generic SCSI * transport and an intelligent host adapter. * * This driver supports CISS as defined in the document "CISS Command * Interface for SCSI-3 Support Open Specification", Version 1.04, * Valence Number 1, dated 20001127, produced by Compaq Computer * Corporation. This document appears to be a hastily and somewhat * arbitrarlily cut-down version of a larger (and probably even more * chaotic and inconsistent) Compaq internal document. Various * details were also gleaned from Compaq's "cciss" driver for Linux. * * We provide a shim layer between the CISS interface and CAM, * offloading most of the queueing and being-a-disk chores onto CAM. * Entry to the driver is via the PCI bus attachment (ciss_probe, * ciss_attach, etc) and via the CAM interface (ciss_cam_action, * ciss_cam_poll). The Compaq CISS adapters are, however, poor SCSI * citizens and we have to fake up some responses to get reasonable * behaviour out of them. In addition, the CISS command set is by no * means adequate to support the functionality of a RAID controller, * and thus the supported Compaq adapters utilise portions of the * control protocol from earlier Compaq adapter families. * * Note that we only support the "simple" transport layer over PCI. * This interface (ab)uses the I2O register set (specifically the post * queues) to exchange commands with the adapter. Other interfaces * are available, but we aren't supposed to know about them, and it is * dubious whether they would provide major performance improvements * except under extreme load. * * Currently the only supported CISS adapters are the Compaq Smart * Array 5* series (5300, 5i, 532). Even with only three adapters, * Compaq still manage to have interface variations. * * * Thanks must go to Fred Harris and Darryl DeVinney at Compaq, as * well as Paul Saab at Yahoo! for their assistance in making this * driver happen. * * More thanks must go to John Cagle at HP for the countless hours * spent making this driver "work" with the MSA* series storage * enclosures. Without his help (and nagging), this driver could not * be used with these enclosures. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(CISS_MALLOC_CLASS, "ciss_data", "ciss internal data buffers"); /* pci interface */ static int ciss_lookup(device_t dev); static int ciss_probe(device_t dev); static int ciss_attach(device_t dev); static int ciss_detach(device_t dev); static int ciss_shutdown(device_t dev); /* (de)initialisation functions, control wrappers */ static int ciss_init_pci(struct ciss_softc *sc); static int ciss_setup_msix(struct ciss_softc *sc); static int ciss_init_perf(struct ciss_softc *sc); static int ciss_wait_adapter(struct ciss_softc *sc); static int ciss_flush_adapter(struct ciss_softc *sc); static int ciss_init_requests(struct ciss_softc *sc); static void ciss_command_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int ciss_identify_adapter(struct ciss_softc *sc); static int ciss_init_logical(struct ciss_softc *sc); static int ciss_init_physical(struct ciss_softc *sc); static int ciss_filter_physical(struct ciss_softc *sc, struct ciss_lun_report *cll); static int ciss_identify_logical(struct ciss_softc *sc, struct ciss_ldrive *ld); static int ciss_get_ldrive_status(struct ciss_softc *sc, struct ciss_ldrive *ld); static int ciss_update_config(struct ciss_softc *sc); static int ciss_accept_media(struct ciss_softc *sc, struct ciss_ldrive *ld); static void ciss_init_sysctl(struct ciss_softc *sc); static void ciss_soft_reset(struct ciss_softc *sc); static void ciss_free(struct ciss_softc *sc); static void ciss_spawn_notify_thread(struct ciss_softc *sc); static void ciss_kill_notify_thread(struct ciss_softc *sc); /* request submission/completion */ static int ciss_start(struct ciss_request *cr); static void ciss_done(struct ciss_softc *sc, cr_qhead_t *qh); static void ciss_perf_done(struct ciss_softc *sc, cr_qhead_t *qh); static void ciss_intr(void *arg); static void ciss_perf_intr(void *arg); static void ciss_perf_msi_intr(void *arg); static void ciss_complete(struct ciss_softc *sc, cr_qhead_t *qh); static int _ciss_report_request(struct ciss_request *cr, int *command_status, int *scsi_status, const char *func); static int ciss_synch_request(struct ciss_request *cr, int timeout); static int ciss_poll_request(struct ciss_request *cr, int timeout); static int ciss_wait_request(struct ciss_request *cr, int timeout); #if 0 static int ciss_abort_request(struct ciss_request *cr); #endif /* request queueing */ static int ciss_get_request(struct ciss_softc *sc, struct ciss_request **crp); static void ciss_preen_command(struct ciss_request *cr); static void ciss_release_request(struct ciss_request *cr); /* request helpers */ static int ciss_get_bmic_request(struct ciss_softc *sc, struct ciss_request **crp, int opcode, void **bufp, size_t bufsize); static int ciss_user_command(struct ciss_softc *sc, IOCTL_Command_struct *ioc); /* DMA map/unmap */ static int ciss_map_request(struct ciss_request *cr); static void ciss_request_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static void ciss_unmap_request(struct ciss_request *cr); /* CAM interface */ static int ciss_cam_init(struct ciss_softc *sc); static void ciss_cam_rescan_target(struct ciss_softc *sc, int bus, int target); static void ciss_cam_action(struct cam_sim *sim, union ccb *ccb); static int ciss_cam_action_io(struct cam_sim *sim, struct ccb_scsiio *csio); static int ciss_cam_emulate(struct ciss_softc *sc, struct ccb_scsiio *csio); static void ciss_cam_poll(struct cam_sim *sim); static void ciss_cam_complete(struct ciss_request *cr); static void ciss_cam_complete_fixup(struct ciss_softc *sc, struct ccb_scsiio *csio); static int ciss_name_device(struct ciss_softc *sc, int bus, int target); /* periodic status monitoring */ static void ciss_periodic(void *arg); static void ciss_nop_complete(struct ciss_request *cr); static void ciss_disable_adapter(struct ciss_softc *sc); static void ciss_notify_event(struct ciss_softc *sc); static void ciss_notify_complete(struct ciss_request *cr); static int ciss_notify_abort(struct ciss_softc *sc); static int ciss_notify_abort_bmic(struct ciss_softc *sc); static void ciss_notify_hotplug(struct ciss_softc *sc, struct ciss_notify *cn); static void ciss_notify_logical(struct ciss_softc *sc, struct ciss_notify *cn); static void ciss_notify_physical(struct ciss_softc *sc, struct ciss_notify *cn); /* debugging output */ static void ciss_print_request(struct ciss_request *cr); static void ciss_print_ldrive(struct ciss_softc *sc, struct ciss_ldrive *ld); static const char *ciss_name_ldrive_status(int status); static int ciss_decode_ldrive_status(int status); static const char *ciss_name_ldrive_org(int org); static const char *ciss_name_command_status(int status); /* * PCI bus interface. */ static device_method_t ciss_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ciss_probe), DEVMETHOD(device_attach, ciss_attach), DEVMETHOD(device_detach, ciss_detach), DEVMETHOD(device_shutdown, ciss_shutdown), { 0, 0 } }; static driver_t ciss_pci_driver = { "ciss", ciss_methods, sizeof(struct ciss_softc) }; static devclass_t ciss_devclass; DRIVER_MODULE(ciss, pci, ciss_pci_driver, ciss_devclass, 0, 0); MODULE_DEPEND(ciss, cam, 1, 1, 1); MODULE_DEPEND(ciss, pci, 1, 1, 1); /* * Control device interface. */ static d_open_t ciss_open; static d_close_t ciss_close; static d_ioctl_t ciss_ioctl; static struct cdevsw ciss_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = ciss_open, .d_close = ciss_close, .d_ioctl = ciss_ioctl, .d_name = "ciss", }; /* * This tunable can be set at boot time and controls whether physical devices * that are marked hidden by the firmware should be exposed anyways. */ static unsigned int ciss_expose_hidden_physical = 0; TUNABLE_INT("hw.ciss.expose_hidden_physical", &ciss_expose_hidden_physical); static unsigned int ciss_nop_message_heartbeat = 0; TUNABLE_INT("hw.ciss.nop_message_heartbeat", &ciss_nop_message_heartbeat); /* * This tunable can force a particular transport to be used: * <= 0 : use default * 1 : force simple * 2 : force performant */ static int ciss_force_transport = 0; TUNABLE_INT("hw.ciss.force_transport", &ciss_force_transport); /* * This tunable can force a particular interrupt delivery method to be used: * <= 0 : use default * 1 : force INTx * 2 : force MSIX */ static int ciss_force_interrupt = 0; TUNABLE_INT("hw.ciss.force_interrupt", &ciss_force_interrupt); /************************************************************************ * CISS adapters amazingly don't have a defined programming interface * value. (One could say some very despairing things about PCI and * people just not getting the general idea.) So we are forced to * stick with matching against subvendor/subdevice, and thus have to * be updated for every new CISS adapter that appears. */ #define CISS_BOARD_UNKNWON 0 #define CISS_BOARD_SA5 1 #define CISS_BOARD_SA5B 2 #define CISS_BOARD_NOMSI (1<<4) #define CISS_BOARD_SIMPLE (1<<5) static struct { u_int16_t subvendor; u_int16_t subdevice; int flags; char *desc; } ciss_vendor_data[] = { { 0x0e11, 0x4070, CISS_BOARD_SA5|CISS_BOARD_NOMSI|CISS_BOARD_SIMPLE, "Compaq Smart Array 5300" }, { 0x0e11, 0x4080, CISS_BOARD_SA5B|CISS_BOARD_NOMSI, "Compaq Smart Array 5i" }, { 0x0e11, 0x4082, CISS_BOARD_SA5B|CISS_BOARD_NOMSI, "Compaq Smart Array 532" }, { 0x0e11, 0x4083, CISS_BOARD_SA5B|CISS_BOARD_NOMSI, "HP Smart Array 5312" }, { 0x0e11, 0x4091, CISS_BOARD_SA5, "HP Smart Array 6i" }, { 0x0e11, 0x409A, CISS_BOARD_SA5, "HP Smart Array 641" }, { 0x0e11, 0x409B, CISS_BOARD_SA5, "HP Smart Array 642" }, { 0x0e11, 0x409C, CISS_BOARD_SA5, "HP Smart Array 6400" }, { 0x0e11, 0x409D, CISS_BOARD_SA5, "HP Smart Array 6400 EM" }, { 0x103C, 0x3211, CISS_BOARD_SA5, "HP Smart Array E200i" }, { 0x103C, 0x3212, CISS_BOARD_SA5, "HP Smart Array E200" }, { 0x103C, 0x3213, CISS_BOARD_SA5, "HP Smart Array E200i" }, { 0x103C, 0x3214, CISS_BOARD_SA5, "HP Smart Array E200i" }, { 0x103C, 0x3215, CISS_BOARD_SA5, "HP Smart Array E200i" }, { 0x103C, 0x3220, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3222, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3223, CISS_BOARD_SA5, "HP Smart Array P800" }, { 0x103C, 0x3225, CISS_BOARD_SA5, "HP Smart Array P600" }, { 0x103C, 0x3230, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3231, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3232, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3233, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3234, CISS_BOARD_SA5, "HP Smart Array P400" }, { 0x103C, 0x3235, CISS_BOARD_SA5, "HP Smart Array P400i" }, { 0x103C, 0x3236, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3237, CISS_BOARD_SA5, "HP Smart Array E500" }, { 0x103C, 0x3238, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3239, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x323A, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x323B, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x323C, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x323D, CISS_BOARD_SA5, "HP Smart Array P700m" }, { 0x103C, 0x3241, CISS_BOARD_SA5, "HP Smart Array P212" }, { 0x103C, 0x3243, CISS_BOARD_SA5, "HP Smart Array P410" }, { 0x103C, 0x3245, CISS_BOARD_SA5, "HP Smart Array P410i" }, { 0x103C, 0x3247, CISS_BOARD_SA5, "HP Smart Array P411" }, { 0x103C, 0x3249, CISS_BOARD_SA5, "HP Smart Array P812" }, { 0x103C, 0x324A, CISS_BOARD_SA5, "HP Smart Array P712m" }, { 0x103C, 0x324B, CISS_BOARD_SA5, "HP Smart Array" }, { 0x103C, 0x3350, CISS_BOARD_SA5, "HP Smart Array P222" }, { 0x103C, 0x3351, CISS_BOARD_SA5, "HP Smart Array P420" }, { 0x103C, 0x3352, CISS_BOARD_SA5, "HP Smart Array P421" }, { 0x103C, 0x3353, CISS_BOARD_SA5, "HP Smart Array P822" }, { 0x103C, 0x3354, CISS_BOARD_SA5, "HP Smart Array P420i" }, { 0x103C, 0x3355, CISS_BOARD_SA5, "HP Smart Array P220i" }, { 0x103C, 0x3356, CISS_BOARD_SA5, "HP Smart Array P721m" }, { 0x103C, 0x1920, CISS_BOARD_SA5, "HP Smart Array P430i" }, { 0x103C, 0x1921, CISS_BOARD_SA5, "HP Smart Array P830i" }, { 0x103C, 0x1922, CISS_BOARD_SA5, "HP Smart Array P430" }, { 0x103C, 0x1923, CISS_BOARD_SA5, "HP Smart Array P431" }, { 0x103C, 0x1924, CISS_BOARD_SA5, "HP Smart Array P830" }, { 0x103C, 0x1926, CISS_BOARD_SA5, "HP Smart Array P731m" }, { 0x103C, 0x1928, CISS_BOARD_SA5, "HP Smart Array P230i" }, { 0x103C, 0x1929, CISS_BOARD_SA5, "HP Smart Array P530" }, { 0x103C, 0x192A, CISS_BOARD_SA5, "HP Smart Array P531" }, { 0x103C, 0x21BD, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21BE, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21BF, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C0, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C2, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C3, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C5, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C6, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C7, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21C8, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21CA, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21CB, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21CC, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21CD, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0x103C, 0x21CE, CISS_BOARD_SA5, "HP Smart Array TBD" }, { 0, 0, 0, NULL } }; /************************************************************************ * Find a match for the device in our list of known adapters. */ static int ciss_lookup(device_t dev) { int i; for (i = 0; ciss_vendor_data[i].desc != NULL; i++) if ((pci_get_subvendor(dev) == ciss_vendor_data[i].subvendor) && (pci_get_subdevice(dev) == ciss_vendor_data[i].subdevice)) { return(i); } return(-1); } /************************************************************************ * Match a known CISS adapter. */ static int ciss_probe(device_t dev) { int i; i = ciss_lookup(dev); if (i != -1) { device_set_desc(dev, ciss_vendor_data[i].desc); return(BUS_PROBE_DEFAULT); } return(ENOENT); } /************************************************************************ * Attach the driver to this adapter. */ static int ciss_attach(device_t dev) { struct ciss_softc *sc; int error; debug_called(1); #ifdef CISS_DEBUG /* print structure/union sizes */ debug_struct(ciss_command); debug_struct(ciss_header); debug_union(ciss_device_address); debug_struct(ciss_cdb); debug_struct(ciss_report_cdb); debug_struct(ciss_notify_cdb); debug_struct(ciss_notify); debug_struct(ciss_message_cdb); debug_struct(ciss_error_info_pointer); debug_struct(ciss_error_info); debug_struct(ciss_sg_entry); debug_struct(ciss_config_table); debug_struct(ciss_bmic_cdb); debug_struct(ciss_bmic_id_ldrive); debug_struct(ciss_bmic_id_lstatus); debug_struct(ciss_bmic_id_table); debug_struct(ciss_bmic_id_pdrive); debug_struct(ciss_bmic_blink_pdrive); debug_struct(ciss_bmic_flush_cache); debug_const(CISS_MAX_REQUESTS); debug_const(CISS_MAX_LOGICAL); debug_const(CISS_INTERRUPT_COALESCE_DELAY); debug_const(CISS_INTERRUPT_COALESCE_COUNT); debug_const(CISS_COMMAND_ALLOC_SIZE); debug_const(CISS_COMMAND_SG_LENGTH); debug_type(cciss_pci_info_struct); debug_type(cciss_coalint_struct); debug_type(cciss_coalint_struct); debug_type(NodeName_type); debug_type(NodeName_type); debug_type(Heartbeat_type); debug_type(BusTypes_type); debug_type(FirmwareVer_type); debug_type(DriverVer_type); debug_type(IOCTL_Command_struct); #endif sc = device_get_softc(dev); sc->ciss_dev = dev; mtx_init(&sc->ciss_mtx, "cissmtx", NULL, MTX_DEF); callout_init_mtx(&sc->ciss_periodic, &sc->ciss_mtx, 0); /* * Do PCI-specific init. */ if ((error = ciss_init_pci(sc)) != 0) goto out; /* * Initialise driver queues. */ ciss_initq_free(sc); ciss_initq_notify(sc); /* * Initalize device sysctls. */ ciss_init_sysctl(sc); /* * Initialise command/request pool. */ if ((error = ciss_init_requests(sc)) != 0) goto out; /* * Get adapter information. */ if ((error = ciss_identify_adapter(sc)) != 0) goto out; /* * Find all the physical devices. */ if ((error = ciss_init_physical(sc)) != 0) goto out; /* * Build our private table of logical devices. */ if ((error = ciss_init_logical(sc)) != 0) goto out; /* * Enable interrupts so that the CAM scan can complete. */ CISS_TL_SIMPLE_ENABLE_INTERRUPTS(sc); /* * Initialise the CAM interface. */ if ((error = ciss_cam_init(sc)) != 0) goto out; /* * Start the heartbeat routine and event chain. */ ciss_periodic(sc); /* * Create the control device. */ sc->ciss_dev_t = make_dev(&ciss_cdevsw, device_get_unit(sc->ciss_dev), UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "ciss%d", device_get_unit(sc->ciss_dev)); sc->ciss_dev_t->si_drv1 = sc; /* * The adapter is running; synchronous commands can now sleep * waiting for an interrupt to signal completion. */ sc->ciss_flags |= CISS_FLAG_RUNNING; ciss_spawn_notify_thread(sc); error = 0; out: if (error != 0) { /* ciss_free() expects the mutex to be held */ mtx_lock(&sc->ciss_mtx); ciss_free(sc); } return(error); } /************************************************************************ * Detach the driver from this adapter. */ static int ciss_detach(device_t dev) { struct ciss_softc *sc = device_get_softc(dev); debug_called(1); mtx_lock(&sc->ciss_mtx); if (sc->ciss_flags & CISS_FLAG_CONTROL_OPEN) { mtx_unlock(&sc->ciss_mtx); return (EBUSY); } /* flush adapter cache */ ciss_flush_adapter(sc); /* release all resources. The mutex is released and freed here too. */ ciss_free(sc); return(0); } /************************************************************************ * Prepare adapter for system shutdown. */ static int ciss_shutdown(device_t dev) { struct ciss_softc *sc = device_get_softc(dev); debug_called(1); mtx_lock(&sc->ciss_mtx); /* flush adapter cache */ ciss_flush_adapter(sc); if (sc->ciss_soft_reset) ciss_soft_reset(sc); mtx_unlock(&sc->ciss_mtx); return(0); } static void ciss_init_sysctl(struct ciss_softc *sc) { SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->ciss_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->ciss_dev)), OID_AUTO, "soft_reset", CTLFLAG_RW, &sc->ciss_soft_reset, 0, ""); } /************************************************************************ * Perform PCI-specific attachment actions. */ static int ciss_init_pci(struct ciss_softc *sc) { uintptr_t cbase, csize, cofs; uint32_t method, supported_methods; int error, sqmask, i; void *intr; debug_called(1); /* * Work out adapter type. */ i = ciss_lookup(sc->ciss_dev); if (i < 0) { ciss_printf(sc, "unknown adapter type\n"); return (ENXIO); } if (ciss_vendor_data[i].flags & CISS_BOARD_SA5) { sqmask = CISS_TL_SIMPLE_INTR_OPQ_SA5; } else if (ciss_vendor_data[i].flags & CISS_BOARD_SA5B) { sqmask = CISS_TL_SIMPLE_INTR_OPQ_SA5B; } else { /* * XXX Big hammer, masks/unmasks all possible interrupts. This should * work on all hardware variants. Need to add code to handle the * "controller crashed" interupt bit that this unmasks. */ sqmask = ~0; } /* * Allocate register window first (we need this to find the config * struct). */ error = ENXIO; sc->ciss_regs_rid = CISS_TL_SIMPLE_BAR_REGS; if ((sc->ciss_regs_resource = bus_alloc_resource_any(sc->ciss_dev, SYS_RES_MEMORY, &sc->ciss_regs_rid, RF_ACTIVE)) == NULL) { ciss_printf(sc, "can't allocate register window\n"); return(ENXIO); } sc->ciss_regs_bhandle = rman_get_bushandle(sc->ciss_regs_resource); sc->ciss_regs_btag = rman_get_bustag(sc->ciss_regs_resource); /* * Find the BAR holding the config structure. If it's not the one * we already mapped for registers, map it too. */ sc->ciss_cfg_rid = CISS_TL_SIMPLE_READ(sc, CISS_TL_SIMPLE_CFG_BAR) & 0xffff; if (sc->ciss_cfg_rid != sc->ciss_regs_rid) { if ((sc->ciss_cfg_resource = bus_alloc_resource_any(sc->ciss_dev, SYS_RES_MEMORY, &sc->ciss_cfg_rid, RF_ACTIVE)) == NULL) { ciss_printf(sc, "can't allocate config window\n"); return(ENXIO); } cbase = (uintptr_t)rman_get_virtual(sc->ciss_cfg_resource); csize = rman_get_end(sc->ciss_cfg_resource) - rman_get_start(sc->ciss_cfg_resource) + 1; } else { cbase = (uintptr_t)rman_get_virtual(sc->ciss_regs_resource); csize = rman_get_end(sc->ciss_regs_resource) - rman_get_start(sc->ciss_regs_resource) + 1; } cofs = CISS_TL_SIMPLE_READ(sc, CISS_TL_SIMPLE_CFG_OFF); /* * Use the base/size/offset values we just calculated to * sanity-check the config structure. If it's OK, point to it. */ if ((cofs + sizeof(struct ciss_config_table)) > csize) { ciss_printf(sc, "config table outside window\n"); return(ENXIO); } sc->ciss_cfg = (struct ciss_config_table *)(cbase + cofs); debug(1, "config struct at %p", sc->ciss_cfg); /* * Calculate the number of request structures/commands we are * going to provide for this adapter. */ sc->ciss_max_requests = min(CISS_MAX_REQUESTS, sc->ciss_cfg->max_outstanding_commands); /* * Validate the config structure. If we supported other transport * methods, we could select amongst them at this point in time. */ if (strncmp(sc->ciss_cfg->signature, "CISS", 4)) { ciss_printf(sc, "config signature mismatch (got '%c%c%c%c')\n", sc->ciss_cfg->signature[0], sc->ciss_cfg->signature[1], sc->ciss_cfg->signature[2], sc->ciss_cfg->signature[3]); return(ENXIO); } /* * Select the mode of operation, prefer Performant. */ if (!(sc->ciss_cfg->supported_methods & (CISS_TRANSPORT_METHOD_SIMPLE | CISS_TRANSPORT_METHOD_PERF))) { ciss_printf(sc, "No supported transport layers: 0x%x\n", sc->ciss_cfg->supported_methods); } switch (ciss_force_transport) { case 1: supported_methods = CISS_TRANSPORT_METHOD_SIMPLE; break; case 2: supported_methods = CISS_TRANSPORT_METHOD_PERF; break; default: /* * Override the capabilities of the BOARD and specify SIMPLE * MODE */ if (ciss_vendor_data[i].flags & CISS_BOARD_SIMPLE) supported_methods = CISS_TRANSPORT_METHOD_SIMPLE; else supported_methods = sc->ciss_cfg->supported_methods; break; } setup: if ((supported_methods & CISS_TRANSPORT_METHOD_PERF) != 0) { method = CISS_TRANSPORT_METHOD_PERF; sc->ciss_perf = (struct ciss_perf_config *)(cbase + cofs + sc->ciss_cfg->transport_offset); if (ciss_init_perf(sc)) { supported_methods &= ~method; goto setup; } } else if (supported_methods & CISS_TRANSPORT_METHOD_SIMPLE) { method = CISS_TRANSPORT_METHOD_SIMPLE; } else { ciss_printf(sc, "No supported transport methods: 0x%x\n", sc->ciss_cfg->supported_methods); return(ENXIO); } /* * Tell it we're using the low 4GB of RAM. Set the default interrupt * coalescing options. */ sc->ciss_cfg->requested_method = method; sc->ciss_cfg->command_physlimit = 0; sc->ciss_cfg->interrupt_coalesce_delay = CISS_INTERRUPT_COALESCE_DELAY; sc->ciss_cfg->interrupt_coalesce_count = CISS_INTERRUPT_COALESCE_COUNT; #ifdef __i386__ sc->ciss_cfg->host_driver |= CISS_DRIVER_SCSI_PREFETCH; #endif if (ciss_update_config(sc)) { ciss_printf(sc, "adapter refuses to accept config update (IDBR 0x%x)\n", CISS_TL_SIMPLE_READ(sc, CISS_TL_SIMPLE_IDBR)); return(ENXIO); } if ((sc->ciss_cfg->active_method & method) == 0) { supported_methods &= ~method; if (supported_methods == 0) { ciss_printf(sc, "adapter refuses to go into available transports " "mode (0x%x, 0x%x)\n", supported_methods, sc->ciss_cfg->active_method); return(ENXIO); } else goto setup; } /* * Wait for the adapter to come ready. */ if ((error = ciss_wait_adapter(sc)) != 0) return(error); /* Prepare to possibly use MSIX and/or PERFORMANT interrupts. Normal * interrupts have a rid of 0, this will be overridden if MSIX is used. */ sc->ciss_irq_rid[0] = 0; if (method == CISS_TRANSPORT_METHOD_PERF) { ciss_printf(sc, "PERFORMANT Transport\n"); if ((ciss_force_interrupt != 1) && (ciss_setup_msix(sc) == 0)) { intr = ciss_perf_msi_intr; } else { intr = ciss_perf_intr; } /* XXX The docs say that the 0x01 bit is only for SAS controllers. * Unfortunately, there is no good way to know if this is a SAS * controller. Hopefully enabling this bit universally will work OK. * It seems to work fine for SA6i controllers. */ sc->ciss_interrupt_mask = CISS_TL_PERF_INTR_OPQ | CISS_TL_PERF_INTR_MSI; } else { ciss_printf(sc, "SIMPLE Transport\n"); /* MSIX doesn't seem to work in SIMPLE mode, only enable if it forced */ if (ciss_force_interrupt == 2) /* If this fails, we automatically revert to INTx */ ciss_setup_msix(sc); sc->ciss_perf = NULL; intr = ciss_intr; sc->ciss_interrupt_mask = sqmask; } /* * Turn off interrupts before we go routing anything. */ CISS_TL_SIMPLE_DISABLE_INTERRUPTS(sc); /* * Allocate and set up our interrupt. */ if ((sc->ciss_irq_resource = bus_alloc_resource_any(sc->ciss_dev, SYS_RES_IRQ, &sc->ciss_irq_rid[0], RF_ACTIVE | RF_SHAREABLE)) == NULL) { ciss_printf(sc, "can't allocate interrupt\n"); return(ENXIO); } if (bus_setup_intr(sc->ciss_dev, sc->ciss_irq_resource, INTR_TYPE_CAM|INTR_MPSAFE, NULL, intr, sc, &sc->ciss_intr)) { ciss_printf(sc, "can't set up interrupt\n"); return(ENXIO); } /* * Allocate the parent bus DMA tag appropriate for our PCI * interface. * * Note that "simple" adapters can only address within a 32-bit * span. */ if (bus_dma_tag_create(bus_get_dma_tag(sc->ciss_dev),/* PCI parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ - CISS_MAX_SG_ELEMENTS, /* nsegments */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ciss_parent_dmat)) { ciss_printf(sc, "can't allocate parent DMA tag\n"); return(ENOMEM); } /* * Create DMA tag for mapping buffers into adapter-addressable * space. */ if (bus_dma_tag_create(sc->ciss_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, CISS_MAX_SG_ELEMENTS, /* maxsize, nsegments */ + (CISS_MAX_SG_ELEMENTS - 1) * PAGE_SIZE, /* maxsize */ + CISS_MAX_SG_ELEMENTS, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, &sc->ciss_mtx, /* lockfunc, lockarg */ &sc->ciss_buffer_dmat)) { ciss_printf(sc, "can't allocate buffer DMA tag\n"); return(ENOMEM); } return(0); } /************************************************************************ * Setup MSI/MSIX operation (Performant only) * Four interrupts are available, but we only use 1 right now. If MSI-X * isn't avaialble, try using MSI instead. */ static int ciss_setup_msix(struct ciss_softc *sc) { int val, i; /* Weed out devices that don't actually support MSI */ i = ciss_lookup(sc->ciss_dev); if (ciss_vendor_data[i].flags & CISS_BOARD_NOMSI) return (EINVAL); /* * Only need to use the minimum number of MSI vectors, as the driver * doesn't support directed MSIX interrupts. */ val = pci_msix_count(sc->ciss_dev); if (val < CISS_MSI_COUNT) { val = pci_msi_count(sc->ciss_dev); device_printf(sc->ciss_dev, "got %d MSI messages]\n", val); if (val < CISS_MSI_COUNT) return (EINVAL); } val = MIN(val, CISS_MSI_COUNT); if (pci_alloc_msix(sc->ciss_dev, &val) != 0) { if (pci_alloc_msi(sc->ciss_dev, &val) != 0) return (EINVAL); } sc->ciss_msi = val; if (bootverbose) ciss_printf(sc, "Using %d MSIX interrupt%s\n", val, (val != 1) ? "s" : ""); for (i = 0; i < val; i++) sc->ciss_irq_rid[i] = i + 1; return (0); } /************************************************************************ * Setup the Performant structures. */ static int ciss_init_perf(struct ciss_softc *sc) { struct ciss_perf_config *pc = sc->ciss_perf; int reply_size; /* * Create the DMA tag for the reply queue. */ reply_size = sizeof(uint64_t) * sc->ciss_max_requests; if (bus_dma_tag_create(sc->ciss_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ reply_size, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ciss_reply_dmat)) { ciss_printf(sc, "can't allocate reply DMA tag\n"); return(ENOMEM); } /* * Allocate memory and make it available for DMA. */ if (bus_dmamem_alloc(sc->ciss_reply_dmat, (void **)&sc->ciss_reply, BUS_DMA_NOWAIT, &sc->ciss_reply_map)) { ciss_printf(sc, "can't allocate reply memory\n"); return(ENOMEM); } bus_dmamap_load(sc->ciss_reply_dmat, sc->ciss_reply_map, sc->ciss_reply, reply_size, ciss_command_map_helper, &sc->ciss_reply_phys, 0); bzero(sc->ciss_reply, reply_size); sc->ciss_cycle = 0x1; sc->ciss_rqidx = 0; /* * Preload the fetch table with common command sizes. This allows the * hardware to not waste bus cycles for typical i/o commands, but also not * tax the driver to be too exact in choosing sizes. The table is optimized * for page-aligned i/o's, but since most i/o comes from the various pagers, * it's a reasonable assumption to make. */ pc->fetch_count[CISS_SG_FETCH_NONE] = (sizeof(struct ciss_command) + 15) / 16; pc->fetch_count[CISS_SG_FETCH_1] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 1 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_2] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 2 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_4] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 4 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_8] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 8 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_16] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 16 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_32] = (sizeof(struct ciss_command) + sizeof(struct ciss_sg_entry) * 32 + 15) / 16; pc->fetch_count[CISS_SG_FETCH_MAX] = (CISS_COMMAND_ALLOC_SIZE + 15) / 16; pc->rq_size = sc->ciss_max_requests; /* XXX less than the card supports? */ pc->rq_count = 1; /* XXX Hardcode for a single queue */ pc->rq_bank_hi = 0; pc->rq_bank_lo = 0; pc->rq[0].rq_addr_hi = 0x0; pc->rq[0].rq_addr_lo = sc->ciss_reply_phys; return(0); } /************************************************************************ * Wait for the adapter to come ready. */ static int ciss_wait_adapter(struct ciss_softc *sc) { int i; debug_called(1); /* * Wait for the adapter to come ready. */ if (!(sc->ciss_cfg->active_method & CISS_TRANSPORT_METHOD_READY)) { ciss_printf(sc, "waiting for adapter to come ready...\n"); for (i = 0; !(sc->ciss_cfg->active_method & CISS_TRANSPORT_METHOD_READY); i++) { DELAY(1000000); /* one second */ if (i > 30) { ciss_printf(sc, "timed out waiting for adapter to come ready\n"); return(EIO); } } } return(0); } /************************************************************************ * Flush the adapter cache. */ static int ciss_flush_adapter(struct ciss_softc *sc) { struct ciss_request *cr; struct ciss_bmic_flush_cache *cbfc; int error, command_status; debug_called(1); cr = NULL; cbfc = NULL; /* * Build a BMIC request to flush the cache. We don't disable * it, as we may be going to do more I/O (eg. we are emulating * the Synchronise Cache command). */ if ((cbfc = malloc(sizeof(*cbfc), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO)) == NULL) { error = ENOMEM; goto out; } if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_FLUSH_CACHE, (void **)&cbfc, sizeof(*cbfc))) != 0) goto out; /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC FLUSH_CACHE command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: break; default: ciss_printf(sc, "error flushing cache (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } out: if (cbfc != NULL) free(cbfc, CISS_MALLOC_CLASS); if (cr != NULL) ciss_release_request(cr); return(error); } static void ciss_soft_reset(struct ciss_softc *sc) { struct ciss_request *cr = NULL; struct ciss_command *cc; int i, error = 0; for (i = 0; i < sc->ciss_max_logical_bus; i++) { /* only reset proxy controllers */ if (sc->ciss_controllers[i].physical.bus == 0) continue; if ((error = ciss_get_request(sc, &cr)) != 0) break; if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_SOFT_RESET, NULL, 0)) != 0) break; cc = cr->cr_cc; cc->header.address = sc->ciss_controllers[i]; if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) break; ciss_release_request(cr); } if (error) ciss_printf(sc, "error resetting controller (%d)\n", error); if (cr != NULL) ciss_release_request(cr); } /************************************************************************ * Allocate memory for the adapter command structures, initialise * the request structures. * * Note that the entire set of commands are allocated in a single * contiguous slab. */ static int ciss_init_requests(struct ciss_softc *sc) { struct ciss_request *cr; int i; debug_called(1); if (bootverbose) ciss_printf(sc, "using %d of %d available commands\n", sc->ciss_max_requests, sc->ciss_cfg->max_outstanding_commands); /* * Create the DMA tag for commands. */ if (bus_dma_tag_create(sc->ciss_parent_dmat, /* parent */ 32, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ CISS_COMMAND_ALLOC_SIZE * sc->ciss_max_requests, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ciss_command_dmat)) { ciss_printf(sc, "can't allocate command DMA tag\n"); return(ENOMEM); } /* * Allocate memory and make it available for DMA. */ if (bus_dmamem_alloc(sc->ciss_command_dmat, (void **)&sc->ciss_command, BUS_DMA_NOWAIT, &sc->ciss_command_map)) { ciss_printf(sc, "can't allocate command memory\n"); return(ENOMEM); } bus_dmamap_load(sc->ciss_command_dmat, sc->ciss_command_map,sc->ciss_command, CISS_COMMAND_ALLOC_SIZE * sc->ciss_max_requests, ciss_command_map_helper, &sc->ciss_command_phys, 0); bzero(sc->ciss_command, CISS_COMMAND_ALLOC_SIZE * sc->ciss_max_requests); /* * Set up the request and command structures, push requests onto * the free queue. */ for (i = 1; i < sc->ciss_max_requests; i++) { cr = &sc->ciss_request[i]; cr->cr_sc = sc; cr->cr_tag = i; cr->cr_cc = (struct ciss_command *)((uintptr_t)sc->ciss_command + CISS_COMMAND_ALLOC_SIZE * i); cr->cr_ccphys = sc->ciss_command_phys + CISS_COMMAND_ALLOC_SIZE * i; bus_dmamap_create(sc->ciss_buffer_dmat, 0, &cr->cr_datamap); ciss_enqueue_free(cr); } return(0); } static void ciss_command_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { uint32_t *addr; addr = arg; *addr = segs[0].ds_addr; } /************************************************************************ * Identify the adapter, print some information about it. */ static int ciss_identify_adapter(struct ciss_softc *sc) { struct ciss_request *cr; int error, command_status; debug_called(1); cr = NULL; /* * Get a request, allocate storage for the adapter data. */ if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_ID_CTLR, (void **)&sc->ciss_id, sizeof(*sc->ciss_id))) != 0) goto out; /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC ID_CTLR command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: /* buffer right size */ break; case CISS_CMD_STATUS_DATA_UNDERRUN: case CISS_CMD_STATUS_DATA_OVERRUN: ciss_printf(sc, "data over/underrun reading adapter information\n"); default: ciss_printf(sc, "error reading adapter information (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } /* sanity-check reply */ if (!(sc->ciss_id->controller_flags & CONTROLLER_FLAGS_BIG_MAP_SUPPORT)) { ciss_printf(sc, "adapter does not support BIG_MAP\n"); error = ENXIO; goto out; } #if 0 /* XXX later revisions may not need this */ sc->ciss_flags |= CISS_FLAG_FAKE_SYNCH; #endif /* XXX only really required for old 5300 adapters? */ sc->ciss_flags |= CISS_FLAG_BMIC_ABORT; /* * Earlier controller specs do not contain these config * entries, so assume that a 0 means its old and assign * these values to the defaults that were established * when this driver was developed for them */ if (sc->ciss_cfg->max_logical_supported == 0) sc->ciss_cfg->max_logical_supported = CISS_MAX_LOGICAL; if (sc->ciss_cfg->max_physical_supported == 0) sc->ciss_cfg->max_physical_supported = CISS_MAX_PHYSICAL; /* print information */ if (bootverbose) { ciss_printf(sc, " %d logical drive%s configured\n", sc->ciss_id->configured_logical_drives, (sc->ciss_id->configured_logical_drives == 1) ? "" : "s"); ciss_printf(sc, " firmware %4.4s\n", sc->ciss_id->running_firmware_revision); ciss_printf(sc, " %d SCSI channels\n", sc->ciss_id->scsi_chip_count); ciss_printf(sc, " signature '%.4s'\n", sc->ciss_cfg->signature); ciss_printf(sc, " valence %d\n", sc->ciss_cfg->valence); ciss_printf(sc, " supported I/O methods 0x%b\n", sc->ciss_cfg->supported_methods, "\20\1READY\2simple\3performant\4MEMQ\n"); ciss_printf(sc, " active I/O method 0x%b\n", sc->ciss_cfg->active_method, "\20\2simple\3performant\4MEMQ\n"); ciss_printf(sc, " 4G page base 0x%08x\n", sc->ciss_cfg->command_physlimit); ciss_printf(sc, " interrupt coalesce delay %dus\n", sc->ciss_cfg->interrupt_coalesce_delay); ciss_printf(sc, " interrupt coalesce count %d\n", sc->ciss_cfg->interrupt_coalesce_count); ciss_printf(sc, " max outstanding commands %d\n", sc->ciss_cfg->max_outstanding_commands); ciss_printf(sc, " bus types 0x%b\n", sc->ciss_cfg->bus_types, "\20\1ultra2\2ultra3\10fibre1\11fibre2\n"); ciss_printf(sc, " server name '%.16s'\n", sc->ciss_cfg->server_name); ciss_printf(sc, " heartbeat 0x%x\n", sc->ciss_cfg->heartbeat); ciss_printf(sc, " max logical logical volumes: %d\n", sc->ciss_cfg->max_logical_supported); ciss_printf(sc, " max physical disks supported: %d\n", sc->ciss_cfg->max_physical_supported); ciss_printf(sc, " max physical disks per logical volume: %d\n", sc->ciss_cfg->max_physical_per_logical); ciss_printf(sc, " JBOD Support is %s\n", (sc->ciss_id->uiYetMoreControllerFlags & YMORE_CONTROLLER_FLAGS_JBOD_SUPPORTED) ? "Available" : "Unavailable"); ciss_printf(sc, " JBOD Mode is %s\n", (sc->ciss_id->PowerUPNvramFlags & PWR_UP_FLAG_JBOD_ENABLED) ? "Enabled" : "Disabled"); } out: if (error) { if (sc->ciss_id != NULL) { free(sc->ciss_id, CISS_MALLOC_CLASS); sc->ciss_id = NULL; } } if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Helper routine for generating a list of logical and physical luns. */ static struct ciss_lun_report * ciss_report_luns(struct ciss_softc *sc, int opcode, int nunits) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_report_cdb *crc; struct ciss_lun_report *cll; int command_status; int report_size; int error = 0; debug_called(1); cr = NULL; cll = NULL; /* * Get a request, allocate storage for the address list. */ if ((error = ciss_get_request(sc, &cr)) != 0) goto out; report_size = sizeof(*cll) + nunits * sizeof(union ciss_device_address); if ((cll = malloc(report_size, CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO)) == NULL) { ciss_printf(sc, "can't allocate memory for lun report\n"); error = ENOMEM; goto out; } /* * Build the Report Logical/Physical LUNs command. */ cc = cr->cr_cc; cr->cr_data = cll; cr->cr_length = report_size; cr->cr_flags = CISS_REQ_DATAIN; cc->header.address.physical.mode = CISS_HDR_ADDRESS_MODE_PERIPHERAL; cc->header.address.physical.bus = 0; cc->header.address.physical.target = 0; cc->cdb.cdb_length = sizeof(*crc); cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_READ; cc->cdb.timeout = 30; /* XXX better suggestions? */ crc = (struct ciss_report_cdb *)&(cc->cdb.cdb[0]); bzero(crc, sizeof(*crc)); crc->opcode = opcode; crc->length = htonl(report_size); /* big-endian field */ cll->list_size = htonl(report_size - sizeof(*cll)); /* big-endian field */ /* * Submit the request and wait for it to complete. (timeout * here should be much greater than above) */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending %d LUN command (%d)\n", opcode, error); goto out; } /* * Check response. Note that data over/underrun is OK. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: /* buffer right size */ case CISS_CMD_STATUS_DATA_UNDERRUN: /* buffer too large, not bad */ break; case CISS_CMD_STATUS_DATA_OVERRUN: ciss_printf(sc, "WARNING: more units than driver limit (%d)\n", sc->ciss_cfg->max_logical_supported); break; default: ciss_printf(sc, "error detecting logical drive configuration (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } ciss_release_request(cr); cr = NULL; out: if (cr != NULL) ciss_release_request(cr); if (error && cll != NULL) { free(cll, CISS_MALLOC_CLASS); cll = NULL; } return(cll); } /************************************************************************ * Find logical drives on the adapter. */ static int ciss_init_logical(struct ciss_softc *sc) { struct ciss_lun_report *cll; int error = 0, i, j; int ndrives; debug_called(1); cll = ciss_report_luns(sc, CISS_OPCODE_REPORT_LOGICAL_LUNS, sc->ciss_cfg->max_logical_supported); if (cll == NULL) { error = ENXIO; goto out; } /* sanity-check reply */ ndrives = (ntohl(cll->list_size) / sizeof(union ciss_device_address)); if ((ndrives < 0) || (ndrives > sc->ciss_cfg->max_logical_supported)) { ciss_printf(sc, "adapter claims to report absurd number of logical drives (%d > %d)\n", ndrives, sc->ciss_cfg->max_logical_supported); error = ENXIO; goto out; } /* * Save logical drive information. */ if (bootverbose) { ciss_printf(sc, "%d logical drive%s\n", ndrives, (ndrives > 1 || ndrives == 0) ? "s" : ""); } sc->ciss_logical = malloc(sc->ciss_max_logical_bus * sizeof(struct ciss_ldrive *), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_logical == NULL) { error = ENXIO; goto out; } for (i = 0; i <= sc->ciss_max_logical_bus; i++) { sc->ciss_logical[i] = malloc(sc->ciss_cfg->max_logical_supported * sizeof(struct ciss_ldrive), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_logical[i] == NULL) { error = ENXIO; goto out; } for (j = 0; j < sc->ciss_cfg->max_logical_supported; j++) sc->ciss_logical[i][j].cl_status = CISS_LD_NONEXISTENT; } for (i = 0; i < sc->ciss_cfg->max_logical_supported; i++) { if (i < ndrives) { struct ciss_ldrive *ld; int bus, target; bus = CISS_LUN_TO_BUS(cll->lun[i].logical.lun); target = CISS_LUN_TO_TARGET(cll->lun[i].logical.lun); ld = &sc->ciss_logical[bus][target]; ld->cl_address = cll->lun[i]; ld->cl_controller = &sc->ciss_controllers[bus]; if (ciss_identify_logical(sc, ld) != 0) continue; /* * If the drive has had media exchanged, we should bring it online. */ if (ld->cl_lstatus->media_exchanged) ciss_accept_media(sc, ld); } } out: if (cll != NULL) free(cll, CISS_MALLOC_CLASS); return(error); } static int ciss_init_physical(struct ciss_softc *sc) { struct ciss_lun_report *cll; int error = 0, i; int nphys; int bus, target; debug_called(1); bus = 0; target = 0; cll = ciss_report_luns(sc, CISS_OPCODE_REPORT_PHYSICAL_LUNS, sc->ciss_cfg->max_physical_supported); if (cll == NULL) { error = ENXIO; goto out; } nphys = (ntohl(cll->list_size) / sizeof(union ciss_device_address)); if (bootverbose) { ciss_printf(sc, "%d physical device%s\n", nphys, (nphys > 1 || nphys == 0) ? "s" : ""); } /* * Figure out the bus mapping. * Logical buses include both the local logical bus for local arrays and * proxy buses for remote arrays. Physical buses are numbered by the * controller and represent physical buses that hold physical devices. * We shift these bus numbers so that everything fits into a single flat * numbering space for CAM. Logical buses occupy the first 32 CAM bus * numbers, and the physical bus numbers are shifted to be above that. * This results in the various driver arrays being indexed as follows: * * ciss_controllers[] - indexed by logical bus * ciss_cam_sim[] - indexed by both logical and physical, with physical * being shifted by 32. * ciss_logical[][] - indexed by logical bus * ciss_physical[][] - indexed by physical bus * * XXX This is getting more and more hackish. CISS really doesn't play * well with a standard SCSI model; devices are addressed via magic * cookies, not via b/t/l addresses. Since there is no way to store * the cookie in the CAM device object, we have to keep these lookup * tables handy so that the devices can be found quickly at the cost * of wasting memory and having a convoluted lookup scheme. This * driver should probably be converted to block interface. */ /* * If the L2 and L3 SCSI addresses are 0, this signifies a proxy * controller. A proxy controller is another physical controller * behind the primary PCI controller. We need to know about this * so that BMIC commands can be properly targeted. There can be * proxy controllers attached to a single PCI controller, so * find the highest numbered one so the array can be properly * sized. */ sc->ciss_max_logical_bus = 1; for (i = 0; i < nphys; i++) { if (cll->lun[i].physical.extra_address == 0) { bus = cll->lun[i].physical.bus; sc->ciss_max_logical_bus = max(sc->ciss_max_logical_bus, bus) + 1; } else { bus = CISS_EXTRA_BUS2(cll->lun[i].physical.extra_address); sc->ciss_max_physical_bus = max(sc->ciss_max_physical_bus, bus); } } sc->ciss_controllers = malloc(sc->ciss_max_logical_bus * sizeof (union ciss_device_address), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_controllers == NULL) { ciss_printf(sc, "Could not allocate memory for controller map\n"); error = ENOMEM; goto out; } /* setup a map of controller addresses */ for (i = 0; i < nphys; i++) { if (cll->lun[i].physical.extra_address == 0) { sc->ciss_controllers[cll->lun[i].physical.bus] = cll->lun[i]; } } sc->ciss_physical = malloc(sc->ciss_max_physical_bus * sizeof(struct ciss_pdrive *), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_physical == NULL) { ciss_printf(sc, "Could not allocate memory for physical device map\n"); error = ENOMEM; goto out; } for (i = 0; i < sc->ciss_max_physical_bus; i++) { sc->ciss_physical[i] = malloc(sizeof(struct ciss_pdrive) * CISS_MAX_PHYSTGT, CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_physical[i] == NULL) { ciss_printf(sc, "Could not allocate memory for target map\n"); error = ENOMEM; goto out; } } ciss_filter_physical(sc, cll); out: if (cll != NULL) free(cll, CISS_MALLOC_CLASS); return(error); } static int ciss_filter_physical(struct ciss_softc *sc, struct ciss_lun_report *cll) { u_int32_t ea; int i, nphys; int bus, target; nphys = (ntohl(cll->list_size) / sizeof(union ciss_device_address)); for (i = 0; i < nphys; i++) { if (cll->lun[i].physical.extra_address == 0) continue; /* * Filter out devices that we don't want. Level 3 LUNs could * probably be supported, but the docs don't give enough of a * hint to know how. * * The mode field of the physical address is likely set to have * hard disks masked out. Honor it unless the user has overridden * us with the tunable. We also munge the inquiry data for these * disks so that they only show up as passthrough devices. Keeping * them visible in this fashion is useful for doing things like * flashing firmware. */ ea = cll->lun[i].physical.extra_address; if ((CISS_EXTRA_BUS3(ea) != 0) || (CISS_EXTRA_TARGET3(ea) != 0) || (CISS_EXTRA_MODE2(ea) == 0x3)) continue; if ((ciss_expose_hidden_physical == 0) && (cll->lun[i].physical.mode == CISS_HDR_ADDRESS_MODE_MASK_PERIPHERAL)) continue; /* * Note: CISS firmware numbers physical busses starting at '1', not * '0'. This numbering is internal to the firmware and is only * used as a hint here. */ bus = CISS_EXTRA_BUS2(ea) - 1; target = CISS_EXTRA_TARGET2(ea); sc->ciss_physical[bus][target].cp_address = cll->lun[i]; sc->ciss_physical[bus][target].cp_online = 1; } return (0); } static int ciss_inquiry_logical(struct ciss_softc *sc, struct ciss_ldrive *ld) { struct ciss_request *cr; struct ciss_command *cc; struct scsi_inquiry *inq; int error; int command_status; cr = NULL; bzero(&ld->cl_geometry, sizeof(ld->cl_geometry)); if ((error = ciss_get_request(sc, &cr)) != 0) goto out; cc = cr->cr_cc; cr->cr_data = &ld->cl_geometry; cr->cr_length = sizeof(ld->cl_geometry); cr->cr_flags = CISS_REQ_DATAIN; cc->header.address = ld->cl_address; cc->cdb.cdb_length = 6; cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_READ; cc->cdb.timeout = 30; inq = (struct scsi_inquiry *)&(cc->cdb.cdb[0]); inq->opcode = INQUIRY; inq->byte2 = SI_EVPD; inq->page_code = CISS_VPD_LOGICAL_DRIVE_GEOMETRY; scsi_ulto2b(sizeof(ld->cl_geometry), inq->length); if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error getting geometry (%d)\n", error); goto out; } ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: case CISS_CMD_STATUS_DATA_UNDERRUN: break; case CISS_CMD_STATUS_DATA_OVERRUN: ciss_printf(sc, "WARNING: Data overrun\n"); break; default: ciss_printf(sc, "Error detecting logical drive geometry (%s)\n", ciss_name_command_status(command_status)); break; } out: if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Identify a logical drive, initialise state related to it. */ static int ciss_identify_logical(struct ciss_softc *sc, struct ciss_ldrive *ld) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_bmic_cdb *cbc; int error, command_status; debug_called(1); cr = NULL; /* * Build a BMIC request to fetch the drive ID. */ if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_ID_LDRIVE, (void **)&ld->cl_ldrive, sizeof(*ld->cl_ldrive))) != 0) goto out; cc = cr->cr_cc; cc->header.address = *ld->cl_controller; /* target controller */ cbc = (struct ciss_bmic_cdb *)&(cc->cdb.cdb[0]); cbc->log_drive = CISS_LUN_TO_TARGET(ld->cl_address.logical.lun); /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC LDRIVE command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: /* buffer right size */ break; case CISS_CMD_STATUS_DATA_UNDERRUN: case CISS_CMD_STATUS_DATA_OVERRUN: ciss_printf(sc, "data over/underrun reading logical drive ID\n"); default: ciss_printf(sc, "error reading logical drive ID (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } ciss_release_request(cr); cr = NULL; /* * Build a CISS BMIC command to get the logical drive status. */ if ((error = ciss_get_ldrive_status(sc, ld)) != 0) goto out; /* * Get the logical drive geometry. */ if ((error = ciss_inquiry_logical(sc, ld)) != 0) goto out; /* * Print the drive's basic characteristics. */ if (bootverbose) { ciss_printf(sc, "logical drive (b%dt%d): %s, %dMB ", CISS_LUN_TO_BUS(ld->cl_address.logical.lun), CISS_LUN_TO_TARGET(ld->cl_address.logical.lun), ciss_name_ldrive_org(ld->cl_ldrive->fault_tolerance), ((ld->cl_ldrive->blocks_available / (1024 * 1024)) * ld->cl_ldrive->block_size)); ciss_print_ldrive(sc, ld); } out: if (error != 0) { /* make the drive not-exist */ ld->cl_status = CISS_LD_NONEXISTENT; if (ld->cl_ldrive != NULL) { free(ld->cl_ldrive, CISS_MALLOC_CLASS); ld->cl_ldrive = NULL; } if (ld->cl_lstatus != NULL) { free(ld->cl_lstatus, CISS_MALLOC_CLASS); ld->cl_lstatus = NULL; } } if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Get status for a logical drive. * * XXX should we also do this in response to Test Unit Ready? */ static int ciss_get_ldrive_status(struct ciss_softc *sc, struct ciss_ldrive *ld) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_bmic_cdb *cbc; int error, command_status; /* * Build a CISS BMIC command to get the logical drive status. */ if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_ID_LSTATUS, (void **)&ld->cl_lstatus, sizeof(*ld->cl_lstatus))) != 0) goto out; cc = cr->cr_cc; cc->header.address = *ld->cl_controller; /* target controller */ cbc = (struct ciss_bmic_cdb *)&(cc->cdb.cdb[0]); cbc->log_drive = CISS_LUN_TO_TARGET(ld->cl_address.logical.lun); /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC LSTATUS command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: /* buffer right size */ break; case CISS_CMD_STATUS_DATA_UNDERRUN: case CISS_CMD_STATUS_DATA_OVERRUN: ciss_printf(sc, "data over/underrun reading logical drive status\n"); default: ciss_printf(sc, "error reading logical drive status (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } /* * Set the drive's summary status based on the returned status. * * XXX testing shows that a failed JBOD drive comes back at next * boot in "queued for expansion" mode. WTF? */ ld->cl_status = ciss_decode_ldrive_status(ld->cl_lstatus->status); out: if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Notify the adapter of a config update. */ static int ciss_update_config(struct ciss_softc *sc) { int i; debug_called(1); CISS_TL_SIMPLE_WRITE(sc, CISS_TL_SIMPLE_IDBR, CISS_TL_SIMPLE_IDBR_CFG_TABLE); for (i = 0; i < 1000; i++) { if (!(CISS_TL_SIMPLE_READ(sc, CISS_TL_SIMPLE_IDBR) & CISS_TL_SIMPLE_IDBR_CFG_TABLE)) { return(0); } DELAY(1000); } return(1); } /************************************************************************ * Accept new media into a logical drive. * * XXX The drive has previously been offline; it would be good if we * could make sure it's not open right now. */ static int ciss_accept_media(struct ciss_softc *sc, struct ciss_ldrive *ld) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_bmic_cdb *cbc; int command_status; int error = 0, ldrive; ldrive = CISS_LUN_TO_TARGET(ld->cl_address.logical.lun); debug(0, "bringing logical drive %d back online", ldrive); /* * Build a CISS BMIC command to bring the drive back online. */ if ((error = ciss_get_bmic_request(sc, &cr, CISS_BMIC_ACCEPT_MEDIA, NULL, 0)) != 0) goto out; cc = cr->cr_cc; cc->header.address = *ld->cl_controller; /* target controller */ cbc = (struct ciss_bmic_cdb *)&(cc->cdb.cdb[0]); cbc->log_drive = ldrive; /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC ACCEPT MEDIA command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: /* all OK */ /* we should get a logical drive status changed event here */ break; default: ciss_printf(cr->cr_sc, "error accepting media into failed logical drive (%s)\n", ciss_name_command_status(command_status)); break; } out: if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Release adapter resources. */ static void ciss_free(struct ciss_softc *sc) { struct ciss_request *cr; int i, j; debug_called(1); /* we're going away */ sc->ciss_flags |= CISS_FLAG_ABORTING; /* terminate the periodic heartbeat routine */ callout_stop(&sc->ciss_periodic); /* cancel the Event Notify chain */ ciss_notify_abort(sc); ciss_kill_notify_thread(sc); /* disconnect from CAM */ if (sc->ciss_cam_sim) { for (i = 0; i < sc->ciss_max_logical_bus; i++) { if (sc->ciss_cam_sim[i]) { xpt_bus_deregister(cam_sim_path(sc->ciss_cam_sim[i])); cam_sim_free(sc->ciss_cam_sim[i], 0); } } for (i = CISS_PHYSICAL_BASE; i < sc->ciss_max_physical_bus + CISS_PHYSICAL_BASE; i++) { if (sc->ciss_cam_sim[i]) { xpt_bus_deregister(cam_sim_path(sc->ciss_cam_sim[i])); cam_sim_free(sc->ciss_cam_sim[i], 0); } } free(sc->ciss_cam_sim, CISS_MALLOC_CLASS); } if (sc->ciss_cam_devq) cam_simq_free(sc->ciss_cam_devq); /* remove the control device */ mtx_unlock(&sc->ciss_mtx); if (sc->ciss_dev_t != NULL) destroy_dev(sc->ciss_dev_t); /* Final cleanup of the callout. */ callout_drain(&sc->ciss_periodic); mtx_destroy(&sc->ciss_mtx); /* free the controller data */ if (sc->ciss_id != NULL) free(sc->ciss_id, CISS_MALLOC_CLASS); /* release I/O resources */ if (sc->ciss_regs_resource != NULL) bus_release_resource(sc->ciss_dev, SYS_RES_MEMORY, sc->ciss_regs_rid, sc->ciss_regs_resource); if (sc->ciss_cfg_resource != NULL) bus_release_resource(sc->ciss_dev, SYS_RES_MEMORY, sc->ciss_cfg_rid, sc->ciss_cfg_resource); if (sc->ciss_intr != NULL) bus_teardown_intr(sc->ciss_dev, sc->ciss_irq_resource, sc->ciss_intr); if (sc->ciss_irq_resource != NULL) bus_release_resource(sc->ciss_dev, SYS_RES_IRQ, sc->ciss_irq_rid[0], sc->ciss_irq_resource); if (sc->ciss_msi) pci_release_msi(sc->ciss_dev); while ((cr = ciss_dequeue_free(sc)) != NULL) bus_dmamap_destroy(sc->ciss_buffer_dmat, cr->cr_datamap); if (sc->ciss_buffer_dmat) bus_dma_tag_destroy(sc->ciss_buffer_dmat); /* destroy command memory and DMA tag */ if (sc->ciss_command != NULL) { bus_dmamap_unload(sc->ciss_command_dmat, sc->ciss_command_map); bus_dmamem_free(sc->ciss_command_dmat, sc->ciss_command, sc->ciss_command_map); } if (sc->ciss_command_dmat) bus_dma_tag_destroy(sc->ciss_command_dmat); if (sc->ciss_reply) { bus_dmamap_unload(sc->ciss_reply_dmat, sc->ciss_reply_map); bus_dmamem_free(sc->ciss_reply_dmat, sc->ciss_reply, sc->ciss_reply_map); } if (sc->ciss_reply_dmat) bus_dma_tag_destroy(sc->ciss_reply_dmat); /* destroy DMA tags */ if (sc->ciss_parent_dmat) bus_dma_tag_destroy(sc->ciss_parent_dmat); if (sc->ciss_logical) { for (i = 0; i <= sc->ciss_max_logical_bus; i++) { for (j = 0; j < sc->ciss_cfg->max_logical_supported; j++) { if (sc->ciss_logical[i][j].cl_ldrive) free(sc->ciss_logical[i][j].cl_ldrive, CISS_MALLOC_CLASS); if (sc->ciss_logical[i][j].cl_lstatus) free(sc->ciss_logical[i][j].cl_lstatus, CISS_MALLOC_CLASS); } free(sc->ciss_logical[i], CISS_MALLOC_CLASS); } free(sc->ciss_logical, CISS_MALLOC_CLASS); } if (sc->ciss_physical) { for (i = 0; i < sc->ciss_max_physical_bus; i++) free(sc->ciss_physical[i], CISS_MALLOC_CLASS); free(sc->ciss_physical, CISS_MALLOC_CLASS); } if (sc->ciss_controllers) free(sc->ciss_controllers, CISS_MALLOC_CLASS); } /************************************************************************ * Give a command to the adapter. * * Note that this uses the simple transport layer directly. If we * want to add support for other layers, we'll need a switch of some * sort. * * Note that the simple transport layer has no way of refusing a * command; we only have as many request structures as the adapter * supports commands, so we don't have to check (this presumes that * the adapter can handle commands as fast as we throw them at it). */ static int ciss_start(struct ciss_request *cr) { struct ciss_command *cc; /* XXX debugging only */ int error; cc = cr->cr_cc; debug(2, "post command %d tag %d ", cr->cr_tag, cc->header.host_tag); /* * Map the request's data. */ if ((error = ciss_map_request(cr))) return(error); #if 0 ciss_print_request(cr); #endif return(0); } /************************************************************************ * Fetch completed request(s) from the adapter, queue them for * completion handling. * * Note that this uses the simple transport layer directly. If we * want to add support for other layers, we'll need a switch of some * sort. * * Note that the simple transport mechanism does not require any * reentrancy protection; the OPQ read is atomic. If there is a * chance of a race with something else that might move the request * off the busy list, then we will have to lock against that * (eg. timeouts, etc.) */ static void ciss_done(struct ciss_softc *sc, cr_qhead_t *qh) { struct ciss_request *cr; struct ciss_command *cc; u_int32_t tag, index; debug_called(3); /* * Loop quickly taking requests from the adapter and moving them * to the completed queue. */ for (;;) { tag = CISS_TL_SIMPLE_FETCH_CMD(sc); if (tag == CISS_TL_SIMPLE_OPQ_EMPTY) break; index = tag >> 2; debug(2, "completed command %d%s", index, (tag & CISS_HDR_HOST_TAG_ERROR) ? " with error" : ""); if (index >= sc->ciss_max_requests) { ciss_printf(sc, "completed invalid request %d (0x%x)\n", index, tag); continue; } cr = &(sc->ciss_request[index]); cc = cr->cr_cc; cc->header.host_tag = tag; /* not updated by adapter */ ciss_enqueue_complete(cr, qh); } } static void ciss_perf_done(struct ciss_softc *sc, cr_qhead_t *qh) { struct ciss_request *cr; struct ciss_command *cc; u_int32_t tag, index; debug_called(3); /* * Loop quickly taking requests from the adapter and moving them * to the completed queue. */ for (;;) { tag = sc->ciss_reply[sc->ciss_rqidx]; if ((tag & CISS_CYCLE_MASK) != sc->ciss_cycle) break; index = tag >> 2; debug(2, "completed command %d%s\n", index, (tag & CISS_HDR_HOST_TAG_ERROR) ? " with error" : ""); if (index < sc->ciss_max_requests) { cr = &(sc->ciss_request[index]); cc = cr->cr_cc; cc->header.host_tag = tag; /* not updated by adapter */ ciss_enqueue_complete(cr, qh); } else { ciss_printf(sc, "completed invalid request %d (0x%x)\n", index, tag); } if (++sc->ciss_rqidx == sc->ciss_max_requests) { sc->ciss_rqidx = 0; sc->ciss_cycle ^= 1; } } } /************************************************************************ * Take an interrupt from the adapter. */ static void ciss_intr(void *arg) { cr_qhead_t qh; struct ciss_softc *sc = (struct ciss_softc *)arg; /* * The only interrupt we recognise indicates that there are * entries in the outbound post queue. */ STAILQ_INIT(&qh); ciss_done(sc, &qh); mtx_lock(&sc->ciss_mtx); ciss_complete(sc, &qh); mtx_unlock(&sc->ciss_mtx); } static void ciss_perf_intr(void *arg) { struct ciss_softc *sc = (struct ciss_softc *)arg; /* Clear the interrupt and flush the bridges. Docs say that the flush * needs to be done twice, which doesn't seem right. */ CISS_TL_PERF_CLEAR_INT(sc); CISS_TL_PERF_FLUSH_INT(sc); ciss_perf_msi_intr(sc); } static void ciss_perf_msi_intr(void *arg) { cr_qhead_t qh; struct ciss_softc *sc = (struct ciss_softc *)arg; STAILQ_INIT(&qh); ciss_perf_done(sc, &qh); mtx_lock(&sc->ciss_mtx); ciss_complete(sc, &qh); mtx_unlock(&sc->ciss_mtx); } /************************************************************************ * Process completed requests. * * Requests can be completed in three fashions: * * - by invoking a callback function (cr_complete is non-null) * - by waking up a sleeper (cr_flags has CISS_REQ_SLEEP set) * - by clearing the CISS_REQ_POLL flag in interrupt/timeout context */ static void ciss_complete(struct ciss_softc *sc, cr_qhead_t *qh) { struct ciss_request *cr; debug_called(2); /* * Loop taking requests off the completed queue and performing * completion processing on them. */ for (;;) { if ((cr = ciss_dequeue_complete(sc, qh)) == NULL) break; ciss_unmap_request(cr); if ((cr->cr_flags & CISS_REQ_BUSY) == 0) ciss_printf(sc, "WARNING: completing non-busy request\n"); cr->cr_flags &= ~CISS_REQ_BUSY; /* * If the request has a callback, invoke it. */ if (cr->cr_complete != NULL) { cr->cr_complete(cr); continue; } /* * If someone is sleeping on this request, wake them up. */ if (cr->cr_flags & CISS_REQ_SLEEP) { cr->cr_flags &= ~CISS_REQ_SLEEP; wakeup(cr); continue; } /* * If someone is polling this request for completion, signal. */ if (cr->cr_flags & CISS_REQ_POLL) { cr->cr_flags &= ~CISS_REQ_POLL; continue; } /* * Give up and throw the request back on the free queue. This * should never happen; resources will probably be lost. */ ciss_printf(sc, "WARNING: completed command with no submitter\n"); ciss_enqueue_free(cr); } } /************************************************************************ * Report on the completion status of a request, and pass back SCSI * and command status values. */ static int _ciss_report_request(struct ciss_request *cr, int *command_status, int *scsi_status, const char *func) { struct ciss_command *cc; struct ciss_error_info *ce; debug_called(2); cc = cr->cr_cc; ce = (struct ciss_error_info *)&(cc->sg[0]); /* * We don't consider data under/overrun an error for the Report * Logical/Physical LUNs commands. */ if ((cc->header.host_tag & CISS_HDR_HOST_TAG_ERROR) && ((ce->command_status == CISS_CMD_STATUS_DATA_OVERRUN) || (ce->command_status == CISS_CMD_STATUS_DATA_UNDERRUN)) && ((cc->cdb.cdb[0] == CISS_OPCODE_REPORT_LOGICAL_LUNS) || (cc->cdb.cdb[0] == CISS_OPCODE_REPORT_PHYSICAL_LUNS) || (cc->cdb.cdb[0] == INQUIRY))) { cc->header.host_tag &= ~CISS_HDR_HOST_TAG_ERROR; debug(2, "ignoring irrelevant under/overrun error"); } /* * Check the command's error bit, if clear, there's no status and * everything is OK. */ if (!(cc->header.host_tag & CISS_HDR_HOST_TAG_ERROR)) { if (scsi_status != NULL) *scsi_status = SCSI_STATUS_OK; if (command_status != NULL) *command_status = CISS_CMD_STATUS_SUCCESS; return(0); } else { if (command_status != NULL) *command_status = ce->command_status; if (scsi_status != NULL) { if (ce->command_status == CISS_CMD_STATUS_TARGET_STATUS) { *scsi_status = ce->scsi_status; } else { *scsi_status = -1; } } if (bootverbose) ciss_printf(cr->cr_sc, "command status 0x%x (%s) scsi status 0x%x\n", ce->command_status, ciss_name_command_status(ce->command_status), ce->scsi_status); if (ce->command_status == CISS_CMD_STATUS_INVALID_COMMAND) { ciss_printf(cr->cr_sc, "invalid command, offense size %d at %d, value 0x%x, function %s\n", ce->additional_error_info.invalid_command.offense_size, ce->additional_error_info.invalid_command.offense_offset, ce->additional_error_info.invalid_command.offense_value, func); } } #if 0 ciss_print_request(cr); #endif return(1); } /************************************************************************ * Issue a request and don't return until it's completed. * * Depending on adapter status, we may poll or sleep waiting for * completion. */ static int ciss_synch_request(struct ciss_request *cr, int timeout) { if (cr->cr_sc->ciss_flags & CISS_FLAG_RUNNING) { return(ciss_wait_request(cr, timeout)); } else { return(ciss_poll_request(cr, timeout)); } } /************************************************************************ * Issue a request and poll for completion. * * Timeout in milliseconds. */ static int ciss_poll_request(struct ciss_request *cr, int timeout) { cr_qhead_t qh; struct ciss_softc *sc; int error; debug_called(2); STAILQ_INIT(&qh); sc = cr->cr_sc; cr->cr_flags |= CISS_REQ_POLL; if ((error = ciss_start(cr)) != 0) return(error); do { if (sc->ciss_perf) ciss_perf_done(sc, &qh); else ciss_done(sc, &qh); ciss_complete(sc, &qh); if (!(cr->cr_flags & CISS_REQ_POLL)) return(0); DELAY(1000); } while (timeout-- >= 0); return(EWOULDBLOCK); } /************************************************************************ * Issue a request and sleep waiting for completion. * * Timeout in milliseconds. Note that a spurious wakeup will reset * the timeout. */ static int ciss_wait_request(struct ciss_request *cr, int timeout) { int error; debug_called(2); cr->cr_flags |= CISS_REQ_SLEEP; if ((error = ciss_start(cr)) != 0) return(error); while ((cr->cr_flags & CISS_REQ_SLEEP) && (error != EWOULDBLOCK)) { error = msleep_sbt(cr, &cr->cr_sc->ciss_mtx, PRIBIO, "cissREQ", SBT_1MS * timeout, 0, 0); } return(error); } #if 0 /************************************************************************ * Abort a request. Note that a potential exists here to race the * request being completed; the caller must deal with this. */ static int ciss_abort_request(struct ciss_request *ar) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_message_cdb *cmc; int error; debug_called(1); /* get a request */ if ((error = ciss_get_request(ar->cr_sc, &cr)) != 0) return(error); /* build the abort command */ cc = cr->cr_cc; cc->header.address.mode.mode = CISS_HDR_ADDRESS_MODE_PERIPHERAL; /* addressing? */ cc->header.address.physical.target = 0; cc->header.address.physical.bus = 0; cc->cdb.cdb_length = sizeof(*cmc); cc->cdb.type = CISS_CDB_TYPE_MESSAGE; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_NONE; cc->cdb.timeout = 30; cmc = (struct ciss_message_cdb *)&(cc->cdb.cdb[0]); cmc->opcode = CISS_OPCODE_MESSAGE_ABORT; cmc->type = CISS_MESSAGE_ABORT_TASK; cmc->abort_tag = ar->cr_tag; /* endianness?? */ /* * Send the request and wait for a response. If we believe we * aborted the request OK, clear the flag that indicates it's * running. */ error = ciss_synch_request(cr, 35 * 1000); if (!error) error = ciss_report_request(cr, NULL, NULL); ciss_release_request(cr); return(error); } #endif /************************************************************************ * Fetch and initialise a request */ static int ciss_get_request(struct ciss_softc *sc, struct ciss_request **crp) { struct ciss_request *cr; debug_called(2); /* * Get a request and clean it up. */ if ((cr = ciss_dequeue_free(sc)) == NULL) return(ENOMEM); cr->cr_data = NULL; cr->cr_flags = 0; cr->cr_complete = NULL; cr->cr_private = NULL; cr->cr_sg_tag = CISS_SG_MAX; /* Backstop to prevent accidents */ ciss_preen_command(cr); *crp = cr; return(0); } static void ciss_preen_command(struct ciss_request *cr) { struct ciss_command *cc; u_int32_t cmdphys; /* * Clean up the command structure. * * Note that we set up the error_info structure here, since the * length can be overwritten by any command. */ cc = cr->cr_cc; cc->header.sg_in_list = 0; /* kinda inefficient this way */ cc->header.sg_total = 0; cc->header.host_tag = cr->cr_tag << 2; cc->header.host_tag_zeroes = 0; bzero(&(cc->sg[0]), CISS_COMMAND_ALLOC_SIZE - sizeof(struct ciss_command)); cmdphys = cr->cr_ccphys; cc->error_info.error_info_address = cmdphys + sizeof(struct ciss_command); cc->error_info.error_info_length = CISS_COMMAND_ALLOC_SIZE - sizeof(struct ciss_command); } /************************************************************************ * Release a request to the free list. */ static void ciss_release_request(struct ciss_request *cr) { struct ciss_softc *sc; debug_called(2); sc = cr->cr_sc; /* release the request to the free queue */ ciss_requeue_free(cr); } /************************************************************************ * Allocate a request that will be used to send a BMIC command. Do some * of the common setup here to avoid duplicating it everywhere else. */ static int ciss_get_bmic_request(struct ciss_softc *sc, struct ciss_request **crp, int opcode, void **bufp, size_t bufsize) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_bmic_cdb *cbc; void *buf; int error; int dataout; debug_called(2); cr = NULL; buf = NULL; /* * Get a request. */ if ((error = ciss_get_request(sc, &cr)) != 0) goto out; /* * Allocate data storage if requested, determine the data direction. */ dataout = 0; if ((bufsize > 0) && (bufp != NULL)) { if (*bufp == NULL) { if ((buf = malloc(bufsize, CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO)) == NULL) { error = ENOMEM; goto out; } } else { buf = *bufp; dataout = 1; /* we are given a buffer, so we are writing */ } } /* * Build a CISS BMIC command to get the logical drive ID. */ cr->cr_data = buf; cr->cr_length = bufsize; if (!dataout) cr->cr_flags = CISS_REQ_DATAIN; cc = cr->cr_cc; cc->header.address.physical.mode = CISS_HDR_ADDRESS_MODE_PERIPHERAL; cc->header.address.physical.bus = 0; cc->header.address.physical.target = 0; cc->cdb.cdb_length = sizeof(*cbc); cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = dataout ? CISS_CDB_DIRECTION_WRITE : CISS_CDB_DIRECTION_READ; cc->cdb.timeout = 0; cbc = (struct ciss_bmic_cdb *)&(cc->cdb.cdb[0]); bzero(cbc, sizeof(*cbc)); cbc->opcode = dataout ? CISS_ARRAY_CONTROLLER_WRITE : CISS_ARRAY_CONTROLLER_READ; cbc->bmic_opcode = opcode; cbc->size = htons((u_int16_t)bufsize); out: if (error) { if (cr != NULL) ciss_release_request(cr); } else { *crp = cr; if ((bufp != NULL) && (*bufp == NULL) && (buf != NULL)) *bufp = buf; } return(error); } /************************************************************************ * Handle a command passed in from userspace. */ static int ciss_user_command(struct ciss_softc *sc, IOCTL_Command_struct *ioc) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_error_info *ce; int error = 0; debug_called(1); cr = NULL; /* * Get a request. */ while (ciss_get_request(sc, &cr) != 0) msleep(sc, &sc->ciss_mtx, PPAUSE, "cissREQ", hz); cc = cr->cr_cc; /* * Allocate an in-kernel databuffer if required, copy in user data. */ mtx_unlock(&sc->ciss_mtx); cr->cr_length = ioc->buf_size; if (ioc->buf_size > 0) { if ((cr->cr_data = malloc(ioc->buf_size, CISS_MALLOC_CLASS, M_NOWAIT)) == NULL) { error = ENOMEM; goto out_unlocked; } if ((error = copyin(ioc->buf, cr->cr_data, ioc->buf_size))) { debug(0, "copyin: bad data buffer %p/%d", ioc->buf, ioc->buf_size); goto out_unlocked; } } /* * Build the request based on the user command. */ bcopy(&ioc->LUN_info, &cc->header.address, sizeof(cc->header.address)); bcopy(&ioc->Request, &cc->cdb, sizeof(cc->cdb)); /* XXX anything else to populate here? */ mtx_lock(&sc->ciss_mtx); /* * Run the command. */ if ((error = ciss_synch_request(cr, 60 * 1000))) { debug(0, "request failed - %d", error); goto out; } /* * Check to see if the command succeeded. */ ce = (struct ciss_error_info *)&(cc->sg[0]); if ((cc->header.host_tag & CISS_HDR_HOST_TAG_ERROR) == 0) bzero(ce, sizeof(*ce)); /* * Copy the results back to the user. */ bcopy(ce, &ioc->error_info, sizeof(*ce)); mtx_unlock(&sc->ciss_mtx); if ((ioc->buf_size > 0) && (error = copyout(cr->cr_data, ioc->buf, ioc->buf_size))) { debug(0, "copyout: bad data buffer %p/%d", ioc->buf, ioc->buf_size); goto out_unlocked; } /* done OK */ error = 0; out_unlocked: mtx_lock(&sc->ciss_mtx); out: if ((cr != NULL) && (cr->cr_data != NULL)) free(cr->cr_data, CISS_MALLOC_CLASS); if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Map a request into bus-visible space, initialise the scatter/gather * list. */ static int ciss_map_request(struct ciss_request *cr) { struct ciss_softc *sc; int error = 0; debug_called(2); sc = cr->cr_sc; /* check that mapping is necessary */ if (cr->cr_flags & CISS_REQ_MAPPED) return(0); cr->cr_flags |= CISS_REQ_MAPPED; bus_dmamap_sync(sc->ciss_command_dmat, sc->ciss_command_map, BUS_DMASYNC_PREWRITE); if (cr->cr_data != NULL) { if (cr->cr_flags & CISS_REQ_CCB) error = bus_dmamap_load_ccb(sc->ciss_buffer_dmat, cr->cr_datamap, cr->cr_data, ciss_request_map_helper, cr, 0); else error = bus_dmamap_load(sc->ciss_buffer_dmat, cr->cr_datamap, cr->cr_data, cr->cr_length, ciss_request_map_helper, cr, 0); if (error != 0) return (error); } else { /* * Post the command to the adapter. */ cr->cr_sg_tag = CISS_SG_NONE; cr->cr_flags |= CISS_REQ_BUSY; if (sc->ciss_perf) CISS_TL_PERF_POST_CMD(sc, cr); else CISS_TL_SIMPLE_POST_CMD(sc, cr->cr_ccphys); } return(0); } static void ciss_request_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct ciss_command *cc; struct ciss_request *cr; struct ciss_softc *sc; int i; debug_called(2); cr = (struct ciss_request *)arg; sc = cr->cr_sc; cc = cr->cr_cc; for (i = 0; i < nseg; i++) { cc->sg[i].address = segs[i].ds_addr; cc->sg[i].length = segs[i].ds_len; cc->sg[i].extension = 0; } /* we leave the s/g table entirely within the command */ cc->header.sg_in_list = nseg; cc->header.sg_total = nseg; if (cr->cr_flags & CISS_REQ_DATAIN) bus_dmamap_sync(sc->ciss_buffer_dmat, cr->cr_datamap, BUS_DMASYNC_PREREAD); if (cr->cr_flags & CISS_REQ_DATAOUT) bus_dmamap_sync(sc->ciss_buffer_dmat, cr->cr_datamap, BUS_DMASYNC_PREWRITE); if (nseg == 0) cr->cr_sg_tag = CISS_SG_NONE; else if (nseg == 1) cr->cr_sg_tag = CISS_SG_1; else if (nseg == 2) cr->cr_sg_tag = CISS_SG_2; else if (nseg <= 4) cr->cr_sg_tag = CISS_SG_4; else if (nseg <= 8) cr->cr_sg_tag = CISS_SG_8; else if (nseg <= 16) cr->cr_sg_tag = CISS_SG_16; else if (nseg <= 32) cr->cr_sg_tag = CISS_SG_32; else cr->cr_sg_tag = CISS_SG_MAX; /* * Post the command to the adapter. */ cr->cr_flags |= CISS_REQ_BUSY; if (sc->ciss_perf) CISS_TL_PERF_POST_CMD(sc, cr); else CISS_TL_SIMPLE_POST_CMD(sc, cr->cr_ccphys); } /************************************************************************ * Unmap a request from bus-visible space. */ static void ciss_unmap_request(struct ciss_request *cr) { struct ciss_softc *sc; debug_called(2); sc = cr->cr_sc; /* check that unmapping is necessary */ if ((cr->cr_flags & CISS_REQ_MAPPED) == 0) return; bus_dmamap_sync(sc->ciss_command_dmat, sc->ciss_command_map, BUS_DMASYNC_POSTWRITE); if (cr->cr_data == NULL) goto out; if (cr->cr_flags & CISS_REQ_DATAIN) bus_dmamap_sync(sc->ciss_buffer_dmat, cr->cr_datamap, BUS_DMASYNC_POSTREAD); if (cr->cr_flags & CISS_REQ_DATAOUT) bus_dmamap_sync(sc->ciss_buffer_dmat, cr->cr_datamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->ciss_buffer_dmat, cr->cr_datamap); out: cr->cr_flags &= ~CISS_REQ_MAPPED; } /************************************************************************ * Attach the driver to CAM. * * We put all the logical drives on a single SCSI bus. */ static int ciss_cam_init(struct ciss_softc *sc) { int i, maxbus; debug_called(1); /* * Allocate a devq. We can reuse this for the masked physical * devices if we decide to export these as well. */ if ((sc->ciss_cam_devq = cam_simq_alloc(sc->ciss_max_requests - 2)) == NULL) { ciss_printf(sc, "can't allocate CAM SIM queue\n"); return(ENOMEM); } /* * Create a SIM. * * This naturally wastes a bit of memory. The alternative is to allocate * and register each bus as it is found, and then track them on a linked * list. Unfortunately, the driver has a few places where it needs to * look up the SIM based solely on bus number, and it's unclear whether * a list traversal would work for these situations. */ maxbus = max(sc->ciss_max_logical_bus, sc->ciss_max_physical_bus + CISS_PHYSICAL_BASE); sc->ciss_cam_sim = malloc(maxbus * sizeof(struct cam_sim*), CISS_MALLOC_CLASS, M_NOWAIT | M_ZERO); if (sc->ciss_cam_sim == NULL) { ciss_printf(sc, "can't allocate memory for controller SIM\n"); return(ENOMEM); } for (i = 0; i < sc->ciss_max_logical_bus; i++) { if ((sc->ciss_cam_sim[i] = cam_sim_alloc(ciss_cam_action, ciss_cam_poll, "ciss", sc, device_get_unit(sc->ciss_dev), &sc->ciss_mtx, 2, sc->ciss_max_requests - 2, sc->ciss_cam_devq)) == NULL) { ciss_printf(sc, "can't allocate CAM SIM for controller %d\n", i); return(ENOMEM); } /* * Register bus with this SIM. */ mtx_lock(&sc->ciss_mtx); if (i == 0 || sc->ciss_controllers[i].physical.bus != 0) { if (xpt_bus_register(sc->ciss_cam_sim[i], sc->ciss_dev, i) != 0) { ciss_printf(sc, "can't register SCSI bus %d\n", i); mtx_unlock(&sc->ciss_mtx); return (ENXIO); } } mtx_unlock(&sc->ciss_mtx); } for (i = CISS_PHYSICAL_BASE; i < sc->ciss_max_physical_bus + CISS_PHYSICAL_BASE; i++) { if ((sc->ciss_cam_sim[i] = cam_sim_alloc(ciss_cam_action, ciss_cam_poll, "ciss", sc, device_get_unit(sc->ciss_dev), &sc->ciss_mtx, 1, sc->ciss_max_requests - 2, sc->ciss_cam_devq)) == NULL) { ciss_printf(sc, "can't allocate CAM SIM for controller %d\n", i); return (ENOMEM); } mtx_lock(&sc->ciss_mtx); if (xpt_bus_register(sc->ciss_cam_sim[i], sc->ciss_dev, i) != 0) { ciss_printf(sc, "can't register SCSI bus %d\n", i); mtx_unlock(&sc->ciss_mtx); return (ENXIO); } mtx_unlock(&sc->ciss_mtx); } return(0); } /************************************************************************ * Initiate a rescan of the 'logical devices' SIM */ static void ciss_cam_rescan_target(struct ciss_softc *sc, int bus, int target) { union ccb *ccb; debug_called(1); if ((ccb = xpt_alloc_ccb_nowait()) == NULL) { ciss_printf(sc, "rescan failed (can't allocate CCB)\n"); return; } if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(sc->ciss_cam_sim[bus]), target, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { ciss_printf(sc, "rescan failed (can't create path)\n"); xpt_free_ccb(ccb); return; } xpt_rescan(ccb); /* scan is now in progress */ } /************************************************************************ * Handle requests coming from CAM */ static void ciss_cam_action(struct cam_sim *sim, union ccb *ccb) { struct ciss_softc *sc; struct ccb_scsiio *csio; int bus, target; int physical; sc = cam_sim_softc(sim); bus = cam_sim_bus(sim); csio = (struct ccb_scsiio *)&ccb->csio; target = csio->ccb_h.target_id; physical = CISS_IS_PHYSICAL(bus); switch (ccb->ccb_h.func_code) { /* perform SCSI I/O */ case XPT_SCSI_IO: if (!ciss_cam_action_io(sim, csio)) return; break; /* perform geometry calculations */ case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg = &ccb->ccg; struct ciss_ldrive *ld; debug(1, "XPT_CALC_GEOMETRY %d:%d:%d", cam_sim_bus(sim), ccb->ccb_h.target_id, ccb->ccb_h.target_lun); ld = NULL; if (!physical) ld = &sc->ciss_logical[bus][target]; /* * Use the cached geometry settings unless the fault tolerance * is invalid. */ if (physical || ld->cl_geometry.fault_tolerance == 0xFF) { u_int32_t secs_per_cylinder; ccg->heads = 255; ccg->secs_per_track = 32; secs_per_cylinder = ccg->heads * ccg->secs_per_track; ccg->cylinders = ccg->volume_size / secs_per_cylinder; } else { ccg->heads = ld->cl_geometry.heads; ccg->secs_per_track = ld->cl_geometry.sectors; ccg->cylinders = ntohs(ld->cl_geometry.cylinders); } ccb->ccb_h.status = CAM_REQ_CMP; break; } /* handle path attribute inquiry */ case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; int sg_length; debug(1, "XPT_PATH_INQ %d:%d:%d", cam_sim_bus(sim), ccb->ccb_h.target_id, ccb->ccb_h.target_lun); cpi->version_num = 1; cpi->hba_inquiry = PI_TAG_ABLE; /* XXX is this correct? */ cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->max_target = sc->ciss_cfg->max_logical_supported; cpi->max_lun = 0; /* 'logical drive' channel only */ cpi->initiator_id = sc->ciss_cfg->max_logical_supported; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "msmith@freebsd.org", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 132 * 1024; /* XXX what to set this to? */ cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; if (sc->ciss_cfg->max_sg_length == 0) { sg_length = 17; } else { /* XXX Fix for ZMR cards that advertise max_sg_length == 32 * Confusing bit here. max_sg_length is usually a power of 2. We always * need to subtract 1 to account for partial pages. Then we need to * align on a valid PAGE_SIZE so we round down to the nearest power of 2. * Add 1 so we can then subtract it out in the assignment to maxio. * The reason for all these shenanigans is to create a maxio value that * creates IO operations to volumes that yield consistent operations * with good performance. */ sg_length = sc->ciss_cfg->max_sg_length - 1; sg_length = (1 << (fls(sg_length) - 1)) + 1; } cpi->maxio = (min(CISS_MAX_SG_ELEMENTS, sg_length) - 1) * PAGE_SIZE; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; int bus, target; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; bus = cam_sim_bus(sim); target = cts->ccb_h.target_id; debug(1, "XPT_GET_TRAN_SETTINGS %d:%d", bus, target); /* disconnect always OK */ cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; spi->valid = CTS_SPI_VALID_DISC; spi->flags = CTS_SPI_FLAGS_DISC_ENB; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; cts->ccb_h.status = CAM_REQ_CMP; break; } default: /* we can't do this */ debug(1, "unspported func_code = 0x%x", ccb->ccb_h.func_code); ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); } /************************************************************************ * Handle a CAM SCSI I/O request. */ static int ciss_cam_action_io(struct cam_sim *sim, struct ccb_scsiio *csio) { struct ciss_softc *sc; int bus, target; struct ciss_request *cr; struct ciss_command *cc; int error; sc = cam_sim_softc(sim); bus = cam_sim_bus(sim); target = csio->ccb_h.target_id; debug(2, "XPT_SCSI_IO %d:%d:%d", bus, target, csio->ccb_h.target_lun); /* check that the CDB pointer is not to a physical address */ if ((csio->ccb_h.flags & CAM_CDB_POINTER) && (csio->ccb_h.flags & CAM_CDB_PHYS)) { debug(3, " CDB pointer is to physical address"); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* abandon aborted ccbs or those that have failed validation */ if ((csio->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { debug(3, "abandoning CCB due to abort/validation failure"); return(EINVAL); } /* handle emulation of some SCSI commands ourself */ if (ciss_cam_emulate(sc, csio)) return(0); /* * Get a request to manage this command. If we can't, return the * ccb, freeze the queue and flag so that we unfreeze it when a * request completes. */ if ((error = ciss_get_request(sc, &cr)) != 0) { xpt_freeze_simq(sim, 1); sc->ciss_flags |= CISS_FLAG_BUSY; csio->ccb_h.status |= CAM_REQUEUE_REQ; return(error); } /* * Build the command. */ cc = cr->cr_cc; cr->cr_data = csio; cr->cr_length = csio->dxfer_len; cr->cr_complete = ciss_cam_complete; cr->cr_private = csio; /* * Target the right logical volume. */ if (CISS_IS_PHYSICAL(bus)) cc->header.address = sc->ciss_physical[CISS_CAM_TO_PBUS(bus)][target].cp_address; else cc->header.address = sc->ciss_logical[bus][target].cl_address; cc->cdb.cdb_length = csio->cdb_len; cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; /* XXX ordered tags? */ if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) { cr->cr_flags = CISS_REQ_DATAOUT | CISS_REQ_CCB; cc->cdb.direction = CISS_CDB_DIRECTION_WRITE; } else if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { cr->cr_flags = CISS_REQ_DATAIN | CISS_REQ_CCB; cc->cdb.direction = CISS_CDB_DIRECTION_READ; } else { cr->cr_data = NULL; cr->cr_flags = 0; cc->cdb.direction = CISS_CDB_DIRECTION_NONE; } cc->cdb.timeout = (csio->ccb_h.timeout / 1000) + 1; if (csio->ccb_h.flags & CAM_CDB_POINTER) { bcopy(csio->cdb_io.cdb_ptr, &cc->cdb.cdb[0], csio->cdb_len); } else { bcopy(csio->cdb_io.cdb_bytes, &cc->cdb.cdb[0], csio->cdb_len); } /* * Submit the request to the adapter. * * Note that this may fail if we're unable to map the request (and * if we ever learn a transport layer other than simple, may fail * if the adapter rejects the command). */ if ((error = ciss_start(cr)) != 0) { xpt_freeze_simq(sim, 1); csio->ccb_h.status |= CAM_RELEASE_SIMQ; if (error == EINPROGRESS) { error = 0; } else { csio->ccb_h.status |= CAM_REQUEUE_REQ; ciss_release_request(cr); } return(error); } return(0); } /************************************************************************ * Emulate SCSI commands the adapter doesn't handle as we might like. */ static int ciss_cam_emulate(struct ciss_softc *sc, struct ccb_scsiio *csio) { int bus, target; u_int8_t opcode; target = csio->ccb_h.target_id; bus = cam_sim_bus(xpt_path_sim(csio->ccb_h.path)); opcode = (csio->ccb_h.flags & CAM_CDB_POINTER) ? *(u_int8_t *)csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes[0]; if (CISS_IS_PHYSICAL(bus)) { if (sc->ciss_physical[CISS_CAM_TO_PBUS(bus)][target].cp_online != 1) { csio->ccb_h.status |= CAM_SEL_TIMEOUT; xpt_done((union ccb *)csio); return(1); } else return(0); } /* * Handle requests for volumes that don't exist or are not online. * A selection timeout is slightly better than an illegal request. * Other errors might be better. */ if (sc->ciss_logical[bus][target].cl_status != CISS_LD_ONLINE) { csio->ccb_h.status |= CAM_SEL_TIMEOUT; xpt_done((union ccb *)csio); return(1); } /* if we have to fake Synchronise Cache */ if (sc->ciss_flags & CISS_FLAG_FAKE_SYNCH) { /* * If this is a Synchronise Cache command, typically issued when * a device is closed, flush the adapter and complete now. */ if (((csio->ccb_h.flags & CAM_CDB_POINTER) ? *(u_int8_t *)csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes[0]) == SYNCHRONIZE_CACHE) { ciss_flush_adapter(sc); csio->ccb_h.status |= CAM_REQ_CMP; xpt_done((union ccb *)csio); return(1); } } /* * A CISS target can only ever have one lun per target. REPORT_LUNS requires * at least one LUN field to be pre created for us, so snag it and fill in * the least significant byte indicating 1 LUN here. Emulate the command * return to shut up warning on console of a CDB error. swb */ if (opcode == REPORT_LUNS && csio->dxfer_len > 0) { csio->data_ptr[3] = 8; csio->ccb_h.status |= CAM_REQ_CMP; xpt_done((union ccb *)csio); return(1); } return(0); } /************************************************************************ * Check for possibly-completed commands. */ static void ciss_cam_poll(struct cam_sim *sim) { cr_qhead_t qh; struct ciss_softc *sc = cam_sim_softc(sim); debug_called(2); STAILQ_INIT(&qh); if (sc->ciss_perf) ciss_perf_done(sc, &qh); else ciss_done(sc, &qh); ciss_complete(sc, &qh); } /************************************************************************ * Handle completion of a command - pass results back through the CCB */ static void ciss_cam_complete(struct ciss_request *cr) { struct ciss_softc *sc; struct ciss_command *cc; struct ciss_error_info *ce; struct ccb_scsiio *csio; int scsi_status; int command_status; debug_called(2); sc = cr->cr_sc; cc = cr->cr_cc; ce = (struct ciss_error_info *)&(cc->sg[0]); csio = (struct ccb_scsiio *)cr->cr_private; /* * Extract status values from request. */ ciss_report_request(cr, &command_status, &scsi_status); csio->scsi_status = scsi_status; /* * Handle specific SCSI status values. */ switch(scsi_status) { /* no status due to adapter error */ case -1: debug(0, "adapter error"); csio->ccb_h.status |= CAM_REQ_CMP_ERR; break; /* no status due to command completed OK */ case SCSI_STATUS_OK: /* CISS_SCSI_STATUS_GOOD */ debug(2, "SCSI_STATUS_OK"); csio->ccb_h.status |= CAM_REQ_CMP; break; /* check condition, sense data included */ case SCSI_STATUS_CHECK_COND: /* CISS_SCSI_STATUS_CHECK_CONDITION */ debug(0, "SCSI_STATUS_CHECK_COND sense size %d resid %d\n", ce->sense_length, ce->residual_count); bzero(&csio->sense_data, SSD_FULL_SIZE); bcopy(&ce->sense_info[0], &csio->sense_data, ce->sense_length); if (csio->sense_len > ce->sense_length) csio->sense_resid = csio->sense_len - ce->sense_length; else csio->sense_resid = 0; csio->resid = ce->residual_count; csio->ccb_h.status |= CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; #ifdef CISS_DEBUG { struct scsi_sense_data *sns = (struct scsi_sense_data *)&ce->sense_info[0]; debug(0, "sense key %x", scsi_get_sense_key(sns, csio->sense_len - csio->sense_resid, /*show_errors*/ 1)); } #endif break; case SCSI_STATUS_BUSY: /* CISS_SCSI_STATUS_BUSY */ debug(0, "SCSI_STATUS_BUSY"); csio->ccb_h.status |= CAM_SCSI_BUSY; break; default: debug(0, "unknown status 0x%x", csio->scsi_status); csio->ccb_h.status |= CAM_REQ_CMP_ERR; break; } /* handle post-command fixup */ ciss_cam_complete_fixup(sc, csio); ciss_release_request(cr); if (sc->ciss_flags & CISS_FLAG_BUSY) { sc->ciss_flags &= ~CISS_FLAG_BUSY; if (csio->ccb_h.status & CAM_RELEASE_SIMQ) xpt_release_simq(xpt_path_sim(csio->ccb_h.path), 0); else csio->ccb_h.status |= CAM_RELEASE_SIMQ; } xpt_done((union ccb *)csio); } /******************************************************************************** * Fix up the result of some commands here. */ static void ciss_cam_complete_fixup(struct ciss_softc *sc, struct ccb_scsiio *csio) { struct scsi_inquiry_data *inq; struct ciss_ldrive *cl; uint8_t *cdb; int bus, target; cdb = (csio->ccb_h.flags & CAM_CDB_POINTER) ? (uint8_t *)csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes; if (cdb[0] == INQUIRY && (cdb[1] & SI_EVPD) == 0 && (csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN && csio->dxfer_len >= SHORT_INQUIRY_LENGTH) { inq = (struct scsi_inquiry_data *)csio->data_ptr; target = csio->ccb_h.target_id; bus = cam_sim_bus(xpt_path_sim(csio->ccb_h.path)); /* * If the controller is in JBOD mode, there are no logical volumes. * Let the disks be probed and dealt with via CAM. Else, mask off * the physical disks and setup the parts of the inq structure for * the logical volume. swb */ if( !(sc->ciss_id->PowerUPNvramFlags & PWR_UP_FLAG_JBOD_ENABLED)){ if (CISS_IS_PHYSICAL(bus)) { if (SID_TYPE(inq) == T_DIRECT) inq->device = (inq->device & 0xe0) | T_NODEVICE; return; } cl = &sc->ciss_logical[bus][target]; padstr(inq->vendor, "HP", SID_VENDOR_SIZE); padstr(inq->product, ciss_name_ldrive_org(cl->cl_ldrive->fault_tolerance), SID_PRODUCT_SIZE); padstr(inq->revision, ciss_name_ldrive_status(cl->cl_lstatus->status), SID_REVISION_SIZE); } } } /******************************************************************************** * Name the device at (target) * * XXX is this strictly correct? */ static int ciss_name_device(struct ciss_softc *sc, int bus, int target) { struct cam_periph *periph; struct cam_path *path; int status; if (CISS_IS_PHYSICAL(bus)) return (0); status = xpt_create_path(&path, NULL, cam_sim_path(sc->ciss_cam_sim[bus]), target, 0); if (status == CAM_REQ_CMP) { mtx_lock(&sc->ciss_mtx); xpt_path_lock(path); periph = cam_periph_find(path, NULL); xpt_path_unlock(path); mtx_unlock(&sc->ciss_mtx); xpt_free_path(path); if (periph != NULL) { sprintf(sc->ciss_logical[bus][target].cl_name, "%s%d", periph->periph_name, periph->unit_number); return(0); } } sc->ciss_logical[bus][target].cl_name[0] = 0; return(ENOENT); } /************************************************************************ * Periodic status monitoring. */ static void ciss_periodic(void *arg) { struct ciss_softc *sc; struct ciss_request *cr = NULL; struct ciss_command *cc = NULL; int error = 0; debug_called(1); sc = (struct ciss_softc *)arg; /* * Check the adapter heartbeat. */ if (sc->ciss_cfg->heartbeat == sc->ciss_heartbeat) { sc->ciss_heart_attack++; debug(0, "adapter heart attack in progress 0x%x/%d", sc->ciss_heartbeat, sc->ciss_heart_attack); if (sc->ciss_heart_attack == 3) { ciss_printf(sc, "ADAPTER HEARTBEAT FAILED\n"); ciss_disable_adapter(sc); return; } } else { sc->ciss_heartbeat = sc->ciss_cfg->heartbeat; sc->ciss_heart_attack = 0; debug(3, "new heartbeat 0x%x", sc->ciss_heartbeat); } /* * Send the NOP message and wait for a response. */ if (ciss_nop_message_heartbeat != 0 && (error = ciss_get_request(sc, &cr)) == 0) { cc = cr->cr_cc; cr->cr_complete = ciss_nop_complete; cc->cdb.cdb_length = 1; cc->cdb.type = CISS_CDB_TYPE_MESSAGE; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_WRITE; cc->cdb.timeout = 0; cc->cdb.cdb[0] = CISS_OPCODE_MESSAGE_NOP; if ((error = ciss_start(cr)) != 0) { ciss_printf(sc, "SENDING NOP MESSAGE FAILED\n"); } } /* * If the notify event request has died for some reason, or has * not started yet, restart it. */ if (!(sc->ciss_flags & CISS_FLAG_NOTIFY_OK)) { debug(0, "(re)starting Event Notify chain"); ciss_notify_event(sc); } /* * Reschedule. */ callout_reset(&sc->ciss_periodic, CISS_HEARTBEAT_RATE * hz, ciss_periodic, sc); } static void ciss_nop_complete(struct ciss_request *cr) { struct ciss_softc *sc; static int first_time = 1; sc = cr->cr_sc; if (ciss_report_request(cr, NULL, NULL) != 0) { if (first_time == 1) { first_time = 0; ciss_printf(sc, "SENDING NOP MESSAGE FAILED (not logging anymore)\n"); } } ciss_release_request(cr); } /************************************************************************ * Disable the adapter. * * The all requests in completed queue is failed with hardware error. * This will cause failover in a multipath configuration. */ static void ciss_disable_adapter(struct ciss_softc *sc) { cr_qhead_t qh; struct ciss_request *cr; struct ciss_command *cc; struct ciss_error_info *ce; int i; CISS_TL_SIMPLE_DISABLE_INTERRUPTS(sc); pci_disable_busmaster(sc->ciss_dev); sc->ciss_flags &= ~CISS_FLAG_RUNNING; for (i = 1; i < sc->ciss_max_requests; i++) { cr = &sc->ciss_request[i]; if ((cr->cr_flags & CISS_REQ_BUSY) == 0) continue; cc = cr->cr_cc; ce = (struct ciss_error_info *)&(cc->sg[0]); ce->command_status = CISS_CMD_STATUS_HARDWARE_ERROR; ciss_enqueue_complete(cr, &qh); } for (;;) { if ((cr = ciss_dequeue_complete(sc, &qh)) == NULL) break; /* * If the request has a callback, invoke it. */ if (cr->cr_complete != NULL) { cr->cr_complete(cr); continue; } /* * If someone is sleeping on this request, wake them up. */ if (cr->cr_flags & CISS_REQ_SLEEP) { cr->cr_flags &= ~CISS_REQ_SLEEP; wakeup(cr); continue; } } } /************************************************************************ * Request a notification response from the adapter. * * If (cr) is NULL, this is the first request of the adapter, so * reset the adapter's message pointer and start with the oldest * message available. */ static void ciss_notify_event(struct ciss_softc *sc) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_notify_cdb *cnc; int error; debug_called(1); cr = sc->ciss_periodic_notify; /* get a request if we don't already have one */ if (cr == NULL) { if ((error = ciss_get_request(sc, &cr)) != 0) { debug(0, "can't get notify event request"); goto out; } sc->ciss_periodic_notify = cr; cr->cr_complete = ciss_notify_complete; debug(1, "acquired request %d", cr->cr_tag); } /* * Get a databuffer if we don't already have one, note that the * adapter command wants a larger buffer than the actual * structure. */ if (cr->cr_data == NULL) { if ((cr->cr_data = malloc(CISS_NOTIFY_DATA_SIZE, CISS_MALLOC_CLASS, M_NOWAIT)) == NULL) { debug(0, "can't get notify event request buffer"); error = ENOMEM; goto out; } cr->cr_length = CISS_NOTIFY_DATA_SIZE; } /* re-setup the request's command (since we never release it) XXX overkill*/ ciss_preen_command(cr); /* (re)build the notify event command */ cc = cr->cr_cc; cc->header.address.physical.mode = CISS_HDR_ADDRESS_MODE_PERIPHERAL; cc->header.address.physical.bus = 0; cc->header.address.physical.target = 0; cc->cdb.cdb_length = sizeof(*cnc); cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_READ; cc->cdb.timeout = 0; /* no timeout, we hope */ cnc = (struct ciss_notify_cdb *)&(cc->cdb.cdb[0]); bzero(cr->cr_data, CISS_NOTIFY_DATA_SIZE); cnc->opcode = CISS_OPCODE_READ; cnc->command = CISS_COMMAND_NOTIFY_ON_EVENT; cnc->timeout = 0; /* no timeout, we hope */ cnc->synchronous = 0; cnc->ordered = 0; cnc->seek_to_oldest = 0; if ((sc->ciss_flags & CISS_FLAG_RUNNING) == 0) cnc->new_only = 1; else cnc->new_only = 0; cnc->length = htonl(CISS_NOTIFY_DATA_SIZE); /* submit the request */ error = ciss_start(cr); out: if (error) { if (cr != NULL) { if (cr->cr_data != NULL) free(cr->cr_data, CISS_MALLOC_CLASS); ciss_release_request(cr); } sc->ciss_periodic_notify = NULL; debug(0, "can't submit notify event request"); sc->ciss_flags &= ~CISS_FLAG_NOTIFY_OK; } else { debug(1, "notify event submitted"); sc->ciss_flags |= CISS_FLAG_NOTIFY_OK; } } static void ciss_notify_complete(struct ciss_request *cr) { struct ciss_command *cc; struct ciss_notify *cn; struct ciss_softc *sc; int scsi_status; int command_status; debug_called(1); cc = cr->cr_cc; cn = (struct ciss_notify *)cr->cr_data; sc = cr->cr_sc; /* * Report request results, decode status. */ ciss_report_request(cr, &command_status, &scsi_status); /* * Abort the chain on a fatal error. * * XXX which of these are actually errors? */ if ((command_status != CISS_CMD_STATUS_SUCCESS) && (command_status != CISS_CMD_STATUS_TARGET_STATUS) && (command_status != CISS_CMD_STATUS_TIMEOUT)) { /* XXX timeout? */ ciss_printf(sc, "fatal error in Notify Event request (%s)\n", ciss_name_command_status(command_status)); ciss_release_request(cr); sc->ciss_flags &= ~CISS_FLAG_NOTIFY_OK; return; } /* * If the adapter gave us a text message, print it. */ if (cn->message[0] != 0) ciss_printf(sc, "*** %.80s\n", cn->message); debug(0, "notify event class %d subclass %d detail %d", cn->class, cn->subclass, cn->detail); /* * If the response indicates that the notifier has been aborted, * release the notifier command. */ if ((cn->class == CISS_NOTIFY_NOTIFIER) && (cn->subclass == CISS_NOTIFY_NOTIFIER_STATUS) && (cn->detail == 1)) { debug(0, "notifier exiting"); sc->ciss_flags &= ~CISS_FLAG_NOTIFY_OK; ciss_release_request(cr); sc->ciss_periodic_notify = NULL; wakeup(&sc->ciss_periodic_notify); } else { /* Handle notify events in a kernel thread */ ciss_enqueue_notify(cr); sc->ciss_periodic_notify = NULL; wakeup(&sc->ciss_periodic_notify); wakeup(&sc->ciss_notify); } /* * Send a new notify event command, if we're not aborting. */ if (!(sc->ciss_flags & CISS_FLAG_ABORTING)) { ciss_notify_event(sc); } } /************************************************************************ * Abort the Notify Event chain. * * Note that we can't just abort the command in progress; we have to * explicitly issue an Abort Notify Event command in order for the * adapter to clean up correctly. * * If we are called with CISS_FLAG_ABORTING set in the adapter softc, * the chain will not restart itself. */ static int ciss_notify_abort(struct ciss_softc *sc) { struct ciss_request *cr; struct ciss_command *cc; struct ciss_notify_cdb *cnc; int error, command_status, scsi_status; debug_called(1); cr = NULL; error = 0; /* verify that there's an outstanding command */ if (!(sc->ciss_flags & CISS_FLAG_NOTIFY_OK)) goto out; /* get a command to issue the abort with */ if ((error = ciss_get_request(sc, &cr))) goto out; /* get a buffer for the result */ if ((cr->cr_data = malloc(CISS_NOTIFY_DATA_SIZE, CISS_MALLOC_CLASS, M_NOWAIT)) == NULL) { debug(0, "can't get notify event request buffer"); error = ENOMEM; goto out; } cr->cr_length = CISS_NOTIFY_DATA_SIZE; /* build the CDB */ cc = cr->cr_cc; cc->header.address.physical.mode = CISS_HDR_ADDRESS_MODE_PERIPHERAL; cc->header.address.physical.bus = 0; cc->header.address.physical.target = 0; cc->cdb.cdb_length = sizeof(*cnc); cc->cdb.type = CISS_CDB_TYPE_COMMAND; cc->cdb.attribute = CISS_CDB_ATTRIBUTE_SIMPLE; cc->cdb.direction = CISS_CDB_DIRECTION_READ; cc->cdb.timeout = 0; /* no timeout, we hope */ cnc = (struct ciss_notify_cdb *)&(cc->cdb.cdb[0]); bzero(cnc, sizeof(*cnc)); cnc->opcode = CISS_OPCODE_WRITE; cnc->command = CISS_COMMAND_ABORT_NOTIFY; cnc->length = htonl(CISS_NOTIFY_DATA_SIZE); ciss_print_request(cr); /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "Abort Notify Event command failed (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, &scsi_status); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: break; case CISS_CMD_STATUS_INVALID_COMMAND: /* * Some older adapters don't support the CISS version of this * command. Fall back to using the BMIC version. */ error = ciss_notify_abort_bmic(sc); if (error != 0) goto out; break; case CISS_CMD_STATUS_TARGET_STATUS: /* * This can happen if the adapter thinks there wasn't an outstanding * Notify Event command but we did. We clean up here. */ if (scsi_status == CISS_SCSI_STATUS_CHECK_CONDITION) { if (sc->ciss_periodic_notify != NULL) ciss_release_request(sc->ciss_periodic_notify); error = 0; goto out; } /* FALLTHROUGH */ default: ciss_printf(sc, "Abort Notify Event command failed (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } /* * Sleep waiting for the notifier command to complete. Note * that if it doesn't, we may end up in a bad situation, since * the adapter may deliver it later. Also note that the adapter * requires the Notify Event command to be cancelled in order to * maintain internal bookkeeping. */ while (sc->ciss_periodic_notify != NULL) { error = msleep(&sc->ciss_periodic_notify, &sc->ciss_mtx, PRIBIO, "cissNEA", hz * 5); if (error == EWOULDBLOCK) { ciss_printf(sc, "Notify Event command failed to abort, adapter may wedge.\n"); break; } } out: /* release the cancel request */ if (cr != NULL) { if (cr->cr_data != NULL) free(cr->cr_data, CISS_MALLOC_CLASS); ciss_release_request(cr); } if (error == 0) sc->ciss_flags &= ~CISS_FLAG_NOTIFY_OK; return(error); } /************************************************************************ * Abort the Notify Event chain using a BMIC command. */ static int ciss_notify_abort_bmic(struct ciss_softc *sc) { struct ciss_request *cr; int error, command_status; debug_called(1); cr = NULL; error = 0; /* verify that there's an outstanding command */ if (!(sc->ciss_flags & CISS_FLAG_NOTIFY_OK)) goto out; /* * Build a BMIC command to cancel the Notify on Event command. * * Note that we are sending a CISS opcode here. Odd. */ if ((error = ciss_get_bmic_request(sc, &cr, CISS_COMMAND_ABORT_NOTIFY, NULL, 0)) != 0) goto out; /* * Submit the request and wait for it to complete. */ if ((error = ciss_synch_request(cr, 60 * 1000)) != 0) { ciss_printf(sc, "error sending BMIC Cancel Notify on Event command (%d)\n", error); goto out; } /* * Check response. */ ciss_report_request(cr, &command_status, NULL); switch(command_status) { case CISS_CMD_STATUS_SUCCESS: break; default: ciss_printf(sc, "error cancelling Notify on Event (%s)\n", ciss_name_command_status(command_status)); error = EIO; goto out; } out: if (cr != NULL) ciss_release_request(cr); return(error); } /************************************************************************ * Handle rescanning all the logical volumes when a notify event * causes the drives to come online or offline. */ static void ciss_notify_rescan_logical(struct ciss_softc *sc) { struct ciss_lun_report *cll; struct ciss_ldrive *ld; int i, j, ndrives; /* * We must rescan all logical volumes to get the right logical * drive address. */ cll = ciss_report_luns(sc, CISS_OPCODE_REPORT_LOGICAL_LUNS, sc->ciss_cfg->max_logical_supported); if (cll == NULL) return; ndrives = (ntohl(cll->list_size) / sizeof(union ciss_device_address)); /* * Delete any of the drives which were destroyed by the * firmware. */ for (i = 0; i < sc->ciss_max_logical_bus; i++) { for (j = 0; j < sc->ciss_cfg->max_logical_supported; j++) { ld = &sc->ciss_logical[i][j]; if (ld->cl_update == 0) continue; if (ld->cl_status != CISS_LD_ONLINE) { ciss_cam_rescan_target(sc, i, j); ld->cl_update = 0; if (ld->cl_ldrive) free(ld->cl_ldrive, CISS_MALLOC_CLASS); if (ld->cl_lstatus) free(ld->cl_lstatus, CISS_MALLOC_CLASS); ld->cl_ldrive = NULL; ld->cl_lstatus = NULL; } } } /* * Scan for new drives. */ for (i = 0; i < ndrives; i++) { int bus, target; bus = CISS_LUN_TO_BUS(cll->lun[i].logical.lun); target = CISS_LUN_TO_TARGET(cll->lun[i].logical.lun); ld = &sc->ciss_logical[bus][target]; if (ld->cl_update == 0) continue; ld->cl_update = 0; ld->cl_address = cll->lun[i]; ld->cl_controller = &sc->ciss_controllers[bus]; if (ciss_identify_logical(sc, ld) == 0) { ciss_cam_rescan_target(sc, bus, target); } } free(cll, CISS_MALLOC_CLASS); } /************************************************************************ * Handle a notify event relating to the status of a logical drive. * * XXX need to be able to defer some of these to properly handle * calling the "ID Physical drive" command, unless the 'extended' * drive IDs are always in BIG_MAP format. */ static void ciss_notify_logical(struct ciss_softc *sc, struct ciss_notify *cn) { struct ciss_ldrive *ld; int bus, target; int rescan_ld; debug_called(2); bus = cn->device.physical.bus; target = cn->data.logical_status.logical_drive; ld = &sc->ciss_logical[bus][target]; switch (cn->subclass) { case CISS_NOTIFY_LOGICAL_STATUS: switch (cn->detail) { case 0: ciss_name_device(sc, bus, target); ciss_printf(sc, "logical drive %d (%s) changed status %s->%s, spare status 0x%b\n", cn->data.logical_status.logical_drive, ld->cl_name, ciss_name_ldrive_status(cn->data.logical_status.previous_state), ciss_name_ldrive_status(cn->data.logical_status.new_state), cn->data.logical_status.spare_state, "\20\1configured\2rebuilding\3failed\4in use\5available\n"); /* * Update our idea of the drive's status. */ ld->cl_status = ciss_decode_ldrive_status(cn->data.logical_status.new_state); if (ld->cl_lstatus != NULL) ld->cl_lstatus->status = cn->data.logical_status.new_state; /* * Have CAM rescan the drive if its status has changed. */ rescan_ld = (cn->data.logical_status.previous_state != cn->data.logical_status.new_state) ? 1 : 0; if (rescan_ld) { ld->cl_update = 1; ciss_notify_rescan_logical(sc); } break; case 1: /* logical drive has recognised new media, needs Accept Media Exchange */ ciss_name_device(sc, bus, target); ciss_printf(sc, "logical drive %d (%s) media exchanged, ready to go online\n", cn->data.logical_status.logical_drive, ld->cl_name); ciss_accept_media(sc, ld); ld->cl_update = 1; ld->cl_status = ciss_decode_ldrive_status(cn->data.logical_status.new_state); ciss_notify_rescan_logical(sc); break; case 2: case 3: ciss_printf(sc, "rebuild of logical drive %d (%s) failed due to %s error\n", cn->data.rebuild_aborted.logical_drive, ld->cl_name, (cn->detail == 2) ? "read" : "write"); break; } break; case CISS_NOTIFY_LOGICAL_ERROR: if (cn->detail == 0) { ciss_printf(sc, "FATAL I/O ERROR on logical drive %d (%s), SCSI port %d ID %d\n", cn->data.io_error.logical_drive, ld->cl_name, cn->data.io_error.failure_bus, cn->data.io_error.failure_drive); /* XXX should we take the drive down at this point, or will we be told? */ } break; case CISS_NOTIFY_LOGICAL_SURFACE: if (cn->detail == 0) ciss_printf(sc, "logical drive %d (%s) completed consistency initialisation\n", cn->data.consistency_completed.logical_drive, ld->cl_name); break; } } /************************************************************************ * Handle a notify event relating to the status of a physical drive. */ static void ciss_notify_physical(struct ciss_softc *sc, struct ciss_notify *cn) { } /************************************************************************ * Handle a notify event relating to the status of a physical drive. */ static void ciss_notify_hotplug(struct ciss_softc *sc, struct ciss_notify *cn) { struct ciss_lun_report *cll = NULL; int bus, target; switch (cn->subclass) { case CISS_NOTIFY_HOTPLUG_PHYSICAL: case CISS_NOTIFY_HOTPLUG_NONDISK: bus = CISS_BIG_MAP_BUS(sc, cn->data.drive.big_physical_drive_number); target = CISS_BIG_MAP_TARGET(sc, cn->data.drive.big_physical_drive_number); if (cn->detail == 0) { /* * Mark the device offline so that it'll start producing selection * timeouts to the upper layer. */ if ((bus >= 0) && (target >= 0)) sc->ciss_physical[bus][target].cp_online = 0; } else { /* * Rescan the physical lun list for new items */ cll = ciss_report_luns(sc, CISS_OPCODE_REPORT_PHYSICAL_LUNS, sc->ciss_cfg->max_physical_supported); if (cll == NULL) { ciss_printf(sc, "Warning, cannot get physical lun list\n"); break; } ciss_filter_physical(sc, cll); } break; default: ciss_printf(sc, "Unknown hotplug event %d\n", cn->subclass); return; } if (cll != NULL) free(cll, CISS_MALLOC_CLASS); } /************************************************************************ * Handle deferred processing of notify events. Notify events may need * sleep which is unsafe during an interrupt. */ static void ciss_notify_thread(void *arg) { struct ciss_softc *sc; struct ciss_request *cr; struct ciss_notify *cn; sc = (struct ciss_softc *)arg; mtx_lock(&sc->ciss_mtx); for (;;) { if (STAILQ_EMPTY(&sc->ciss_notify) != 0 && (sc->ciss_flags & CISS_FLAG_THREAD_SHUT) == 0) { msleep(&sc->ciss_notify, &sc->ciss_mtx, PUSER, "idle", 0); } if (sc->ciss_flags & CISS_FLAG_THREAD_SHUT) break; cr = ciss_dequeue_notify(sc); if (cr == NULL) panic("cr null"); cn = (struct ciss_notify *)cr->cr_data; switch (cn->class) { case CISS_NOTIFY_HOTPLUG: ciss_notify_hotplug(sc, cn); break; case CISS_NOTIFY_LOGICAL: ciss_notify_logical(sc, cn); break; case CISS_NOTIFY_PHYSICAL: ciss_notify_physical(sc, cn); break; } ciss_release_request(cr); } sc->ciss_notify_thread = NULL; wakeup(&sc->ciss_notify_thread); mtx_unlock(&sc->ciss_mtx); kproc_exit(0); } /************************************************************************ * Start the notification kernel thread. */ static void ciss_spawn_notify_thread(struct ciss_softc *sc) { if (kproc_create((void(*)(void *))ciss_notify_thread, sc, &sc->ciss_notify_thread, 0, 0, "ciss_notify%d", device_get_unit(sc->ciss_dev))) panic("Could not create notify thread\n"); } /************************************************************************ * Kill the notification kernel thread. */ static void ciss_kill_notify_thread(struct ciss_softc *sc) { if (sc->ciss_notify_thread == NULL) return; sc->ciss_flags |= CISS_FLAG_THREAD_SHUT; wakeup(&sc->ciss_notify); msleep(&sc->ciss_notify_thread, &sc->ciss_mtx, PUSER, "thtrm", 0); } /************************************************************************ * Print a request. */ static void ciss_print_request(struct ciss_request *cr) { struct ciss_softc *sc; struct ciss_command *cc; int i; sc = cr->cr_sc; cc = cr->cr_cc; ciss_printf(sc, "REQUEST @ %p\n", cr); ciss_printf(sc, " data %p/%d tag %d flags %b\n", cr->cr_data, cr->cr_length, cr->cr_tag, cr->cr_flags, "\20\1mapped\2sleep\3poll\4dataout\5datain\n"); ciss_printf(sc, " sg list/total %d/%d host tag 0x%x\n", cc->header.sg_in_list, cc->header.sg_total, cc->header.host_tag); switch(cc->header.address.mode.mode) { case CISS_HDR_ADDRESS_MODE_PERIPHERAL: case CISS_HDR_ADDRESS_MODE_MASK_PERIPHERAL: ciss_printf(sc, " physical bus %d target %d\n", cc->header.address.physical.bus, cc->header.address.physical.target); break; case CISS_HDR_ADDRESS_MODE_LOGICAL: ciss_printf(sc, " logical unit %d\n", cc->header.address.logical.lun); break; } ciss_printf(sc, " %s cdb length %d type %s attribute %s\n", (cc->cdb.direction == CISS_CDB_DIRECTION_NONE) ? "no-I/O" : (cc->cdb.direction == CISS_CDB_DIRECTION_READ) ? "READ" : (cc->cdb.direction == CISS_CDB_DIRECTION_WRITE) ? "WRITE" : "??", cc->cdb.cdb_length, (cc->cdb.type == CISS_CDB_TYPE_COMMAND) ? "command" : (cc->cdb.type == CISS_CDB_TYPE_MESSAGE) ? "message" : "??", (cc->cdb.attribute == CISS_CDB_ATTRIBUTE_UNTAGGED) ? "untagged" : (cc->cdb.attribute == CISS_CDB_ATTRIBUTE_SIMPLE) ? "simple" : (cc->cdb.attribute == CISS_CDB_ATTRIBUTE_HEAD_OF_QUEUE) ? "head-of-queue" : (cc->cdb.attribute == CISS_CDB_ATTRIBUTE_ORDERED) ? "ordered" : (cc->cdb.attribute == CISS_CDB_ATTRIBUTE_AUTO_CONTINGENT) ? "auto-contingent" : "??"); ciss_printf(sc, " %*D\n", cc->cdb.cdb_length, &cc->cdb.cdb[0], " "); if (cc->header.host_tag & CISS_HDR_HOST_TAG_ERROR) { /* XXX print error info */ } else { /* since we don't use chained s/g, don't support it here */ for (i = 0; i < cc->header.sg_in_list; i++) { if ((i % 4) == 0) ciss_printf(sc, " "); printf("0x%08x/%d ", (u_int32_t)cc->sg[i].address, cc->sg[i].length); if ((((i + 1) % 4) == 0) || (i == (cc->header.sg_in_list - 1))) printf("\n"); } } } /************************************************************************ * Print information about the status of a logical drive. */ static void ciss_print_ldrive(struct ciss_softc *sc, struct ciss_ldrive *ld) { int bus, target, i; if (ld->cl_lstatus == NULL) { printf("does not exist\n"); return; } /* print drive status */ switch(ld->cl_lstatus->status) { case CISS_LSTATUS_OK: printf("online\n"); break; case CISS_LSTATUS_INTERIM_RECOVERY: printf("in interim recovery mode\n"); break; case CISS_LSTATUS_READY_RECOVERY: printf("ready to begin recovery\n"); break; case CISS_LSTATUS_RECOVERING: bus = CISS_BIG_MAP_BUS(sc, ld->cl_lstatus->drive_rebuilding); target = CISS_BIG_MAP_BUS(sc, ld->cl_lstatus->drive_rebuilding); printf("being recovered, working on physical drive %d.%d, %u blocks remaining\n", bus, target, ld->cl_lstatus->blocks_to_recover); break; case CISS_LSTATUS_EXPANDING: printf("being expanded, %u blocks remaining\n", ld->cl_lstatus->blocks_to_recover); break; case CISS_LSTATUS_QUEUED_FOR_EXPANSION: printf("queued for expansion\n"); break; case CISS_LSTATUS_FAILED: printf("queued for expansion\n"); break; case CISS_LSTATUS_WRONG_PDRIVE: printf("wrong physical drive inserted\n"); break; case CISS_LSTATUS_MISSING_PDRIVE: printf("missing a needed physical drive\n"); break; case CISS_LSTATUS_BECOMING_READY: printf("becoming ready\n"); break; } /* print failed physical drives */ for (i = 0; i < CISS_BIG_MAP_ENTRIES / 8; i++) { bus = CISS_BIG_MAP_BUS(sc, ld->cl_lstatus->drive_failure_map[i]); target = CISS_BIG_MAP_TARGET(sc, ld->cl_lstatus->drive_failure_map[i]); if (bus == -1) continue; ciss_printf(sc, "physical drive %d:%d (%x) failed\n", bus, target, ld->cl_lstatus->drive_failure_map[i]); } } #ifdef CISS_DEBUG #include "opt_ddb.h" #ifdef DDB #include /************************************************************************ * Print information about the controller/driver. */ static void ciss_print_adapter(struct ciss_softc *sc) { int i, j; ciss_printf(sc, "ADAPTER:\n"); for (i = 0; i < CISSQ_COUNT; i++) { ciss_printf(sc, "%s %d/%d\n", i == 0 ? "free" : i == 1 ? "busy" : "complete", sc->ciss_qstat[i].q_length, sc->ciss_qstat[i].q_max); } ciss_printf(sc, "max_requests %d\n", sc->ciss_max_requests); ciss_printf(sc, "flags %b\n", sc->ciss_flags, "\20\1notify_ok\2control_open\3aborting\4running\21fake_synch\22bmic_abort\n"); for (i = 0; i < sc->ciss_max_logical_bus; i++) { for (j = 0; j < sc->ciss_cfg->max_logical_supported; j++) { ciss_printf(sc, "LOGICAL DRIVE %d: ", i); ciss_print_ldrive(sc, &sc->ciss_logical[i][j]); } } /* XXX Should physical drives be printed out here? */ for (i = 1; i < sc->ciss_max_requests; i++) ciss_print_request(sc->ciss_request + i); } /* DDB hook */ DB_COMMAND(ciss_prt, db_ciss_prt) { struct ciss_softc *sc; devclass_t dc; int maxciss, i; dc = devclass_find("ciss"); if ( dc == NULL ) { printf("%s: can't find devclass!\n", __func__); return; } maxciss = devclass_get_maxunit(dc); for (i = 0; i < maxciss; i++) { sc = devclass_get_softc(dc, i); ciss_print_adapter(sc); } } #endif #endif /************************************************************************ * Return a name for a logical drive status value. */ static const char * ciss_name_ldrive_status(int status) { switch (status) { case CISS_LSTATUS_OK: return("OK"); case CISS_LSTATUS_FAILED: return("failed"); case CISS_LSTATUS_NOT_CONFIGURED: return("not configured"); case CISS_LSTATUS_INTERIM_RECOVERY: return("interim recovery"); case CISS_LSTATUS_READY_RECOVERY: return("ready for recovery"); case CISS_LSTATUS_RECOVERING: return("recovering"); case CISS_LSTATUS_WRONG_PDRIVE: return("wrong physical drive inserted"); case CISS_LSTATUS_MISSING_PDRIVE: return("missing physical drive"); case CISS_LSTATUS_EXPANDING: return("expanding"); case CISS_LSTATUS_BECOMING_READY: return("becoming ready"); case CISS_LSTATUS_QUEUED_FOR_EXPANSION: return("queued for expansion"); } return("unknown status"); } /************************************************************************ * Return an online/offline/nonexistent value for a logical drive * status value. */ static int ciss_decode_ldrive_status(int status) { switch(status) { case CISS_LSTATUS_NOT_CONFIGURED: return(CISS_LD_NONEXISTENT); case CISS_LSTATUS_OK: case CISS_LSTATUS_INTERIM_RECOVERY: case CISS_LSTATUS_READY_RECOVERY: case CISS_LSTATUS_RECOVERING: case CISS_LSTATUS_EXPANDING: case CISS_LSTATUS_QUEUED_FOR_EXPANSION: return(CISS_LD_ONLINE); case CISS_LSTATUS_FAILED: case CISS_LSTATUS_WRONG_PDRIVE: case CISS_LSTATUS_MISSING_PDRIVE: case CISS_LSTATUS_BECOMING_READY: default: return(CISS_LD_OFFLINE); } } /************************************************************************ * Return a name for a logical drive's organisation. */ static const char * ciss_name_ldrive_org(int org) { switch(org) { case CISS_LDRIVE_RAID0: return("RAID 0"); case CISS_LDRIVE_RAID1: return("RAID 1(1+0)"); case CISS_LDRIVE_RAID4: return("RAID 4"); case CISS_LDRIVE_RAID5: return("RAID 5"); case CISS_LDRIVE_RAID51: return("RAID 5+1"); case CISS_LDRIVE_RAIDADG: return("RAID ADG"); } return("unkown"); } /************************************************************************ * Return a name for a command status value. */ static const char * ciss_name_command_status(int status) { switch(status) { case CISS_CMD_STATUS_SUCCESS: return("success"); case CISS_CMD_STATUS_TARGET_STATUS: return("target status"); case CISS_CMD_STATUS_DATA_UNDERRUN: return("data underrun"); case CISS_CMD_STATUS_DATA_OVERRUN: return("data overrun"); case CISS_CMD_STATUS_INVALID_COMMAND: return("invalid command"); case CISS_CMD_STATUS_PROTOCOL_ERROR: return("protocol error"); case CISS_CMD_STATUS_HARDWARE_ERROR: return("hardware error"); case CISS_CMD_STATUS_CONNECTION_LOST: return("connection lost"); case CISS_CMD_STATUS_ABORTED: return("aborted"); case CISS_CMD_STATUS_ABORT_FAILED: return("abort failed"); case CISS_CMD_STATUS_UNSOLICITED_ABORT: return("unsolicited abort"); case CISS_CMD_STATUS_TIMEOUT: return("timeout"); case CISS_CMD_STATUS_UNABORTABLE: return("unabortable"); } return("unknown status"); } /************************************************************************ * Handle an open on the control device. */ static int ciss_open(struct cdev *dev, int flags, int fmt, struct thread *p) { struct ciss_softc *sc; debug_called(1); sc = (struct ciss_softc *)dev->si_drv1; /* we might want to veto if someone already has us open */ mtx_lock(&sc->ciss_mtx); sc->ciss_flags |= CISS_FLAG_CONTROL_OPEN; mtx_unlock(&sc->ciss_mtx); return(0); } /************************************************************************ * Handle the last close on the control device. */ static int ciss_close(struct cdev *dev, int flags, int fmt, struct thread *p) { struct ciss_softc *sc; debug_called(1); sc = (struct ciss_softc *)dev->si_drv1; mtx_lock(&sc->ciss_mtx); sc->ciss_flags &= ~CISS_FLAG_CONTROL_OPEN; mtx_unlock(&sc->ciss_mtx); return (0); } /******************************************************************************** * Handle adapter-specific control operations. * * Note that the API here is compatible with the Linux driver, in order to * simplify the porting of Compaq's userland tools. */ static int ciss_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *p) { struct ciss_softc *sc; IOCTL_Command_struct *ioc = (IOCTL_Command_struct *)addr; #ifdef __amd64__ IOCTL_Command_struct32 *ioc32 = (IOCTL_Command_struct32 *)addr; IOCTL_Command_struct ioc_swab; #endif int error; debug_called(1); sc = (struct ciss_softc *)dev->si_drv1; error = 0; mtx_lock(&sc->ciss_mtx); switch(cmd) { case CCISS_GETQSTATS: { union ciss_statrequest *cr = (union ciss_statrequest *)addr; switch (cr->cs_item) { case CISSQ_FREE: case CISSQ_NOTIFY: bcopy(&sc->ciss_qstat[cr->cs_item], &cr->cs_qstat, sizeof(struct ciss_qstat)); break; default: error = ENOIOCTL; break; } break; } case CCISS_GETPCIINFO: { cciss_pci_info_struct *pis = (cciss_pci_info_struct *)addr; pis->bus = pci_get_bus(sc->ciss_dev); pis->dev_fn = pci_get_slot(sc->ciss_dev); pis->board_id = (pci_get_subvendor(sc->ciss_dev) << 16) | pci_get_subdevice(sc->ciss_dev); break; } case CCISS_GETINTINFO: { cciss_coalint_struct *cis = (cciss_coalint_struct *)addr; cis->delay = sc->ciss_cfg->interrupt_coalesce_delay; cis->count = sc->ciss_cfg->interrupt_coalesce_count; break; } case CCISS_SETINTINFO: { cciss_coalint_struct *cis = (cciss_coalint_struct *)addr; if ((cis->delay == 0) && (cis->count == 0)) { error = EINVAL; break; } /* * XXX apparently this is only safe if the controller is idle, * we should suspend it before doing this. */ sc->ciss_cfg->interrupt_coalesce_delay = cis->delay; sc->ciss_cfg->interrupt_coalesce_count = cis->count; if (ciss_update_config(sc)) error = EIO; /* XXX resume the controller here */ break; } case CCISS_GETNODENAME: bcopy(sc->ciss_cfg->server_name, (NodeName_type *)addr, sizeof(NodeName_type)); break; case CCISS_SETNODENAME: bcopy((NodeName_type *)addr, sc->ciss_cfg->server_name, sizeof(NodeName_type)); if (ciss_update_config(sc)) error = EIO; break; case CCISS_GETHEARTBEAT: *(Heartbeat_type *)addr = sc->ciss_cfg->heartbeat; break; case CCISS_GETBUSTYPES: *(BusTypes_type *)addr = sc->ciss_cfg->bus_types; break; case CCISS_GETFIRMVER: bcopy(sc->ciss_id->running_firmware_revision, (FirmwareVer_type *)addr, sizeof(FirmwareVer_type)); break; case CCISS_GETDRIVERVER: *(DriverVer_type *)addr = CISS_DRIVER_VERSION; break; case CCISS_REVALIDVOLS: /* * This is a bit ugly; to do it "right" we really need * to find any disks that have changed, kick CAM off them, * then rescan only these disks. It'd be nice if they * a) told us which disk(s) they were going to play with, * and b) which ones had arrived. 8( */ break; #ifdef __amd64__ case CCISS_PASSTHRU32: ioc_swab.LUN_info = ioc32->LUN_info; ioc_swab.Request = ioc32->Request; ioc_swab.error_info = ioc32->error_info; ioc_swab.buf_size = ioc32->buf_size; ioc_swab.buf = (u_int8_t *)(uintptr_t)ioc32->buf; ioc = &ioc_swab; /* FALLTHROUGH */ #endif case CCISS_PASSTHRU: error = ciss_user_command(sc, ioc); break; default: debug(0, "unknown ioctl 0x%lx", cmd); debug(1, "CCISS_GETPCIINFO: 0x%lx", CCISS_GETPCIINFO); debug(1, "CCISS_GETINTINFO: 0x%lx", CCISS_GETINTINFO); debug(1, "CCISS_SETINTINFO: 0x%lx", CCISS_SETINTINFO); debug(1, "CCISS_GETNODENAME: 0x%lx", CCISS_GETNODENAME); debug(1, "CCISS_SETNODENAME: 0x%lx", CCISS_SETNODENAME); debug(1, "CCISS_GETHEARTBEAT: 0x%lx", CCISS_GETHEARTBEAT); debug(1, "CCISS_GETBUSTYPES: 0x%lx", CCISS_GETBUSTYPES); debug(1, "CCISS_GETFIRMVER: 0x%lx", CCISS_GETFIRMVER); debug(1, "CCISS_GETDRIVERVER: 0x%lx", CCISS_GETDRIVERVER); debug(1, "CCISS_REVALIDVOLS: 0x%lx", CCISS_REVALIDVOLS); debug(1, "CCISS_PASSTHRU: 0x%lx", CCISS_PASSTHRU); error = ENOIOCTL; break; } mtx_unlock(&sc->ciss_mtx); return(error); } Index: head/sys/dev/ct/ct_isa.c =================================================================== --- head/sys/dev/ct/ct_isa.c (revision 280346) +++ head/sys/dev/ct/ct_isa.c (revision 280347) @@ -1,382 +1,382 @@ /* $NecBSD: ct_isa.c,v 1.6 1999/07/26 06:32:01 honda Exp $ */ #include __FBSDID("$FreeBSD$"); /* $NetBSD$ */ /*- * [NetBSD for NEC PC-98 series] * Copyright (c) 1995, 1996, 1997, 1998 * NetBSD/pc98 porting staff. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #define SCSIBUS_RESCAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BSHW_IOSZ 0x08 #define BSHW_IOBASE 0xcc0 #define BSHW_MEMSZ (PAGE_SIZE * 2) static int ct_isa_match(device_t); static int ct_isa_attach(device_t); static int ct_space_map(device_t, struct bshw *, struct resource **, struct resource **); static void ct_space_unmap(device_t, struct ct_softc *); static struct bshw *ct_find_hw(device_t); static void ct_dmamap(void *, bus_dma_segment_t *, int, int); static void ct_isa_bus_access_weight(struct ct_bus_access_handle *); static void ct_isa_dmasync_before(struct ct_softc *); static void ct_isa_dmasync_after(struct ct_softc *); struct ct_isa_softc { struct ct_softc sc_ct; struct bshw_softc sc_bshw; }; static struct isa_pnp_id ct_pnp_ids[] = { { 0x0100e7b1, "Logitec LHA-301" }, { 0x110154dc, "I-O DATA SC-98III" }, { 0x4120acb4, "MELCO IFC-NN" }, { 0, NULL } }; static device_method_t ct_isa_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ct_isa_match), DEVMETHOD(device_attach, ct_isa_attach), { 0, 0 } }; static driver_t ct_isa_driver = { "ct", ct_isa_methods, sizeof(struct ct_isa_softc), }; static devclass_t ct_devclass; DRIVER_MODULE(ct, isa, ct_isa_driver, ct_devclass, 0, 0); static int ct_isa_match(device_t dev) { struct bshw *hw; struct resource *port_res, *mem_res; struct ct_bus_access_handle ch; int rv; if (ISA_PNP_PROBE(device_get_parent(dev), dev, ct_pnp_ids) == ENXIO) return ENXIO; switch (isa_get_logicalid(dev)) { case 0x0100e7b1: /* LHA-301 */ case 0x110154dc: /* SC-98III */ case 0x4120acb4: /* IFC-NN */ /* XXX - force to SMIT mode */ device_set_flags(dev, device_get_flags(dev) | 0x40000); break; } if (isa_get_port(dev) == -1) bus_set_resource(dev, SYS_RES_IOPORT, 0, BSHW_IOBASE, BSHW_IOSZ); if ((hw = ct_find_hw(dev)) == NULL) return ENXIO; if (ct_space_map(dev, hw, &port_res, &mem_res) != 0) return ENXIO; bzero(&ch, sizeof(ch)); ch.ch_io = port_res; ch.ch_bus_weight = ct_isa_bus_access_weight; rv = ctprobesubr(&ch, 0, BSHW_DEFAULT_HOSTID, BSHW_DEFAULT_CHIPCLK, NULL); if (rv != 0) { struct bshw_softc bshw_tab; struct bshw_softc *bs = &bshw_tab; memset(bs, 0, sizeof(*bs)); bshw_read_settings(&ch, bs); bus_set_resource(dev, SYS_RES_IRQ, 0, bs->sc_irq, 1); bus_set_resource(dev, SYS_RES_DRQ, 0, bs->sc_drq, 1); } bus_release_resource(dev, SYS_RES_IOPORT, 0, port_res); if (mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, mem_res); if (rv != 0) return (BUS_PROBE_DEFAULT); return ENXIO; } static int ct_isa_attach(device_t dev) { struct ct_isa_softc *pct = device_get_softc(dev); struct ct_softc *ct = &pct->sc_ct; struct ct_bus_access_handle *chp = &ct->sc_ch; struct scsi_low_softc *slp = &ct->sc_sclow; struct bshw_softc *bs = &pct->sc_bshw; struct bshw *hw; int irq_rid, drq_rid, chiprev; u_int8_t *vaddr; bus_addr_t addr; hw = ct_find_hw(dev); if (ct_space_map(dev, hw, &ct->port_res, &ct->mem_res) != 0) { device_printf(dev, "bus io mem map failed\n"); return ENXIO; } chp->ch_io = ct->port_res; chp->ch_mem = ct->mem_res; chp->ch_bus_weight = ct_isa_bus_access_weight; irq_rid = 0; ct->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irq_rid, RF_ACTIVE); drq_rid = 0; ct->drq_res = bus_alloc_resource_any(dev, SYS_RES_DRQ, &drq_rid, RF_ACTIVE); if (ct->irq_res == NULL || ct->drq_res == NULL) { ct_space_unmap(dev, ct); return ENXIO; } if (ctprobesubr(chp, 0, BSHW_DEFAULT_HOSTID, BSHW_DEFAULT_CHIPCLK, &chiprev) == 0) { device_printf(dev, "hardware missing\n"); ct_space_unmap(dev, ct); return ENXIO; } /* setup DMA map */ if (bus_dma_tag_create(NULL, 1, 0, BUS_SPACE_MAXADDR_24BIT, BUS_SPACE_MAXADDR, - NULL, NULL, MAXBSIZE, 1, + NULL, NULL, DFLTPHYS, 1, BUS_SPACE_MAXSIZE_32BIT, BUS_DMA_ALLOCNOW, NULL, NULL, &ct->sc_dmat) != 0) { device_printf(dev, "can't set up ISA DMA map\n"); ct_space_unmap(dev, ct); return ENXIO; } if (bus_dmamem_alloc(ct->sc_dmat, (void **)&vaddr, BUS_DMA_NOWAIT, &ct->sc_dmamapt) != 0) { device_printf(dev, "can't set up ISA DMA map\n"); ct_space_unmap(dev, ct); return ENXIO; } - bus_dmamap_load(ct->sc_dmat, ct->sc_dmamapt, vaddr, MAXBSIZE, + bus_dmamap_load(ct->sc_dmat, ct->sc_dmamapt, vaddr, DFLTPHYS, ct_dmamap, &addr, BUS_DMA_NOWAIT); /* setup machdep softc */ bs->sc_hw = hw; bs->sc_io_control = 0; bs->sc_bounce_phys = (u_int8_t *)addr; bs->sc_bounce_addr = vaddr; - bs->sc_bounce_size = MAXBSIZE; + bs->sc_bounce_size = DFLTPHYS; bs->sc_minphys = (1 << 24); bs->sc_dmasync_before = ct_isa_dmasync_before; bs->sc_dmasync_after = ct_isa_dmasync_after; bshw_read_settings(chp, bs); /* setup ct driver softc */ ct->ct_hw = bs; ct->ct_dma_xfer_start = bshw_dma_xfer_start; ct->ct_pio_xfer_start = bshw_smit_xfer_start; ct->ct_dma_xfer_stop = bshw_dma_xfer_stop; ct->ct_pio_xfer_stop = bshw_smit_xfer_stop; ct->ct_bus_reset = bshw_bus_reset; ct->ct_synch_setup = bshw_synch_setup; ct->sc_xmode = CT_XMODE_DMA; if (chp->ch_mem != NULL) ct->sc_xmode |= CT_XMODE_PIO; ct->sc_chiprev = chiprev; switch (chiprev) { case CT_WD33C93: /* s = "WD33C93"; */ ct->sc_chipclk = 8; break; case CT_WD33C93_A: if (DVCFG_MAJOR(device_get_flags(dev)) > 0) { /* s = "AM33C93_A"; */ ct->sc_chipclk = 20; ct->sc_chiprev = CT_AM33C93_A; } else { /* s = "WD33C93_A"; */ ct->sc_chipclk = 10; } break; case CT_AM33C93_A: /* s = "AM33C93_A"; */ ct->sc_chipclk = 20; break; default: case CT_WD33C93_B: /* s = "WD33C93_B"; */ ct->sc_chipclk = 20; break; } #if 0 printf("%s: chiprev %s chipclk %d MHz\n", slp->sl_dev.dv_xname, s, ct->sc_chipclk); #endif slp->sl_dev = dev; slp->sl_hostid = bs->sc_hostid; slp->sl_cfgflags = device_get_flags(dev); mtx_init(&slp->sl_lock, "ct", NULL, MTX_DEF); ctattachsubr(ct); if (bus_setup_intr(dev, ct->irq_res, INTR_TYPE_CAM | INTR_MPSAFE, NULL, ctintr, ct, &ct->sc_ih)) { ct_space_unmap(dev, ct); return ENXIO; } return 0; } static struct bshw * ct_find_hw(device_t dev) { return DVCFG_HW(&bshw_hwsel, DVCFG_MAJOR(device_get_flags(dev))); } static int ct_space_map(device_t dev, struct bshw *hw, struct resource **iohp, struct resource **memhp) { int port_rid, mem_rid; *memhp = NULL; port_rid = 0; *iohp = bus_alloc_resource(dev, SYS_RES_IOPORT, &port_rid, 0ul, ~0ul, BSHW_IOSZ, RF_ACTIVE); if (*iohp == NULL) return ENXIO; if ((hw->hw_flags & BSHW_SMFIFO) == 0 || isa_get_maddr(dev) == -1) return 0; mem_rid = 0; *memhp = bus_alloc_resource(dev, SYS_RES_MEMORY, &mem_rid, 0ul, ~0ul, BSHW_MEMSZ, RF_ACTIVE); if (*memhp == NULL) { bus_release_resource(dev, SYS_RES_IOPORT, port_rid, *iohp); return ENXIO; } return 0; } static void ct_space_unmap(device_t dev, struct ct_softc *ct) { if (ct->port_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, 0, ct->port_res); if (ct->mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, ct->mem_res); if (ct->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, ct->irq_res); if (ct->drq_res != NULL) bus_release_resource(dev, SYS_RES_DRQ, 0, ct->drq_res); } static void ct_dmamap(void *arg, bus_dma_segment_t *seg, int nseg, int error) { bus_addr_t *addr = (bus_addr_t *)arg; *addr = seg->ds_addr; } static void ct_isa_bus_access_weight(struct ct_bus_access_handle *chp) { outb(0x5f, 0); } static void ct_isa_dmasync_before(struct ct_softc *ct) { if (need_pre_dma_flush) wbinvd(); } static void ct_isa_dmasync_after(struct ct_softc *ct) { if (need_post_dma_flush) invd(); } Index: head/sys/dev/dpt/dpt_scsi.c =================================================================== --- head/sys/dev/dpt/dpt_scsi.c (revision 280346) +++ head/sys/dev/dpt/dpt_scsi.c (revision 280347) @@ -1,2523 +1,2523 @@ /*- * Copyright (c) 1997 by Simon Shapiro * All Rights Reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * dpt_scsi.c: SCSI dependant code for the DPT driver * * credits: Assisted by Mike Neuffer in the early low level DPT code * Thanx to Mark Salyzyn of DPT for his assistance. * Special thanx to Justin Gibbs for invaluable help in * making this driver look and work like a FreeBSD component. * Last but not least, many thanx to UCB and the FreeBSD * team for creating and maintaining such a wonderful O/S. * * TODO: * Add ISA probe code. * * Add driver-level RAID-0. This will allow interoperability with * NiceTry, M$-Doze, Win-Dog, Slowlaris, etc., in recognizing RAID * arrays that span controllers (Wow!). */ #define _DPT_C_ #include "opt_dpt.h" #include "opt_eisa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* dpt_isa.c, dpt_eisa.c, and dpt_pci.c need this in a central place */ devclass_t dpt_devclass; #define microtime_now dpt_time_now() #define dpt_inl(dpt, port) \ bus_read_4((dpt)->io_res, (dpt)->io_offset + port) #define dpt_inb(dpt, port) \ bus_read_1((dpt)->io_res, (dpt)->io_offset + port) #define dpt_outl(dpt, port, value) \ bus_write_4((dpt)->io_res, (dpt)->io_offset + port, value) #define dpt_outb(dpt, port, value) \ bus_write_1((dpt)->io_res, (dpt)->io_offset + port, value) /* * These will have to be setup by parameters passed at boot/load time. For * perfromance reasons, we make them constants for the time being. */ #define dpt_min_segs DPT_MAX_SEGS #define dpt_max_segs DPT_MAX_SEGS /* Definitions for our use of the SIM private CCB area */ #define ccb_dccb_ptr spriv_ptr0 #define ccb_dpt_ptr spriv_ptr1 /* ================= Private Inline Function declarations ===================*/ static __inline int dpt_just_reset(dpt_softc_t * dpt); static __inline int dpt_raid_busy(dpt_softc_t * dpt); #ifdef DEV_EISA static __inline int dpt_pio_wait (u_int32_t, u_int, u_int, u_int); #endif static __inline int dpt_wait(dpt_softc_t *dpt, u_int bits, u_int state); static __inline struct dpt_ccb* dptgetccb(struct dpt_softc *dpt); static __inline void dptfreeccb(struct dpt_softc *dpt, struct dpt_ccb *dccb); static __inline bus_addr_t dptccbvtop(struct dpt_softc *dpt, struct dpt_ccb *dccb); static __inline int dpt_send_immediate(dpt_softc_t *dpt, eata_ccb_t *cmd_block, u_int32_t cmd_busaddr, u_int retries, u_int ifc, u_int code, u_int code2); /* ==================== Private Function declarations =======================*/ static void dptmapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error); static struct sg_map_node* dptallocsgmap(struct dpt_softc *dpt); static int dptallocccbs(dpt_softc_t *dpt); static int dpt_get_conf(dpt_softc_t *dpt, dpt_ccb_t *dccb, u_int32_t dccb_busaddr, u_int size, u_int page, u_int target, int extent); static void dpt_detect_cache(dpt_softc_t *dpt, dpt_ccb_t *dccb, u_int32_t dccb_busaddr, u_int8_t *buff); static void dpt_poll(struct cam_sim *sim); static void dpt_intr_locked(dpt_softc_t *dpt); static void dptexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error); static void dpt_action(struct cam_sim *sim, union ccb *ccb); static int dpt_send_eata_command(dpt_softc_t *dpt, eata_ccb_t *cmd, u_int32_t cmd_busaddr, u_int command, u_int retries, u_int ifc, u_int code, u_int code2); static void dptprocesserror(dpt_softc_t *dpt, dpt_ccb_t *dccb, union ccb *ccb, u_int hba_stat, u_int scsi_stat, u_int32_t resid); static void dpttimeout(void *arg); static void dptshutdown(void *arg, int howto); /* ================= Private Inline Function definitions ====================*/ static __inline int dpt_just_reset(dpt_softc_t * dpt) { if ((dpt_inb(dpt, 2) == 'D') && (dpt_inb(dpt, 3) == 'P') && (dpt_inb(dpt, 4) == 'T') && (dpt_inb(dpt, 5) == 'H')) return (1); else return (0); } static __inline int dpt_raid_busy(dpt_softc_t * dpt) { if ((dpt_inb(dpt, 0) == 'D') && (dpt_inb(dpt, 1) == 'P') && (dpt_inb(dpt, 2) == 'T')) return (1); else return (0); } #ifdef DEV_EISA static __inline int dpt_pio_wait (u_int32_t base, u_int reg, u_int bits, u_int state) { int i; u_int c; for (i = 0; i < 20000; i++) { /* wait 20ms for not busy */ c = inb(base + reg) & bits; if (!(c == state)) return (0); else DELAY(50); } return (-1); } #endif static __inline int dpt_wait(dpt_softc_t *dpt, u_int bits, u_int state) { int i; u_int c; for (i = 0; i < 20000; i++) { /* wait 20ms for not busy */ c = dpt_inb(dpt, HA_RSTATUS) & bits; if (c == state) return (0); else DELAY(50); } return (-1); } static __inline struct dpt_ccb* dptgetccb(struct dpt_softc *dpt) { struct dpt_ccb* dccb; if (!dumping) mtx_assert(&dpt->lock, MA_OWNED); if ((dccb = SLIST_FIRST(&dpt->free_dccb_list)) != NULL) { SLIST_REMOVE_HEAD(&dpt->free_dccb_list, links); dpt->free_dccbs--; } else if (dpt->total_dccbs < dpt->max_dccbs) { dptallocccbs(dpt); dccb = SLIST_FIRST(&dpt->free_dccb_list); if (dccb == NULL) device_printf(dpt->dev, "Can't malloc DCCB\n"); else { SLIST_REMOVE_HEAD(&dpt->free_dccb_list, links); dpt->free_dccbs--; } } return (dccb); } static __inline void dptfreeccb(struct dpt_softc *dpt, struct dpt_ccb *dccb) { if (!dumping) mtx_assert(&dpt->lock, MA_OWNED); if ((dccb->state & DCCB_ACTIVE) != 0) LIST_REMOVE(&dccb->ccb->ccb_h, sim_links.le); if ((dccb->state & DCCB_RELEASE_SIMQ) != 0) dccb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; else if (dpt->resource_shortage != 0 && (dccb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) { dccb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ; dpt->resource_shortage = FALSE; } dccb->state = DCCB_FREE; SLIST_INSERT_HEAD(&dpt->free_dccb_list, dccb, links); ++dpt->free_dccbs; } static __inline bus_addr_t dptccbvtop(struct dpt_softc *dpt, struct dpt_ccb *dccb) { return (dpt->dpt_ccb_busbase + (u_int32_t)((caddr_t)dccb - (caddr_t)dpt->dpt_dccbs)); } static __inline struct dpt_ccb * dptccbptov(struct dpt_softc *dpt, bus_addr_t busaddr) { return (dpt->dpt_dccbs + ((struct dpt_ccb *)busaddr - (struct dpt_ccb *)dpt->dpt_ccb_busbase)); } /* * Send a command for immediate execution by the DPT * See above function for IMPORTANT notes. */ static __inline int dpt_send_immediate(dpt_softc_t *dpt, eata_ccb_t *cmd_block, u_int32_t cmd_busaddr, u_int retries, u_int ifc, u_int code, u_int code2) { return (dpt_send_eata_command(dpt, cmd_block, cmd_busaddr, EATA_CMD_IMMEDIATE, retries, ifc, code, code2)); } /* ===================== Private Function definitions =======================*/ static void dptmapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *busaddrp; busaddrp = (bus_addr_t *)arg; *busaddrp = segs->ds_addr; } static struct sg_map_node * dptallocsgmap(struct dpt_softc *dpt) { struct sg_map_node *sg_map; sg_map = malloc(sizeof(*sg_map), M_DEVBUF, M_NOWAIT); if (sg_map == NULL) return (NULL); /* Allocate S/G space for the next batch of CCBS */ if (bus_dmamem_alloc(dpt->sg_dmat, (void **)&sg_map->sg_vaddr, BUS_DMA_NOWAIT, &sg_map->sg_dmamap) != 0) { free(sg_map, M_DEVBUF); return (NULL); } (void)bus_dmamap_load(dpt->sg_dmat, sg_map->sg_dmamap, sg_map->sg_vaddr, PAGE_SIZE, dptmapmem, &sg_map->sg_physaddr, /*flags*/0); SLIST_INSERT_HEAD(&dpt->sg_maps, sg_map, links); return (sg_map); } /* * Allocate another chunk of CCB's. Return count of entries added. */ static int dptallocccbs(dpt_softc_t *dpt) { struct dpt_ccb *next_ccb; struct sg_map_node *sg_map; bus_addr_t physaddr; dpt_sg_t *segs; int newcount; int i; if (!dumping) mtx_assert(&dpt->lock, MA_OWNED); next_ccb = &dpt->dpt_dccbs[dpt->total_dccbs]; if (next_ccb == dpt->dpt_dccbs) { /* * First time through. Re-use the S/G * space we allocated for initialization * CCBS. */ sg_map = SLIST_FIRST(&dpt->sg_maps); } else { sg_map = dptallocsgmap(dpt); } if (sg_map == NULL) return (0); segs = sg_map->sg_vaddr; physaddr = sg_map->sg_physaddr; newcount = (PAGE_SIZE / (dpt->sgsize * sizeof(dpt_sg_t))); for (i = 0; dpt->total_dccbs < dpt->max_dccbs && i < newcount; i++) { int error; error = bus_dmamap_create(dpt->buffer_dmat, /*flags*/0, &next_ccb->dmamap); if (error != 0) break; callout_init_mtx(&next_ccb->timer, &dpt->lock, 0); next_ccb->sg_list = segs; next_ccb->sg_busaddr = htonl(physaddr); next_ccb->eata_ccb.cp_dataDMA = htonl(physaddr); next_ccb->eata_ccb.cp_statDMA = htonl(dpt->sp_physaddr); next_ccb->eata_ccb.cp_reqDMA = htonl(dptccbvtop(dpt, next_ccb) + offsetof(struct dpt_ccb, sense_data)); next_ccb->eata_ccb.cp_busaddr = dpt->dpt_ccb_busend; next_ccb->state = DCCB_FREE; next_ccb->tag = dpt->total_dccbs; SLIST_INSERT_HEAD(&dpt->free_dccb_list, next_ccb, links); segs += dpt->sgsize; physaddr += (dpt->sgsize * sizeof(dpt_sg_t)); dpt->dpt_ccb_busend += sizeof(*next_ccb); next_ccb++; dpt->total_dccbs++; } return (i); } #ifdef DEV_EISA dpt_conf_t * dpt_pio_get_conf (u_int32_t base) { static dpt_conf_t * conf; u_int16_t * p; int i; /* * Allocate a dpt_conf_t */ if (!conf) { conf = (dpt_conf_t *)malloc(sizeof(dpt_conf_t), M_DEVBUF, M_NOWAIT | M_ZERO); } /* * If we didn't get one then we probably won't ever get one. */ if (!conf) { printf("dpt: unable to allocate dpt_conf_t\n"); return (NULL); } /* * Reset the controller. */ outb((base + HA_WCOMMAND), EATA_CMD_RESET); /* * Wait for the controller to become ready. * For some reason there can be -no- delays after calling reset * before we wait on ready status. */ if (dpt_pio_wait(base, HA_RSTATUS, HA_SBUSY, 0)) { printf("dpt: timeout waiting for controller to become ready\n"); return (NULL); } if (dpt_pio_wait(base, HA_RAUXSTAT, HA_ABUSY, 0)) { printf("dpt: timetout waiting for adapter ready.\n"); return (NULL); } /* * Send the PIO_READ_CONFIG command. */ outb((base + HA_WCOMMAND), EATA_CMD_PIO_READ_CONFIG); /* * Read the data into the struct. */ p = (u_int16_t *)conf; for (i = 0; i < (sizeof(dpt_conf_t) / 2); i++) { if (dpt_pio_wait(base, HA_RSTATUS, HA_SDRQ, 0)) { if (bootverbose) printf("dpt: timeout in data read.\n"); return (NULL); } (*p) = inw(base + HA_RDATA); p++; } if (inb(base + HA_RSTATUS) & HA_SERROR) { if (bootverbose) printf("dpt: error reading configuration data.\n"); return (NULL); } #define BE_EATA_SIGNATURE 0x45415441 #define LE_EATA_SIGNATURE 0x41544145 /* * Test to see if we have a valid card. */ if ((conf->signature == BE_EATA_SIGNATURE) || (conf->signature == LE_EATA_SIGNATURE)) { while (inb(base + HA_RSTATUS) & HA_SDRQ) { inw(base + HA_RDATA); } return (conf); } return (NULL); } #endif /* * Read a configuration page into the supplied dpt_cont_t buffer. */ static int dpt_get_conf(dpt_softc_t *dpt, dpt_ccb_t *dccb, u_int32_t dccb_busaddr, u_int size, u_int page, u_int target, int extent) { eata_ccb_t *cp; u_int8_t status; int ndx; int result; mtx_assert(&dpt->lock, MA_OWNED); cp = &dccb->eata_ccb; bzero((void *)(uintptr_t)(volatile void *)dpt->sp, sizeof(*dpt->sp)); cp->Interpret = 1; cp->DataIn = 1; cp->Auto_Req_Sen = 1; cp->reqlen = sizeof(struct scsi_sense_data); cp->cp_id = target; cp->cp_LUN = 0; /* In the EATA packet */ cp->cp_lun = 0; /* In the SCSI command */ cp->cp_scsi_cmd = INQUIRY; cp->cp_len = size; cp->cp_extent = extent; cp->cp_page = page; cp->cp_channel = 0; /* DNC, Interpret mode is set */ cp->cp_identify = 1; cp->cp_datalen = htonl(size); /* * This could be a simple for loop, but we suspected the compiler To * have optimized it a bit too much. Wait for the controller to * become ready */ while (((status = dpt_inb(dpt, HA_RSTATUS)) != (HA_SREADY | HA_SSC) && (status != (HA_SREADY | HA_SSC | HA_SERROR)) && (status != (HA_SDRDY | HA_SERROR | HA_SDRQ))) || (dpt_wait(dpt, HA_SBUSY, 0))) { /* * RAID Drives still Spinning up? (This should only occur if * the DPT controller is in a NON PC (PCI?) platform). */ if (dpt_raid_busy(dpt)) { device_printf(dpt->dev, "WARNING: Get_conf() RSUS failed.\n"); return (0); } } DptStat_Reset_BUSY(dpt->sp); /* * XXXX We might want to do something more clever than aborting at * this point, like resetting (rebooting) the controller and trying * again. */ if ((result = dpt_send_eata_command(dpt, cp, dccb_busaddr, EATA_CMD_DMA_SEND_CP, 10000, 0, 0, 0)) != 0) { device_printf(dpt->dev, "WARNING: Get_conf() failed (%d) to send " "EATA_CMD_DMA_READ_CONFIG\n", result); return (0); } /* Wait for two seconds for a response. This can be slow */ for (ndx = 0; (ndx < 20000) && !((status = dpt_inb(dpt, HA_RAUXSTAT)) & HA_AIRQ); ndx++) { DELAY(50); } /* Grab the status and clear interrupts */ status = dpt_inb(dpt, HA_RSTATUS); /* * Check the status carefully. Return only if the * command was successful. */ if (((status & HA_SERROR) == 0) && (dpt->sp->hba_stat == 0) && (dpt->sp->scsi_stat == 0) && (dpt->sp->residue_len == 0)) return (0); if (dpt->sp->scsi_stat == SCSI_STATUS_CHECK_COND) return (0); return (1); } /* Detect Cache parameters and size */ static void dpt_detect_cache(dpt_softc_t *dpt, dpt_ccb_t *dccb, u_int32_t dccb_busaddr, u_int8_t *buff) { eata_ccb_t *cp; u_int8_t *param; int bytes; int result; int ndx; u_int8_t status; mtx_assert(&dpt->lock, MA_OWNED); /* * Default setting, for best perfromance.. * This is what virtually all cards default to.. */ dpt->cache_type = DPT_CACHE_WRITEBACK; dpt->cache_size = 0; cp = &dccb->eata_ccb; bzero((void *)(uintptr_t)(volatile void *)dpt->sp, sizeof(dpt->sp)); bzero(buff, 512); /* Setup the command structure */ cp->Interpret = 1; cp->DataIn = 1; cp->Auto_Req_Sen = 1; cp->reqlen = sizeof(struct scsi_sense_data); cp->cp_id = 0; /* who cares? The HBA will interpret.. */ cp->cp_LUN = 0; /* In the EATA packet */ cp->cp_lun = 0; /* In the SCSI command */ cp->cp_channel = 0; cp->cp_scsi_cmd = EATA_CMD_DMA_SEND_CP; cp->cp_len = 56; cp->cp_extent = 0; cp->cp_page = 0; cp->cp_identify = 1; cp->cp_dispri = 1; /* * Build the EATA Command Packet structure * for a Log Sense Command. */ cp->cp_cdb[0] = 0x4d; cp->cp_cdb[1] = 0x0; cp->cp_cdb[2] = 0x40 | 0x33; cp->cp_cdb[7] = 1; cp->cp_datalen = htonl(512); result = dpt_send_eata_command(dpt, cp, dccb_busaddr, EATA_CMD_DMA_SEND_CP, 10000, 0, 0, 0); if (result != 0) { device_printf(dpt->dev, "WARNING: detect_cache() failed (%d) to send " "EATA_CMD_DMA_SEND_CP\n", result); return; } /* Wait for two seconds for a response. This can be slow... */ for (ndx = 0; (ndx < 20000) && !((status = dpt_inb(dpt, HA_RAUXSTAT)) & HA_AIRQ); ndx++) { DELAY(50); } /* Grab the status and clear interrupts */ status = dpt_inb(dpt, HA_RSTATUS); /* * Sanity check */ if (buff[0] != 0x33) { return; } bytes = DPT_HCP_LENGTH(buff); param = DPT_HCP_FIRST(buff); if (DPT_HCP_CODE(param) != 1) { /* * DPT Log Page layout error */ device_printf(dpt->dev, "NOTICE: Log Page (1) layout error\n"); return; } if (!(param[4] & 0x4)) { dpt->cache_type = DPT_NO_CACHE; return; } while (DPT_HCP_CODE(param) != 6) { param = DPT_HCP_NEXT(param); if ((param < buff) || (param >= &buff[bytes])) { return; } } if (param[4] & 0x2) { /* * Cache disabled */ dpt->cache_type = DPT_NO_CACHE; return; } if (param[4] & 0x4) { dpt->cache_type = DPT_CACHE_WRITETHROUGH; } /* XXX This isn't correct. This log parameter only has two bytes.... */ #if 0 dpt->cache_size = param[5] | (param[6] << 8) | (param[7] << 16) | (param[8] << 24); #endif } static void dpt_poll(struct cam_sim *sim) { dpt_intr_locked(cam_sim_softc(sim)); } static void dptexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct dpt_ccb *dccb; union ccb *ccb; struct dpt_softc *dpt; dccb = (struct dpt_ccb *)arg; ccb = dccb->ccb; dpt = (struct dpt_softc *)ccb->ccb_h.ccb_dpt_ptr; if (!dumping) mtx_assert(&dpt->lock, MA_OWNED); if (error != 0) { if (error != EFBIG) device_printf(dpt->dev, "Unexepected error 0x%x returned from " "bus_dmamap_load\n", error); if (ccb->ccb_h.status == CAM_REQ_INPROG) { xpt_freeze_devq(ccb->ccb_h.path, /*count*/1); ccb->ccb_h.status = CAM_REQ_TOO_BIG|CAM_DEV_QFRZN; } dptfreeccb(dpt, dccb); xpt_done(ccb); return; } if (nseg != 0) { dpt_sg_t *sg; bus_dma_segment_t *end_seg; bus_dmasync_op_t op; end_seg = dm_segs + nseg; /* Copy the segments into our SG list */ sg = dccb->sg_list; while (dm_segs < end_seg) { sg->seg_len = htonl(dm_segs->ds_len); sg->seg_addr = htonl(dm_segs->ds_addr); sg++; dm_segs++; } if (nseg > 1) { dccb->eata_ccb.scatter = 1; dccb->eata_ccb.cp_dataDMA = dccb->sg_busaddr; dccb->eata_ccb.cp_datalen = htonl(nseg * sizeof(dpt_sg_t)); } else { dccb->eata_ccb.cp_dataDMA = dccb->sg_list[0].seg_addr; dccb->eata_ccb.cp_datalen = dccb->sg_list[0].seg_len; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_PREREAD; else op = BUS_DMASYNC_PREWRITE; bus_dmamap_sync(dpt->buffer_dmat, dccb->dmamap, op); } else { dccb->eata_ccb.cp_dataDMA = 0; dccb->eata_ccb.cp_datalen = 0; } /* * Last time we need to check if this CCB needs to * be aborted. */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(dpt->buffer_dmat, dccb->dmamap); dptfreeccb(dpt, dccb); xpt_done(ccb); return; } dccb->state |= DCCB_ACTIVE; ccb->ccb_h.status |= CAM_SIM_QUEUED; LIST_INSERT_HEAD(&dpt->pending_ccb_list, &ccb->ccb_h, sim_links.le); callout_reset_sbt(&dccb->timer, SBT_1MS * ccb->ccb_h.timeout, 0, dpttimeout, dccb, 0); if (dpt_send_eata_command(dpt, &dccb->eata_ccb, dccb->eata_ccb.cp_busaddr, EATA_CMD_DMA_SEND_CP, 0, 0, 0, 0) != 0) { ccb->ccb_h.status = CAM_NO_HBA; /* HBA dead or just busy?? */ if (nseg != 0) bus_dmamap_unload(dpt->buffer_dmat, dccb->dmamap); dptfreeccb(dpt, dccb); xpt_done(ccb); } } static void dpt_action(struct cam_sim *sim, union ccb *ccb) { struct dpt_softc *dpt; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("dpt_action\n")); dpt = (struct dpt_softc *)cam_sim_softc(sim); mtx_assert(&dpt->lock, MA_OWNED); if ((dpt->state & DPT_HA_SHUTDOWN_ACTIVE) != 0) { xpt_print_path(ccb->ccb_h.path); printf("controller is shutdown. Aborting CCB.\n"); ccb->ccb_h.status = CAM_NO_HBA; xpt_done(ccb); return; } switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_SCSI_IO: /* Execute the requested I/O operation */ { struct ccb_scsiio *csio; struct ccb_hdr *ccbh; struct dpt_ccb *dccb; struct eata_ccb *eccb; csio = &ccb->csio; ccbh = &ccb->ccb_h; /* Max CDB length is 12 bytes */ if (csio->cdb_len > 12) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); return; } if ((dccb = dptgetccb(dpt)) == NULL) { dpt->resource_shortage = 1; xpt_freeze_simq(sim, /*count*/1); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); return; } eccb = &dccb->eata_ccb; /* Link dccb and ccb so we can find one from the other */ dccb->ccb = ccb; ccb->ccb_h.ccb_dccb_ptr = dccb; ccb->ccb_h.ccb_dpt_ptr = dpt; /* * Explicitly set all flags so that the compiler can * be smart about setting them. */ eccb->SCSI_Reset = 0; eccb->HBA_Init = 0; eccb->Auto_Req_Sen = (ccb->ccb_h.flags & CAM_DIS_AUTOSENSE) ? 0 : 1; eccb->scatter = 0; eccb->Quick = 0; eccb->Interpret = ccb->ccb_h.target_id == dpt->hostid[cam_sim_bus(sim)] ? 1 : 0; eccb->DataOut = (ccb->ccb_h.flags & CAM_DIR_OUT) ? 1 : 0; eccb->DataIn = (ccb->ccb_h.flags & CAM_DIR_IN) ? 1 : 0; eccb->reqlen = csio->sense_len; eccb->cp_id = ccb->ccb_h.target_id; eccb->cp_channel = cam_sim_bus(sim); eccb->cp_LUN = ccb->ccb_h.target_lun; eccb->cp_luntar = 0; eccb->cp_dispri = (ccb->ccb_h.flags & CAM_DIS_DISCONNECT) ? 0 : 1; eccb->cp_identify = 1; if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0 && csio->tag_action != CAM_TAG_ACTION_NONE) { eccb->cp_msg[0] = csio->tag_action; eccb->cp_msg[1] = dccb->tag; } else { eccb->cp_msg[0] = 0; eccb->cp_msg[1] = 0; } eccb->cp_msg[2] = 0; if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((ccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { bcopy(csio->cdb_io.cdb_ptr, eccb->cp_cdb, csio->cdb_len); } else { /* I guess I could map it in... */ ccb->ccb_h.status = CAM_REQ_INVALID; dptfreeccb(dpt, dccb); xpt_done(ccb); return; } } else { bcopy(csio->cdb_io.cdb_bytes, eccb->cp_cdb, csio->cdb_len); } /* * If we have any data to send with this command, * map it into bus space. */ /* Only use S/G if there is a transfer */ if ((ccbh->flags & CAM_DIR_MASK) != CAM_DIR_NONE) { int error; error = bus_dmamap_load_ccb(dpt->buffer_dmat, dccb->dmamap, ccb, dptexecuteccb, dccb, /*flags*/0); if (error == EINPROGRESS) { /* * So as to maintain ordering, * freeze the controller queue * until our mapping is * returned. */ xpt_freeze_simq(sim, 1); dccb->state |= CAM_RELEASE_SIMQ; } } else { /* * XXX JGibbs. * Does it want them both on or both off? * CAM_DIR_NONE is both on, so this code can * be removed if this is also what the DPT * exptects. */ eccb->DataOut = 0; eccb->DataIn = 0; dptexecuteccb(dccb, NULL, 0, 0); } break; } case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: { ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; if (cts->type == CTS_TYPE_USER_SETTINGS) { spi->flags = CTS_SPI_FLAGS_DISC_ENB; spi->bus_width = (dpt->max_id > 7) ? MSG_EXT_WDTR_BUS_8_BIT : MSG_EXT_WDTR_BUS_16_BIT; spi->sync_period = 25; /* 10MHz */ if (spi->sync_period != 0) spi->sync_offset = 15; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; ccb->ccb_h.status = CAM_REQ_CMP; } else { ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; } xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { /* * XXX Use Adaptec translation until I find out how to * get this information from the card. */ cam_calc_geometry(&ccb->ccg, /*extended*/1); xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; if (dpt->max_id > 7) cpi->hba_inquiry |= PI_WIDE_16; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = dpt->max_id; cpi->max_lun = dpt->max_lun; cpi->initiator_id = dpt->hostid[cam_sim_bus(sim)]; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "DPT", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } /* * This routine will try to send an EATA command to the DPT HBA. * It will, by default, try 20,000 times, waiting 50us between tries. * It returns 0 on success and 1 on failure. */ static int dpt_send_eata_command(dpt_softc_t *dpt, eata_ccb_t *cmd_block, u_int32_t cmd_busaddr, u_int command, u_int retries, u_int ifc, u_int code, u_int code2) { u_int loop; if (!retries) retries = 20000; /* * I hate this polling nonsense. Wish there was a way to tell the DPT * to go get commands at its own pace, or to interrupt when ready. * In the mean time we will measure how many itterations it really * takes. */ for (loop = 0; loop < retries; loop++) { if ((dpt_inb(dpt, HA_RAUXSTAT) & HA_ABUSY) == 0) break; else DELAY(50); } if (loop < retries) { #ifdef DPT_MEASURE_PERFORMANCE if (loop > dpt->performance.max_eata_tries) dpt->performance.max_eata_tries = loop; if (loop < dpt->performance.min_eata_tries) dpt->performance.min_eata_tries = loop; #endif } else { #ifdef DPT_MEASURE_PERFORMANCE ++dpt->performance.command_too_busy; #endif return (1); } /* The controller is alive, advance the wedge timer */ #ifdef DPT_RESET_HBA dpt->last_contact = microtime_now; #endif if (cmd_block == NULL) cmd_busaddr = 0; #if (BYTE_ORDER == BIG_ENDIAN) else { cmd_busaddr = ((cmd_busaddr >> 24) & 0xFF) | ((cmd_busaddr >> 16) & 0xFF) | ((cmd_busaddr >> 8) & 0xFF) | (cmd_busaddr & 0xFF); } #endif /* And now the address */ dpt_outl(dpt, HA_WDMAADDR, cmd_busaddr); if (command == EATA_CMD_IMMEDIATE) { if (cmd_block == NULL) { dpt_outb(dpt, HA_WCODE2, code2); dpt_outb(dpt, HA_WCODE, code); } dpt_outb(dpt, HA_WIFC, ifc); } dpt_outb(dpt, HA_WCOMMAND, command); return (0); } /* ==================== Exported Function definitions =======================*/ void dpt_alloc(device_t dev) { dpt_softc_t *dpt = device_get_softc(dev); int i; mtx_init(&dpt->lock, "dpt", NULL, MTX_DEF); SLIST_INIT(&dpt->free_dccb_list); LIST_INIT(&dpt->pending_ccb_list); for (i = 0; i < MAX_CHANNELS; i++) dpt->resetlevel[i] = DPT_HA_OK; #ifdef DPT_MEASURE_PERFORMANCE dpt_reset_performance(dpt); #endif /* DPT_MEASURE_PERFORMANCE */ return; } void dpt_free(struct dpt_softc *dpt) { switch (dpt->init_level) { default: case 5: bus_dmamap_unload(dpt->dccb_dmat, dpt->dccb_dmamap); case 4: bus_dmamem_free(dpt->dccb_dmat, dpt->dpt_dccbs, dpt->dccb_dmamap); case 3: bus_dma_tag_destroy(dpt->dccb_dmat); case 2: bus_dma_tag_destroy(dpt->buffer_dmat); case 1: { struct sg_map_node *sg_map; while ((sg_map = SLIST_FIRST(&dpt->sg_maps)) != NULL) { SLIST_REMOVE_HEAD(&dpt->sg_maps, links); bus_dmamap_unload(dpt->sg_dmat, sg_map->sg_dmamap); bus_dmamem_free(dpt->sg_dmat, sg_map->sg_vaddr, sg_map->sg_dmamap); free(sg_map, M_DEVBUF); } bus_dma_tag_destroy(dpt->sg_dmat); } case 0: break; } mtx_destroy(&dpt->lock); } int dpt_alloc_resources (device_t dev) { dpt_softc_t * dpt; int error; dpt = device_get_softc(dev); dpt->io_res = bus_alloc_resource_any(dev, dpt->io_type, &dpt->io_rid, RF_ACTIVE); if (dpt->io_res == NULL) { device_printf(dev, "No I/O space?!\n"); error = ENOMEM; goto bad; } dpt->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &dpt->irq_rid, RF_ACTIVE); if (dpt->irq_res == NULL) { device_printf(dev, "No IRQ!\n"); error = ENOMEM; goto bad; } return (0); bad: return(error); } void dpt_release_resources (device_t dev) { struct dpt_softc * dpt; dpt = device_get_softc(dev); if (dpt->ih) bus_teardown_intr(dev, dpt->irq_res, dpt->ih); if (dpt->io_res) bus_release_resource(dev, dpt->io_type, dpt->io_rid, dpt->io_res); if (dpt->irq_res) bus_release_resource(dev, SYS_RES_IRQ, dpt->irq_rid, dpt->irq_res); if (dpt->drq_res) bus_release_resource(dev, SYS_RES_DRQ, dpt->drq_rid, dpt->drq_res); return; } static u_int8_t string_sizes[] = { sizeof(((dpt_inq_t*)NULL)->vendor), sizeof(((dpt_inq_t*)NULL)->modelNum), sizeof(((dpt_inq_t*)NULL)->firmware), sizeof(((dpt_inq_t*)NULL)->protocol), }; int dpt_init(struct dpt_softc *dpt) { dpt_conf_t conf; struct sg_map_node *sg_map; dpt_ccb_t *dccb; u_int8_t *strp; int index; int i; int retval; dpt->init_level = 0; SLIST_INIT(&dpt->sg_maps); mtx_lock(&dpt->lock); #ifdef DPT_RESET_BOARD device_printf(dpt->dev, "resetting HBA\n"); dpt_outb(dpt, HA_WCOMMAND, EATA_CMD_RESET); DELAY(750000); /* XXX Shouldn't we poll a status register or something??? */ #endif /* DMA tag for our S/G structures. We allocate in page sized chunks */ if (bus_dma_tag_create( /* parent */ dpt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ PAGE_SIZE, /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &dpt->sg_dmat) != 0) { goto error_exit; } dpt->init_level++; /* * We allocate our DPT ccbs as a contiguous array of bus dma'able * memory. To get the allocation size, we need to know how many * ccbs the card supports. This requires a ccb. We solve this * chicken and egg problem by allocating some re-usable S/G space * up front, and treating it as our status packet, CCB, and target * memory space for these commands. */ sg_map = dptallocsgmap(dpt); if (sg_map == NULL) goto error_exit; dpt->sp = (volatile dpt_sp_t *)sg_map->sg_vaddr; dccb = (struct dpt_ccb *)(uintptr_t)(volatile void *)&dpt->sp[1]; bzero(dccb, sizeof(*dccb)); dpt->sp_physaddr = sg_map->sg_physaddr; dccb->eata_ccb.cp_dataDMA = htonl(sg_map->sg_physaddr + sizeof(dpt_sp_t) + sizeof(*dccb)); dccb->eata_ccb.cp_busaddr = ~0; dccb->eata_ccb.cp_statDMA = htonl(dpt->sp_physaddr); dccb->eata_ccb.cp_reqDMA = htonl(dpt->sp_physaddr + sizeof(*dccb) + offsetof(struct dpt_ccb, sense_data)); /* Okay. Fetch our config */ bzero(&dccb[1], sizeof(conf)); /* data area */ retval = dpt_get_conf(dpt, dccb, sg_map->sg_physaddr + sizeof(dpt_sp_t), sizeof(conf), 0xc1, 7, 1); if (retval != 0) { device_printf(dpt->dev, "Failed to get board configuration\n"); goto error_exit; } bcopy(&dccb[1], &conf, sizeof(conf)); bzero(&dccb[1], sizeof(dpt->board_data)); retval = dpt_get_conf(dpt, dccb, sg_map->sg_physaddr + sizeof(dpt_sp_t), sizeof(dpt->board_data), 0, conf.scsi_id0, 0); if (retval != 0) { device_printf(dpt->dev, "Failed to get inquiry information\n"); goto error_exit; } bcopy(&dccb[1], &dpt->board_data, sizeof(dpt->board_data)); dpt_detect_cache(dpt, dccb, sg_map->sg_physaddr + sizeof(dpt_sp_t), (u_int8_t *)&dccb[1]); switch (ntohl(conf.splen)) { case DPT_EATA_REVA: dpt->EATA_revision = 'a'; break; case DPT_EATA_REVB: dpt->EATA_revision = 'b'; break; case DPT_EATA_REVC: dpt->EATA_revision = 'c'; break; case DPT_EATA_REVZ: dpt->EATA_revision = 'z'; break; default: dpt->EATA_revision = '?'; } dpt->max_id = conf.MAX_ID; dpt->max_lun = conf.MAX_LUN; dpt->irq = conf.IRQ; dpt->dma_channel = (8 - conf.DMA_channel) & 7; dpt->channels = conf.MAX_CHAN + 1; dpt->state |= DPT_HA_OK; if (conf.SECOND) dpt->primary = FALSE; else dpt->primary = TRUE; dpt->more_support = conf.MORE_support; if (strncmp(dpt->board_data.firmware, "07G0", 4) >= 0) dpt->immediate_support = 1; else dpt->immediate_support = 0; dpt->broken_INQUIRY = FALSE; dpt->cplen = ntohl(conf.cplen); dpt->cppadlen = ntohs(conf.cppadlen); dpt->max_dccbs = ntohs(conf.queuesiz); if (dpt->max_dccbs > 256) { device_printf(dpt->dev, "Max CCBs reduced from %d to " "256 due to tag algorithm\n", dpt->max_dccbs); dpt->max_dccbs = 256; } dpt->hostid[0] = conf.scsi_id0; dpt->hostid[1] = conf.scsi_id1; dpt->hostid[2] = conf.scsi_id2; if (conf.SG_64K) dpt->sgsize = 8192; else dpt->sgsize = ntohs(conf.SGsiz); /* We can only get 64k buffers, so don't bother to waste space. */ if (dpt->sgsize < 17 || dpt->sgsize > 32) dpt->sgsize = 32; if (dpt->sgsize > dpt_max_segs) dpt->sgsize = dpt_max_segs; /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create( /* parent */ dpt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ dpt->sgsize, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &dpt->lock, &dpt->buffer_dmat) != 0) { device_printf(dpt->dev, "bus_dma_tag_create(...,dpt->buffer_dmat) failed\n"); goto error_exit; } dpt->init_level++; /* DMA tag for our ccb structures and interrupt status packet */ if (bus_dma_tag_create( /* parent */ dpt->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ (dpt->max_dccbs * sizeof(struct dpt_ccb)) + sizeof(dpt_sp_t), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &dpt->dccb_dmat) != 0) { device_printf(dpt->dev, "bus_dma_tag_create(...,dpt->dccb_dmat) failed\n"); goto error_exit; } dpt->init_level++; /* Allocation for our ccbs and interrupt status packet */ if (bus_dmamem_alloc(dpt->dccb_dmat, (void **)&dpt->dpt_dccbs, BUS_DMA_NOWAIT, &dpt->dccb_dmamap) != 0) { device_printf(dpt->dev, "bus_dmamem_alloc(dpt->dccb_dmat,...) failed\n"); goto error_exit; } dpt->init_level++; /* And permanently map them */ bus_dmamap_load(dpt->dccb_dmat, dpt->dccb_dmamap, dpt->dpt_dccbs, (dpt->max_dccbs * sizeof(struct dpt_ccb)) + sizeof(dpt_sp_t), dptmapmem, &dpt->dpt_ccb_busbase, /*flags*/0); /* Clear them out. */ bzero(dpt->dpt_dccbs, (dpt->max_dccbs * sizeof(struct dpt_ccb)) + sizeof(dpt_sp_t)); dpt->dpt_ccb_busend = dpt->dpt_ccb_busbase; dpt->sp = (dpt_sp_t*)&dpt->dpt_dccbs[dpt->max_dccbs]; dpt->sp_physaddr = dpt->dpt_ccb_busbase + (dpt->max_dccbs * sizeof(dpt_ccb_t)); dpt->init_level++; /* Allocate our first batch of ccbs */ if (dptallocccbs(dpt) == 0) { device_printf(dpt->dev, "dptallocccbs(dpt) == 0\n"); mtx_unlock(&dpt->lock); return (2); } /* Prepare for Target Mode */ dpt->target_mode_enabled = 1; /* Nuke excess spaces from inquiry information */ strp = dpt->board_data.vendor; for (i = 0; i < sizeof(string_sizes); i++) { index = string_sizes[i] - 1; while (index && (strp[index] == ' ')) strp[index--] = '\0'; strp += string_sizes[i]; } device_printf(dpt->dev, "%.8s %.16s FW Rev. %.4s, ", dpt->board_data.vendor, dpt->board_data.modelNum, dpt->board_data.firmware); printf("%d channel%s, ", dpt->channels, dpt->channels > 1 ? "s" : ""); if (dpt->cache_type != DPT_NO_CACHE && dpt->cache_size != 0) { printf("%s Cache, ", dpt->cache_type == DPT_CACHE_WRITETHROUGH ? "Write-Through" : "Write-Back"); } printf("%d CCBs\n", dpt->max_dccbs); mtx_unlock(&dpt->lock); return (0); error_exit: mtx_unlock(&dpt->lock); return (1); } int dpt_attach(dpt_softc_t *dpt) { struct cam_devq *devq; int i; /* * Create the device queue for our SIM. */ devq = cam_simq_alloc(dpt->max_dccbs); if (devq == NULL) return (0); mtx_lock(&dpt->lock); for (i = 0; i < dpt->channels; i++) { /* * Construct our SIM entry */ dpt->sims[i] = cam_sim_alloc(dpt_action, dpt_poll, "dpt", dpt, device_get_unit(dpt->dev), &dpt->lock, /*untagged*/2, /*tagged*/dpt->max_dccbs, devq); if (dpt->sims[i] == NULL) { if (i == 0) cam_simq_free(devq); else printf( "%s(): Unable to attach bus %d " "due to resource shortage\n", __func__, i); break; } if (xpt_bus_register(dpt->sims[i], dpt->dev, i) != CAM_SUCCESS){ cam_sim_free(dpt->sims[i], /*free_devq*/i == 0); dpt->sims[i] = NULL; break; } if (xpt_create_path(&dpt->paths[i], /*periph*/NULL, cam_sim_path(dpt->sims[i]), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(dpt->sims[i])); cam_sim_free(dpt->sims[i], /*free_devq*/i == 0); dpt->sims[i] = NULL; break; } } mtx_unlock(&dpt->lock); if (i > 0) EVENTHANDLER_REGISTER(shutdown_final, dptshutdown, dpt, SHUTDOWN_PRI_DEFAULT); return (i); } int dpt_detach (device_t dev) { struct dpt_softc * dpt; int i; dpt = device_get_softc(dev); mtx_lock(&dpt->lock); for (i = 0; i < dpt->channels; i++) { #if 0 xpt_async(AC_LOST_DEVICE, dpt->paths[i], NULL); #endif xpt_free_path(dpt->paths[i]); xpt_bus_deregister(cam_sim_path(dpt->sims[i])); cam_sim_free(dpt->sims[i], /*free_devq*/TRUE); } mtx_unlock(&dpt->lock); dptshutdown((void *)dpt, SHUTDOWN_PRI_DEFAULT); dpt_release_resources(dev); dpt_free(dpt); return (0); } /* * This is the interrupt handler for the DPT driver. */ void dpt_intr(void *arg) { dpt_softc_t *dpt; dpt = arg; mtx_lock(&dpt->lock); dpt_intr_locked(dpt); mtx_unlock(&dpt->lock); } void dpt_intr_locked(dpt_softc_t *dpt) { dpt_ccb_t *dccb; union ccb *ccb; u_int status; u_int aux_status; u_int hba_stat; u_int scsi_stat; u_int32_t residue_len; /* Number of bytes not transferred */ /* First order of business is to check if this interrupt is for us */ while (((aux_status = dpt_inb(dpt, HA_RAUXSTAT)) & HA_AIRQ) != 0) { /* * What we want to do now, is to capture the status, all of it, * move it where it belongs, wake up whoever sleeps waiting to * process this result, and get out of here. */ if (dpt->sp->ccb_busaddr < dpt->dpt_ccb_busbase || dpt->sp->ccb_busaddr >= dpt->dpt_ccb_busend) { device_printf(dpt->dev, "Encountered bogus status packet\n"); status = dpt_inb(dpt, HA_RSTATUS); return; } dccb = dptccbptov(dpt, dpt->sp->ccb_busaddr); dpt->sp->ccb_busaddr = ~0; /* Ignore status packets with EOC not set */ if (dpt->sp->EOC == 0) { device_printf(dpt->dev, "ERROR: Request %d received with " "clear EOC.\n Marking as LOST.\n", dccb->transaction_id); /* This CLEARS the interrupt! */ status = dpt_inb(dpt, HA_RSTATUS); continue; } dpt->sp->EOC = 0; /* * Double buffer the status information so the hardware can * work on updating the status packet while we decifer the * one we were just interrupted for. * According to Mark Salyzyn, we only need few pieces of it. */ hba_stat = dpt->sp->hba_stat; scsi_stat = dpt->sp->scsi_stat; residue_len = dpt->sp->residue_len; /* Clear interrupts, check for error */ if ((status = dpt_inb(dpt, HA_RSTATUS)) & HA_SERROR) { /* * Error Condition. Check for magic cookie. Exit * this test on earliest sign of non-reset condition */ /* Check that this is not a board reset interrupt */ if (dpt_just_reset(dpt)) { device_printf(dpt->dev, "HBA rebooted.\n" " All transactions should be " "resubmitted\n"); device_printf(dpt->dev, ">>---->> This is incomplete, " "fix me.... <<----<<"); panic("DPT Rebooted"); } } /* Process CCB */ ccb = dccb->ccb; callout_stop(&dccb->timer); if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(dpt->buffer_dmat, dccb->dmamap, op); bus_dmamap_unload(dpt->buffer_dmat, dccb->dmamap); } /* Common Case inline... */ if (hba_stat == HA_NO_ERROR) { ccb->csio.scsi_status = scsi_stat; ccb->ccb_h.status = 0; switch (scsi_stat) { case SCSI_STATUS_OK: ccb->ccb_h.status |= CAM_REQ_CMP; break; case SCSI_STATUS_CHECK_COND: case SCSI_STATUS_CMD_TERMINATED: bcopy(&dccb->sense_data, &ccb->csio.sense_data, ccb->csio.sense_len); ccb->ccb_h.status |= CAM_AUTOSNS_VALID; /* FALLTHROUGH */ default: ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR; /* XXX Freeze DevQ */ break; } ccb->csio.resid = residue_len; dptfreeccb(dpt, dccb); xpt_done(ccb); } else { dptprocesserror(dpt, dccb, ccb, hba_stat, scsi_stat, residue_len); } } } static void dptprocesserror(dpt_softc_t *dpt, dpt_ccb_t *dccb, union ccb *ccb, u_int hba_stat, u_int scsi_stat, u_int32_t resid) { ccb->csio.resid = resid; switch (hba_stat) { case HA_ERR_SEL_TO: ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; case HA_ERR_CMD_TO: ccb->ccb_h.status = CAM_CMD_TIMEOUT; break; case HA_SCSIBUS_RESET: case HA_HBA_POWER_UP: /* Similar effect to a bus reset??? */ ccb->ccb_h.status = CAM_SCSI_BUS_RESET; break; case HA_CP_ABORTED: case HA_CP_RESET: /* XXX ??? */ case HA_CP_ABORT_NA: /* XXX ??? */ case HA_CP_RESET_NA: /* XXX ??? */ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG) ccb->ccb_h.status = CAM_REQ_ABORTED; break; case HA_PCI_PARITY: case HA_PCI_MABORT: case HA_PCI_TABORT: case HA_PCI_STABORT: case HA_BUS_PARITY: case HA_PARITY_ERR: case HA_ECC_ERR: ccb->ccb_h.status = CAM_UNCOR_PARITY; break; case HA_UNX_MSGRJCT: ccb->ccb_h.status = CAM_MSG_REJECT_REC; break; case HA_UNX_BUSPHASE: ccb->ccb_h.status = CAM_SEQUENCE_FAIL; break; case HA_UNX_BUS_FREE: ccb->ccb_h.status = CAM_UNEXP_BUSFREE; break; case HA_SCSI_HUNG: case HA_RESET_STUCK: /* * Dead??? Can the controller get unstuck * from these conditions */ ccb->ccb_h.status = CAM_NO_HBA; break; case HA_RSENSE_FAIL: ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; break; default: device_printf(dpt->dev, "Undocumented Error %x\n", hba_stat); printf("Please mail this message to shimon@simon-shapiro.org\n"); ccb->ccb_h.status = CAM_REQ_CMP_ERR; break; } dptfreeccb(dpt, dccb); xpt_done(ccb); } static void dpttimeout(void *arg) { struct dpt_ccb *dccb; union ccb *ccb; struct dpt_softc *dpt; dccb = (struct dpt_ccb *)arg; ccb = dccb->ccb; dpt = (struct dpt_softc *)ccb->ccb_h.ccb_dpt_ptr; mtx_assert(&dpt->lock, MA_OWNED); xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out\n", (void *)dccb); /* * Try to clear any pending jobs. FreeBSD will lose interrupts, * leaving the controller suspended, and commands timed-out. * By calling the interrupt handler, any command thus stuck will be * completed. */ dpt_intr_locked(dpt); if ((dccb->state & DCCB_ACTIVE) == 0) { xpt_print_path(ccb->ccb_h.path); printf("CCB %p - timed out CCB already completed\n", (void *)dccb); return; } /* Abort this particular command. Leave all others running */ dpt_send_immediate(dpt, &dccb->eata_ccb, dccb->eata_ccb.cp_busaddr, /*retries*/20000, EATA_SPECIFIC_ABORT, 0, 0); ccb->ccb_h.status = CAM_CMD_TIMEOUT; } /* * Shutdown the controller and ensure that the cache is completely flushed. * Called from the shutdown_final event after all disk access has completed. */ static void dptshutdown(void *arg, int howto) { dpt_softc_t *dpt; dpt = (dpt_softc_t *)arg; device_printf(dpt->dev, "Shutting down (mode %x) HBA. Please wait...\n", howto); /* * What we do for a shutdown, is give the DPT early power loss warning */ mtx_lock(&dpt->lock); dpt_send_immediate(dpt, NULL, 0, EATA_POWER_OFF_WARN, 0, 0, 0); mtx_unlock(&dpt->lock); DELAY(1000 * 1000 * 5); device_printf(dpt->dev, "Controller was warned of shutdown and is now " "disabled\n"); } /*============================================================================*/ #if 0 #ifdef DPT_RESET_HBA /* ** Function name : dpt_reset_hba ** ** Description : Reset the HBA and properly discard all pending work ** Input : Softc ** Output : Nothing */ static void dpt_reset_hba(dpt_softc_t *dpt) { eata_ccb_t *ccb; dpt_ccb_t dccb, *dccbp; int result; struct scsi_xfer *xs; mtx_assert(&dpt->lock, MA_OWNED); /* Prepare a control block. The SCSI command part is immaterial */ dccb.xs = NULL; dccb.flags = 0; dccb.state = DPT_CCB_STATE_NEW; dccb.std_callback = NULL; dccb.wrbuff_callback = NULL; ccb = &dccb.eata_ccb; ccb->CP_OpCode = EATA_CMD_RESET; ccb->SCSI_Reset = 0; ccb->HBA_Init = 1; ccb->Auto_Req_Sen = 1; ccb->cp_id = 0; /* Should be ignored */ ccb->DataIn = 1; ccb->DataOut = 0; ccb->Interpret = 1; ccb->reqlen = htonl(sizeof(struct scsi_sense_data)); ccb->cp_statDMA = htonl(vtophys(&ccb->cp_statDMA)); ccb->cp_reqDMA = htonl(vtophys(&ccb->cp_reqDMA)); ccb->cp_viraddr = (u_int32_t) & ccb; ccb->cp_msg[0] = HA_IDENTIFY_MSG | HA_DISCO_RECO; ccb->cp_scsi_cmd = 0; /* Should be ignored */ /* Lock up the submitted queue. We are very persistant here */ while (dpt->queue_status & DPT_SUBMITTED_QUEUE_ACTIVE) { DELAY(100); } dpt->queue_status |= DPT_SUBMITTED_QUEUE_ACTIVE; /* Send the RESET message */ if ((result = dpt_send_eata_command(dpt, &dccb.eata_ccb, EATA_CMD_RESET, 0, 0, 0, 0)) != 0) { device_printf(dpt->dev, "Failed to send the RESET message.\n" " Trying cold boot (ouch!)\n"); if ((result = dpt_send_eata_command(dpt, &dccb.eata_ccb, EATA_COLD_BOOT, 0, 0, 0, 0)) != 0) { panic("%s: Faild to cold boot the HBA\n", device_get_nameunit(dpt->dev)); } #ifdef DPT_MEASURE_PERFORMANCE dpt->performance.cold_boots++; #endif /* DPT_MEASURE_PERFORMANCE */ } #ifdef DPT_MEASURE_PERFORMANCE dpt->performance.warm_starts++; #endif /* DPT_MEASURE_PERFORMANCE */ device_printf(dpt->dev, "Aborting pending requests. O/S should re-submit\n"); while ((dccbp = TAILQ_FIRST(&dpt->completed_ccbs)) != NULL) { struct scsi_xfer *xs = dccbp->xs; /* Not all transactions have xs structs */ if (xs != NULL) { /* Tell the kernel proper this did not complete well */ xs->error |= XS_SELTIMEOUT; xs->flags |= SCSI_ITSDONE; scsi_done(xs); } dpt_Qremove_submitted(dpt, dccbp); /* Remember, Callbacks are NOT in the standard queue */ if (dccbp->std_callback != NULL) { (dccbp->std_callback)(dpt, dccbp->eata_ccb.cp_channel, dccbp); } else { dpt_Qpush_free(dpt, dccbp); } } device_printf(dpt->dev, "reset done aborting all pending commands\n"); dpt->queue_status &= ~DPT_SUBMITTED_QUEUE_ACTIVE; } #endif /* DPT_RESET_HBA */ /* * Build a Command Block for target mode READ/WRITE BUFFER, * with the ``sync'' bit ON. * * Although the length and offset are 24 bit fields in the command, they cannot * exceed 8192 bytes, so we take them as short integers andcheck their range. * If they are sensless, we round them to zero offset, maximum length and * complain. */ static void dpt_target_ccb(dpt_softc_t * dpt, int bus, u_int8_t target, u_int8_t lun, dpt_ccb_t * ccb, int mode, u_int8_t command, u_int16_t length, u_int16_t offset) { eata_ccb_t *cp; mtx_assert(&dpt->lock, MA_OWNED); if ((length + offset) > DPT_MAX_TARGET_MODE_BUFFER_SIZE) { device_printf(dpt->dev, "Length of %d, and offset of %d are wrong\n", length, offset); length = DPT_MAX_TARGET_MODE_BUFFER_SIZE; offset = 0; } ccb->xs = NULL; ccb->flags = 0; ccb->state = DPT_CCB_STATE_NEW; ccb->std_callback = (ccb_callback) dpt_target_done; ccb->wrbuff_callback = NULL; cp = &ccb->eata_ccb; cp->CP_OpCode = EATA_CMD_DMA_SEND_CP; cp->SCSI_Reset = 0; cp->HBA_Init = 0; cp->Auto_Req_Sen = 1; cp->cp_id = target; cp->DataIn = 1; cp->DataOut = 0; cp->Interpret = 0; cp->reqlen = htonl(sizeof(struct scsi_sense_data)); cp->cp_statDMA = htonl(vtophys(&cp->cp_statDMA)); cp->cp_reqDMA = htonl(vtophys(&cp->cp_reqDMA)); cp->cp_viraddr = (u_int32_t) & ccb; cp->cp_msg[0] = HA_IDENTIFY_MSG | HA_DISCO_RECO; cp->cp_scsi_cmd = command; cp->cp_cdb[1] = (u_int8_t) (mode & SCSI_TM_MODE_MASK); cp->cp_lun = lun; /* Order is important here! */ cp->cp_cdb[2] = 0x00; /* Buffer Id, only 1 :-( */ cp->cp_cdb[3] = (length >> 16) & 0xFF; /* Buffer offset MSB */ cp->cp_cdb[4] = (length >> 8) & 0xFF; cp->cp_cdb[5] = length & 0xFF; cp->cp_cdb[6] = (length >> 16) & 0xFF; /* Length MSB */ cp->cp_cdb[7] = (length >> 8) & 0xFF; cp->cp_cdb[8] = length & 0xFF; /* Length LSB */ cp->cp_cdb[9] = 0; /* No sync, no match bits */ /* * This could be optimized to live in dpt_register_buffer. * We keep it here, just in case the kernel decides to reallocate pages */ if (dpt_scatter_gather(dpt, ccb, DPT_RW_BUFFER_SIZE, dpt->rw_buffer[bus][target][lun])) { device_printf(dpt->dev, "Failed to setup Scatter/Gather for " "Target-Mode buffer\n"); } } /* Setup a target mode READ command */ static void dpt_set_target(int redo, dpt_softc_t * dpt, u_int8_t bus, u_int8_t target, u_int8_t lun, int mode, u_int16_t length, u_int16_t offset, dpt_ccb_t * ccb) { mtx_assert(&dpt->lock, MA_OWNED); if (dpt->target_mode_enabled) { if (!redo) dpt_target_ccb(dpt, bus, target, lun, ccb, mode, SCSI_TM_READ_BUFFER, length, offset); ccb->transaction_id = ++dpt->commands_processed; #ifdef DPT_MEASURE_PERFORMANCE dpt->performance.command_count[ccb->eata_ccb.cp_scsi_cmd]++; ccb->command_started = microtime_now; #endif dpt_Qadd_waiting(dpt, ccb); dpt_sched_queue(dpt); } else { device_printf(dpt->dev, "Target Mode Request, but Target Mode is OFF\n"); } } /* * Schedule a buffer to be sent to another target. * The work will be scheduled and the callback provided will be called when * the work is actually done. * * Please NOTE: ``Anyone'' can send a buffer, but only registered clients * get notified of receipt of buffers. */ int dpt_send_buffer(int unit, u_int8_t channel, u_int8_t target, u_int8_t lun, u_int8_t mode, u_int16_t length, u_int16_t offset, void *data, buff_wr_done callback) { dpt_softc_t *dpt; dpt_ccb_t *ccb = NULL; /* This is an external call. Be a bit paranoid */ dpt = devclass_get_device(dpt_devclass, unit); if (dpt == NULL) return (INVALID_UNIT); mtx_lock(&dpt->lock); if (dpt->target_mode_enabled) { if ((channel >= dpt->channels) || (target > dpt->max_id) || (lun > dpt->max_lun)) { mtx_unlock(&dpt->lock); return (INVALID_SENDER); } if ((dpt->rw_buffer[channel][target][lun] == NULL) || (dpt->buffer_receiver[channel][target][lun] == NULL)) { mtx_unlock(&dpt->lock); return (NOT_REGISTERED); } /* Process the free list */ if ((TAILQ_EMPTY(&dpt->free_ccbs)) && dpt_alloc_freelist(dpt)) { device_printf(dpt->dev, "ERROR: Cannot allocate any more free CCB's.\n" " Please try later\n"); mtx_unlock(&dpt->lock); return (NO_RESOURCES); } /* Now grab the newest CCB */ if ((ccb = dpt_Qpop_free(dpt)) == NULL) { mtx_unlock(&dpt->lock); panic("%s: Got a NULL CCB from pop_free()\n", device_get_nameunit(dpt->dev)); } bcopy(dpt->rw_buffer[channel][target][lun] + offset, data, length); dpt_target_ccb(dpt, channel, target, lun, ccb, mode, SCSI_TM_WRITE_BUFFER, length, offset); ccb->std_callback = (ccb_callback) callback; /* Potential trouble */ ccb->transaction_id = ++dpt->commands_processed; #ifdef DPT_MEASURE_PERFORMANCE dpt->performance.command_count[ccb->eata_ccb.cp_scsi_cmd]++; ccb->command_started = microtime_now; #endif dpt_Qadd_waiting(dpt, ccb); dpt_sched_queue(dpt); mtx_unlock(&dpt->lock); return (0); } mtx_unlock(&dpt->lock); return (DRIVER_DOWN); } static void dpt_target_done(dpt_softc_t * dpt, int bus, dpt_ccb_t * ccb) { eata_ccb_t *cp; cp = &ccb->eata_ccb; /* * Remove the CCB from the waiting queue. * We do NOT put it back on the free, etc., queues as it is a special * ccb, owned by the dpt_softc of this unit. */ dpt_Qremove_completed(dpt, ccb); #define br_channel (ccb->eata_ccb.cp_channel) #define br_target (ccb->eata_ccb.cp_id) #define br_lun (ccb->eata_ccb.cp_LUN) #define br_index [br_channel][br_target][br_lun] #define read_buffer_callback (dpt->buffer_receiver br_index ) #define read_buffer (dpt->rw_buffer[br_channel][br_target][br_lun]) #define cb(offset) (ccb->eata_ccb.cp_cdb[offset]) #define br_offset ((cb(3) << 16) | (cb(4) << 8) | cb(5)) #define br_length ((cb(6) << 16) | (cb(7) << 8) | cb(8)) /* Different reasons for being here, you know... */ switch (ccb->eata_ccb.cp_scsi_cmd) { case SCSI_TM_READ_BUFFER: if (read_buffer_callback != NULL) { /* This is a buffer generated by a kernel process */ read_buffer_callback(device_get_unit(dpt->dev), br_channel, br_target, br_lun, read_buffer, br_offset, br_length); } else { /* * This is a buffer waited for by a user (sleeping) * command */ wakeup(ccb); } /* We ALWAYS re-issue the same command; args are don't-care */ dpt_set_target(1, 0, 0, 0, 0, 0, 0, 0, 0); break; case SCSI_TM_WRITE_BUFFER: (ccb->wrbuff_callback) (device_get_unit(dpt->dev), br_channel, br_target, br_offset, br_length, br_lun, ccb->status_packet.hba_stat); break; default: device_printf(dpt->dev, "%s is an unsupported command for target mode\n", scsi_cmd_name(ccb->eata_ccb.cp_scsi_cmd)); } dpt->target_ccb[br_channel][br_target][br_lun] = NULL; dpt_Qpush_free(dpt, ccb); } /* * Use this function to register a client for a buffer read target operation. * The function you register will be called every time a buffer is received * by the target mode code. */ dpt_rb_t dpt_register_buffer(int unit, u_int8_t channel, u_int8_t target, u_int8_t lun, u_int8_t mode, u_int16_t length, u_int16_t offset, dpt_rec_buff callback, dpt_rb_op_t op) { dpt_softc_t *dpt; dpt_ccb_t *ccb = NULL; int ospl; dpt = devclass_get_device(dpt_devclass, unit); if (dpt == NULL) return (INVALID_UNIT); mtx_lock(&dpt->lock); if (dpt->state & DPT_HA_SHUTDOWN_ACTIVE) { mtx_unlock(&dpt->lock); return (DRIVER_DOWN); } if ((channel > (dpt->channels - 1)) || (target > (dpt->max_id - 1)) || (lun > (dpt->max_lun - 1))) { mtx_unlock(&dpt->lock); return (INVALID_SENDER); } if (dpt->buffer_receiver[channel][target][lun] == NULL) { if (op == REGISTER_BUFFER) { /* Assign the requested callback */ dpt->buffer_receiver[channel][target][lun] = callback; /* Get a CCB */ /* Process the free list */ if ((TAILQ_EMPTY(&dpt->free_ccbs)) && dpt_alloc_freelist(dpt)) { device_printf(dpt->dev, "ERROR: Cannot allocate any more free CCB's.\n" " Please try later\n"); mtx_unlock(&dpt->lock); return (NO_RESOURCES); } /* Now grab the newest CCB */ if ((ccb = dpt_Qpop_free(dpt)) == NULL) { mtx_unlock(&dpt->lock); panic("%s: Got a NULL CCB from pop_free()\n", device_get_nameunit(dpt->dev)); } /* Clean up the leftover of the previous tenant */ ccb->status = DPT_CCB_STATE_NEW; dpt->target_ccb[channel][target][lun] = ccb; dpt->rw_buffer[channel][target][lun] = malloc(DPT_RW_BUFFER_SIZE, M_DEVBUF, M_NOWAIT); if (dpt->rw_buffer[channel][target][lun] == NULL) { device_printf(dpt->dev, "Failed to allocate " "Target-Mode buffer\n"); dpt_Qpush_free(dpt, ccb); mtx_unlock(&dpt->lock); return (NO_RESOURCES); } dpt_set_target(0, dpt, channel, target, lun, mode, length, offset, ccb); mtx_unlock(&dpt->lock); return (SUCCESSFULLY_REGISTERED); } else { mtx_unlock(&dpt->lock); return (NOT_REGISTERED); } } else { if (op == REGISTER_BUFFER) { if (dpt->buffer_receiver[channel][target][lun] == callback) { mtx_unlock(&dpt->lock); return (ALREADY_REGISTERED); } else { mtx_unlock(&dpt->lock); return (REGISTERED_TO_ANOTHER); } } else { if (dpt->buffer_receiver[channel][target][lun] == callback) { dpt->buffer_receiver[channel][target][lun] = NULL; dpt_Qpush_free(dpt, ccb); free(dpt->rw_buffer[channel][target][lun], M_DEVBUF); mtx_unlock(&dpt->lock); return (SUCCESSFULLY_REGISTERED); } else { mtx_unlock(&dpt->lock); return (INVALID_CALLBACK); } } } mtx_unlock(&dpt->lock); } /* Return the state of the blinking DPT LED's */ u_int8_t dpt_blinking_led(dpt_softc_t * dpt) { int ndx; u_int32_t state; u_int32_t previous; u_int8_t result; mtx_assert(&dpt->lock, MA_OWNED); result = 0; for (ndx = 0, state = 0, previous = 0; (ndx < 10) && (state != previous); ndx++) { previous = state; state = dpt_inl(dpt, 1); } if ((state == previous) && (state == DPT_BLINK_INDICATOR)) result = dpt_inb(dpt, 5); return (result); } /* * Execute a command which did not come from the kernel's SCSI layer. * The only way to map user commands to bus and target is to comply with the * standard DPT wire-down scheme: */ int dpt_user_cmd(dpt_softc_t * dpt, eata_pt_t * user_cmd, caddr_t cmdarg, int minor_no) { dpt_ccb_t *ccb; void *data; int channel, target, lun; int huh; int result; int submitted; mtx_assert(&dpt->lock, MA_OWNED); data = NULL; channel = minor2hba(minor_no); target = minor2target(minor_no); lun = minor2lun(minor_no); if ((channel > (dpt->channels - 1)) || (target > dpt->max_id) || (lun > dpt->max_lun)) return (ENXIO); if (target == dpt->sc_scsi_link[channel].adapter_targ) { /* This one is for the controller itself */ if ((user_cmd->eataID[0] != 'E') || (user_cmd->eataID[1] != 'A') || (user_cmd->eataID[2] != 'T') || (user_cmd->eataID[3] != 'A')) { return (ENXIO); } } /* Get a DPT CCB, so we can prepare a command */ /* Process the free list */ if ((TAILQ_EMPTY(&dpt->free_ccbs)) && dpt_alloc_freelist(dpt)) { device_printf(dpt->dev, "ERROR: Cannot allocate any more free CCB's.\n" " Please try later\n"); return (EFAULT); } /* Now grab the newest CCB */ if ((ccb = dpt_Qpop_free(dpt)) == NULL) { panic("%s: Got a NULL CCB from pop_free()\n", device_get_nameunit(dpt->dev)); } else { /* Clean up the leftover of the previous tenant */ ccb->status = DPT_CCB_STATE_NEW; } bcopy((caddr_t) & user_cmd->command_packet, (caddr_t) & ccb->eata_ccb, sizeof(eata_ccb_t)); /* We do not want to do user specified scatter/gather. Why?? */ if (ccb->eata_ccb.scatter == 1) return (EINVAL); ccb->eata_ccb.Auto_Req_Sen = 1; ccb->eata_ccb.reqlen = htonl(sizeof(struct scsi_sense_data)); ccb->eata_ccb.cp_datalen = htonl(sizeof(ccb->eata_ccb.cp_datalen)); ccb->eata_ccb.cp_dataDMA = htonl(vtophys(ccb->eata_ccb.cp_dataDMA)); ccb->eata_ccb.cp_statDMA = htonl(vtophys(&ccb->eata_ccb.cp_statDMA)); ccb->eata_ccb.cp_reqDMA = htonl(vtophys(&ccb->eata_ccb.cp_reqDMA)); ccb->eata_ccb.cp_viraddr = (u_int32_t) & ccb; if (ccb->eata_ccb.DataIn || ccb->eata_ccb.DataOut) { /* Data I/O is involved in this command. Alocate buffer */ if (ccb->eata_ccb.cp_datalen > PAGE_SIZE) { data = contigmalloc(ccb->eata_ccb.cp_datalen, M_TEMP, M_WAITOK, 0, ~0, ccb->eata_ccb.cp_datalen, 0x10000); } else { data = malloc(ccb->eata_ccb.cp_datalen, M_TEMP, M_WAITOK); } if (data == NULL) { device_printf(dpt->dev, "Cannot allocate %d bytes " "for EATA command\n", ccb->eata_ccb.cp_datalen); return (EFAULT); } #define usr_cmd_DMA (caddr_t)user_cmd->command_packet.cp_dataDMA if (ccb->eata_ccb.DataIn == 1) { if (copyin(usr_cmd_DMA, data, ccb->eata_ccb.cp_datalen) == -1) return (EFAULT); } } else { /* No data I/O involved here. Make sure the DPT knows that */ ccb->eata_ccb.cp_datalen = 0; data = NULL; } if (ccb->eata_ccb.FWNEST == 1) ccb->eata_ccb.FWNEST = 0; if (ccb->eata_ccb.cp_datalen != 0) { if (dpt_scatter_gather(dpt, ccb, ccb->eata_ccb.cp_datalen, data) != 0) { if (data != NULL) free(data, M_TEMP); return (EFAULT); } } /** * We are required to quiet a SCSI bus. * since we do not queue comands on a bus basis, * we wait for ALL commands on a controller to complete. * In the mean time, sched_queue() will not schedule new commands. */ if ((ccb->eata_ccb.cp_cdb[0] == MULTIFUNCTION_CMD) && (ccb->eata_ccb.cp_cdb[2] == BUS_QUIET)) { /* We wait for ALL traffic for this HBa to subside */ dpt->state |= DPT_HA_QUIET; while ((submitted = dpt->submitted_ccbs_count) != 0) { huh = mtx_sleep((void *) dpt, &dpt->lock, PCATCH | PRIBIO, "dptqt", 100 * hz); switch (huh) { case 0: /* Wakeup call received */ break; case EWOULDBLOCK: /* Timer Expired */ break; default: /* anything else */ break; } } } /* Resume normal operation */ if ((ccb->eata_ccb.cp_cdb[0] == MULTIFUNCTION_CMD) && (ccb->eata_ccb.cp_cdb[2] == BUS_UNQUIET)) { dpt->state &= ~DPT_HA_QUIET; } /** * Schedule the command and submit it. * We bypass dpt_sched_queue, as it will block on DPT_HA_QUIET */ ccb->xs = NULL; ccb->flags = 0; ccb->eata_ccb.Auto_Req_Sen = 1; /* We always want this feature */ ccb->transaction_id = ++dpt->commands_processed; ccb->std_callback = (ccb_callback) dpt_user_cmd_done; ccb->result = (u_int32_t) & cmdarg; ccb->data = data; #ifdef DPT_MEASURE_PERFORMANCE ++dpt->performance.command_count[ccb->eata_ccb.cp_scsi_cmd]; ccb->command_started = microtime_now; #endif dpt_Qadd_waiting(dpt, ccb); dpt_sched_queue(dpt); /* Wait for the command to complete */ (void) mtx_sleep((void *) ccb, &dpt->lock, PCATCH | PRIBIO, "dptucw", 100 * hz); /* Free allocated memory */ if (data != NULL) free(data, M_TEMP); return (0); } static void dpt_user_cmd_done(dpt_softc_t * dpt, int bus, dpt_ccb_t * ccb) { u_int32_t result; caddr_t cmd_arg; mtx_unlock(&dpt->lock); /** * If Auto Request Sense is on, copyout the sense struct */ #define usr_pckt_DMA (caddr_t)(intptr_t)ntohl(ccb->eata_ccb.cp_reqDMA) #define usr_pckt_len ntohl(ccb->eata_ccb.cp_datalen) if (ccb->eata_ccb.Auto_Req_Sen == 1) { if (copyout((caddr_t) & ccb->sense_data, usr_pckt_DMA, sizeof(struct scsi_sense_data))) { mtx_lock(&dpt->lock); ccb->result = EFAULT; dpt_Qpush_free(dpt, ccb); wakeup(ccb); return; } } /* If DataIn is on, copyout the data */ if ((ccb->eata_ccb.DataIn == 1) && (ccb->status_packet.hba_stat == HA_NO_ERROR)) { if (copyout(ccb->data, usr_pckt_DMA, usr_pckt_len)) { mtx_lock(&dpt->lock); dpt_Qpush_free(dpt, ccb); ccb->result = EFAULT; wakeup(ccb); return; } } /* Copyout the status */ result = ccb->status_packet.hba_stat; cmd_arg = (caddr_t) ccb->result; if (copyout((caddr_t) & result, cmd_arg, sizeof(result))) { mtx_lock(&dpt->lock); dpt_Qpush_free(dpt, ccb); ccb->result = EFAULT; wakeup(ccb); return; } mtx_lock(&dpt->lock); /* Put the CCB back in the freelist */ ccb->state |= DPT_CCB_STATE_COMPLETED; dpt_Qpush_free(dpt, ccb); /* Free allocated memory */ return; } #endif Index: head/sys/dev/ida/ida.c =================================================================== --- head/sys/dev/ida/ida.c (revision 280346) +++ head/sys/dev/ida/ida.c (revision 280347) @@ -1,839 +1,839 @@ /*- * Copyright (c) 1999,2000 Jonathan Lemon * All rights reserved. * # Derived from the original IDA Compaq RAID driver, which is * Copyright (c) 1996, 1997, 1998, 1999 * Mark Dawson and David James. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Generic driver for Compaq SMART RAID adapters. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* prototypes */ static int ida_alloc_qcbs(struct ida_softc *ida); static void ida_done(struct ida_softc *ida, struct ida_qcb *qcb); static void ida_start(struct ida_softc *ida); static void ida_startio(struct ida_softc *ida); static void ida_startup(void *arg); static void ida_timeout(void *arg); static int ida_wait(struct ida_softc *ida, struct ida_qcb *qcb); static d_ioctl_t ida_ioctl; static struct cdevsw ida_cdevsw = { .d_version = D_VERSION, .d_ioctl = ida_ioctl, .d_name = "ida", }; void ida_free(struct ida_softc *ida) { int i; if (ida->ih != NULL) bus_teardown_intr(ida->dev, ida->irq, ida->ih); mtx_lock(&ida->lock); callout_stop(&ida->ch); mtx_unlock(&ida->lock); callout_drain(&ida->ch); if (ida->buffer_dmat) { for (i = 0; i < IDA_QCB_MAX; i++) bus_dmamap_destroy(ida->buffer_dmat, ida->qcbs[i].dmamap); bus_dma_tag_destroy(ida->buffer_dmat); } if (ida->hwqcb_dmat) { if (ida->hwqcb_busaddr) bus_dmamap_unload(ida->hwqcb_dmat, ida->hwqcb_dmamap); if (ida->hwqcbs) bus_dmamem_free(ida->hwqcb_dmat, ida->hwqcbs, ida->hwqcb_dmamap); bus_dma_tag_destroy(ida->hwqcb_dmat); } if (ida->qcbs != NULL) free(ida->qcbs, M_DEVBUF); if (ida->irq != NULL) bus_release_resource(ida->dev, ida->irq_res_type, 0, ida->irq); if (ida->parent_dmat != NULL) bus_dma_tag_destroy(ida->parent_dmat); if (ida->regs != NULL) bus_release_resource(ida->dev, ida->regs_res_type, ida->regs_res_id, ida->regs); mtx_destroy(&ida->lock); } /* * record bus address from bus_dmamap_load */ static void ida_dma_map_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *baddr; baddr = (bus_addr_t *)arg; *baddr = segs->ds_addr; } static __inline struct ida_qcb * ida_get_qcb(struct ida_softc *ida) { struct ida_qcb *qcb; if ((qcb = SLIST_FIRST(&ida->free_qcbs)) != NULL) { SLIST_REMOVE_HEAD(&ida->free_qcbs, link.sle); bzero(qcb->hwqcb, sizeof(struct ida_hdr) + sizeof(struct ida_req)); } return (qcb); } static __inline void ida_free_qcb(struct ida_softc *ida, struct ida_qcb *qcb) { qcb->state = QCB_FREE; qcb->buf = NULL; qcb->error = 0; SLIST_INSERT_HEAD(&ida->free_qcbs, qcb, link.sle); } static __inline bus_addr_t idahwqcbvtop(struct ida_softc *ida, struct ida_hardware_qcb *hwqcb) { return (ida->hwqcb_busaddr + ((bus_addr_t)hwqcb - (bus_addr_t)ida->hwqcbs)); } static __inline struct ida_qcb * idahwqcbptov(struct ida_softc *ida, bus_addr_t hwqcb_addr) { struct ida_hardware_qcb *hwqcb; hwqcb = (struct ida_hardware_qcb *) ((bus_addr_t)ida->hwqcbs + (hwqcb_addr - ida->hwqcb_busaddr)); return (hwqcb->qcb); } static int ida_alloc_qcbs(struct ida_softc *ida) { struct ida_qcb *qcb; int error, i; for (i = 0; i < IDA_QCB_MAX; i++) { qcb = &ida->qcbs[i]; error = bus_dmamap_create(ida->buffer_dmat, /*flags*/0, &qcb->dmamap); if (error != 0) return (error); qcb->ida = ida; qcb->flags = QCB_FREE; qcb->hwqcb = &ida->hwqcbs[i]; qcb->hwqcb->qcb = qcb; qcb->hwqcb_busaddr = idahwqcbvtop(ida, qcb->hwqcb); SLIST_INSERT_HEAD(&ida->free_qcbs, qcb, link.sle); } return (0); } int ida_init(struct ida_softc *ida) { struct ida_controller_info cinfo; device_t child; int error, i, unit; SLIST_INIT(&ida->free_qcbs); STAILQ_INIT(&ida->qcb_queue); bioq_init(&ida->bio_queue); ida->qcbs = (struct ida_qcb *) malloc(IDA_QCB_MAX * sizeof(struct ida_qcb), M_DEVBUF, M_NOWAIT | M_ZERO); if (ida->qcbs == NULL) return (ENOMEM); /* * Create our DMA tags */ /* DMA tag for our hardware QCB structures */ error = bus_dma_tag_create( /* parent */ ida->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, /* maxsize */ IDA_QCB_MAX * sizeof(struct ida_hardware_qcb), /* nsegments */ 1, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ NULL, /* lockarg */ NULL, &ida->hwqcb_dmat); if (error) return (ENOMEM); /* DMA tag for mapping buffers into device space */ error = bus_dma_tag_create( /* parent */ ida->parent_dmat, /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, + /* maxsize */ DFLTPHYS, /* nsegments */ IDA_NSEG, /* maxsegsz */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ 0, /* lockfunc */ busdma_lock_mutex, /* lockarg */ &Giant, &ida->buffer_dmat); if (error) return (ENOMEM); /* Allocation of hardware QCBs */ /* XXX allocation is rounded to hardware page size */ error = bus_dmamem_alloc(ida->hwqcb_dmat, (void **)&ida->hwqcbs, BUS_DMA_NOWAIT, &ida->hwqcb_dmamap); if (error) return (ENOMEM); /* And permanently map them in */ bus_dmamap_load(ida->hwqcb_dmat, ida->hwqcb_dmamap, ida->hwqcbs, IDA_QCB_MAX * sizeof(struct ida_hardware_qcb), ida_dma_map_cb, &ida->hwqcb_busaddr, /*flags*/0); bzero(ida->hwqcbs, IDA_QCB_MAX * sizeof(struct ida_hardware_qcb)); error = ida_alloc_qcbs(ida); if (error) return (error); mtx_lock(&ida->lock); ida->cmd.int_enable(ida, 0); error = ida_command(ida, CMD_GET_CTRL_INFO, &cinfo, sizeof(cinfo), IDA_CONTROLLER, 0, DMA_DATA_IN); if (error) { mtx_unlock(&ida->lock); device_printf(ida->dev, "CMD_GET_CTRL_INFO failed.\n"); return (error); } device_printf(ida->dev, "drives=%d firm_rev=%c%c%c%c\n", cinfo.num_drvs, cinfo.firm_rev[0], cinfo.firm_rev[1], cinfo.firm_rev[2], cinfo.firm_rev[3]); if (ida->flags & IDA_FIRMWARE) { int data; error = ida_command(ida, CMD_START_FIRMWARE, &data, sizeof(data), IDA_CONTROLLER, 0, DMA_DATA_IN); if (error) { mtx_unlock(&ida->lock); device_printf(ida->dev, "CMD_START_FIRMWARE failed.\n"); return (error); } } ida->cmd.int_enable(ida, 1); ida->flags |= IDA_ATTACHED; mtx_unlock(&ida->lock); for (i = 0; i < cinfo.num_drvs; i++) { child = device_add_child(ida->dev, /*"idad"*/NULL, -1); if (child != NULL) device_set_ivars(child, (void *)(intptr_t)i); } ida->ich.ich_func = ida_startup; ida->ich.ich_arg = ida; if (config_intrhook_establish(&ida->ich) != 0) { device_delete_children(ida->dev); device_printf(ida->dev, "Cannot establish configuration hook\n"); return (error); } unit = device_get_unit(ida->dev); ida->ida_dev_t = make_dev(&ida_cdevsw, unit, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "ida%d", unit); ida->ida_dev_t->si_drv1 = ida; return (0); } static void ida_startup(void *arg) { struct ida_softc *ida; ida = arg; config_intrhook_disestablish(&ida->ich); mtx_lock(&Giant); bus_generic_attach(ida->dev); mtx_unlock(&Giant); } int ida_detach(device_t dev) { struct ida_softc *ida; int error; ida = (struct ida_softc *)device_get_softc(dev); error = bus_generic_detach(dev); if (error) return (error); error = device_delete_children(dev); if (error) return (error); /* * XXX * before detaching, we must make sure that the system is * quiescent; nothing mounted, no pending activity. */ /* * XXX * now, how are we supposed to maintain a list of our drives? * iterate over our "child devices"? */ destroy_dev(ida->ida_dev_t); ida_free(ida); return (error); } static void ida_data_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct ida_hardware_qcb *hwqcb; struct ida_softc *ida; struct ida_qcb *qcb; bus_dmasync_op_t op; int i; qcb = arg; ida = qcb->ida; if (!dumping) mtx_assert(&ida->lock, MA_OWNED); if (error) { qcb->error = error; ida_done(ida, qcb); return; } hwqcb = qcb->hwqcb; hwqcb->hdr.size = htole16((sizeof(struct ida_req) + sizeof(struct ida_sgb) * IDA_NSEG) >> 2); for (i = 0; i < nsegments; i++) { hwqcb->seg[i].addr = htole32(segs[i].ds_addr); hwqcb->seg[i].length = htole32(segs[i].ds_len); } hwqcb->req.sgcount = nsegments; if (qcb->flags & DMA_DATA_TRANSFER) { switch (qcb->flags & DMA_DATA_TRANSFER) { case DMA_DATA_TRANSFER: op = BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE; break; case DMA_DATA_IN: op = BUS_DMASYNC_PREREAD; break; default: KASSERT((qcb->flags & DMA_DATA_TRANSFER) == DMA_DATA_OUT, ("bad DMA data flags")); op = BUS_DMASYNC_PREWRITE; break; } bus_dmamap_sync(ida->buffer_dmat, qcb->dmamap, op); } bus_dmamap_sync(ida->hwqcb_dmat, ida->hwqcb_dmamap, BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); STAILQ_INSERT_TAIL(&ida->qcb_queue, qcb, link.stqe); ida_start(ida); ida->flags &= ~IDA_QFROZEN; } static int ida_map_qcb(struct ida_softc *ida, struct ida_qcb *qcb, void *data, bus_size_t datasize) { int error, flags; if (ida->flags & IDA_INTERRUPTS) flags = BUS_DMA_WAITOK; else flags = BUS_DMA_NOWAIT; error = bus_dmamap_load(ida->buffer_dmat, qcb->dmamap, data, datasize, ida_data_cb, qcb, flags); if (error == EINPROGRESS) { ida->flags |= IDA_QFROZEN; error = 0; } return (error); } int ida_command(struct ida_softc *ida, int command, void *data, int datasize, int drive, u_int32_t pblkno, int flags) { struct ida_hardware_qcb *hwqcb; struct ida_qcb *qcb; int error; if (!dumping) mtx_assert(&ida->lock, MA_OWNED); qcb = ida_get_qcb(ida); if (qcb == NULL) { device_printf(ida->dev, "out of QCBs\n"); return (EAGAIN); } qcb->flags = flags | IDA_COMMAND; hwqcb = qcb->hwqcb; hwqcb->hdr.drive = drive; hwqcb->req.blkno = htole32(pblkno); hwqcb->req.bcount = htole16(howmany(datasize, DEV_BSIZE)); hwqcb->req.command = command; error = ida_map_qcb(ida, qcb, data, datasize); if (error == 0) { error = ida_wait(ida, qcb); /* Don't free QCB on a timeout in case it later completes. */ if (error) return (error); error = qcb->error; } /* XXX should have status returned here? */ /* XXX have "status pointer" area in QCB? */ ida_free_qcb(ida, qcb); return (error); } void ida_submit_buf(struct ida_softc *ida, struct bio *bp) { mtx_lock(&ida->lock); bioq_insert_tail(&ida->bio_queue, bp); ida_startio(ida); mtx_unlock(&ida->lock); } static void ida_startio(struct ida_softc *ida) { struct ida_hardware_qcb *hwqcb; struct ida_qcb *qcb; struct idad_softc *drv; struct bio *bp; int error; mtx_assert(&ida->lock, MA_OWNED); for (;;) { if (ida->flags & IDA_QFROZEN) return; bp = bioq_first(&ida->bio_queue); if (bp == NULL) return; /* no more buffers */ qcb = ida_get_qcb(ida); if (qcb == NULL) return; /* out of resources */ bioq_remove(&ida->bio_queue, bp); qcb->buf = bp; qcb->flags = bp->bio_cmd == BIO_READ ? DMA_DATA_IN : DMA_DATA_OUT; hwqcb = qcb->hwqcb; drv = bp->bio_driver1; hwqcb->hdr.drive = drv->drive; hwqcb->req.blkno = bp->bio_pblkno; hwqcb->req.bcount = howmany(bp->bio_bcount, DEV_BSIZE); hwqcb->req.command = bp->bio_cmd == BIO_READ ? CMD_READ : CMD_WRITE; error = ida_map_qcb(ida, qcb, bp->bio_data, bp->bio_bcount); if (error) { qcb->error = error; ida_done(ida, qcb); } } } static void ida_start(struct ida_softc *ida) { struct ida_qcb *qcb; if (!dumping) mtx_assert(&ida->lock, MA_OWNED); while ((qcb = STAILQ_FIRST(&ida->qcb_queue)) != NULL) { if (ida->cmd.fifo_full(ida)) break; STAILQ_REMOVE_HEAD(&ida->qcb_queue, link.stqe); /* * XXX * place the qcb on an active list? */ /* Set a timeout. */ if (!ida->qactive && !dumping) callout_reset(&ida->ch, hz * 5, ida_timeout, ida); ida->qactive++; qcb->state = QCB_ACTIVE; ida->cmd.submit(ida, qcb); } } static int ida_wait(struct ida_softc *ida, struct ida_qcb *qcb) { struct ida_qcb *qcb_done = NULL; bus_addr_t completed; int delay; if (!dumping) mtx_assert(&ida->lock, MA_OWNED); if (ida->flags & IDA_INTERRUPTS) { if (mtx_sleep(qcb, &ida->lock, PRIBIO, "idacmd", 5 * hz)) { qcb->state = QCB_TIMEDOUT; return (ETIMEDOUT); } return (0); } again: delay = 5 * 1000 * 100; /* 5 sec delay */ while ((completed = ida->cmd.done(ida)) == 0) { if (delay-- == 0) { qcb->state = QCB_TIMEDOUT; return (ETIMEDOUT); } DELAY(10); } qcb_done = idahwqcbptov(ida, completed & ~3); if (qcb_done != qcb) goto again; ida_done(ida, qcb); return (0); } void ida_intr(void *data) { struct ida_softc *ida; struct ida_qcb *qcb; bus_addr_t completed; ida = (struct ida_softc *)data; mtx_lock(&ida->lock); if (ida->cmd.int_pending(ida) == 0) { mtx_unlock(&ida->lock); return; /* not our interrupt */ } while ((completed = ida->cmd.done(ida)) != 0) { qcb = idahwqcbptov(ida, completed & ~3); if (qcb == NULL || qcb->state != QCB_ACTIVE) { device_printf(ida->dev, "ignoring completion %jx\n", (intmax_t)completed); continue; } /* Handle "Bad Command List" errors. */ if ((completed & 3) && (qcb->hwqcb->req.error == 0)) qcb->hwqcb->req.error = CMD_REJECTED; ida_done(ida, qcb); } ida_startio(ida); mtx_unlock(&ida->lock); } /* * should switch out command type; may be status, not just I/O. */ static void ida_done(struct ida_softc *ida, struct ida_qcb *qcb) { bus_dmasync_op_t op; int active, error = 0; /* * finish up command */ if (!dumping) mtx_assert(&ida->lock, MA_OWNED); active = (qcb->state != QCB_FREE); if (qcb->flags & DMA_DATA_TRANSFER && active) { switch (qcb->flags & DMA_DATA_TRANSFER) { case DMA_DATA_TRANSFER: op = BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE; break; case DMA_DATA_IN: op = BUS_DMASYNC_POSTREAD; break; default: KASSERT((qcb->flags & DMA_DATA_TRANSFER) == DMA_DATA_OUT, ("bad DMA data flags")); op = BUS_DMASYNC_POSTWRITE; break; } bus_dmamap_sync(ida->buffer_dmat, qcb->dmamap, op); bus_dmamap_unload(ida->buffer_dmat, qcb->dmamap); } if (active) bus_dmamap_sync(ida->hwqcb_dmat, ida->hwqcb_dmamap, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); if (qcb->hwqcb->req.error & SOFT_ERROR) { if (qcb->buf) device_printf(ida->dev, "soft %s error\n", qcb->buf->bio_cmd == BIO_READ ? "read" : "write"); else device_printf(ida->dev, "soft error\n"); } if (qcb->hwqcb->req.error & HARD_ERROR) { error = 1; if (qcb->buf) device_printf(ida->dev, "hard %s error\n", qcb->buf->bio_cmd == BIO_READ ? "read" : "write"); else device_printf(ida->dev, "hard error\n"); } if (qcb->hwqcb->req.error & CMD_REJECTED) { error = 1; device_printf(ida->dev, "invalid request\n"); } if (qcb->error) { error = 1; device_printf(ida->dev, "request failed to map: %d\n", qcb->error); } if (qcb->flags & IDA_COMMAND) { if (ida->flags & IDA_INTERRUPTS) wakeup(qcb); if (qcb->state == QCB_TIMEDOUT) ida_free_qcb(ida, qcb); } else { KASSERT(qcb->buf != NULL, ("ida_done(): qcb->buf is NULL!")); if (error) qcb->buf->bio_flags |= BIO_ERROR; idad_intr(qcb->buf); ida_free_qcb(ida, qcb); } if (!active) return; ida->qactive--; /* Reschedule or cancel timeout */ if (ida->qactive) callout_reset(&ida->ch, hz * 5, ida_timeout, ida); else callout_stop(&ida->ch); } static void ida_timeout(void *arg) { struct ida_softc *ida; ida = (struct ida_softc *)arg; device_printf(ida->dev, "%s() qactive %d\n", __func__, ida->qactive); if (ida->flags & IDA_INTERRUPTS) device_printf(ida->dev, "IDA_INTERRUPTS\n"); device_printf(ida->dev, "\t R_CMD_FIFO: %08x\n" "\t R_DONE_FIFO: %08x\n" "\t R_INT_MASK: %08x\n" "\t R_STATUS: %08x\n" "\tR_INT_PENDING: %08x\n", ida_inl(ida, R_CMD_FIFO), ida_inl(ida, R_DONE_FIFO), ida_inl(ida, R_INT_MASK), ida_inl(ida, R_STATUS), ida_inl(ida, R_INT_PENDING)); return; } /* * IOCTL stuff follows. */ struct cmd_info { int cmd; int len; int flags; }; static struct cmd_info *ida_cmd_lookup(int); static int ida_ioctl (struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { struct ida_softc *sc; struct ida_user_command *uc; struct cmd_info *ci; int len; int flags; int error; int data; void *daddr; sc = (struct ida_softc *)dev->si_drv1; uc = (struct ida_user_command *)addr; error = 0; switch (cmd) { case IDAIO_COMMAND: ci = ida_cmd_lookup(uc->command); if (ci == NULL) { error = EINVAL; break; } len = ci->len; flags = ci->flags; if (len) daddr = &uc->d.buf; else { daddr = &data; len = sizeof(data); } mtx_lock(&sc->lock); error = ida_command(sc, uc->command, daddr, len, uc->drive, uc->blkno, flags); mtx_unlock(&sc->lock); break; default: error = ENOIOCTL; break; } return (error); } static struct cmd_info ci_list[] = { { CMD_GET_LOG_DRV_INFO, sizeof(struct ida_drive_info), DMA_DATA_IN }, { CMD_GET_CTRL_INFO, sizeof(struct ida_controller_info), DMA_DATA_IN }, { CMD_SENSE_DRV_STATUS, sizeof(struct ida_drive_status), DMA_DATA_IN }, { CMD_START_RECOVERY, 0, 0 }, { CMD_GET_PHYS_DRV_INFO, sizeof(struct ida_phys_drv_info), DMA_DATA_TRANSFER }, { CMD_BLINK_DRV_LEDS, sizeof(struct ida_blink_drv_leds), DMA_DATA_OUT }, { CMD_SENSE_DRV_LEDS, sizeof(struct ida_blink_drv_leds), DMA_DATA_IN }, { CMD_GET_LOG_DRV_EXT, sizeof(struct ida_drive_info_ext), DMA_DATA_IN }, { CMD_RESET_CTRL, 0, 0 }, { CMD_GET_CONFIG, 0, 0 }, { CMD_SET_CONFIG, 0, 0 }, { CMD_LABEL_LOG_DRV, sizeof(struct ida_label_logical), DMA_DATA_OUT }, { CMD_SET_SURFACE_DELAY, 0, 0 }, { CMD_SENSE_BUS_PARAMS, 0, 0 }, { CMD_SENSE_SUBSYS_INFO, 0, 0 }, { CMD_SENSE_SURFACE_ATS, 0, 0 }, { CMD_PASSTHROUGH, 0, 0 }, { CMD_RESET_SCSI_DEV, 0, 0 }, { CMD_PAUSE_BG_ACT, 0, 0 }, { CMD_RESUME_BG_ACT, 0, 0 }, { CMD_START_FIRMWARE, 0, 0 }, { CMD_SENSE_DRV_ERR_LOG, 0, 0 }, { CMD_START_CPM, 0, 0 }, { CMD_SENSE_CP, 0, 0 }, { CMD_STOP_CPM, 0, 0 }, { CMD_FLUSH_CACHE, 0, 0 }, { CMD_ACCEPT_MEDIA_EXCH, 0, 0 }, { 0, 0, 0 } }; static struct cmd_info * ida_cmd_lookup (int command) { struct cmd_info *ci; ci = ci_list; while (ci->cmd) { if (ci->cmd == command) return (ci); ci++; } return (NULL); } Index: head/sys/dev/ida/ida_eisa.c =================================================================== --- head/sys/dev/ida/ida_eisa.c (revision 280346) +++ head/sys/dev/ida/ida_eisa.c (revision 280347) @@ -1,345 +1,345 @@ /*- * Copyright (c) 2000 Jonathan Lemon * Copyright (c) 1999 by Matthew N. Dodd * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IDA_EISA_IOPORT_START 0x0c88 #define IDA_EISA_IOPORT_LEN 0x0017 #define IDA_EISA_IRQ_REG 0x0cc0 #define IDA_EISA_IRQ_MASK 0xf0 #define IDA_EISA_IRQ_15 0x80 #define IDA_EISA_IRQ_14 0x40 #define IDA_EISA_IRQ_11 0x10 #define IDA_EISA_IRQ_10 0x20 static int ida_v1_fifo_full(struct ida_softc *ida) { u_int8_t status; status = ida_inb(ida, R_EISA_SYSTEM_DOORBELL); return ((status & EISA_CHANNEL_CLEAR) == 0); } static void ida_v1_submit(struct ida_softc *ida, struct ida_qcb *qcb) { u_int16_t size; /* * On these cards, this location is actually for control flags. * Set them to zero and pass in structure size via an I/O port. */ size = qcb->hwqcb->hdr.size << 2; qcb->hwqcb->hdr.size = 0; ida_outb(ida, R_EISA_SYSTEM_DOORBELL, EISA_CHANNEL_CLEAR); ida_outl(ida, R_EISA_LIST_ADDR, qcb->hwqcb_busaddr); ida_outw(ida, R_EISA_LIST_LEN, size); ida_outb(ida, R_EISA_LOCAL_DOORBELL, EISA_CHANNEL_BUSY); } static bus_addr_t ida_v1_done(struct ida_softc *ida) { struct ida_hardware_qcb *hwqcb; bus_addr_t completed; u_int8_t status; if ((ida_inb(ida, R_EISA_SYSTEM_DOORBELL) & EISA_CHANNEL_BUSY) == 0) return (0); ida_outb(ida, R_EISA_SYSTEM_DOORBELL, EISA_CHANNEL_BUSY); completed = ida_inl(ida, R_EISA_COMPLETE_ADDR); status = ida_inb(ida, R_EISA_LIST_STATUS); ida_outb(ida, R_EISA_LOCAL_DOORBELL, EISA_CHANNEL_CLEAR); if (completed != 0) { hwqcb = (struct ida_hardware_qcb *) ((bus_addr_t)ida->hwqcbs + ((completed & ~3) - ida->hwqcb_busaddr)); hwqcb->req.error = status; } return (completed); } static int ida_v1_int_pending(struct ida_softc *ida) { return (ida_inb(ida, R_EISA_SYSTEM_DOORBELL) & EISA_CHANNEL_BUSY); } static void ida_v1_int_enable(struct ida_softc *ida, int enable) { if (enable) { ida_outb(ida, R_EISA_SYSTEM_DOORBELL, ~EISA_CHANNEL_CLEAR); ida_outb(ida, R_EISA_LOCAL_DOORBELL, EISA_CHANNEL_BUSY); ida_outb(ida, R_EISA_INT_MASK, INT_ENABLE); ida_outb(ida, R_EISA_SYSTEM_MASK, INT_ENABLE); ida->flags |= IDA_INTERRUPTS; } else { ida_outb(ida, R_EISA_SYSTEM_MASK, INT_DISABLE); ida->flags &= ~IDA_INTERRUPTS; } } static int ida_v2_fifo_full(struct ida_softc *ida) { return (ida_inl(ida, R_CMD_FIFO) == 0); } static void ida_v2_submit(struct ida_softc *ida, struct ida_qcb *qcb) { ida_outl(ida, R_CMD_FIFO, qcb->hwqcb_busaddr); } static bus_addr_t ida_v2_done(struct ida_softc *ida) { return (ida_inl(ida, R_DONE_FIFO)); } static int ida_v2_int_pending(struct ida_softc *ida) { return (ida_inl(ida, R_INT_PENDING)); } static void ida_v2_int_enable(struct ida_softc *ida, int enable) { if (enable) ida->flags |= IDA_INTERRUPTS; else ida->flags &= ~IDA_INTERRUPTS; ida_outl(ida, R_INT_MASK, enable ? INT_ENABLE : INT_DISABLE); } static struct ida_access ida_v1_access = { ida_v1_fifo_full, ida_v1_submit, ida_v1_done, ida_v1_int_pending, ida_v1_int_enable, }; static struct ida_access ida_v2_access = { ida_v2_fifo_full, ida_v2_submit, ida_v2_done, ida_v2_int_pending, ida_v2_int_enable, }; static struct ida_board board_id[] = { { 0x0e114001, "Compaq IDA controller", &ida_v1_access, 0 }, { 0x0e114002, "Compaq IDA-2 controller", &ida_v1_access, 0 }, { 0x0e114010, "Compaq IAES controller", &ida_v1_access, 0 }, { 0x0e114020, "Compaq SMART array controller", &ida_v1_access, 0 }, { 0x0e114030, "Compaq SMART-2/E array controller", &ida_v2_access, 0 }, { 0, "", 0, 0 } }; static struct ida_board *ida_eisa_match(eisa_id_t); static int ida_eisa_probe(device_t); static int ida_eisa_attach(device_t); static device_method_t ida_eisa_methods[] = { DEVMETHOD(device_probe, ida_eisa_probe), DEVMETHOD(device_attach, ida_eisa_attach), DEVMETHOD(device_detach, ida_detach), { 0, 0 } }; static driver_t ida_eisa_driver = { "ida", ida_eisa_methods, sizeof(struct ida_softc) }; static devclass_t ida_devclass; static struct ida_board * ida_eisa_match(eisa_id_t id) { int i; for (i = 0; board_id[i].board; i++) if (board_id[i].board == id) return (&board_id[i]); return (NULL); } static int ida_eisa_probe(device_t dev) { struct ida_board *board; u_int32_t io_base; u_int irq = 0; board = ida_eisa_match(eisa_get_id(dev)); if (board == NULL) return (ENXIO); device_set_desc(dev, board->desc); io_base = (eisa_get_slot(dev) * EISA_SLOT_SIZE); switch (IDA_EISA_IRQ_MASK & (inb(IDA_EISA_IRQ_REG + io_base))) { case IDA_EISA_IRQ_15: irq = 15; break; case IDA_EISA_IRQ_14: irq = 14; break; case IDA_EISA_IRQ_11: irq = 11; break; case IDA_EISA_IRQ_10: irq = 10; break; default: device_printf(dev, "slot %d, illegal irq setting.\n", eisa_get_slot(dev)); return (ENXIO); } eisa_add_iospace(dev, (io_base + IDA_EISA_IOPORT_START), IDA_EISA_IOPORT_LEN, RESVADDR_NONE); eisa_add_intr(dev, irq, EISA_TRIGGER_LEVEL); /* XXX ??? */ return (0); } static int ida_eisa_attach(device_t dev) { struct ida_softc *ida; struct ida_board *board; int error; int rid; ida = device_get_softc(dev); ida->dev = dev; board = ida_eisa_match(eisa_get_id(dev)); ida->cmd = *board->accessor; ida->flags = board->flags; mtx_init(&ida->lock, "ida", NULL, MTX_DEF); callout_init_mtx(&ida->ch, &ida->lock, 0); ida->regs_res_type = SYS_RES_IOPORT; ida->regs_res_id = 0; ida->regs = bus_alloc_resource_any(dev, ida->regs_res_type, &ida->regs_res_id, RF_ACTIVE); if (ida->regs == NULL) { device_printf(dev, "can't allocate register resources\n"); return (ENOMEM); } error = bus_dma_tag_create( /* parent */ bus_get_dma_tag(dev), /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, - /* nsegments */ IDA_NSEG, + /* maxsize */ BUS_SPACE_MAXSIZE_32BIT, + /* nsegments */ BUS_SPACE_UNRESTRICTED, /* maxsegsize */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ NULL, /* lockarg */ NULL, &ida->parent_dmat); if (error != 0) { device_printf(dev, "can't allocate DMA tag\n"); ida_free(ida); return (ENOMEM); } rid = 0; ida->irq_res_type = SYS_RES_IRQ; ida->irq = bus_alloc_resource_any(dev, ida->irq_res_type, &rid, RF_ACTIVE | RF_SHAREABLE); if (ida->irq == NULL) { ida_free(ida); return (ENOMEM); } error = bus_setup_intr(dev, ida->irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, ida_intr, ida, &ida->ih); if (error) { device_printf(dev, "can't setup interrupt\n"); ida_free(ida); return (ENOMEM); } error = ida_init(ida); if (error) { ida_free(ida); return (error); } return (0); } DRIVER_MODULE(ida, eisa, ida_eisa_driver, ida_devclass, 0, 0); Index: head/sys/dev/ida/ida_pci.c =================================================================== --- head/sys/dev/ida/ida_pci.c (revision 280346) +++ head/sys/dev/ida/ida_pci.c (revision 280347) @@ -1,306 +1,306 @@ /*- * Copyright (c) 1999,2000 Jonathan Lemon * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IDA_PCI_MAX_DMA_ADDR 0xFFFFFFFF #define IDA_PCI_MAX_DMA_COUNT 0xFFFFFFFF #define IDA_PCI_MEMADDR PCIR_BAR(1) /* Mem I/O Address */ #define IDA_DEVICEID_SMART 0xAE100E11 #define IDA_DEVICEID_DEC_SMART 0x00461011 #define IDA_DEVICEID_NCR_53C1510 0x00101000 static int ida_v3_fifo_full(struct ida_softc *ida) { return (ida_inl(ida, R_CMD_FIFO) == 0); } static void ida_v3_submit(struct ida_softc *ida, struct ida_qcb *qcb) { ida_outl(ida, R_CMD_FIFO, qcb->hwqcb_busaddr); } static bus_addr_t ida_v3_done(struct ida_softc *ida) { bus_addr_t completed; completed = ida_inl(ida, R_DONE_FIFO); if (completed == -1) { return (0); /* fifo is empty */ } return (completed); } static int ida_v3_int_pending(struct ida_softc *ida) { return (ida_inl(ida, R_INT_PENDING)); } static void ida_v3_int_enable(struct ida_softc *ida, int enable) { if (enable) ida->flags |= IDA_INTERRUPTS; else ida->flags &= ~IDA_INTERRUPTS; ida_outl(ida, R_INT_MASK, enable ? INT_ENABLE : INT_DISABLE); } static int ida_v4_fifo_full(struct ida_softc *ida) { return (ida_inl(ida, R_42XX_REQUEST) != 0); } static void ida_v4_submit(struct ida_softc *ida, struct ida_qcb *qcb) { ida_outl(ida, R_42XX_REQUEST, qcb->hwqcb_busaddr); } static bus_addr_t ida_v4_done(struct ida_softc *ida) { bus_addr_t completed; completed = ida_inl(ida, R_42XX_REPLY); if (completed == -1) return (0); /* fifo is empty */ ida_outl(ida, R_42XX_REPLY, 0); /* confirm read */ return (completed); } static int ida_v4_int_pending(struct ida_softc *ida) { return (ida_inl(ida, R_42XX_STATUS) & STATUS_42XX_INT_PENDING); } static void ida_v4_int_enable(struct ida_softc *ida, int enable) { if (enable) ida->flags |= IDA_INTERRUPTS; else ida->flags &= ~IDA_INTERRUPTS; ida_outl(ida, R_42XX_INT_MASK, enable ? INT_ENABLE_42XX : INT_DISABLE_42XX); } static struct ida_access ida_v3_access = { ida_v3_fifo_full, ida_v3_submit, ida_v3_done, ida_v3_int_pending, ida_v3_int_enable, }; static struct ida_access ida_v4_access = { ida_v4_fifo_full, ida_v4_submit, ida_v4_done, ida_v4_int_pending, ida_v4_int_enable, }; static struct ida_board board_id[] = { { 0x40300E11, "Compaq SMART-2/P array controller", &ida_v3_access, 0 }, { 0x40310E11, "Compaq SMART-2SL array controller", &ida_v3_access, 0 }, { 0x40320E11, "Compaq Smart Array 3200 controller", &ida_v3_access, 0 }, { 0x40330E11, "Compaq Smart Array 3100ES controller", &ida_v3_access, 0 }, { 0x40340E11, "Compaq Smart Array 221 controller", &ida_v3_access, 0 }, { 0x40400E11, "Compaq Integrated Array controller", &ida_v4_access, IDA_FIRMWARE }, { 0x40480E11, "Compaq RAID LC2 controller", &ida_v4_access, IDA_FIRMWARE }, { 0x40500E11, "Compaq Smart Array 4200 controller", &ida_v4_access, 0 }, { 0x40510E11, "Compaq Smart Array 4250ES controller", &ida_v4_access, 0 }, { 0x40580E11, "Compaq Smart Array 431 controller", &ida_v4_access, 0 }, { 0, "", 0, 0 }, }; static int ida_pci_probe(device_t dev); static int ida_pci_attach(device_t dev); static device_method_t ida_pci_methods[] = { DEVMETHOD(device_probe, ida_pci_probe), DEVMETHOD(device_attach, ida_pci_attach), DEVMETHOD(device_detach, ida_detach), DEVMETHOD_END }; static driver_t ida_pci_driver = { "ida", ida_pci_methods, sizeof(struct ida_softc) }; static devclass_t ida_devclass; static struct ida_board * ida_pci_match(device_t dev) { int i; u_int32_t id, sub_id; id = pci_get_devid(dev); sub_id = pci_get_subdevice(dev) << 16 | pci_get_subvendor(dev); if (id == IDA_DEVICEID_SMART || id == IDA_DEVICEID_DEC_SMART || id == IDA_DEVICEID_NCR_53C1510) { for (i = 0; board_id[i].board; i++) if (board_id[i].board == sub_id) return (&board_id[i]); } return (NULL); } static int ida_pci_probe(device_t dev) { struct ida_board *board = ida_pci_match(dev); if (board != NULL) { device_set_desc(dev, board->desc); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int ida_pci_attach(device_t dev) { struct ida_board *board = ida_pci_match(dev); u_int32_t id = pci_get_devid(dev); struct ida_softc *ida; int error, rid; ida = (struct ida_softc *)device_get_softc(dev); ida->dev = dev; ida->cmd = *board->accessor; ida->flags = board->flags; mtx_init(&ida->lock, "ida", NULL, MTX_DEF); callout_init_mtx(&ida->ch, &ida->lock, 0); ida->regs_res_type = SYS_RES_MEMORY; ida->regs_res_id = IDA_PCI_MEMADDR; if (id == IDA_DEVICEID_DEC_SMART) ida->regs_res_id = PCIR_BAR(0); ida->regs = bus_alloc_resource_any(dev, ida->regs_res_type, &ida->regs_res_id, RF_ACTIVE); if (ida->regs == NULL) { device_printf(dev, "can't allocate memory resources\n"); return (ENOMEM); } error = bus_dma_tag_create( /* parent */ bus_get_dma_tag(dev), /* alignment */ 1, /* boundary */ 0, /* lowaddr */ BUS_SPACE_MAXADDR_32BIT, /* highaddr */ BUS_SPACE_MAXADDR, /* filter */ NULL, /* filterarg */ NULL, - /* maxsize */ MAXBSIZE, - /* nsegments */ IDA_NSEG, + /* maxsize */ BUS_SPACE_MAXSIZE_32BIT, + /* nsegments */ BUS_SPACE_UNRESTRICTED, /* maxsegsize */ BUS_SPACE_MAXSIZE_32BIT, /* flags */ BUS_DMA_ALLOCNOW, /* lockfunc */ NULL, /* lockarg */ NULL, &ida->parent_dmat); if (error != 0) { device_printf(dev, "can't allocate DMA tag\n"); ida_free(ida); return (ENOMEM); } rid = 0; ida->irq_res_type = SYS_RES_IRQ; ida->irq = bus_alloc_resource_any(dev, ida->irq_res_type, &rid, RF_ACTIVE | RF_SHAREABLE); if (ida->irq == NULL) { ida_free(ida); return (ENOMEM); } error = bus_setup_intr(dev, ida->irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, ida_intr, ida, &ida->ih); if (error) { device_printf(dev, "can't setup interrupt\n"); ida_free(ida); return (ENOMEM); } error = ida_init(ida); if (error) { ida_free(ida); return (error); } return (0); } DRIVER_MODULE(ida, pci, ida_pci_driver, ida_devclass, 0, 0); Index: head/sys/dev/if_ndis/if_ndis_pci.c =================================================================== --- head/sys/dev/if_ndis/if_ndis_pci.c (revision 280346) +++ head/sys/dev/if_ndis/if_ndis_pci.c (revision 280347) @@ -1,352 +1,353 @@ /*- * Copyright (c) 2003 * Bill Paul . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DEPEND(ndis, pci, 1, 1, 1); static int ndis_probe_pci (device_t); static int ndis_attach_pci (device_t); static struct resource_list *ndis_get_resource_list (device_t, device_t); static int ndis_devcompare (interface_type, struct ndis_pci_type *, device_t); extern int ndisdrv_modevent (module_t, int, void *); extern int ndis_attach (device_t); extern int ndis_shutdown (device_t); extern int ndis_detach (device_t); extern int ndis_suspend (device_t); extern int ndis_resume (device_t); static device_method_t ndis_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ndis_probe_pci), DEVMETHOD(device_attach, ndis_attach_pci), DEVMETHOD(device_detach, ndis_detach), DEVMETHOD(device_shutdown, ndis_shutdown), DEVMETHOD(device_suspend, ndis_suspend), DEVMETHOD(device_resume, ndis_resume), /* Bus interface */ DEVMETHOD(bus_get_resource_list, ndis_get_resource_list), { 0, 0 } }; static driver_t ndis_driver = { "ndis", ndis_methods, sizeof(struct ndis_softc) }; static devclass_t ndis_devclass; DRIVER_MODULE(ndis, pci, ndis_driver, ndis_devclass, ndisdrv_modevent, 0); static int ndis_devcompare(bustype, t, dev) interface_type bustype; struct ndis_pci_type *t; device_t dev; { uint16_t vid, did; uint32_t subsys; if (bustype != PCIBus) return(FALSE); vid = pci_get_vendor(dev); did = pci_get_device(dev); subsys = pci_get_subdevice(dev); subsys = (subsys << 16) | pci_get_subvendor(dev); while(t->ndis_name != NULL) { if ((t->ndis_vid == vid) && (t->ndis_did == did) && (t->ndis_subsys == subsys || t->ndis_subsys == 0)) { device_set_desc(dev, t->ndis_name); return(TRUE); } t++; } return(FALSE); } /* * Probe for an NDIS device. Check the PCI vendor and device * IDs against our list and return a device name if we find a match. */ static int ndis_probe_pci(dev) device_t dev; { driver_object *drv; struct drvdb_ent *db; drv = windrv_lookup(0, "PCI Bus"); if (drv == NULL) return(ENXIO); db = windrv_match((matchfuncptr)ndis_devcompare, dev); if (db != NULL) { /* Create PDO for this device instance */ windrv_create_pdo(drv, dev); return(0); } return(ENXIO); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ static int ndis_attach_pci(dev) device_t dev; { struct ndis_softc *sc; int unit, error = 0, rid; struct ndis_pci_type *t; int devidx = 0, defidx = 0; struct resource_list *rl; struct resource_list_entry *rle; struct drvdb_ent *db; uint16_t vid, did; uint32_t subsys; sc = device_get_softc(dev); unit = device_get_unit(dev); sc->ndis_dev = dev; db = windrv_match((matchfuncptr)ndis_devcompare, dev); if (db == NULL) return (ENXIO); sc->ndis_dobj = db->windrv_object; sc->ndis_regvals = db->windrv_regvals; /* * Map control/status registers. */ pci_enable_busmaster(dev); rl = BUS_GET_RESOURCE_LIST(device_get_parent(dev), dev); if (rl != NULL) { STAILQ_FOREACH(rle, rl, link) { switch (rle->type) { case SYS_RES_IOPORT: sc->ndis_io_rid = rle->rid; sc->ndis_res_io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->ndis_io_rid, RF_ACTIVE); if (sc->ndis_res_io == NULL) { device_printf(dev, "couldn't map iospace\n"); error = ENXIO; goto fail; } break; case SYS_RES_MEMORY: if (sc->ndis_res_altmem != NULL && sc->ndis_res_mem != NULL) { device_printf(dev, "too many memory resources\n"); error = ENXIO; goto fail; } if (sc->ndis_res_mem) { sc->ndis_altmem_rid = rle->rid; sc->ndis_res_altmem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->ndis_altmem_rid, RF_ACTIVE); if (sc->ndis_res_altmem == NULL) { device_printf(dev, "couldn't map alt " "memory\n"); error = ENXIO; goto fail; } } else { sc->ndis_mem_rid = rle->rid; sc->ndis_res_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->ndis_mem_rid, RF_ACTIVE); if (sc->ndis_res_mem == NULL) { device_printf(dev, "couldn't map memory\n"); error = ENXIO; goto fail; } } break; case SYS_RES_IRQ: rid = rle->rid; sc->ndis_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->ndis_irq == NULL) { device_printf(dev, "couldn't map interrupt\n"); error = ENXIO; goto fail; } break; default: break; } sc->ndis_rescnt++; } } /* * If the BIOS did not set up an interrupt for this device, * the resource traversal code above will fail to set up * an IRQ resource. This is usually a bad thing, so try to * force the allocation of an interrupt here. If one was * not assigned to us by the BIOS, bus_alloc_resource() * should route one for us. */ if (sc->ndis_irq == NULL) { rid = 0; sc->ndis_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->ndis_irq == NULL) { device_printf(dev, "couldn't route interrupt\n"); error = ENXIO; goto fail; } sc->ndis_rescnt++; } /* * Allocate the parent bus DMA tag appropriate for PCI. */ #define NDIS_NSEG_NEW 32 error = bus_dma_tag_create(bus_get_dma_tag(dev),/* PCI parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT,/* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, NDIS_NSEG_NEW,/* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + NDIS_NSEG_NEW, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT,/* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->ndis_parent_tag); if (error) goto fail; sc->ndis_iftype = PCIBus; /* Figure out exactly which device we matched. */ vid = pci_get_vendor(dev); did = pci_get_device(dev); subsys = pci_get_subdevice(dev); subsys = (subsys << 16) | pci_get_subvendor(dev); t = db->windrv_devlist; while(t->ndis_name != NULL) { if (t->ndis_vid == vid && t->ndis_did == did) { if (t->ndis_subsys == 0) defidx = devidx; else if (t->ndis_subsys == subsys) break; } t++; devidx++; } if (t->ndis_name == NULL) sc->ndis_devidx = defidx; else sc->ndis_devidx = devidx; error = ndis_attach(dev); fail: return(error); } static struct resource_list * ndis_get_resource_list(dev, child) device_t dev; device_t child; { struct ndis_softc *sc; sc = device_get_softc(dev); return (BUS_GET_RESOURCE_LIST(device_get_parent(sc->ndis_dev), dev)); } Index: head/sys/dev/iir/iir.c =================================================================== --- head/sys/dev/iir/iir.c (revision 280346) +++ head/sys/dev/iir/iir.c (revision 280347) @@ -1,1911 +1,1912 @@ /*- * Copyright (c) 2000-04 ICP vortex GmbH * Copyright (c) 2002-04 Intel Corporation * Copyright (c) 2003-04 Adaptec Inc. * All Rights Reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * iir.c: SCSI dependant code for the Intel Integrated RAID Controller driver * * Written by: Achim Leubner * Fixes/Additions: Boji Tony Kannanthanam * * credits: Niklas Hallqvist; OpenBSD driver for the ICP Controllers. * Mike Smith; Some driver source code. * FreeBSD.ORG; Great O/S to work on and for. * * $Id: iir.c 1.5 2004/03/30 10:17:53 achim Exp $" */ #include __FBSDID("$FreeBSD$"); #define _IIR_C_ /* #include "opt_iir.h" */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_GDTBUF, "iirbuf", "iir driver buffer"); #ifdef GDT_DEBUG int gdt_debug = GDT_DEBUG; #ifdef __SERIAL__ #define MAX_SERBUF 160 static void ser_init(void); static void ser_puts(char *str); static void ser_putc(int c); static char strbuf[MAX_SERBUF+1]; #ifdef __COM2__ #define COM_BASE 0x2f8 #else #define COM_BASE 0x3f8 #endif static void ser_init() { unsigned port=COM_BASE; outb(port+3, 0x80); outb(port+1, 0); /* 19200 Baud, if 9600: outb(12,port) */ outb(port, 6); outb(port+3, 3); outb(port+1, 0); } static void ser_puts(char *str) { char *ptr; ser_init(); for (ptr=str;*ptr;++ptr) ser_putc((int)(*ptr)); } static void ser_putc(int c) { unsigned port=COM_BASE; while ((inb(port+5) & 0x20)==0); outb(port, c); if (c==0x0a) { while ((inb(port+5) & 0x20)==0); outb(port, 0x0d); } } int ser_printf(const char *fmt, ...) { va_list args; int i; va_start(args,fmt); i = vsprintf(strbuf,fmt,args); ser_puts(strbuf); va_end(args); return i; } #endif #endif /* controller cnt. */ int gdt_cnt = 0; /* event buffer */ static gdt_evt_str ebuffer[GDT_MAX_EVENTS]; static int elastidx, eoldidx; static struct mtx elock; MTX_SYSINIT(iir_elock, &elock, "iir events", MTX_DEF); /* statistics */ gdt_statist_t gdt_stat; /* Definitions for our use of the SIM private CCB area */ #define ccb_sim_ptr spriv_ptr0 #define ccb_priority spriv_field1 static void iir_action(struct cam_sim *sim, union ccb *ccb); static int iir_intr_locked(struct gdt_softc *gdt); static void iir_poll(struct cam_sim *sim); static void iir_shutdown(void *arg, int howto); static void iir_timeout(void *arg); static void gdt_eval_mapping(u_int32_t size, int *cyls, int *heads, int *secs); static int gdt_internal_cmd(struct gdt_softc *gdt, struct gdt_ccb *gccb, u_int8_t service, u_int16_t opcode, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3); static int gdt_wait(struct gdt_softc *gdt, struct gdt_ccb *ccb, int timeout); static struct gdt_ccb *gdt_get_ccb(struct gdt_softc *gdt); static int gdt_sync_event(struct gdt_softc *gdt, int service, u_int8_t index, struct gdt_ccb *gccb); static int gdt_async_event(struct gdt_softc *gdt, int service); static struct gdt_ccb *gdt_raw_cmd(struct gdt_softc *gdt, union ccb *ccb); static struct gdt_ccb *gdt_cache_cmd(struct gdt_softc *gdt, union ccb *ccb); static struct gdt_ccb *gdt_ioctl_cmd(struct gdt_softc *gdt, gdt_ucmd_t *ucmd); static void gdt_internal_cache_cmd(struct gdt_softc *gdt, union ccb *ccb); static void gdtmapmem(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error); static void gdtexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error); int iir_init(struct gdt_softc *gdt) { u_int16_t cdev_cnt; int i, id, drv_cyls, drv_hds, drv_secs; struct gdt_ccb *gccb; GDT_DPRINTF(GDT_D_DEBUG, ("iir_init()\n")); gdt->sc_state = GDT_POLLING; gdt_clear_events(); bzero(&gdt_stat, sizeof(gdt_statist_t)); SLIST_INIT(&gdt->sc_free_gccb); SLIST_INIT(&gdt->sc_pending_gccb); TAILQ_INIT(&gdt->sc_ccb_queue); TAILQ_INIT(&gdt->sc_ucmd_queue); /* DMA tag for mapping buffers into device visible space. */ if (bus_dma_tag_create(gdt->sc_parent_dmat, /*alignment*/1, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/MAXBSIZE, /*nsegments*/GDT_MAXSG, + /*maxsize*/DFLTPHYS, + /*nsegments*/GDT_MAXSG, /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, /*flags*/BUS_DMA_ALLOCNOW, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&gdt->sc_lock, &gdt->sc_buffer_dmat) != 0) { device_printf(gdt->sc_devnode, "bus_dma_tag_create(..., gdt->sc_buffer_dmat) failed\n"); return (1); } gdt->sc_init_level++; /* DMA tag for our ccb structures */ if (bus_dma_tag_create(gdt->sc_parent_dmat, /*alignment*/1, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, GDT_MAXCMDS * GDT_SCRATCH_SZ, /* maxsize */ /*nsegments*/1, /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&gdt->sc_lock, &gdt->sc_gcscratch_dmat) != 0) { device_printf(gdt->sc_devnode, "bus_dma_tag_create(...,gdt->sc_gcscratch_dmat) failed\n"); return (1); } gdt->sc_init_level++; /* Allocation for our ccb scratch area */ if (bus_dmamem_alloc(gdt->sc_gcscratch_dmat, (void **)&gdt->sc_gcscratch, BUS_DMA_NOWAIT, &gdt->sc_gcscratch_dmamap) != 0) { device_printf(gdt->sc_devnode, "bus_dmamem_alloc(...,&gdt->sc_gccbs,...) failed\n"); return (1); } gdt->sc_init_level++; /* And permanently map them */ bus_dmamap_load(gdt->sc_gcscratch_dmat, gdt->sc_gcscratch_dmamap, gdt->sc_gcscratch, GDT_MAXCMDS * GDT_SCRATCH_SZ, gdtmapmem, &gdt->sc_gcscratch_busbase, /*flags*/0); gdt->sc_init_level++; /* Clear them out. */ bzero(gdt->sc_gcscratch, GDT_MAXCMDS * GDT_SCRATCH_SZ); /* Initialize the ccbs */ gdt->sc_gccbs = malloc(sizeof(struct gdt_ccb) * GDT_MAXCMDS, M_GDTBUF, M_NOWAIT | M_ZERO); if (gdt->sc_gccbs == NULL) { device_printf(gdt->sc_devnode, "no memory for gccbs.\n"); return (1); } for (i = GDT_MAXCMDS-1; i >= 0; i--) { gccb = &gdt->sc_gccbs[i]; gccb->gc_cmd_index = i + 2; gccb->gc_flags = GDT_GCF_UNUSED; gccb->gc_map_flag = FALSE; if (bus_dmamap_create(gdt->sc_buffer_dmat, /*flags*/0, &gccb->gc_dmamap) != 0) return(1); gccb->gc_map_flag = TRUE; gccb->gc_scratch = &gdt->sc_gcscratch[GDT_SCRATCH_SZ * i]; gccb->gc_scratch_busbase = gdt->sc_gcscratch_busbase + GDT_SCRATCH_SZ * i; callout_init_mtx(&gccb->gc_timeout, &gdt->sc_lock, 0); SLIST_INSERT_HEAD(&gdt->sc_free_gccb, gccb, sle); } gdt->sc_init_level++; /* create the control device */ gdt->sc_dev = gdt_make_dev(gdt); /* allocate ccb for gdt_internal_cmd() */ mtx_lock(&gdt->sc_lock); gccb = gdt_get_ccb(gdt); if (gccb == NULL) { mtx_unlock(&gdt->sc_lock); device_printf(gdt->sc_devnode, "No free command index found\n"); return (1); } bzero(gccb->gc_cmd, GDT_CMD_SZ); if (!gdt_internal_cmd(gdt, gccb, GDT_SCREENSERVICE, GDT_INIT, 0, 0, 0)) { device_printf(gdt->sc_devnode, "Screen service initialization error %d\n", gdt->sc_status); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_UNFREEZE_IO, 0, 0, 0); if (!gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_INIT, GDT_LINUX_OS, 0, 0)) { device_printf(gdt->sc_devnode, "Cache service initialization error %d\n", gdt->sc_status); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } cdev_cnt = (u_int16_t)gdt->sc_info; gdt->sc_fw_vers = gdt->sc_service; /* Detect number of buses */ gdt_enc32(gccb->gc_scratch + GDT_IOC_VERSION, GDT_IOC_NEWEST); gccb->gc_scratch[GDT_IOC_LIST_ENTRIES] = GDT_MAXBUS; gccb->gc_scratch[GDT_IOC_FIRST_CHAN] = 0; gccb->gc_scratch[GDT_IOC_LAST_CHAN] = GDT_MAXBUS - 1; gdt_enc32(gccb->gc_scratch + GDT_IOC_LIST_OFFSET, GDT_IOC_HDR_SZ); if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_IOCTL, GDT_IOCHAN_RAW_DESC, GDT_INVALID_CHANNEL, GDT_IOC_HDR_SZ + GDT_MAXBUS * GDT_RAWIOC_SZ)) { gdt->sc_bus_cnt = gccb->gc_scratch[GDT_IOC_CHAN_COUNT]; for (i = 0; i < gdt->sc_bus_cnt; i++) { id = gccb->gc_scratch[GDT_IOC_HDR_SZ + i * GDT_RAWIOC_SZ + GDT_RAWIOC_PROC_ID]; gdt->sc_bus_id[i] = id < GDT_MAXID_FC ? id : 0xff; } } else { /* New method failed, use fallback. */ for (i = 0; i < GDT_MAXBUS; i++) { gdt_enc32(gccb->gc_scratch + GDT_GETCH_CHANNEL_NO, i); if (!gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_IOCTL, GDT_SCSI_CHAN_CNT | GDT_L_CTRL_PATTERN, GDT_IO_CHANNEL | GDT_INVALID_CHANNEL, GDT_GETCH_SZ)) { if (i == 0) { device_printf(gdt->sc_devnode, "Cannot get channel count, " "error %d\n", gdt->sc_status); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } break; } gdt->sc_bus_id[i] = (gccb->gc_scratch[GDT_GETCH_SIOP_ID] < GDT_MAXID_FC) ? gccb->gc_scratch[GDT_GETCH_SIOP_ID] : 0xff; } gdt->sc_bus_cnt = i; } /* add one "virtual" channel for the host drives */ gdt->sc_virt_bus = gdt->sc_bus_cnt; gdt->sc_bus_cnt++; if (!gdt_internal_cmd(gdt, gccb, GDT_SCSIRAWSERVICE, GDT_INIT, 0, 0, 0)) { device_printf(gdt->sc_devnode, "Raw service initialization error %d\n", gdt->sc_status); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } /* Set/get features raw service (scatter/gather) */ gdt->sc_raw_feat = 0; if (gdt_internal_cmd(gdt, gccb, GDT_SCSIRAWSERVICE, GDT_SET_FEAT, GDT_SCATTER_GATHER, 0, 0)) { if (gdt_internal_cmd(gdt, gccb, GDT_SCSIRAWSERVICE, GDT_GET_FEAT, 0, 0, 0)) { gdt->sc_raw_feat = gdt->sc_info; if (!(gdt->sc_info & GDT_SCATTER_GATHER)) { panic("%s: Scatter/Gather Raw Service " "required but not supported!\n", device_get_nameunit(gdt->sc_devnode)); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } } } /* Set/get features cache service (scatter/gather) */ gdt->sc_cache_feat = 0; if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_SET_FEAT, 0, GDT_SCATTER_GATHER, 0)) { if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_GET_FEAT, 0, 0, 0)) { gdt->sc_cache_feat = gdt->sc_info; if (!(gdt->sc_info & GDT_SCATTER_GATHER)) { panic("%s: Scatter/Gather Cache Service " "required but not supported!\n", device_get_nameunit(gdt->sc_devnode)); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); return (1); } } } /* OEM */ gdt_enc32(gccb->gc_scratch + GDT_OEM_VERSION, 0x01); gdt_enc32(gccb->gc_scratch + GDT_OEM_BUFSIZE, sizeof(gdt_oem_record_t)); if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_IOCTL, GDT_OEM_STR_RECORD, GDT_INVALID_CHANNEL, sizeof(gdt_oem_str_record_t))) { strncpy(gdt->oem_name, ((gdt_oem_str_record_t *) gccb->gc_scratch)->text.scsi_host_drive_inquiry_vendor_id, 7); gdt->oem_name[7]='\0'; } else { /* Old method, based on PCI ID */ if (gdt->sc_vendor == INTEL_VENDOR_ID_IIR) strcpy(gdt->oem_name,"Intel "); else strcpy(gdt->oem_name,"ICP "); } /* Scan for cache devices */ for (i = 0; i < cdev_cnt && i < GDT_MAX_HDRIVES; i++) { if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_INFO, i, 0, 0)) { gdt->sc_hdr[i].hd_present = 1; gdt->sc_hdr[i].hd_size = gdt->sc_info; /* * Evaluate mapping (sectors per head, heads per cyl) */ gdt->sc_hdr[i].hd_size &= ~GDT_SECS32; if (gdt->sc_info2 == 0) gdt_eval_mapping(gdt->sc_hdr[i].hd_size, &drv_cyls, &drv_hds, &drv_secs); else { drv_hds = gdt->sc_info2 & 0xff; drv_secs = (gdt->sc_info2 >> 8) & 0xff; drv_cyls = gdt->sc_hdr[i].hd_size / drv_hds / drv_secs; } gdt->sc_hdr[i].hd_heads = drv_hds; gdt->sc_hdr[i].hd_secs = drv_secs; /* Round the size */ gdt->sc_hdr[i].hd_size = drv_cyls * drv_hds * drv_secs; if (gdt_internal_cmd(gdt, gccb, GDT_CACHESERVICE, GDT_DEVTYPE, i, 0, 0)) gdt->sc_hdr[i].hd_devtype = gdt->sc_info; } } GDT_DPRINTF(GDT_D_INIT, ("dpmem %x %d-bus %d cache device%s\n", gdt->sc_dpmembase, gdt->sc_bus_cnt, cdev_cnt, cdev_cnt == 1 ? "" : "s")); gdt_free_ccb(gdt, gccb); mtx_unlock(&gdt->sc_lock); atomic_add_int(&gdt_cnt, 1); return (0); } void iir_free(struct gdt_softc *gdt) { int i; GDT_DPRINTF(GDT_D_INIT, ("iir_free()\n")); switch (gdt->sc_init_level) { default: gdt_destroy_dev(gdt->sc_dev); case 5: for (i = GDT_MAXCMDS-1; i >= 0; i--) if (gdt->sc_gccbs[i].gc_map_flag) { callout_drain(&gdt->sc_gccbs[i].gc_timeout); bus_dmamap_destroy(gdt->sc_buffer_dmat, gdt->sc_gccbs[i].gc_dmamap); } bus_dmamap_unload(gdt->sc_gcscratch_dmat, gdt->sc_gcscratch_dmamap); free(gdt->sc_gccbs, M_GDTBUF); case 4: bus_dmamem_free(gdt->sc_gcscratch_dmat, gdt->sc_gcscratch, gdt->sc_gcscratch_dmamap); case 3: bus_dma_tag_destroy(gdt->sc_gcscratch_dmat); case 2: bus_dma_tag_destroy(gdt->sc_buffer_dmat); case 1: bus_dma_tag_destroy(gdt->sc_parent_dmat); case 0: break; } } void iir_attach(struct gdt_softc *gdt) { struct cam_devq *devq; int i; GDT_DPRINTF(GDT_D_INIT, ("iir_attach()\n")); /* * Create the device queue for our SIM. * XXX Throttle this down since the card has problems under load. */ devq = cam_simq_alloc(32); if (devq == NULL) return; for (i = 0; i < gdt->sc_bus_cnt; i++) { /* * Construct our SIM entry */ gdt->sims[i] = cam_sim_alloc(iir_action, iir_poll, "iir", gdt, device_get_unit(gdt->sc_devnode), &gdt->sc_lock, /*untagged*/1, /*tagged*/GDT_MAXCMDS, devq); mtx_lock(&gdt->sc_lock); if (xpt_bus_register(gdt->sims[i], gdt->sc_devnode, i) != CAM_SUCCESS) { cam_sim_free(gdt->sims[i], /*free_devq*/i == 0); mtx_unlock(&gdt->sc_lock); break; } if (xpt_create_path(&gdt->paths[i], /*periph*/NULL, cam_sim_path(gdt->sims[i]), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_bus_deregister(cam_sim_path(gdt->sims[i])); cam_sim_free(gdt->sims[i], /*free_devq*/i == 0); mtx_unlock(&gdt->sc_lock); break; } mtx_unlock(&gdt->sc_lock); } if (i > 0) EVENTHANDLER_REGISTER(shutdown_final, iir_shutdown, gdt, SHUTDOWN_PRI_DEFAULT); /* iir_watchdog(gdt); */ gdt->sc_state = GDT_NORMAL; } static void gdt_eval_mapping(u_int32_t size, int *cyls, int *heads, int *secs) { *cyls = size / GDT_HEADS / GDT_SECS; if (*cyls < GDT_MAXCYLS) { *heads = GDT_HEADS; *secs = GDT_SECS; } else { /* Too high for 64 * 32 */ *cyls = size / GDT_MEDHEADS / GDT_MEDSECS; if (*cyls < GDT_MAXCYLS) { *heads = GDT_MEDHEADS; *secs = GDT_MEDSECS; } else { /* Too high for 127 * 63 */ *cyls = size / GDT_BIGHEADS / GDT_BIGSECS; *heads = GDT_BIGHEADS; *secs = GDT_BIGSECS; } } } static int gdt_wait(struct gdt_softc *gdt, struct gdt_ccb *gccb, int timeout) { int rv = 0; GDT_DPRINTF(GDT_D_INIT, ("gdt_wait(%p, %p, %d)\n", gdt, gccb, timeout)); gdt->sc_state |= GDT_POLL_WAIT; do { if (iir_intr_locked(gdt) == gccb->gc_cmd_index) { rv = 1; break; } DELAY(1); } while (--timeout); gdt->sc_state &= ~GDT_POLL_WAIT; while (gdt->sc_test_busy(gdt)) DELAY(1); /* XXX correct? */ return (rv); } static int gdt_internal_cmd(struct gdt_softc *gdt, struct gdt_ccb *gccb, u_int8_t service, u_int16_t opcode, u_int32_t arg1, u_int32_t arg2, u_int32_t arg3) { int retries; GDT_DPRINTF(GDT_D_CMD, ("gdt_internal_cmd(%p, %d, %d, %d, %d, %d)\n", gdt, service, opcode, arg1, arg2, arg3)); bzero(gccb->gc_cmd, GDT_CMD_SZ); for (retries = GDT_RETRIES; ; ) { gccb->gc_service = service; gccb->gc_flags = GDT_GCF_INTERNAL; gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, opcode); switch (service) { case GDT_CACHESERVICE: if (opcode == GDT_IOCTL) { gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_SUBFUNC, arg1); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_CHANNEL, arg2); gdt_enc16(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_PARAM_SIZE, (u_int16_t)arg3); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_P_PARAM, gccb->gc_scratch_busbase); } else { gdt_enc16(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_DEVICENO, (u_int16_t)arg1); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, arg2); } break; case GDT_SCSIRAWSERVICE: gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_DIRECTION, arg1); gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_BUS] = (u_int8_t)arg2; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_TARGET] = (u_int8_t)arg3; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_LUN] = (u_int8_t)(arg3 >> 8); } gdt->sc_set_sema0(gdt); gccb->gc_cmd_len = GDT_CMD_SZ; gdt->sc_cmd_off = 0; gdt->sc_cmd_cnt = 0; gdt->sc_copy_cmd(gdt, gccb); gdt->sc_release_event(gdt); DELAY(20); if (!gdt_wait(gdt, gccb, GDT_POLL_TIMEOUT)) return (0); if (gdt->sc_status != GDT_S_BSY || --retries == 0) break; DELAY(1); } return (gdt->sc_status == GDT_S_OK); } static struct gdt_ccb * gdt_get_ccb(struct gdt_softc *gdt) { struct gdt_ccb *gccb; GDT_DPRINTF(GDT_D_QUEUE, ("gdt_get_ccb(%p)\n", gdt)); mtx_assert(&gdt->sc_lock, MA_OWNED); gccb = SLIST_FIRST(&gdt->sc_free_gccb); if (gccb != NULL) { SLIST_REMOVE_HEAD(&gdt->sc_free_gccb, sle); SLIST_INSERT_HEAD(&gdt->sc_pending_gccb, gccb, sle); ++gdt_stat.cmd_index_act; if (gdt_stat.cmd_index_act > gdt_stat.cmd_index_max) gdt_stat.cmd_index_max = gdt_stat.cmd_index_act; } return (gccb); } void gdt_free_ccb(struct gdt_softc *gdt, struct gdt_ccb *gccb) { GDT_DPRINTF(GDT_D_QUEUE, ("gdt_free_ccb(%p, %p)\n", gdt, gccb)); mtx_assert(&gdt->sc_lock, MA_OWNED); gccb->gc_flags = GDT_GCF_UNUSED; SLIST_REMOVE(&gdt->sc_pending_gccb, gccb, gdt_ccb, sle); SLIST_INSERT_HEAD(&gdt->sc_free_gccb, gccb, sle); --gdt_stat.cmd_index_act; if (gdt->sc_state & GDT_SHUTDOWN) wakeup(gccb); } void gdt_next(struct gdt_softc *gdt) { union ccb *ccb; gdt_ucmd_t *ucmd; struct cam_sim *sim; int bus, target, lun; int next_cmd; struct ccb_scsiio *csio; struct ccb_hdr *ccbh; struct gdt_ccb *gccb = NULL; u_int8_t cmd; GDT_DPRINTF(GDT_D_QUEUE, ("gdt_next(%p)\n", gdt)); mtx_assert(&gdt->sc_lock, MA_OWNED); if (gdt->sc_test_busy(gdt)) { if (!(gdt->sc_state & GDT_POLLING)) { return; } while (gdt->sc_test_busy(gdt)) DELAY(1); } gdt->sc_cmd_cnt = gdt->sc_cmd_off = 0; next_cmd = TRUE; for (;;) { /* I/Os in queue? controller ready? */ if (!TAILQ_FIRST(&gdt->sc_ucmd_queue) && !TAILQ_FIRST(&gdt->sc_ccb_queue)) break; /* 1.: I/Os without ccb (IOCTLs) */ ucmd = TAILQ_FIRST(&gdt->sc_ucmd_queue); if (ucmd != NULL) { TAILQ_REMOVE(&gdt->sc_ucmd_queue, ucmd, links); if ((gccb = gdt_ioctl_cmd(gdt, ucmd)) == NULL) { TAILQ_INSERT_HEAD(&gdt->sc_ucmd_queue, ucmd, links); break; } break; /* wenn mehrere Kdos. zulassen: if (!gdt_polling) continue; */ } /* 2.: I/Os with ccb */ ccb = (union ccb *)TAILQ_FIRST(&gdt->sc_ccb_queue); /* ist dann immer != NULL, da oben getestet */ sim = (struct cam_sim *)ccb->ccb_h.ccb_sim_ptr; bus = cam_sim_bus(sim); target = ccb->ccb_h.target_id; lun = ccb->ccb_h.target_lun; TAILQ_REMOVE(&gdt->sc_ccb_queue, &ccb->ccb_h, sim_links.tqe); --gdt_stat.req_queue_act; /* ccb->ccb_h.func_code is XPT_SCSI_IO */ GDT_DPRINTF(GDT_D_QUEUE, ("XPT_SCSI_IO flags 0x%x)\n", ccb->ccb_h.flags)); csio = &ccb->csio; ccbh = &ccb->ccb_h; cmd = csio->cdb_io.cdb_bytes[0]; /* Max CDB length is 12 bytes */ if (csio->cdb_len > 12) { ccbh->status = CAM_REQ_INVALID; --gdt_stat.io_count_act; xpt_done(ccb); } else if (bus != gdt->sc_virt_bus) { /* raw service command */ if ((gccb = gdt_raw_cmd(gdt, ccb)) == NULL) { TAILQ_INSERT_HEAD(&gdt->sc_ccb_queue, &ccb->ccb_h, sim_links.tqe); ++gdt_stat.req_queue_act; if (gdt_stat.req_queue_act > gdt_stat.req_queue_max) gdt_stat.req_queue_max = gdt_stat.req_queue_act; next_cmd = FALSE; } } else if (target >= GDT_MAX_HDRIVES || !gdt->sc_hdr[target].hd_present || lun != 0) { ccbh->status = CAM_DEV_NOT_THERE; --gdt_stat.io_count_act; xpt_done(ccb); } else { /* cache service command */ if (cmd == READ_6 || cmd == WRITE_6 || cmd == READ_10 || cmd == WRITE_10) { if ((gccb = gdt_cache_cmd(gdt, ccb)) == NULL) { TAILQ_INSERT_HEAD(&gdt->sc_ccb_queue, &ccb->ccb_h, sim_links.tqe); ++gdt_stat.req_queue_act; if (gdt_stat.req_queue_act > gdt_stat.req_queue_max) gdt_stat.req_queue_max = gdt_stat.req_queue_act; next_cmd = FALSE; } } else { gdt_internal_cache_cmd(gdt, ccb); } } if ((gdt->sc_state & GDT_POLLING) || !next_cmd) break; } if (gdt->sc_cmd_cnt > 0) gdt->sc_release_event(gdt); if ((gdt->sc_state & GDT_POLLING) && gdt->sc_cmd_cnt > 0) { gdt_wait(gdt, gccb, GDT_POLL_TIMEOUT); } } static struct gdt_ccb * gdt_raw_cmd(struct gdt_softc *gdt, union ccb *ccb) { struct gdt_ccb *gccb; struct cam_sim *sim; int error; GDT_DPRINTF(GDT_D_CMD, ("gdt_raw_cmd(%p, %p)\n", gdt, ccb)); if (roundup(GDT_CMD_UNION + GDT_RAW_SZ, sizeof(u_int32_t)) + gdt->sc_cmd_off + GDT_DPMEM_COMMAND_OFFSET > gdt->sc_ic_all_size) { GDT_DPRINTF(GDT_D_INVALID, ("%s: gdt_raw_cmd(): DPMEM overflow\n", device_get_nameunit(gdt->sc_devnode))); return (NULL); } gccb = gdt_get_ccb(gdt); if (gccb == NULL) { GDT_DPRINTF(GDT_D_INVALID, ("%s: No free command index found\n", device_get_nameunit(gdt->sc_devnode))); return (gccb); } bzero(gccb->gc_cmd, GDT_CMD_SZ); sim = (struct cam_sim *)ccb->ccb_h.ccb_sim_ptr; gccb->gc_ccb = ccb; gccb->gc_service = GDT_SCSIRAWSERVICE; gccb->gc_flags = GDT_GCF_SCSI; if (gdt->sc_cmd_cnt == 0) gdt->sc_set_sema0(gdt); gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, GDT_WRITE); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_DIRECTION, (ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN ? GDT_DATA_IN : GDT_DATA_OUT); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SDLEN, ccb->csio.dxfer_len); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_CLEN, ccb->csio.cdb_len); bcopy(ccb->csio.cdb_io.cdb_bytes, gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_CMD, ccb->csio.cdb_len); gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_TARGET] = ccb->ccb_h.target_id; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_LUN] = ccb->ccb_h.target_lun; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_BUS] = cam_sim_bus(sim); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SENSE_LEN, sizeof(struct scsi_sense_data)); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SENSE_DATA, gccb->gc_scratch_busbase); error = bus_dmamap_load_ccb(gdt->sc_buffer_dmat, gccb->gc_dmamap, ccb, gdtexecuteccb, gccb, /*flags*/0); if (error == EINPROGRESS) { xpt_freeze_simq(sim, 1); gccb->gc_ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } return (gccb); } static struct gdt_ccb * gdt_cache_cmd(struct gdt_softc *gdt, union ccb *ccb) { struct gdt_ccb *gccb; struct cam_sim *sim; u_int8_t *cmdp; u_int16_t opcode; u_int32_t blockno, blockcnt; int error; GDT_DPRINTF(GDT_D_CMD, ("gdt_cache_cmd(%p, %p)\n", gdt, ccb)); if (roundup(GDT_CMD_UNION + GDT_CACHE_SZ, sizeof(u_int32_t)) + gdt->sc_cmd_off + GDT_DPMEM_COMMAND_OFFSET > gdt->sc_ic_all_size) { GDT_DPRINTF(GDT_D_INVALID, ("%s: gdt_cache_cmd(): DPMEM overflow\n", device_get_nameunit(gdt->sc_devnode))); return (NULL); } gccb = gdt_get_ccb(gdt); if (gccb == NULL) { GDT_DPRINTF(GDT_D_DEBUG, ("%s: No free command index found\n", device_get_nameunit(gdt->sc_devnode))); return (gccb); } bzero(gccb->gc_cmd, GDT_CMD_SZ); sim = (struct cam_sim *)ccb->ccb_h.ccb_sim_ptr; gccb->gc_ccb = ccb; gccb->gc_service = GDT_CACHESERVICE; gccb->gc_flags = GDT_GCF_SCSI; if (gdt->sc_cmd_cnt == 0) gdt->sc_set_sema0(gdt); gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); cmdp = ccb->csio.cdb_io.cdb_bytes; opcode = (*cmdp == WRITE_6 || *cmdp == WRITE_10) ? GDT_WRITE : GDT_READ; if ((gdt->sc_state & GDT_SHUTDOWN) && opcode == GDT_WRITE) opcode = GDT_WRITE_THR; gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, opcode); gdt_enc16(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_DEVICENO, ccb->ccb_h.target_id); if (ccb->csio.cdb_len == 6) { struct scsi_rw_6 *rw = (struct scsi_rw_6 *)cmdp; blockno = scsi_3btoul(rw->addr) & ((SRW_TOPADDR<<16) | 0xffff); blockcnt = rw->length ? rw->length : 0x100; } else { struct scsi_rw_10 *rw = (struct scsi_rw_10 *)cmdp; blockno = scsi_4btoul(rw->addr); blockcnt = scsi_2btoul(rw->length); } gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, blockno); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKCNT, blockcnt); error = bus_dmamap_load_ccb(gdt->sc_buffer_dmat, gccb->gc_dmamap, ccb, gdtexecuteccb, gccb, /*flags*/0); if (error == EINPROGRESS) { xpt_freeze_simq(sim, 1); gccb->gc_ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } return (gccb); } static struct gdt_ccb * gdt_ioctl_cmd(struct gdt_softc *gdt, gdt_ucmd_t *ucmd) { struct gdt_ccb *gccb; u_int32_t cnt; GDT_DPRINTF(GDT_D_DEBUG, ("gdt_ioctl_cmd(%p, %p)\n", gdt, ucmd)); gccb = gdt_get_ccb(gdt); if (gccb == NULL) { GDT_DPRINTF(GDT_D_DEBUG, ("%s: No free command index found\n", device_get_nameunit(gdt->sc_devnode))); return (gccb); } bzero(gccb->gc_cmd, GDT_CMD_SZ); gccb->gc_ucmd = ucmd; gccb->gc_service = ucmd->service; gccb->gc_flags = GDT_GCF_IOCTL; /* check DPMEM space, copy data buffer from user space */ if (ucmd->service == GDT_CACHESERVICE) { if (ucmd->OpCode == GDT_IOCTL) { gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_IOCTL_SZ, sizeof(u_int32_t)); cnt = ucmd->u.ioctl.param_size; if (cnt > GDT_SCRATCH_SZ) { device_printf(gdt->sc_devnode, "Scratch buffer too small (%d/%d)\n", GDT_SCRATCH_SZ, cnt); gdt_free_ccb(gdt, gccb); return (NULL); } } else { gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_CACHE_SG_LST + GDT_SG_SZ, sizeof(u_int32_t)); cnt = ucmd->u.cache.BlockCnt * GDT_SECTOR_SIZE; if (cnt > GDT_SCRATCH_SZ) { device_printf(gdt->sc_devnode, "Scratch buffer too small (%d/%d)\n", GDT_SCRATCH_SZ, cnt); gdt_free_ccb(gdt, gccb); return (NULL); } } } else { gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_RAW_SG_LST + GDT_SG_SZ, sizeof(u_int32_t)); cnt = ucmd->u.raw.sdlen; if (cnt + ucmd->u.raw.sense_len > GDT_SCRATCH_SZ) { device_printf(gdt->sc_devnode, "Scratch buffer too small (%d/%d)\n", GDT_SCRATCH_SZ, cnt + ucmd->u.raw.sense_len); gdt_free_ccb(gdt, gccb); return (NULL); } } if (cnt != 0) bcopy(ucmd->data, gccb->gc_scratch, cnt); if (gdt->sc_cmd_off + gccb->gc_cmd_len + GDT_DPMEM_COMMAND_OFFSET > gdt->sc_ic_all_size) { GDT_DPRINTF(GDT_D_INVALID, ("%s: gdt_ioctl_cmd(): DPMEM overflow\n", device_get_nameunit(gdt->sc_devnode))); gdt_free_ccb(gdt, gccb); return (NULL); } if (gdt->sc_cmd_cnt == 0) gdt->sc_set_sema0(gdt); /* fill cmd structure */ gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, ucmd->OpCode); if (ucmd->service == GDT_CACHESERVICE) { if (ucmd->OpCode == GDT_IOCTL) { /* IOCTL */ gdt_enc16(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_PARAM_SIZE, ucmd->u.ioctl.param_size); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_SUBFUNC, ucmd->u.ioctl.subfunc); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_CHANNEL, ucmd->u.ioctl.channel); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_IOCTL_P_PARAM, gccb->gc_scratch_busbase); } else { /* cache service command */ gdt_enc16(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_DEVICENO, ucmd->u.cache.DeviceNo); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKNO, ucmd->u.cache.BlockNo); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_BLOCKCNT, ucmd->u.cache.BlockCnt); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_DESTADDR, 0xffffffffUL); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_CANZ, 1); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + GDT_SG_PTR, gccb->gc_scratch_busbase); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + GDT_SG_LEN, ucmd->u.cache.BlockCnt * GDT_SECTOR_SIZE); } } else { /* raw service command */ gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_DIRECTION, ucmd->u.raw.direction); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SDATA, 0xffffffffUL); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SDLEN, ucmd->u.raw.sdlen); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_CLEN, ucmd->u.raw.clen); bcopy(ucmd->u.raw.cmd, gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_CMD, 12); gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_TARGET] = ucmd->u.raw.target; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_LUN] = ucmd->u.raw.lun; gccb->gc_cmd[GDT_CMD_UNION + GDT_RAW_BUS] = ucmd->u.raw.bus; gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SENSE_LEN, ucmd->u.raw.sense_len); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SENSE_DATA, gccb->gc_scratch_busbase + ucmd->u.raw.sdlen); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_RANZ, 1); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_LST + GDT_SG_PTR, gccb->gc_scratch_busbase); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_LST + GDT_SG_LEN, ucmd->u.raw.sdlen); } gdt_stat.sg_count_act = 1; gdt->sc_copy_cmd(gdt, gccb); return (gccb); } static void gdt_internal_cache_cmd(struct gdt_softc *gdt,union ccb *ccb) { int t; t = ccb->ccb_h.target_id; GDT_DPRINTF(GDT_D_CMD, ("gdt_internal_cache_cmd(%p, %p, 0x%x, %d)\n", gdt, ccb, ccb->csio.cdb_io.cdb_bytes[0], t)); switch (ccb->csio.cdb_io.cdb_bytes[0]) { case TEST_UNIT_READY: case START_STOP: break; case REQUEST_SENSE: GDT_DPRINTF(GDT_D_MISC, ("REQUEST_SENSE\n")); break; case INQUIRY: { struct scsi_inquiry_data inq; size_t copylen = MIN(sizeof(inq), ccb->csio.dxfer_len); bzero(&inq, sizeof(inq)); inq.device = (gdt->sc_hdr[t].hd_devtype & 4) ? T_CDROM : T_DIRECT; inq.dev_qual2 = (gdt->sc_hdr[t].hd_devtype & 1) ? 0x80 : 0; inq.version = SCSI_REV_2; inq.response_format = 2; inq.additional_length = 32; inq.flags = SID_CmdQue | SID_Sync; strncpy(inq.vendor, gdt->oem_name, sizeof(inq.vendor)); snprintf(inq.product, sizeof(inq.product), "Host Drive #%02d", t); strncpy(inq.revision, " ", sizeof(inq.revision)); bcopy(&inq, ccb->csio.data_ptr, copylen ); if( ccb->csio.dxfer_len > copylen ) bzero( ccb->csio.data_ptr+copylen, ccb->csio.dxfer_len - copylen ); break; } case MODE_SENSE_6: { struct mpd_data { struct scsi_mode_hdr_6 hd; struct scsi_mode_block_descr bd; struct scsi_control_page cp; } mpd; size_t copylen = MIN(sizeof(mpd), ccb->csio.dxfer_len); u_int8_t page; /*mpd = (struct mpd_data *)ccb->csio.data_ptr;*/ bzero(&mpd, sizeof(mpd)); mpd.hd.datalen = sizeof(struct scsi_mode_hdr_6) + sizeof(struct scsi_mode_block_descr); mpd.hd.dev_specific = (gdt->sc_hdr[t].hd_devtype & 2) ? 0x80 : 0; mpd.hd.block_descr_len = sizeof(struct scsi_mode_block_descr); mpd.bd.block_len[0] = (GDT_SECTOR_SIZE & 0x00ff0000) >> 16; mpd.bd.block_len[1] = (GDT_SECTOR_SIZE & 0x0000ff00) >> 8; mpd.bd.block_len[2] = (GDT_SECTOR_SIZE & 0x000000ff); bcopy(&mpd, ccb->csio.data_ptr, copylen ); if( ccb->csio.dxfer_len > copylen ) bzero( ccb->csio.data_ptr+copylen, ccb->csio.dxfer_len - copylen ); page=((struct scsi_mode_sense_6 *)ccb->csio.cdb_io.cdb_bytes)->page; switch (page) { default: GDT_DPRINTF(GDT_D_MISC, ("MODE_SENSE_6: page 0x%x\n", page)); break; } break; } case READ_CAPACITY: { struct scsi_read_capacity_data rcd; size_t copylen = MIN(sizeof(rcd), ccb->csio.dxfer_len); /*rcd = (struct scsi_read_capacity_data *)ccb->csio.data_ptr;*/ bzero(&rcd, sizeof(rcd)); scsi_ulto4b(gdt->sc_hdr[t].hd_size - 1, rcd.addr); scsi_ulto4b(GDT_SECTOR_SIZE, rcd.length); bcopy(&rcd, ccb->csio.data_ptr, copylen ); if( ccb->csio.dxfer_len > copylen ) bzero( ccb->csio.data_ptr+copylen, ccb->csio.dxfer_len - copylen ); break; } default: GDT_DPRINTF(GDT_D_MISC, ("gdt_internal_cache_cmd(%d) unknown\n", ccb->csio.cdb_io.cdb_bytes[0])); break; } ccb->ccb_h.status |= CAM_REQ_CMP; --gdt_stat.io_count_act; xpt_done(ccb); } static void gdtmapmem(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { bus_addr_t *busaddrp; busaddrp = (bus_addr_t *)arg; *busaddrp = dm_segs->ds_addr; } static void gdtexecuteccb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { struct gdt_ccb *gccb; union ccb *ccb; struct gdt_softc *gdt; int i; gccb = (struct gdt_ccb *)arg; ccb = gccb->gc_ccb; gdt = cam_sim_softc((struct cam_sim *)ccb->ccb_h.ccb_sim_ptr); mtx_assert(&gdt->sc_lock, MA_OWNED); GDT_DPRINTF(GDT_D_CMD, ("gdtexecuteccb(%p, %p, %p, %d, %d)\n", gdt, gccb, dm_segs, nseg, error)); gdt_stat.sg_count_act = nseg; if (nseg > gdt_stat.sg_count_max) gdt_stat.sg_count_max = nseg; /* Copy the segments into our SG list */ if (gccb->gc_service == GDT_CACHESERVICE) { for (i = 0; i < nseg; ++i) { gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + i * GDT_SG_SZ + GDT_SG_PTR, dm_segs->ds_addr); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_LST + i * GDT_SG_SZ + GDT_SG_LEN, dm_segs->ds_len); dm_segs++; } gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_SG_CANZ, nseg); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_CACHE_DESTADDR, 0xffffffffUL); gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_CACHE_SG_LST + nseg * GDT_SG_SZ, sizeof(u_int32_t)); } else { for (i = 0; i < nseg; ++i) { gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_LST + i * GDT_SG_SZ + GDT_SG_PTR, dm_segs->ds_addr); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_LST + i * GDT_SG_SZ + GDT_SG_LEN, dm_segs->ds_len); dm_segs++; } gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SG_RANZ, nseg); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_RAW_SDATA, 0xffffffffUL); gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_RAW_SG_LST + nseg * GDT_SG_SZ, sizeof(u_int32_t)); } if (nseg != 0) { bus_dmamap_sync(gdt->sc_buffer_dmat, gccb->gc_dmamap, (ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); } /* We must NOT abort the command here if CAM_REQ_INPROG is not set, * because command semaphore is already set! */ ccb->ccb_h.status |= CAM_SIM_QUEUED; /* timeout handling */ callout_reset_sbt(&gccb->gc_timeout, SBT_1MS * ccb->ccb_h.timeout, 0, iir_timeout, (caddr_t)gccb, 0); gdt->sc_copy_cmd(gdt, gccb); } static void iir_action( struct cam_sim *sim, union ccb *ccb ) { struct gdt_softc *gdt; int bus, target, lun; gdt = (struct gdt_softc *)cam_sim_softc( sim ); mtx_assert(&gdt->sc_lock, MA_OWNED); ccb->ccb_h.ccb_sim_ptr = sim; bus = cam_sim_bus(sim); target = ccb->ccb_h.target_id; lun = ccb->ccb_h.target_lun; GDT_DPRINTF(GDT_D_CMD, ("iir_action(%p) func 0x%x cmd 0x%x bus %d target %d lun %d\n", gdt, ccb->ccb_h.func_code, ccb->csio.cdb_io.cdb_bytes[0], bus, target, lun)); ++gdt_stat.io_count_act; if (gdt_stat.io_count_act > gdt_stat.io_count_max) gdt_stat.io_count_max = gdt_stat.io_count_act; switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: TAILQ_INSERT_TAIL(&gdt->sc_ccb_queue, &ccb->ccb_h, sim_links.tqe); ++gdt_stat.req_queue_act; if (gdt_stat.req_queue_act > gdt_stat.req_queue_max) gdt_stat.req_queue_max = gdt_stat.req_queue_act; gdt_next(gdt); break; case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; --gdt_stat.io_count_act; xpt_done(ccb); break; case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; --gdt_stat.io_count_act; xpt_done(ccb); break; case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; if (cts->type == CTS_TYPE_USER_SETTINGS) { spi->flags = CTS_SPI_FLAGS_DISC_ENB; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; spi->sync_period = 25; /* 10MHz */ if (spi->sync_period != 0) spi->sync_offset = 15; spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; ccb->ccb_h.status = CAM_REQ_CMP; } else { ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; } --gdt_stat.io_count_act; xpt_done(ccb); break; } case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg; u_int32_t secs_per_cylinder; ccg = &ccb->ccg; ccg->heads = gdt->sc_hdr[target].hd_heads; ccg->secs_per_track = gdt->sc_hdr[target].hd_secs; secs_per_cylinder = ccg->heads * ccg->secs_per_track; ccg->cylinders = ccg->volume_size / secs_per_cylinder; ccb->ccb_h.status = CAM_REQ_CMP; --gdt_stat.io_count_act; xpt_done(ccb); break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ { /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_CMP; --gdt_stat.io_count_act; xpt_done(ccb); break; } case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; --gdt_stat.io_count_act; xpt_done(ccb); break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE; cpi->hba_inquiry |= PI_WIDE_16; cpi->target_sprt = 1; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; if (bus == gdt->sc_virt_bus) cpi->max_target = GDT_MAX_HDRIVES - 1; else if (gdt->sc_class & GDT_FC) cpi->max_target = GDT_MAXID_FC - 1; else cpi->max_target = GDT_MAXID - 1; cpi->max_lun = 7; cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = bus; cpi->initiator_id = (bus == gdt->sc_virt_bus ? 127 : gdt->sc_bus_id[bus]); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); if (gdt->sc_vendor == INTEL_VENDOR_ID_IIR) strncpy(cpi->hba_vid, "Intel Corp.", HBA_IDLEN); else strncpy(cpi->hba_vid, "ICP vortex ", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; --gdt_stat.io_count_act; xpt_done(ccb); break; } default: GDT_DPRINTF(GDT_D_INVALID, ("gdt_next(%p) cmd 0x%x invalid\n", gdt, ccb->ccb_h.func_code)); ccb->ccb_h.status = CAM_REQ_INVALID; --gdt_stat.io_count_act; xpt_done(ccb); break; } } static void iir_poll( struct cam_sim *sim ) { struct gdt_softc *gdt; gdt = (struct gdt_softc *)cam_sim_softc( sim ); GDT_DPRINTF(GDT_D_CMD, ("iir_poll sim %p gdt %p\n", sim, gdt)); iir_intr_locked(gdt); } static void iir_timeout(void *arg) { GDT_DPRINTF(GDT_D_TIMEOUT, ("iir_timeout(%p)\n", gccb)); } static void iir_shutdown( void *arg, int howto ) { struct gdt_softc *gdt; struct gdt_ccb *gccb; gdt_ucmd_t *ucmd; int i; gdt = (struct gdt_softc *)arg; GDT_DPRINTF(GDT_D_CMD, ("iir_shutdown(%p, %d)\n", gdt, howto)); device_printf(gdt->sc_devnode, "Flushing all Host Drives. Please wait ... "); /* allocate ucmd buffer */ ucmd = malloc(sizeof(gdt_ucmd_t), M_GDTBUF, M_NOWAIT); if (ucmd == NULL) { printf("\n"); device_printf(gdt->sc_devnode, "iir_shutdown(): Cannot allocate resource\n"); return; } bzero(ucmd, sizeof(gdt_ucmd_t)); /* wait for pending IOs */ mtx_lock(&gdt->sc_lock); gdt->sc_state = GDT_SHUTDOWN; if ((gccb = SLIST_FIRST(&gdt->sc_pending_gccb)) != NULL) mtx_sleep(gccb, &gdt->sc_lock, PCATCH | PRIBIO, "iirshw", 100 * hz); /* flush */ for (i = 0; i < GDT_MAX_HDRIVES; ++i) { if (gdt->sc_hdr[i].hd_present) { ucmd->service = GDT_CACHESERVICE; ucmd->OpCode = GDT_FLUSH; ucmd->u.cache.DeviceNo = i; TAILQ_INSERT_TAIL(&gdt->sc_ucmd_queue, ucmd, links); ucmd->complete_flag = FALSE; gdt_next(gdt); if (!ucmd->complete_flag) mtx_sleep(ucmd, &gdt->sc_lock, PCATCH | PRIBIO, "iirshw", 10 * hz); } } mtx_unlock(&gdt->sc_lock); free(ucmd, M_DEVBUF); printf("Done.\n"); } void iir_intr(void *arg) { struct gdt_softc *gdt = arg; mtx_lock(&gdt->sc_lock); iir_intr_locked(gdt); mtx_unlock(&gdt->sc_lock); } int iir_intr_locked(struct gdt_softc *gdt) { struct gdt_intr_ctx ctx; struct gdt_ccb *gccb; gdt_ucmd_t *ucmd; u_int32_t cnt; GDT_DPRINTF(GDT_D_INTR, ("gdt_intr(%p)\n", gdt)); mtx_assert(&gdt->sc_lock, MA_OWNED); /* If polling and we were not called from gdt_wait, just return */ if ((gdt->sc_state & GDT_POLLING) && !(gdt->sc_state & GDT_POLL_WAIT)) return (0); ctx.istatus = gdt->sc_get_status(gdt); if (ctx.istatus == 0x00) { gdt->sc_status = GDT_S_NO_STATUS; return (ctx.istatus); } gdt->sc_intr(gdt, &ctx); gdt->sc_status = ctx.cmd_status; gdt->sc_service = ctx.service; gdt->sc_info = ctx.info; gdt->sc_info2 = ctx.info2; if (ctx.istatus == GDT_ASYNCINDEX) { gdt_async_event(gdt, ctx.service); return (ctx.istatus); } if (ctx.istatus == GDT_SPEZINDEX) { GDT_DPRINTF(GDT_D_INVALID, ("%s: Service unknown or not initialized!\n", device_get_nameunit(gdt->sc_devnode))); gdt->sc_dvr.size = sizeof(gdt->sc_dvr.eu.driver); gdt->sc_dvr.eu.driver.ionode = gdt->sc_hanum; gdt_store_event(GDT_ES_DRIVER, 4, &gdt->sc_dvr); return (ctx.istatus); } gccb = &gdt->sc_gccbs[ctx.istatus - 2]; ctx.service = gccb->gc_service; switch (gccb->gc_flags) { case GDT_GCF_UNUSED: GDT_DPRINTF(GDT_D_INVALID, ("%s: Index (%d) to unused command!\n", device_get_nameunit(gdt->sc_devnode), ctx.istatus)); gdt->sc_dvr.size = sizeof(gdt->sc_dvr.eu.driver); gdt->sc_dvr.eu.driver.ionode = gdt->sc_hanum; gdt->sc_dvr.eu.driver.index = ctx.istatus; gdt_store_event(GDT_ES_DRIVER, 1, &gdt->sc_dvr); gdt_free_ccb(gdt, gccb); break; case GDT_GCF_INTERNAL: break; case GDT_GCF_IOCTL: ucmd = gccb->gc_ucmd; if (gdt->sc_status == GDT_S_BSY) { GDT_DPRINTF(GDT_D_DEBUG, ("iir_intr(%p) ioctl: gccb %p busy\n", gdt, gccb)); TAILQ_INSERT_HEAD(&gdt->sc_ucmd_queue, ucmd, links); } else { ucmd->status = gdt->sc_status; ucmd->info = gdt->sc_info; ucmd->complete_flag = TRUE; if (ucmd->service == GDT_CACHESERVICE) { if (ucmd->OpCode == GDT_IOCTL) { cnt = ucmd->u.ioctl.param_size; if (cnt != 0) bcopy(gccb->gc_scratch, ucmd->data, cnt); } else { cnt = ucmd->u.cache.BlockCnt * GDT_SECTOR_SIZE; if (cnt != 0) bcopy(gccb->gc_scratch, ucmd->data, cnt); } } else { cnt = ucmd->u.raw.sdlen; if (cnt != 0) bcopy(gccb->gc_scratch, ucmd->data, cnt); if (ucmd->u.raw.sense_len != 0) bcopy(gccb->gc_scratch, ucmd->data, cnt); } gdt_free_ccb(gdt, gccb); /* wakeup */ wakeup(ucmd); } gdt_next(gdt); break; default: gdt_free_ccb(gdt, gccb); gdt_sync_event(gdt, ctx.service, ctx.istatus, gccb); gdt_next(gdt); break; } return (ctx.istatus); } int gdt_async_event(struct gdt_softc *gdt, int service) { struct gdt_ccb *gccb; GDT_DPRINTF(GDT_D_INTR, ("gdt_async_event(%p, %d)\n", gdt, service)); if (service == GDT_SCREENSERVICE) { if (gdt->sc_status == GDT_MSG_REQUEST) { while (gdt->sc_test_busy(gdt)) DELAY(1); gccb = gdt_get_ccb(gdt); if (gccb == NULL) { device_printf(gdt->sc_devnode, "No free command index found\n"); return (1); } bzero(gccb->gc_cmd, GDT_CMD_SZ); gccb->gc_service = service; gccb->gc_flags = GDT_GCF_SCREEN; gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, GDT_READ); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_HANDLE, GDT_MSG_INV_HANDLE); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_ADDR, gccb->gc_scratch_busbase); gdt->sc_set_sema0(gdt); gdt->sc_cmd_off = 0; gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_SCREEN_SZ, sizeof(u_int32_t)); gdt->sc_cmd_cnt = 0; gdt->sc_copy_cmd(gdt, gccb); device_printf(gdt->sc_devnode, "[PCI %d/%d] ", gdt->sc_bus, gdt->sc_slot); gdt->sc_release_event(gdt); } } else { if ((gdt->sc_fw_vers & 0xff) >= 0x1a) { gdt->sc_dvr.size = 0; gdt->sc_dvr.eu.async.ionode = gdt->sc_hanum; gdt->sc_dvr.eu.async.status = gdt->sc_status; /* severity and event_string already set! */ } else { gdt->sc_dvr.size = sizeof(gdt->sc_dvr.eu.async); gdt->sc_dvr.eu.async.ionode = gdt->sc_hanum; gdt->sc_dvr.eu.async.service = service; gdt->sc_dvr.eu.async.status = gdt->sc_status; gdt->sc_dvr.eu.async.info = gdt->sc_info; *(u_int32_t *)gdt->sc_dvr.eu.async.scsi_coord = gdt->sc_info2; } gdt_store_event(GDT_ES_ASYNC, service, &gdt->sc_dvr); device_printf(gdt->sc_devnode, "%s\n", gdt->sc_dvr.event_string); } return (0); } int gdt_sync_event(struct gdt_softc *gdt, int service, u_int8_t index, struct gdt_ccb *gccb) { union ccb *ccb; GDT_DPRINTF(GDT_D_INTR, ("gdt_sync_event(%p, %d, %d, %p)\n", gdt,service,index,gccb)); ccb = gccb->gc_ccb; if (service == GDT_SCREENSERVICE) { u_int32_t msg_len; msg_len = gdt_dec32(gccb->gc_scratch + GDT_SCR_MSG_LEN); if (msg_len) if (!(gccb->gc_scratch[GDT_SCR_MSG_ANSWER] && gccb->gc_scratch[GDT_SCR_MSG_EXT])) { gccb->gc_scratch[GDT_SCR_MSG_TEXT + msg_len] = '\0'; printf("%s",&gccb->gc_scratch[GDT_SCR_MSG_TEXT]); } if (gccb->gc_scratch[GDT_SCR_MSG_EXT] && !gccb->gc_scratch[GDT_SCR_MSG_ANSWER]) { while (gdt->sc_test_busy(gdt)) DELAY(1); bzero(gccb->gc_cmd, GDT_CMD_SZ); gccb = gdt_get_ccb(gdt); if (gccb == NULL) { device_printf(gdt->sc_devnode, "No free command index found\n"); return (1); } gccb->gc_service = service; gccb->gc_flags = GDT_GCF_SCREEN; gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, GDT_READ); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_HANDLE, gccb->gc_scratch[GDT_SCR_MSG_HANDLE]); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_ADDR, gccb->gc_scratch_busbase); gdt->sc_set_sema0(gdt); gdt->sc_cmd_off = 0; gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_SCREEN_SZ, sizeof(u_int32_t)); gdt->sc_cmd_cnt = 0; gdt->sc_copy_cmd(gdt, gccb); gdt->sc_release_event(gdt); return (0); } if (gccb->gc_scratch[GDT_SCR_MSG_ANSWER] && gdt_dec32(gccb->gc_scratch + GDT_SCR_MSG_ALEN)) { /* default answers (getchar() not possible) */ if (gdt_dec32(gccb->gc_scratch + GDT_SCR_MSG_ALEN) == 1) { gdt_enc32(gccb->gc_scratch + GDT_SCR_MSG_ALEN, 0); gdt_enc32(gccb->gc_scratch + GDT_SCR_MSG_LEN, 1); gccb->gc_scratch[GDT_SCR_MSG_TEXT] = 0; } else { gdt_enc32(gccb->gc_scratch + GDT_SCR_MSG_ALEN, gdt_dec32(gccb->gc_scratch + GDT_SCR_MSG_ALEN) - 2); gdt_enc32(gccb->gc_scratch + GDT_SCR_MSG_LEN, 2); gccb->gc_scratch[GDT_SCR_MSG_TEXT] = 1; gccb->gc_scratch[GDT_SCR_MSG_TEXT + 1] = 0; } gccb->gc_scratch[GDT_SCR_MSG_EXT] = 0; gccb->gc_scratch[GDT_SCR_MSG_ANSWER] = 0; while (gdt->sc_test_busy(gdt)) DELAY(1); bzero(gccb->gc_cmd, GDT_CMD_SZ); gccb = gdt_get_ccb(gdt); if (gccb == NULL) { device_printf(gdt->sc_devnode, "No free command index found\n"); return (1); } gccb->gc_service = service; gccb->gc_flags = GDT_GCF_SCREEN; gdt_enc32(gccb->gc_cmd + GDT_CMD_COMMANDINDEX, gccb->gc_cmd_index); gdt_enc16(gccb->gc_cmd + GDT_CMD_OPCODE, GDT_WRITE); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_HANDLE, gccb->gc_scratch[GDT_SCR_MSG_HANDLE]); gdt_enc32(gccb->gc_cmd + GDT_CMD_UNION + GDT_SCREEN_MSG_ADDR, gccb->gc_scratch_busbase); gdt->sc_set_sema0(gdt); gdt->sc_cmd_off = 0; gccb->gc_cmd_len = roundup(GDT_CMD_UNION + GDT_SCREEN_SZ, sizeof(u_int32_t)); gdt->sc_cmd_cnt = 0; gdt->sc_copy_cmd(gdt, gccb); gdt->sc_release_event(gdt); return (0); } printf("\n"); return (0); } else { callout_stop(&gccb->gc_timeout); if (gdt->sc_status == GDT_S_BSY) { GDT_DPRINTF(GDT_D_DEBUG, ("gdt_sync_event(%p) gccb %p busy\n", gdt, gccb)); TAILQ_INSERT_HEAD(&gdt->sc_ccb_queue, &ccb->ccb_h, sim_links.tqe); ++gdt_stat.req_queue_act; if (gdt_stat.req_queue_act > gdt_stat.req_queue_max) gdt_stat.req_queue_max = gdt_stat.req_queue_act; return (2); } bus_dmamap_sync(gdt->sc_buffer_dmat, gccb->gc_dmamap, (ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(gdt->sc_buffer_dmat, gccb->gc_dmamap); ccb->csio.resid = 0; if (gdt->sc_status == GDT_S_OK) { ccb->ccb_h.status |= CAM_REQ_CMP; ccb->ccb_h.status &= ~CAM_SIM_QUEUED; } else { /* error */ if (gccb->gc_service == GDT_CACHESERVICE) { struct scsi_sense_data *sense; ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; ccb->ccb_h.status &= ~CAM_SIM_QUEUED; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; bzero(&ccb->csio.sense_data, ccb->csio.sense_len); sense = &ccb->csio.sense_data; scsi_set_sense_data(sense, /*sense_format*/ SSD_TYPE_NONE, /*current_error*/ 1, /*sense_key*/ SSD_KEY_NOT_READY, /*asc*/ 0x4, /*ascq*/ 0x01, SSD_ELEM_NONE); gdt->sc_dvr.size = sizeof(gdt->sc_dvr.eu.sync); gdt->sc_dvr.eu.sync.ionode = gdt->sc_hanum; gdt->sc_dvr.eu.sync.service = service; gdt->sc_dvr.eu.sync.status = gdt->sc_status; gdt->sc_dvr.eu.sync.info = gdt->sc_info; gdt->sc_dvr.eu.sync.hostdrive = ccb->ccb_h.target_id; if (gdt->sc_status >= 0x8000) gdt_store_event(GDT_ES_SYNC, 0, &gdt->sc_dvr); else gdt_store_event(GDT_ES_SYNC, service, &gdt->sc_dvr); } else { /* raw service */ if (gdt->sc_status != GDT_S_RAW_SCSI || gdt->sc_info >= 0x100) { ccb->ccb_h.status = CAM_DEV_NOT_THERE; } else { ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR|CAM_AUTOSNS_VALID; ccb->ccb_h.status &= ~CAM_SIM_QUEUED; ccb->csio.scsi_status = gdt->sc_info; bcopy(gccb->gc_scratch, &ccb->csio.sense_data, ccb->csio.sense_len); } } } --gdt_stat.io_count_act; xpt_done(ccb); } return (0); } /* Controller event handling functions */ void gdt_store_event(u_int16_t source, u_int16_t idx, gdt_evt_data *evt) { gdt_evt_str *e; struct timeval tv; GDT_DPRINTF(GDT_D_MISC, ("gdt_store_event(%d, %d)\n", source, idx)); if (source == 0) /* no source -> no event */ return; mtx_lock(&elock); if (ebuffer[elastidx].event_source == source && ebuffer[elastidx].event_idx == idx && ((evt->size != 0 && ebuffer[elastidx].event_data.size != 0 && !memcmp((char *)&ebuffer[elastidx].event_data.eu, (char *)&evt->eu, evt->size)) || (evt->size == 0 && ebuffer[elastidx].event_data.size == 0 && !strcmp((char *)&ebuffer[elastidx].event_data.event_string, (char *)&evt->event_string)))) { e = &ebuffer[elastidx]; getmicrotime(&tv); e->last_stamp = tv.tv_sec; ++e->same_count; } else { if (ebuffer[elastidx].event_source != 0) { /* entry not free ? */ ++elastidx; if (elastidx == GDT_MAX_EVENTS) elastidx = 0; if (elastidx == eoldidx) { /* reached mark ? */ ++eoldidx; if (eoldidx == GDT_MAX_EVENTS) eoldidx = 0; } } e = &ebuffer[elastidx]; e->event_source = source; e->event_idx = idx; getmicrotime(&tv); e->first_stamp = e->last_stamp = tv.tv_sec; e->same_count = 1; e->event_data = *evt; e->application = 0; } mtx_unlock(&elock); } int gdt_read_event(int handle, gdt_evt_str *estr) { gdt_evt_str *e; int eindex; GDT_DPRINTF(GDT_D_MISC, ("gdt_read_event(%d)\n", handle)); mtx_lock(&elock); if (handle == -1) eindex = eoldidx; else eindex = handle; estr->event_source = 0; if (eindex >= GDT_MAX_EVENTS) { mtx_unlock(&elock); return eindex; } e = &ebuffer[eindex]; if (e->event_source != 0) { if (eindex != elastidx) { if (++eindex == GDT_MAX_EVENTS) eindex = 0; } else { eindex = -1; } memcpy(estr, e, sizeof(gdt_evt_str)); } mtx_unlock(&elock); return eindex; } void gdt_readapp_event(u_int8_t application, gdt_evt_str *estr) { gdt_evt_str *e; int found = FALSE; int eindex; GDT_DPRINTF(GDT_D_MISC, ("gdt_readapp_event(%d)\n", application)); mtx_lock(&elock); eindex = eoldidx; for (;;) { e = &ebuffer[eindex]; if (e->event_source == 0) break; if ((e->application & application) == 0) { e->application |= application; found = TRUE; break; } if (eindex == elastidx) break; if (++eindex == GDT_MAX_EVENTS) eindex = 0; } if (found) memcpy(estr, e, sizeof(gdt_evt_str)); else estr->event_source = 0; mtx_unlock(&elock); } void gdt_clear_events() { GDT_DPRINTF(GDT_D_MISC, ("gdt_clear_events\n")); mtx_lock(&elock); eoldidx = elastidx = 0; ebuffer[0].event_source = 0; mtx_unlock(&elock); } Index: head/sys/dev/iir/iir_pci.c =================================================================== --- head/sys/dev/iir/iir_pci.c (revision 280346) +++ head/sys/dev/iir/iir_pci.c (revision 280347) @@ -1,457 +1,457 @@ /*- * Copyright (c) 2000-03 ICP vortex GmbH * Copyright (c) 2002-03 Intel Corporation * Copyright (c) 2003 Adaptec Inc. * All Rights Reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * iir_pci.c: PCI Bus Attachment for Intel Integrated RAID Controller driver * * Written by: Achim Leubner * Written by: Achim Leubner * Fixes/Additions: Boji Tony Kannanthanam * * TODO: */ /* #include "opt_iir.h" */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Mapping registers for various areas */ #define PCI_DPMEM PCIR_BAR(0) /* Product numbers for Fibre-Channel are greater than or equal to 0x200 */ #define GDT_PCI_PRODUCT_FC 0x200 /* PCI SRAM structure */ #define GDT_MAGIC 0x00 /* u_int32_t, controller ID from BIOS */ #define GDT_NEED_DEINIT 0x04 /* u_int16_t, switch between BIOS/driver */ #define GDT_SWITCH_SUPPORT 0x06 /* u_int8_t, see GDT_NEED_DEINIT */ #define GDT_OS_USED 0x10 /* u_int8_t [16], OS code per service */ #define GDT_FW_MAGIC 0x3c /* u_int8_t, controller ID from firmware */ #define GDT_SRAM_SZ 0x40 /* DPRAM PCI controllers */ #define GDT_DPR_IF 0x00 /* interface area */ #define GDT_6SR (0xff0 - GDT_SRAM_SZ) #define GDT_SEMA1 0xff1 /* volatile u_int8_t, command semaphore */ #define GDT_IRQEN 0xff5 /* u_int8_t, board interrupts enable */ #define GDT_EVENT 0xff8 /* u_int8_t, release event */ #define GDT_IRQDEL 0xffc /* u_int8_t, acknowledge board interrupt */ #define GDT_DPRAM_SZ 0x1000 /* PLX register structure (new PCI controllers) */ #define GDT_CFG_REG 0x00 /* u_int8_t, DPRAM cfg. (2: < 1MB, 0: any) */ #define GDT_SEMA0_REG 0x40 /* volatile u_int8_t, command semaphore */ #define GDT_SEMA1_REG 0x41 /* volatile u_int8_t, status semaphore */ #define GDT_PLX_STATUS 0x44 /* volatile u_int16_t, command status */ #define GDT_PLX_SERVICE 0x46 /* u_int16_t, service */ #define GDT_PLX_INFO 0x48 /* u_int32_t [2], additional info */ #define GDT_LDOOR_REG 0x60 /* u_int8_t, PCI to local doorbell */ #define GDT_EDOOR_REG 0x64 /* volatile u_int8_t, local to PCI doorbell */ #define GDT_CONTROL0 0x68 /* u_int8_t, control0 register (unused) */ #define GDT_CONTROL1 0x69 /* u_int8_t, board interrupts enable */ #define GDT_PLX_SZ 0x80 /* DPRAM new PCI controllers */ #define GDT_IC 0x00 /* interface */ #define GDT_PCINEW_6SR (0x4000 - GDT_SRAM_SZ) /* SRAM structure */ #define GDT_PCINEW_SZ 0x4000 /* i960 register structure (PCI MPR controllers) */ #define GDT_MPR_SEMA0 0x10 /* volatile u_int8_t, command semaphore */ #define GDT_MPR_SEMA1 0x12 /* volatile u_int8_t, status semaphore */ #define GDT_MPR_STATUS 0x14 /* volatile u_int16_t, command status */ #define GDT_MPR_SERVICE 0x16 /* u_int16_t, service */ #define GDT_MPR_INFO 0x18 /* u_int32_t [2], additional info */ #define GDT_MPR_LDOOR 0x20 /* u_int8_t, PCI to local doorbell */ #define GDT_MPR_EDOOR 0x2c /* volatile u_int8_t, locl to PCI doorbell */ #define GDT_EDOOR_EN 0x34 /* u_int8_t, board interrupts enable */ #define GDT_SEVERITY 0xefc /* u_int8_t, event severity */ #define GDT_EVT_BUF 0xf00 /* u_int8_t [256], event buffer */ #define GDT_I960_SZ 0x1000 /* DPRAM PCI MPR controllers */ #define GDT_I960R 0x00 /* 4KB i960 registers */ #define GDT_MPR_IC GDT_I960_SZ /* i960 register area */ #define GDT_MPR_6SR (GDT_I960_SZ + 0x3000 - GDT_SRAM_SZ) /* DPRAM struct. */ #define GDT_MPR_SZ (0x3000 - GDT_SRAM_SZ) static int iir_pci_probe(device_t dev); static int iir_pci_attach(device_t dev); void gdt_pci_enable_intr(struct gdt_softc *); void gdt_mpr_copy_cmd(struct gdt_softc *, struct gdt_ccb *); u_int8_t gdt_mpr_get_status(struct gdt_softc *); void gdt_mpr_intr(struct gdt_softc *, struct gdt_intr_ctx *); void gdt_mpr_release_event(struct gdt_softc *); void gdt_mpr_set_sema0(struct gdt_softc *); int gdt_mpr_test_busy(struct gdt_softc *); static device_method_t iir_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, iir_pci_probe), DEVMETHOD(device_attach, iir_pci_attach), { 0, 0} }; static driver_t iir_pci_driver = { "iir", iir_pci_methods, sizeof(struct gdt_softc) }; static devclass_t iir_devclass; DRIVER_MODULE(iir, pci, iir_pci_driver, iir_devclass, 0, 0); MODULE_DEPEND(iir, pci, 1, 1, 1); MODULE_DEPEND(iir, cam, 1, 1, 1); static int iir_pci_probe(device_t dev) { if (pci_get_vendor(dev) == INTEL_VENDOR_ID_IIR && pci_get_device(dev) == INTEL_DEVICE_ID_IIR) { device_set_desc(dev, "Intel Integrated RAID Controller"); return (BUS_PROBE_DEFAULT); } if (pci_get_vendor(dev) == GDT_VENDOR_ID && ((pci_get_device(dev) >= GDT_DEVICE_ID_MIN && pci_get_device(dev) <= GDT_DEVICE_ID_MAX) || pci_get_device(dev) == GDT_DEVICE_ID_NEWRX)) { device_set_desc(dev, "ICP Disk Array Controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int iir_pci_attach(device_t dev) { struct gdt_softc *gdt; struct resource *irq = NULL; int retries, rid, error = 0; void *ih; u_int8_t protocol; gdt = device_get_softc(dev); mtx_init(&gdt->sc_lock, "iir", NULL, MTX_DEF); /* map DPMEM */ rid = PCI_DPMEM; gdt->sc_dpmem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (gdt->sc_dpmem == NULL) { device_printf(dev, "can't allocate register resources\n"); error = ENOMEM; goto err; } /* get IRQ */ rid = 0; irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); if (irq == NULL) { device_printf(dev, "can't find IRQ value\n"); error = ENOMEM; goto err; } gdt->sc_devnode = dev; gdt->sc_init_level = 0; gdt->sc_hanum = device_get_unit(dev); gdt->sc_bus = pci_get_bus(dev); gdt->sc_slot = pci_get_slot(dev); gdt->sc_vendor = pci_get_vendor(dev); gdt->sc_device = pci_get_device(dev); gdt->sc_subdevice = pci_get_subdevice(dev); gdt->sc_class = GDT_MPR; /* no FC ctr. if (gdt->sc_device >= GDT_PCI_PRODUCT_FC) gdt->sc_class |= GDT_FC; */ /* initialize RP controller */ /* check and reset interface area */ bus_write_4(gdt->sc_dpmem, GDT_MPR_IC, htole32(GDT_MPR_MAGIC)); if (bus_read_4(gdt->sc_dpmem, GDT_MPR_IC) != htole32(GDT_MPR_MAGIC)) { device_printf(dev, "cannot access DPMEM at 0x%lx (shadowed?)\n", rman_get_start(gdt->sc_dpmem)); error = ENXIO; goto err; } bus_set_region_4(gdt->sc_dpmem, GDT_I960_SZ, htole32(0), GDT_MPR_SZ >> 2); /* Disable everything */ bus_write_1(gdt->sc_dpmem, GDT_EDOOR_EN, bus_read_1(gdt->sc_dpmem, GDT_EDOOR_EN) | 4); bus_write_1(gdt->sc_dpmem, GDT_MPR_EDOOR, 0xff); bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_STATUS, 0); bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_CMD_INDEX, 0); bus_write_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO, htole32(rman_get_start(gdt->sc_dpmem))); bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_CMD_INDX, 0xff); bus_write_1(gdt->sc_dpmem, GDT_MPR_LDOOR, 1); DELAY(20); retries = GDT_RETRIES; while (bus_read_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_STATUS) != 0xff) { if (--retries == 0) { device_printf(dev, "DEINIT failed\n"); error = ENXIO; goto err; } DELAY(1); } protocol = (uint8_t)le32toh(bus_read_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO)); bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_STATUS, 0); if (protocol != GDT_PROTOCOL_VERSION) { device_printf(dev, "unsupported protocol %d\n", protocol); error = ENXIO; goto err; } /* special command to controller BIOS */ bus_write_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO, htole32(0)); bus_write_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO + sizeof (u_int32_t), htole32(0)); bus_write_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO + 2 * sizeof (u_int32_t), htole32(1)); bus_write_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_INFO + 3 * sizeof (u_int32_t), htole32(0)); bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_CMD_INDX, 0xfe); bus_write_1(gdt->sc_dpmem, GDT_MPR_LDOOR, 1); DELAY(20); retries = GDT_RETRIES; while (bus_read_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_STATUS) != 0xfe) { if (--retries == 0) { device_printf(dev, "initialization error\n"); error = ENXIO; goto err; } DELAY(1); } bus_write_1(gdt->sc_dpmem, GDT_MPR_IC + GDT_S_STATUS, 0); gdt->sc_ic_all_size = GDT_MPR_SZ; gdt->sc_copy_cmd = gdt_mpr_copy_cmd; gdt->sc_get_status = gdt_mpr_get_status; gdt->sc_intr = gdt_mpr_intr; gdt->sc_release_event = gdt_mpr_release_event; gdt->sc_set_sema0 = gdt_mpr_set_sema0; gdt->sc_test_busy = gdt_mpr_test_busy; /* Allocate a dmatag representing the capabilities of this attachment */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignemnt*/1, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/BUS_SPACE_MAXSIZE_32BIT, - /*nsegments*/GDT_MAXSG, + /*nsegments*/BUS_SPACE_UNRESTRICTED, /*maxsegsz*/BUS_SPACE_MAXSIZE_32BIT, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&gdt->sc_lock, &gdt->sc_parent_dmat) != 0) { error = ENXIO; goto err; } gdt->sc_init_level++; if (iir_init(gdt) != 0) { iir_free(gdt); error = ENXIO; goto err; } /* Register with the XPT */ iir_attach(gdt); /* associate interrupt handler */ if (bus_setup_intr(dev, irq, INTR_TYPE_CAM | INTR_MPSAFE, NULL, iir_intr, gdt, &ih )) { device_printf(dev, "Unable to register interrupt handler\n"); error = ENXIO; goto err; } gdt_pci_enable_intr(gdt); return (0); err: if (irq) bus_release_resource( dev, SYS_RES_IRQ, 0, irq ); if (gdt->sc_dpmem) bus_release_resource( dev, SYS_RES_MEMORY, rid, gdt->sc_dpmem ); mtx_destroy(&gdt->sc_lock); return (error); } /* Enable interrupts */ void gdt_pci_enable_intr(struct gdt_softc *gdt) { GDT_DPRINTF(GDT_D_INTR, ("gdt_pci_enable_intr(%p) ", gdt)); switch(GDT_CLASS(gdt)) { case GDT_MPR: bus_write_1(gdt->sc_dpmem, GDT_MPR_EDOOR, 0xff); bus_write_1(gdt->sc_dpmem, GDT_EDOOR_EN, bus_read_1(gdt->sc_dpmem, GDT_EDOOR_EN) & ~4); break; } } /* * MPR PCI controller-specific functions */ void gdt_mpr_copy_cmd(struct gdt_softc *gdt, struct gdt_ccb *gccb) { u_int16_t cp_count = roundup(gccb->gc_cmd_len, sizeof (u_int32_t)); u_int16_t dp_offset = gdt->sc_cmd_off; u_int16_t cmd_no = gdt->sc_cmd_cnt++; GDT_DPRINTF(GDT_D_CMD, ("gdt_mpr_copy_cmd(%p) ", gdt)); gdt->sc_cmd_off += cp_count; bus_write_region_4(gdt->sc_dpmem, GDT_MPR_IC + GDT_DPR_CMD + dp_offset, (u_int32_t *)gccb->gc_cmd, cp_count >> 2); bus_write_2(gdt->sc_dpmem, GDT_MPR_IC + GDT_COMM_QUEUE + cmd_no * GDT_COMM_Q_SZ + GDT_OFFSET, htole16(GDT_DPMEM_COMMAND_OFFSET + dp_offset)); bus_write_2(gdt->sc_dpmem, GDT_MPR_IC + GDT_COMM_QUEUE + cmd_no * GDT_COMM_Q_SZ + GDT_SERV_ID, htole16(gccb->gc_service)); } u_int8_t gdt_mpr_get_status(struct gdt_softc *gdt) { GDT_DPRINTF(GDT_D_MISC, ("gdt_mpr_get_status(%p) ", gdt)); return bus_read_1(gdt->sc_dpmem, GDT_MPR_EDOOR); } void gdt_mpr_intr(struct gdt_softc *gdt, struct gdt_intr_ctx *ctx) { int i; GDT_DPRINTF(GDT_D_INTR, ("gdt_mpr_intr(%p) ", gdt)); bus_write_1(gdt->sc_dpmem, GDT_MPR_EDOOR, 0xff); if (ctx->istatus & 0x80) { /* error flag */ ctx->istatus &= ~0x80; ctx->cmd_status = bus_read_2(gdt->sc_dpmem, GDT_MPR_STATUS); } else /* no error */ ctx->cmd_status = GDT_S_OK; ctx->info = bus_read_4(gdt->sc_dpmem, GDT_MPR_INFO); ctx->service = bus_read_2(gdt->sc_dpmem, GDT_MPR_SERVICE); ctx->info2 = bus_read_4(gdt->sc_dpmem, GDT_MPR_INFO + sizeof (u_int32_t)); /* event string */ if (ctx->istatus == GDT_ASYNCINDEX) { if (ctx->service != GDT_SCREENSERVICE && (gdt->sc_fw_vers & 0xff) >= 0x1a) { gdt->sc_dvr.severity = bus_read_1(gdt->sc_dpmem, GDT_SEVERITY); for (i = 0; i < 256; ++i) { gdt->sc_dvr.event_string[i] = bus_read_1(gdt->sc_dpmem, GDT_EVT_BUF + i); if (gdt->sc_dvr.event_string[i] == 0) break; } } } bus_write_1(gdt->sc_dpmem, GDT_MPR_SEMA1, 0); } void gdt_mpr_release_event(struct gdt_softc *gdt) { GDT_DPRINTF(GDT_D_MISC, ("gdt_mpr_release_event(%p) ", gdt)); bus_write_1(gdt->sc_dpmem, GDT_MPR_LDOOR, 1); } void gdt_mpr_set_sema0(struct gdt_softc *gdt) { GDT_DPRINTF(GDT_D_MISC, ("gdt_mpr_set_sema0(%p) ", gdt)); bus_write_1(gdt->sc_dpmem, GDT_MPR_SEMA0, 1); } int gdt_mpr_test_busy(struct gdt_softc *gdt) { GDT_DPRINTF(GDT_D_MISC, ("gdt_mpr_test_busy(%p) ", gdt)); return (bus_read_1(gdt->sc_dpmem, GDT_MPR_SEMA0) & 1); } Index: head/sys/dev/mlx/mlx.c =================================================================== --- head/sys/dev/mlx/mlx.c (revision 280346) +++ head/sys/dev/mlx/mlx.c (revision 280347) @@ -1,3073 +1,3074 @@ /*- * Copyright (c) 1999 Michael Smith * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Driver for the Mylex DAC960 family of RAID controllers. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct cdevsw mlx_cdevsw = { .d_version = D_VERSION, .d_open = mlx_open, .d_close = mlx_close, .d_ioctl = mlx_ioctl, .d_name = "mlx", }; devclass_t mlx_devclass; /* * Per-interface accessor methods */ static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v3_intaction(struct mlx_softc *sc, int action); static int mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); static int mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v4_intaction(struct mlx_softc *sc, int action); static int mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); static int mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc); static int mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status); static void mlx_v5_intaction(struct mlx_softc *sc, int action); static int mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first); /* * Status monitoring */ static void mlx_periodic(void *data); static void mlx_periodic_enquiry(struct mlx_command *mc); static void mlx_periodic_eventlog_poll(struct mlx_softc *sc); static void mlx_periodic_eventlog_respond(struct mlx_command *mc); static void mlx_periodic_rebuild(struct mlx_command *mc); /* * Channel Pause */ static void mlx_pause_action(struct mlx_softc *sc); static void mlx_pause_done(struct mlx_command *mc); /* * Command submission. */ static void *mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (*complete)(struct mlx_command *mc)); static int mlx_flush(struct mlx_softc *sc); static int mlx_check(struct mlx_softc *sc, int drive); static int mlx_rebuild(struct mlx_softc *sc, int channel, int target); static int mlx_wait_command(struct mlx_command *mc); static int mlx_poll_command(struct mlx_command *mc); void mlx_startio_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error); static void mlx_startio(struct mlx_softc *sc); static void mlx_completeio(struct mlx_command *mc); static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu); void mlx_user_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error); /* * Command buffer allocation. */ static struct mlx_command *mlx_alloccmd(struct mlx_softc *sc); static void mlx_releasecmd(struct mlx_command *mc); static void mlx_freecmd(struct mlx_command *mc); /* * Command management. */ static int mlx_getslot(struct mlx_command *mc); static void mlx_setup_dmamap(struct mlx_command *mc, bus_dma_segment_t *segs, int nsegments, int error); static void mlx_unmapcmd(struct mlx_command *mc); static int mlx_shutdown_locked(struct mlx_softc *sc); static int mlx_start(struct mlx_command *mc); static int mlx_done(struct mlx_softc *sc, int startio); static void mlx_complete(struct mlx_softc *sc); /* * Debugging. */ static char *mlx_diagnose_command(struct mlx_command *mc); static void mlx_describe_controller(struct mlx_softc *sc); static int mlx_fw_message(struct mlx_softc *sc, int status, int param1, int param2); /* * Utility functions. */ static struct mlx_sysdrive *mlx_findunit(struct mlx_softc *sc, int unit); /******************************************************************************** ******************************************************************************** Public Interfaces ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ void mlx_free(struct mlx_softc *sc) { struct mlx_command *mc; debug_called(1); /* destroy control device */ if (sc->mlx_dev_t != NULL) destroy_dev(sc->mlx_dev_t); if (sc->mlx_intr) bus_teardown_intr(sc->mlx_dev, sc->mlx_irq, sc->mlx_intr); /* cancel status timeout */ MLX_IO_LOCK(sc); callout_stop(&sc->mlx_timeout); /* throw away any command buffers */ while ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) { TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); mlx_freecmd(mc); } MLX_IO_UNLOCK(sc); callout_drain(&sc->mlx_timeout); /* destroy data-transfer DMA tag */ if (sc->mlx_buffer_dmat) bus_dma_tag_destroy(sc->mlx_buffer_dmat); /* free and destroy DMA memory and tag for s/g lists */ if (sc->mlx_sgbusaddr) bus_dmamap_unload(sc->mlx_sg_dmat, sc->mlx_sg_dmamap); if (sc->mlx_sgtable) bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); if (sc->mlx_sg_dmat) bus_dma_tag_destroy(sc->mlx_sg_dmat); /* disconnect the interrupt handler */ if (sc->mlx_irq != NULL) bus_release_resource(sc->mlx_dev, SYS_RES_IRQ, 0, sc->mlx_irq); /* destroy the parent DMA tag */ if (sc->mlx_parent_dmat) bus_dma_tag_destroy(sc->mlx_parent_dmat); /* release the register window mapping */ if (sc->mlx_mem != NULL) bus_release_resource(sc->mlx_dev, sc->mlx_mem_type, sc->mlx_mem_rid, sc->mlx_mem); /* free controller enquiry data */ if (sc->mlx_enq2 != NULL) free(sc->mlx_enq2, M_DEVBUF); sx_destroy(&sc->mlx_config_lock); mtx_destroy(&sc->mlx_io_lock); } /******************************************************************************** * Map the scatter/gather table into bus space */ static void mlx_dma_map_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mlx_softc *sc = (struct mlx_softc *)arg; debug_called(1); /* save base of s/g table's address in bus space */ sc->mlx_sgbusaddr = segs->ds_addr; } static int mlx_sglist_map(struct mlx_softc *sc) { size_t segsize; int error, ncmd; debug_called(1); /* destroy any existing mappings */ if (sc->mlx_sgbusaddr) bus_dmamap_unload(sc->mlx_sg_dmat, sc->mlx_sg_dmamap); if (sc->mlx_sgtable) bus_dmamem_free(sc->mlx_sg_dmat, sc->mlx_sgtable, sc->mlx_sg_dmamap); if (sc->mlx_sg_dmat) bus_dma_tag_destroy(sc->mlx_sg_dmat); sc->mlx_sgbusaddr = 0; sc->mlx_sgtable = NULL; sc->mlx_sg_dmat = NULL; /* * Create a single tag describing a region large enough to hold all of * the s/g lists we will need. If we're called early on, we don't know how * many commands we're going to be asked to support, so only allocate enough * for a couple. */ if (sc->mlx_enq2 == NULL) { ncmd = 2; } else { ncmd = sc->mlx_enq2->me_max_commands; } segsize = sizeof(struct mlx_sgentry) * MLX_NSEG * ncmd; error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ segsize, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mlx_sg_dmat); if (error != 0) { device_printf(sc->mlx_dev, "can't allocate scatter/gather DMA tag\n"); return(ENOMEM); } /* * Allocate enough s/g maps for all commands and permanently map them into * controller-visible space. * * XXX this assumes we can get enough space for all the s/g maps in one * contiguous slab. We may need to switch to a more complex arrangement * where we allocate in smaller chunks and keep a lookup table from slot * to bus address. */ error = bus_dmamem_alloc(sc->mlx_sg_dmat, (void **)&sc->mlx_sgtable, BUS_DMA_NOWAIT, &sc->mlx_sg_dmamap); if (error) { device_printf(sc->mlx_dev, "can't allocate s/g table\n"); return(ENOMEM); } (void)bus_dmamap_load(sc->mlx_sg_dmat, sc->mlx_sg_dmamap, sc->mlx_sgtable, segsize, mlx_dma_map_sg, sc, 0); return(0); } /******************************************************************************** * Initialise the controller and softc */ int mlx_attach(struct mlx_softc *sc) { struct mlx_enquiry_old *meo; int rid, error, fwminor, hscode, hserror, hsparam1, hsparam2, hsmsg; debug_called(1); /* * Initialise per-controller queues. */ TAILQ_INIT(&sc->mlx_work); TAILQ_INIT(&sc->mlx_freecmds); bioq_init(&sc->mlx_bioq); /* * Select accessor methods based on controller interface type. */ switch(sc->mlx_iftype) { case MLX_IFTYPE_2: case MLX_IFTYPE_3: sc->mlx_tryqueue = mlx_v3_tryqueue; sc->mlx_findcomplete = mlx_v3_findcomplete; sc->mlx_intaction = mlx_v3_intaction; sc->mlx_fw_handshake = mlx_v3_fw_handshake; break; case MLX_IFTYPE_4: sc->mlx_tryqueue = mlx_v4_tryqueue; sc->mlx_findcomplete = mlx_v4_findcomplete; sc->mlx_intaction = mlx_v4_intaction; sc->mlx_fw_handshake = mlx_v4_fw_handshake; break; case MLX_IFTYPE_5: sc->mlx_tryqueue = mlx_v5_tryqueue; sc->mlx_findcomplete = mlx_v5_findcomplete; sc->mlx_intaction = mlx_v5_intaction; sc->mlx_fw_handshake = mlx_v5_fw_handshake; break; default: return(ENXIO); /* should never happen */ } /* disable interrupts before we start talking to the controller */ MLX_IO_LOCK(sc); sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); MLX_IO_UNLOCK(sc); /* * Wait for the controller to come ready, handshake with the firmware if required. * This is typically only necessary on platforms where the controller BIOS does not * run. */ hsmsg = 0; DELAY(1000); while ((hscode = sc->mlx_fw_handshake(sc, &hserror, &hsparam1, &hsparam2, hsmsg == 0)) != 0) { /* report first time around... */ if (hsmsg == 0) { device_printf(sc->mlx_dev, "controller initialisation in progress...\n"); hsmsg = 1; } /* did we get a real message? */ if (hscode == 2) { hscode = mlx_fw_message(sc, hserror, hsparam1, hsparam2); /* fatal initialisation error? */ if (hscode != 0) { return(ENXIO); } } } if (hsmsg == 1) device_printf(sc->mlx_dev, "initialisation complete.\n"); /* * Allocate and connect our interrupt. */ rid = 0; sc->mlx_irq = bus_alloc_resource_any(sc->mlx_dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->mlx_irq == NULL) { device_printf(sc->mlx_dev, "can't allocate interrupt\n"); return(ENXIO); } error = bus_setup_intr(sc->mlx_dev, sc->mlx_irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, mlx_intr, sc, &sc->mlx_intr); if (error) { device_printf(sc->mlx_dev, "can't set up interrupt\n"); return(ENXIO); } /* * Create DMA tag for mapping buffers into controller-addressable space. */ error = bus_dma_tag_create(sc->mlx_parent_dmat, /* parent */ 1, 0, /* align, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, MLX_NSEG, /* maxsize, nsegments */ + MLX_MAXPHYS, /* maxsize */ + MLX_NSEG, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->mlx_io_lock, /* lockarg */ &sc->mlx_buffer_dmat); if (error != 0) { device_printf(sc->mlx_dev, "can't allocate buffer DMA tag\n"); return(ENOMEM); } /* * Create some initial scatter/gather mappings so we can run the probe * commands. */ error = mlx_sglist_map(sc); if (error != 0) { device_printf(sc->mlx_dev, "can't make initial s/g list mapping\n"); return(error); } /* * We don't (yet) know where the event log is up to. */ sc->mlx_currevent = -1; /* * Obtain controller feature information */ MLX_IO_LOCK(sc); if ((sc->mlx_enq2 = mlx_enquire(sc, MLX_CMD_ENQUIRY2, sizeof(struct mlx_enquiry2), NULL)) == NULL) { MLX_IO_UNLOCK(sc); device_printf(sc->mlx_dev, "ENQUIRY2 failed\n"); return(ENXIO); } /* * Do quirk/feature related things. */ fwminor = (sc->mlx_enq2->me_firmware_id >> 8) & 0xff; switch(sc->mlx_iftype) { case MLX_IFTYPE_2: /* These controllers don't report the firmware version in the ENQUIRY2 response */ if ((meo = mlx_enquire(sc, MLX_CMD_ENQUIRY_OLD, sizeof(struct mlx_enquiry_old), NULL)) == NULL) { MLX_IO_UNLOCK(sc); device_printf(sc->mlx_dev, "ENQUIRY_OLD failed\n"); return(ENXIO); } sc->mlx_enq2->me_firmware_id = ('0' << 24) | (0 << 16) | (meo->me_fwminor << 8) | meo->me_fwmajor; /* XXX require 2.42 or better (PCI) or 2.14 or better (EISA) */ if (meo->me_fwminor < 42) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 2.42 or later\n"); } free(meo, M_DEVBUF); break; case MLX_IFTYPE_3: /* XXX certify 3.52? */ if (fwminor < 51) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 3.51 or later\n"); } break; case MLX_IFTYPE_4: /* XXX certify firmware versions? */ if (fwminor < 6) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 4.06 or later\n"); } break; case MLX_IFTYPE_5: if (fwminor < 7) { device_printf(sc->mlx_dev, " *** WARNING *** This firmware revision is not recommended\n"); device_printf(sc->mlx_dev, " *** WARNING *** Use revision 5.07 or later\n"); } break; default: MLX_IO_UNLOCK(sc); return(ENXIO); /* should never happen */ } MLX_IO_UNLOCK(sc); /* * Create the final scatter/gather mappings now that we have characterised the controller. */ error = mlx_sglist_map(sc); if (error != 0) { device_printf(sc->mlx_dev, "can't make final s/g list mapping\n"); return(error); } /* * No user-requested background operation is in progress. */ sc->mlx_background = 0; sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE; /* * Create the control device. */ sc->mlx_dev_t = make_dev(&mlx_cdevsw, 0, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "mlx%d", device_get_unit(sc->mlx_dev)); sc->mlx_dev_t->si_drv1 = sc; /* * Start the timeout routine. */ callout_reset(&sc->mlx_timeout, hz, mlx_periodic, sc); /* print a little information about the controller */ mlx_describe_controller(sc); return(0); } /******************************************************************************** * Locate disk resources and attach children to them. */ void mlx_startup(struct mlx_softc *sc) { struct mlx_enq_sys_drive *mes; struct mlx_sysdrive *dr; int i, error; debug_called(1); /* * Scan all the system drives and attach children for those that * don't currently have them. */ MLX_IO_LOCK(sc); mes = mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(*mes) * MLX_MAXDRIVES, NULL); MLX_IO_UNLOCK(sc); if (mes == NULL) { device_printf(sc->mlx_dev, "error fetching drive status\n"); return; } /* iterate over drives returned */ MLX_CONFIG_LOCK(sc); for (i = 0, dr = &sc->mlx_sysdrive[0]; (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); i++, dr++) { /* are we already attached to this drive? */ if (dr->ms_disk == 0) { /* pick up drive information */ dr->ms_size = mes[i].sd_size; dr->ms_raidlevel = mes[i].sd_raidlevel & 0xf; dr->ms_state = mes[i].sd_state; /* generate geometry information */ if (sc->mlx_geom == MLX_GEOM_128_32) { dr->ms_heads = 128; dr->ms_sectors = 32; dr->ms_cylinders = dr->ms_size / (128 * 32); } else { /* MLX_GEOM_255/63 */ dr->ms_heads = 255; dr->ms_sectors = 63; dr->ms_cylinders = dr->ms_size / (255 * 63); } dr->ms_disk = device_add_child(sc->mlx_dev, /*"mlxd"*/NULL, -1); if (dr->ms_disk == 0) device_printf(sc->mlx_dev, "device_add_child failed\n"); device_set_ivars(dr->ms_disk, dr); } } free(mes, M_DEVBUF); if ((error = bus_generic_attach(sc->mlx_dev)) != 0) device_printf(sc->mlx_dev, "bus_generic_attach returned %d", error); /* mark controller back up */ MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_SHUTDOWN; /* enable interrupts */ sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); } /******************************************************************************** * Disconnect from the controller completely, in preparation for unload. */ int mlx_detach(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); struct mlxd_softc *mlxd; int i, error; debug_called(1); error = EBUSY; MLX_CONFIG_LOCK(sc); if (sc->mlx_state & MLX_STATE_OPEN) goto out; for (i = 0; i < MLX_MAXDRIVES; i++) { if (sc->mlx_sysdrive[i].ms_disk != 0) { mlxd = device_get_softc(sc->mlx_sysdrive[i].ms_disk); if (mlxd->mlxd_flags & MLXD_OPEN) { /* drive is mounted, abort detach */ device_printf(sc->mlx_sysdrive[i].ms_disk, "still open, can't detach\n"); goto out; } } } if ((error = mlx_shutdown(dev))) goto out; MLX_CONFIG_UNLOCK(sc); mlx_free(sc); return (0); out: MLX_CONFIG_UNLOCK(sc); return(error); } /******************************************************************************** * Bring the controller down to a dormant state and detach all child devices. * * This function is called before detach, system shutdown, or before performing * an operation which may add or delete system disks. (Call mlx_startup to * resume normal operation.) * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ int mlx_shutdown(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); int error; MLX_CONFIG_LOCK(sc); error = mlx_shutdown_locked(sc); MLX_CONFIG_UNLOCK(sc); return (error); } static int mlx_shutdown_locked(struct mlx_softc *sc) { int i, error; debug_called(1); MLX_CONFIG_ASSERT_LOCKED(sc); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_SHUTDOWN; sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); /* flush controller */ device_printf(sc->mlx_dev, "flushing cache..."); if (mlx_flush(sc)) { printf("failed\n"); } else { printf("done\n"); } MLX_IO_UNLOCK(sc); /* delete all our child devices */ for (i = 0; i < MLX_MAXDRIVES; i++) { if (sc->mlx_sysdrive[i].ms_disk != 0) { if ((error = device_delete_child(sc->mlx_dev, sc->mlx_sysdrive[i].ms_disk)) != 0) return (error); sc->mlx_sysdrive[i].ms_disk = 0; } } return (0); } /******************************************************************************** * Bring the controller to a quiescent state, ready for system suspend. */ int mlx_suspend(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); debug_called(1); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_SUSPEND; /* flush controller */ device_printf(sc->mlx_dev, "flushing cache..."); printf("%s\n", mlx_flush(sc) ? "failed" : "done"); sc->mlx_intaction(sc, MLX_INTACTION_DISABLE); MLX_IO_UNLOCK(sc); return(0); } /******************************************************************************** * Bring the controller back to a state ready for operation. */ int mlx_resume(device_t dev) { struct mlx_softc *sc = device_get_softc(dev); debug_called(1); MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_SUSPEND; sc->mlx_intaction(sc, MLX_INTACTION_ENABLE); MLX_IO_UNLOCK(sc); return(0); } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ void mlx_intr(void *arg) { struct mlx_softc *sc = (struct mlx_softc *)arg; debug_called(1); /* collect finished commands, queue anything waiting */ MLX_IO_LOCK(sc); mlx_done(sc, 1); MLX_IO_UNLOCK(sc); }; /******************************************************************************* * Receive a buf structure from a child device and queue it on a particular * disk resource, then poke the disk resource to start as much work as it can. */ int mlx_submit_buf(struct mlx_softc *sc, struct bio *bp) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); bioq_insert_tail(&sc->mlx_bioq, bp); sc->mlx_waitbufs++; mlx_startio(sc); return(0); } /******************************************************************************** * Accept an open operation on the control device. */ int mlx_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; MLX_CONFIG_LOCK(sc); MLX_IO_LOCK(sc); sc->mlx_state |= MLX_STATE_OPEN; MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); return(0); } /******************************************************************************** * Accept the last close on the control device. */ int mlx_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; MLX_CONFIG_LOCK(sc); MLX_IO_LOCK(sc); sc->mlx_state &= ~MLX_STATE_OPEN; MLX_IO_UNLOCK(sc); MLX_CONFIG_UNLOCK(sc); return (0); } /******************************************************************************** * Handle controller-specific control operations. */ int mlx_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { struct mlx_softc *sc = dev->si_drv1; struct mlx_rebuild_request *rb = (struct mlx_rebuild_request *)addr; struct mlx_rebuild_status *rs = (struct mlx_rebuild_status *)addr; int *arg = (int *)addr; struct mlx_pause *mp; struct mlx_sysdrive *dr; struct mlxd_softc *mlxd; int i, error; switch(cmd) { /* * Enumerate connected system drives; returns the first system drive's * unit number if *arg is -1, or the next unit after *arg if it's * a valid unit on this controller. */ case MLX_NEXT_CHILD: /* search system drives */ MLX_CONFIG_LOCK(sc); for (i = 0; i < MLX_MAXDRIVES; i++) { /* is this one attached? */ if (sc->mlx_sysdrive[i].ms_disk != 0) { /* looking for the next one we come across? */ if (*arg == -1) { *arg = device_get_unit(sc->mlx_sysdrive[i].ms_disk); MLX_CONFIG_UNLOCK(sc); return(0); } /* we want the one after this one */ if (*arg == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) *arg = -1; } } MLX_CONFIG_UNLOCK(sc); return(ENOENT); /* * Scan the controller to see whether new drives have appeared. */ case MLX_RESCAN_DRIVES: mtx_lock(&Giant); mlx_startup(sc); mtx_unlock(&Giant); return(0); /* * Disconnect from the specified drive; it may be about to go * away. */ case MLX_DETACH_DRIVE: /* detach one drive */ MLX_CONFIG_LOCK(sc); if (((dr = mlx_findunit(sc, *arg)) == NULL) || ((mlxd = device_get_softc(dr->ms_disk)) == NULL)) { MLX_CONFIG_UNLOCK(sc); return(ENOENT); } device_printf(dr->ms_disk, "detaching..."); error = 0; if (mlxd->mlxd_flags & MLXD_OPEN) { error = EBUSY; goto detach_out; } /* flush controller */ MLX_IO_LOCK(sc); if (mlx_flush(sc)) { MLX_IO_UNLOCK(sc); error = EBUSY; goto detach_out; } MLX_IO_UNLOCK(sc); /* nuke drive */ if ((error = device_delete_child(sc->mlx_dev, dr->ms_disk)) != 0) goto detach_out; dr->ms_disk = 0; detach_out: MLX_CONFIG_UNLOCK(sc); if (error) { printf("failed\n"); } else { printf("done\n"); } return(error); /* * Pause one or more SCSI channels for a period of time, to assist * in the process of hot-swapping devices. * * Note that at least the 3.51 firmware on the DAC960PL doesn't seem * to do this right. */ case MLX_PAUSE_CHANNEL: /* schedule a channel pause */ /* Does this command work on this firmware? */ if (!(sc->mlx_feature & MLX_FEAT_PAUSEWORKS)) return(EOPNOTSUPP); /* check time values */ mp = (struct mlx_pause *)addr; if ((mp->mp_when < 0) || (mp->mp_when > 3600)) return(EINVAL); if ((mp->mp_howlong < 1) || (mp->mp_howlong > (0xf * 30))) return(EINVAL); MLX_IO_LOCK(sc); if ((mp->mp_which == MLX_PAUSE_CANCEL) && (sc->mlx_pause.mp_when != 0)) { /* cancel a pending pause operation */ sc->mlx_pause.mp_which = 0; } else { /* fix for legal channels */ mp->mp_which &= ((1 << sc->mlx_enq2->me_actual_channels) -1); /* check for a pause currently running */ if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0)) { MLX_IO_UNLOCK(sc); return(EBUSY); } /* looks ok, go with it */ sc->mlx_pause.mp_which = mp->mp_which; sc->mlx_pause.mp_when = time_second + mp->mp_when; sc->mlx_pause.mp_howlong = sc->mlx_pause.mp_when + mp->mp_howlong; } MLX_IO_UNLOCK(sc); return(0); /* * Accept a command passthrough-style. */ case MLX_COMMAND: return(mlx_user_command(sc, (struct mlx_usercommand *)addr)); /* * Start a rebuild on a given SCSI disk */ case MLX_REBUILDASYNC: MLX_IO_LOCK(sc); if (sc->mlx_background != 0) { MLX_IO_UNLOCK(sc); rb->rr_status = 0x0106; return(EBUSY); } rb->rr_status = mlx_rebuild(sc, rb->rr_channel, rb->rr_target); switch (rb->rr_status) { case 0: error = 0; break; case 0x10000: error = ENOMEM; /* couldn't set up the command */ break; case 0x0002: error = EBUSY; break; case 0x0104: error = EIO; break; case 0x0105: error = ERANGE; break; case 0x0106: error = EBUSY; break; default: error = EINVAL; break; } if (error == 0) sc->mlx_background = MLX_BACKGROUND_REBUILD; MLX_IO_UNLOCK(sc); return(error); /* * Get the status of the current rebuild or consistency check. */ case MLX_REBUILDSTAT: MLX_IO_LOCK(sc); *rs = sc->mlx_rebuildstat; MLX_IO_UNLOCK(sc); return(0); /* * Return the per-controller system drive number matching the * disk device number in (arg), if it happens to belong to us. */ case MLX_GET_SYSDRIVE: error = ENOENT; MLX_CONFIG_LOCK(sc); mtx_lock(&Giant); mlxd = (struct mlxd_softc *)devclass_get_softc(mlxd_devclass, *arg); mtx_unlock(&Giant); if ((mlxd != NULL) && (mlxd->mlxd_drive >= sc->mlx_sysdrive) && (mlxd->mlxd_drive < (sc->mlx_sysdrive + MLX_MAXDRIVES))) { error = 0; *arg = mlxd->mlxd_drive - sc->mlx_sysdrive; } MLX_CONFIG_UNLOCK(sc); return(error); default: return(ENOTTY); } } /******************************************************************************** * Handle operations requested by a System Drive connected to this controller. */ int mlx_submit_ioctl(struct mlx_softc *sc, struct mlx_sysdrive *drive, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { int *arg = (int *)addr; int error, result; switch(cmd) { /* * Return the current status of this drive. */ case MLXD_STATUS: MLX_IO_LOCK(sc); *arg = drive->ms_state; MLX_IO_UNLOCK(sc); return(0); /* * Start a background consistency check on this drive. */ case MLXD_CHECKASYNC: /* start a background consistency check */ MLX_IO_LOCK(sc); if (sc->mlx_background != 0) { MLX_IO_UNLOCK(sc); *arg = 0x0106; return(EBUSY); } result = mlx_check(sc, drive - &sc->mlx_sysdrive[0]); switch (result) { case 0: error = 0; break; case 0x10000: error = ENOMEM; /* couldn't set up the command */ break; case 0x0002: error = EIO; break; case 0x0105: error = ERANGE; break; case 0x0106: error = EBUSY; break; default: error = EINVAL; break; } if (error == 0) sc->mlx_background = MLX_BACKGROUND_CHECK; MLX_IO_UNLOCK(sc); *arg = result; return(error); } return(ENOIOCTL); } /******************************************************************************** ******************************************************************************** Status Monitoring ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Fire off commands to periodically check the status of connected drives. */ static void mlx_periodic(void *data) { struct mlx_softc *sc = (struct mlx_softc *)data; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* * Run a bus pause? */ if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when > 0) && (time_second >= sc->mlx_pause.mp_when)){ mlx_pause_action(sc); /* pause is running */ sc->mlx_pause.mp_when = 0; sysbeep(500, hz); /* * Bus pause still running? */ } else if ((sc->mlx_pause.mp_which != 0) && (sc->mlx_pause.mp_when == 0)) { /* time to stop bus pause? */ if (time_second >= sc->mlx_pause.mp_howlong) { mlx_pause_action(sc); sc->mlx_pause.mp_which = 0; /* pause is complete */ sysbeep(500, hz); } else { sysbeep((time_second % 5) * 100 + 500, hz/8); } /* * Run normal periodic activities? */ } else if (time_second > (sc->mlx_lastpoll + 10)) { sc->mlx_lastpoll = time_second; /* * Check controller status. * * XXX Note that this may not actually launch a command in situations of high load. */ mlx_enquire(sc, (sc->mlx_iftype == MLX_IFTYPE_2) ? MLX_CMD_ENQUIRY_OLD : MLX_CMD_ENQUIRY, imax(sizeof(struct mlx_enquiry), sizeof(struct mlx_enquiry_old)), mlx_periodic_enquiry); /* * Check system drive status. * * XXX This might be better left to event-driven detection, eg. I/O to an offline * drive will detect it's offline, rebuilds etc. should detect the drive is back * online. */ mlx_enquire(sc, MLX_CMD_ENQSYSDRIVE, sizeof(struct mlx_enq_sys_drive) * MLX_MAXDRIVES, mlx_periodic_enquiry); } /* get drive rebuild/check status */ /* XXX should check sc->mlx_background if this is only valid while in progress */ mlx_enquire(sc, MLX_CMD_REBUILDSTAT, sizeof(struct mlx_rebuild_stat), mlx_periodic_rebuild); /* deal with possibly-missed interrupts and timed-out commands */ mlx_done(sc, 1); /* reschedule another poll next second or so */ callout_reset(&sc->mlx_timeout, hz, mlx_periodic, sc); } /******************************************************************************** * Handle the result of an ENQUIRY command instigated by periodic status polling. */ static void mlx_periodic_enquiry(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* Command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "periodic enquiry failed - %s\n", mlx_diagnose_command(mc)); goto out; } /* respond to command */ switch(mc->mc_mailbox[0]) { /* * This is currently a bit fruitless, as we don't know how to extract the eventlog * pointer yet. */ case MLX_CMD_ENQUIRY_OLD: { struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data; struct mlx_enquiry_old *meo = (struct mlx_enquiry_old *)mc->mc_data; int i; /* convert data in-place to new format */ for (i = (sizeof(me->me_dead) / sizeof(me->me_dead[0])) - 1; i >= 0; i--) { me->me_dead[i].dd_chan = meo->me_dead[i].dd_chan; me->me_dead[i].dd_targ = meo->me_dead[i].dd_targ; } me->me_misc_flags = 0; me->me_rebuild_count = meo->me_rebuild_count; me->me_dead_count = meo->me_dead_count; me->me_critical_sd_count = meo->me_critical_sd_count; me->me_event_log_seq_num = 0; me->me_offline_sd_count = meo->me_offline_sd_count; me->me_max_commands = meo->me_max_commands; me->me_rebuild_flag = meo->me_rebuild_flag; me->me_fwmajor = meo->me_fwmajor; me->me_fwminor = meo->me_fwminor; me->me_status_flags = meo->me_status_flags; me->me_flash_age = meo->me_flash_age; for (i = (sizeof(me->me_drvsize) / sizeof(me->me_drvsize[0])) - 1; i >= 0; i--) { if (i > ((sizeof(meo->me_drvsize) / sizeof(meo->me_drvsize[0])) - 1)) { me->me_drvsize[i] = 0; /* drive beyond supported range */ } else { me->me_drvsize[i] = meo->me_drvsize[i]; } } me->me_num_sys_drvs = meo->me_num_sys_drvs; } /* FALLTHROUGH */ /* * Generic controller status update. We could do more with this than just * checking the event log. */ case MLX_CMD_ENQUIRY: { struct mlx_enquiry *me = (struct mlx_enquiry *)mc->mc_data; if (sc->mlx_currevent == -1) { /* initialise our view of the event log */ sc->mlx_currevent = sc->mlx_lastevent = me->me_event_log_seq_num; } else if ((me->me_event_log_seq_num != sc->mlx_lastevent) && !(sc->mlx_flags & MLX_EVENTLOG_BUSY)) { /* record where current events are up to */ sc->mlx_currevent = me->me_event_log_seq_num; debug(1, "event log pointer was %d, now %d\n", sc->mlx_lastevent, sc->mlx_currevent); /* mark the event log as busy */ sc->mlx_flags |= MLX_EVENTLOG_BUSY; /* drain new eventlog entries */ mlx_periodic_eventlog_poll(sc); } break; } case MLX_CMD_ENQSYSDRIVE: { struct mlx_enq_sys_drive *mes = (struct mlx_enq_sys_drive *)mc->mc_data; struct mlx_sysdrive *dr; int i; for (i = 0, dr = &sc->mlx_sysdrive[0]; (i < MLX_MAXDRIVES) && (mes[i].sd_size != 0xffffffff); i++) { /* has state been changed by controller? */ if (dr->ms_state != mes[i].sd_state) { switch(mes[i].sd_state) { case MLX_SYSD_OFFLINE: device_printf(dr->ms_disk, "drive offline\n"); break; case MLX_SYSD_ONLINE: device_printf(dr->ms_disk, "drive online\n"); break; case MLX_SYSD_CRITICAL: device_printf(dr->ms_disk, "drive critical\n"); break; } /* save new state */ dr->ms_state = mes[i].sd_state; } } break; } default: device_printf(sc->mlx_dev, "%s: unknown command 0x%x", __func__, mc->mc_mailbox[0]); break; } out: free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); } static void mlx_eventlog_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_command *mc; mc = (struct mlx_command *)arg; mlx_setup_dmamap(mc, segs, nsegments, error); /* build the command to get one entry */ mlx_make_type3(mc, MLX_CMD_LOGOP, MLX_LOGOP_GET, 1, mc->mc_sc->mlx_lastevent, 0, 0, mc->mc_dataphys, 0); mc->mc_complete = mlx_periodic_eventlog_respond; mc->mc_private = mc; /* start the command */ if (mlx_start(mc) != 0) { mlx_releasecmd(mc); free(mc->mc_data, M_DEVBUF); mc->mc_data = NULL; } } /******************************************************************************** * Instigate a poll for one event log message on (sc). * We only poll for one message at a time, to keep our command usage down. */ static void mlx_periodic_eventlog_poll(struct mlx_softc *sc) { struct mlx_command *mc; void *result = NULL; int error = 0; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* allocate the response structure */ if ((result = malloc(/*sizeof(struct mlx_eventlog_entry)*/1024, M_DEVBUF, M_NOWAIT)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* map the command so the controller can see it */ mc->mc_data = result; mc->mc_length = /*sizeof(struct mlx_eventlog_entry)*/1024; error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_eventlog_cb, mc, BUS_DMA_NOWAIT); out: if (error != 0) { if (mc != NULL) mlx_releasecmd(mc); if ((result != NULL) && (mc->mc_data != NULL)) free(result, M_DEVBUF); } } /******************************************************************************** * Handle the result of polling for a log message, generate diagnostic output. * If this wasn't the last message waiting for us, we'll go collect another. */ static char *mlx_sense_messages[] = { "because write recovery failed", "because of SCSI bus reset failure", "because of double check condition", "because it was removed", "because of gross error on SCSI chip", "because of bad tag returned from drive", "because of timeout on SCSI command", "because of reset SCSI command issued from system", "because busy or parity error count exceeded limit", "because of 'kill drive' command from system", "because of selection timeout", "due to SCSI phase sequence error", "due to unknown status" }; static void mlx_periodic_eventlog_respond(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct mlx_eventlog_entry *el = (struct mlx_eventlog_entry *)mc->mc_data; char *reason; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); sc->mlx_lastevent++; /* next message... */ if (mc->mc_status == 0) { /* handle event log message */ switch(el->el_type) { /* * This is the only sort of message we understand at the moment. * The tests here are probably incomplete. */ case MLX_LOGMSG_SENSE: /* sense data */ /* Mylex vendor-specific message indicating a drive was killed? */ if ((el->el_sensekey == 9) && (el->el_asc == 0x80)) { if (el->el_asq < (sizeof(mlx_sense_messages) / sizeof(mlx_sense_messages[0]))) { reason = mlx_sense_messages[el->el_asq]; } else { reason = "for unknown reason"; } device_printf(sc->mlx_dev, "physical drive %d:%d killed %s\n", el->el_channel, el->el_target, reason); } /* SCSI drive was reset? */ if ((el->el_sensekey == 6) && (el->el_asc == 0x29)) { device_printf(sc->mlx_dev, "physical drive %d:%d reset\n", el->el_channel, el->el_target); } /* SCSI drive error? */ if (!((el->el_sensekey == 0) || ((el->el_sensekey == 2) && (el->el_asc == 0x04) && ((el->el_asq == 0x01) || (el->el_asq == 0x02))))) { device_printf(sc->mlx_dev, "physical drive %d:%d error log: sense = %d asc = %x asq = %x\n", el->el_channel, el->el_target, el->el_sensekey, el->el_asc, el->el_asq); device_printf(sc->mlx_dev, " info %4D csi %4D\n", el->el_information, ":", el->el_csi, ":"); } break; default: device_printf(sc->mlx_dev, "unknown log message type 0x%x\n", el->el_type); break; } } else { device_printf(sc->mlx_dev, "error reading message log - %s\n", mlx_diagnose_command(mc)); /* give up on all the outstanding messages, as we may have come unsynched */ sc->mlx_lastevent = sc->mlx_currevent; } /* dispose of command and data */ free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); /* is there another message to obtain? */ if (sc->mlx_lastevent != sc->mlx_currevent) { mlx_periodic_eventlog_poll(sc); } else { /* clear log-busy status */ sc->mlx_flags &= ~MLX_EVENTLOG_BUSY; } } /******************************************************************************** * Handle check/rebuild operations in progress. */ static void mlx_periodic_rebuild(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct mlx_rebuild_status *mr = (struct mlx_rebuild_status *)mc->mc_data; MLX_IO_ASSERT_LOCKED(sc); switch(mc->mc_status) { case 0: /* operation running, update stats */ sc->mlx_rebuildstat = *mr; /* spontaneous rebuild/check? */ if (sc->mlx_background == 0) { sc->mlx_background = MLX_BACKGROUND_SPONTANEOUS; device_printf(sc->mlx_dev, "background check/rebuild operation started\n"); } break; case 0x0105: /* nothing running, finalise stats and report */ switch(sc->mlx_background) { case MLX_BACKGROUND_CHECK: device_printf(sc->mlx_dev, "consistency check completed\n"); /* XXX print drive? */ break; case MLX_BACKGROUND_REBUILD: device_printf(sc->mlx_dev, "drive rebuild completed\n"); /* XXX print channel/target? */ break; case MLX_BACKGROUND_SPONTANEOUS: default: /* if we have previously been non-idle, report the transition */ if (sc->mlx_rebuildstat.rs_code != MLX_REBUILDSTAT_IDLE) { device_printf(sc->mlx_dev, "background check/rebuild operation completed\n"); } } sc->mlx_background = 0; sc->mlx_rebuildstat.rs_code = MLX_REBUILDSTAT_IDLE; break; } free(mc->mc_data, M_DEVBUF); mlx_releasecmd(mc); } /******************************************************************************** ******************************************************************************** Channel Pause ******************************************************************************** ********************************************************************************/ /******************************************************************************** * It's time to perform a channel pause action for (sc), either start or stop * the pause. */ static void mlx_pause_action(struct mlx_softc *sc) { struct mlx_command *mc; int failsafe, i, command; MLX_IO_ASSERT_LOCKED(sc); /* What are we doing here? */ if (sc->mlx_pause.mp_when == 0) { command = MLX_CMD_STARTCHANNEL; failsafe = 0; } else { command = MLX_CMD_STOPCHANNEL; /* * Channels will always start again after the failsafe period, * which is specified in multiples of 30 seconds. * This constrains us to a maximum pause of 450 seconds. */ failsafe = ((sc->mlx_pause.mp_howlong - time_second) + 5) / 30; if (failsafe > 0xf) { failsafe = 0xf; sc->mlx_pause.mp_howlong = time_second + (0xf * 30) - 5; } } /* build commands for every channel requested */ for (i = 0; i < sc->mlx_enq2->me_actual_channels; i++) { if ((1 << i) & sc->mlx_pause.mp_which) { /* get ourselves a command buffer */ if ((mc = mlx_alloccmd(sc)) == NULL) goto fail; /* get a command slot */ mc->mc_flags |= MLX_CMD_PRIORITY; if (mlx_getslot(mc)) goto fail; /* build the command */ mlx_make_type2(mc, command, (failsafe << 4) | i, 0, 0, 0, 0, 0, 0, 0); mc->mc_complete = mlx_pause_done; mc->mc_private = sc; /* XXX not needed */ if (mlx_start(mc)) goto fail; /* command submitted OK */ return; fail: device_printf(sc->mlx_dev, "%s failed for channel %d\n", command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", i); if (mc != NULL) mlx_releasecmd(mc); } } } static void mlx_pause_done(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int command = mc->mc_mailbox[0]; int channel = mc->mc_mailbox[2] & 0xf; MLX_IO_ASSERT_LOCKED(sc); if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "%s command failed - %s\n", command == MLX_CMD_STOPCHANNEL ? "pause" : "resume", mlx_diagnose_command(mc)); } else if (command == MLX_CMD_STOPCHANNEL) { device_printf(sc->mlx_dev, "channel %d pausing for %ld seconds\n", channel, (long)(sc->mlx_pause.mp_howlong - time_second)); } else { device_printf(sc->mlx_dev, "channel %d resuming\n", channel); } mlx_releasecmd(mc); } /******************************************************************************** ******************************************************************************** Command Submission ******************************************************************************** ********************************************************************************/ static void mlx_enquire_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_softc *sc; struct mlx_command *mc; mc = (struct mlx_command *)arg; if (error) return; mlx_setup_dmamap(mc, segs, nsegments, error); /* build an enquiry command */ sc = mc->mc_sc; mlx_make_type2(mc, mc->mc_command, 0, 0, 0, 0, 0, 0, mc->mc_dataphys, 0); /* do we want a completion callback? */ if (mc->mc_complete != NULL) { if ((error = mlx_start(mc)) != 0) return; } else { /* run the command in either polled or wait mode */ if ((sc->mlx_state & MLX_STATE_INTEN) ? mlx_wait_command(mc) : mlx_poll_command(mc)) return; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "ENQUIRY failed - %s\n", mlx_diagnose_command(mc)); return; } } } /******************************************************************************** * Perform an Enquiry command using a type-3 command buffer and a return a single * linear result buffer. If the completion function is specified, it will * be called with the completed command (and the result response will not be * valid until that point). Otherwise, the command will either be busy-waited * for (interrupts not enabled), or slept for. */ static void * mlx_enquire(struct mlx_softc *sc, int command, size_t bufsize, void (* complete)(struct mlx_command *mc)) { struct mlx_command *mc; void *result; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; result = NULL; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* allocate the response structure */ if ((result = malloc(bufsize, M_DEVBUF, M_NOWAIT)) == NULL) goto out; /* get a command slot */ mc->mc_flags |= MLX_CMD_PRIORITY | MLX_CMD_DATAOUT; if (mlx_getslot(mc)) goto out; /* map the command so the controller can see it */ mc->mc_data = result; mc->mc_length = bufsize; mc->mc_command = command; if (complete != NULL) { mc->mc_complete = complete; mc->mc_private = mc; } error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_enquire_cb, mc, BUS_DMA_NOWAIT); out: /* we got a command, but nobody else will free it */ if ((mc != NULL) && (mc->mc_complete == NULL)) mlx_releasecmd(mc); /* we got an error, and we allocated a result */ if ((error != 0) && (result != NULL)) { free(result, M_DEVBUF); result = NULL; } return(result); } /******************************************************************************** * Perform a Flush command on the nominated controller. * * May be called with interrupts enabled or disabled; will not return until * the flush operation completes or fails. */ static int mlx_flush(struct mlx_softc *sc) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 1; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a flush command */ mlx_make_type2(mc, MLX_CMD_FLUSH, 0, 0, 0, 0, 0, 0, 0, 0); /* can't assume that interrupts are going to work here, so play it safe */ if (mlx_poll_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "FLUSH failed - %s\n", mlx_diagnose_command(mc)); goto out; } error = 0; /* success */ out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Start a background consistency check on (drive). * * May be called with interrupts enabled or disabled; will return as soon as the * operation has started or been refused. */ static int mlx_check(struct mlx_softc *sc, int drive) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 0x10000; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a checkasync command, set the "fix it" flag */ mlx_make_type2(mc, MLX_CMD_CHECKASYNC, 0, 0, 0, 0, 0, drive | 0x80, 0, 0); /* start the command and wait for it to be returned */ if (mlx_wait_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "CHECK ASYNC failed - %s\n", mlx_diagnose_command(mc)); } else { device_printf(sc->mlx_sysdrive[drive].ms_disk, "consistency check started"); } error = mc->mc_status; out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Start a background rebuild of the physical drive at (channel),(target). * * May be called with interrupts enabled or disabled; will return as soon as the * operation has started or been refused. */ static int mlx_rebuild(struct mlx_softc *sc, int channel, int target) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* get ourselves a command buffer */ error = 0x10000; if ((mc = mlx_alloccmd(sc)) == NULL) goto out; /* get a command slot */ if (mlx_getslot(mc)) goto out; /* build a checkasync command, set the "fix it" flag */ mlx_make_type2(mc, MLX_CMD_REBUILDASYNC, channel, target, 0, 0, 0, 0, 0, 0); /* start the command and wait for it to be returned */ if (mlx_wait_command(mc)) goto out; /* command completed OK? */ if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "REBUILD ASYNC failed - %s\n", mlx_diagnose_command(mc)); } else { device_printf(sc->mlx_dev, "drive rebuild started for %d:%d\n", channel, target); } error = mc->mc_status; out: if (mc != NULL) mlx_releasecmd(mc); return(error); } /******************************************************************************** * Run the command (mc) and return when it completes. * * Interrupts need to be enabled; returns nonzero on error. */ static int mlx_wait_command(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int error, count; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); mc->mc_complete = NULL; mc->mc_private = mc; /* wake us when you're done */ if ((error = mlx_start(mc)) != 0) return(error); count = 0; /* XXX better timeout? */ while ((mc->mc_status == MLX_STATUS_BUSY) && (count < 30)) { mtx_sleep(mc->mc_private, &sc->mlx_io_lock, PRIBIO | PCATCH, "mlxwcmd", hz); } if (mc->mc_status != 0) { device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc)); return(EIO); } return(0); } /******************************************************************************** * Start the command (mc) and busy-wait for it to complete. * * Should only be used when interrupts can't be relied upon. Returns 0 on * success, nonzero on error. * Successfully completed commands are dequeued. */ static int mlx_poll_command(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int error, count; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); mc->mc_complete = NULL; mc->mc_private = NULL; /* we will poll for it */ if ((error = mlx_start(mc)) != 0) return(error); count = 0; do { /* poll for completion */ mlx_done(mc->mc_sc, 1); } while ((mc->mc_status == MLX_STATUS_BUSY) && (count++ < 15000000)); if (mc->mc_status != MLX_STATUS_BUSY) { TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); return(0); } device_printf(sc->mlx_dev, "command failed - %s\n", mlx_diagnose_command(mc)); return(EIO); } void mlx_startio_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_command *mc; struct mlxd_softc *mlxd; struct mlx_softc *sc; struct bio *bp; int blkcount; int driveno; int cmd; mc = (struct mlx_command *)arg; mlx_setup_dmamap(mc, segs, nsegments, error); sc = mc->mc_sc; bp = mc->mc_private; if (bp->bio_cmd == BIO_READ) { mc->mc_flags |= MLX_CMD_DATAIN; cmd = MLX_CMD_READSG; } else { mc->mc_flags |= MLX_CMD_DATAOUT; cmd = MLX_CMD_WRITESG; } /* build a suitable I/O command (assumes 512-byte rounded transfers) */ mlxd = bp->bio_disk->d_drv1; driveno = mlxd->mlxd_drive - sc->mlx_sysdrive; blkcount = (bp->bio_bcount + MLX_BLKSIZE - 1) / MLX_BLKSIZE; if ((bp->bio_pblkno + blkcount) > sc->mlx_sysdrive[driveno].ms_size) device_printf(sc->mlx_dev, "I/O beyond end of unit (%lld,%d > %lu)\n", (long long)bp->bio_pblkno, blkcount, (u_long)sc->mlx_sysdrive[driveno].ms_size); /* * Build the I/O command. Note that the SG list type bits are set to zero, * denoting the format of SG list that we are using. */ if (sc->mlx_iftype == MLX_IFTYPE_2) { mlx_make_type1(mc, (cmd == MLX_CMD_WRITESG) ? MLX_CMD_WRITESG_OLD : MLX_CMD_READSG_OLD, blkcount & 0xff, /* xfer length low byte */ bp->bio_pblkno, /* physical block number */ driveno, /* target drive number */ mc->mc_sgphys, /* location of SG list */ mc->mc_nsgent & 0x3f); /* size of SG list */ } else { mlx_make_type5(mc, cmd, blkcount & 0xff, /* xfer length low byte */ (driveno << 3) | ((blkcount >> 8) & 0x07), /* target+length high 3 bits */ bp->bio_pblkno, /* physical block number */ mc->mc_sgphys, /* location of SG list */ mc->mc_nsgent & 0x3f); /* size of SG list */ } /* try to give command to controller */ if (mlx_start(mc) != 0) { /* fail the command */ mc->mc_status = MLX_STATUS_WEDGED; mlx_completeio(mc); } sc->mlx_state &= ~MLX_STATE_QFROZEN; } /******************************************************************************** * Pull as much work off the softc's work queue as possible and give it to the * controller. Leave a couple of slots free for emergencies. */ static void mlx_startio(struct mlx_softc *sc) { struct mlx_command *mc; struct bio *bp; int error; MLX_IO_ASSERT_LOCKED(sc); /* spin until something prevents us from doing any work */ for (;;) { if (sc->mlx_state & MLX_STATE_QFROZEN) break; /* see if there's work to be done */ if ((bp = bioq_first(&sc->mlx_bioq)) == NULL) break; /* get a command */ if ((mc = mlx_alloccmd(sc)) == NULL) break; /* get a slot for the command */ if (mlx_getslot(mc) != 0) { mlx_releasecmd(mc); break; } /* get the buf containing our work */ bioq_remove(&sc->mlx_bioq, bp); sc->mlx_waitbufs--; /* connect the buf to the command */ mc->mc_complete = mlx_completeio; mc->mc_private = bp; mc->mc_data = bp->bio_data; mc->mc_length = bp->bio_bcount; /* map the command so the controller can work with it */ error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_startio_cb, mc, 0); if (error == EINPROGRESS) { sc->mlx_state |= MLX_STATE_QFROZEN; break; } } } /******************************************************************************** * Handle completion of an I/O command. */ static void mlx_completeio(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; struct bio *bp = mc->mc_private; struct mlxd_softc *mlxd = bp->bio_disk->d_drv1; MLX_IO_ASSERT_LOCKED(sc); if (mc->mc_status != MLX_STATUS_OK) { /* could be more verbose here? */ bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; switch(mc->mc_status) { case MLX_STATUS_RDWROFFLINE: /* system drive has gone offline */ device_printf(mlxd->mlxd_dev, "drive offline\n"); /* should signal this with a return code */ mlxd->mlxd_drive->ms_state = MLX_SYSD_OFFLINE; break; default: /* other I/O error */ device_printf(sc->mlx_dev, "I/O error - %s\n", mlx_diagnose_command(mc)); #if 0 device_printf(sc->mlx_dev, " b_bcount %ld blkcount %ld b_pblkno %d\n", bp->bio_bcount, bp->bio_bcount / MLX_BLKSIZE, bp->bio_pblkno); device_printf(sc->mlx_dev, " %13D\n", mc->mc_mailbox, " "); #endif break; } } mlx_releasecmd(mc); mlxd_intr(bp); } void mlx_user_cb(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_usercommand *mu; struct mlx_command *mc; struct mlx_dcdb *dcdb; mc = (struct mlx_command *)arg; if (error) return; mlx_setup_dmamap(mc, segs, nsegments, error); mu = (struct mlx_usercommand *)mc->mc_private; dcdb = NULL; /* * If this is a passthrough SCSI command, the DCDB is packed at the * beginning of the data area. Fix up the DCDB to point to the correct * physical address and override any bufptr supplied by the caller since * we know what it's meant to be. */ if (mc->mc_mailbox[0] == MLX_CMD_DIRECT_CDB) { dcdb = (struct mlx_dcdb *)mc->mc_data; dcdb->dcdb_physaddr = mc->mc_dataphys + sizeof(*dcdb); mu->mu_bufptr = 8; } /* * If there's a data buffer, fix up the command's buffer pointer. */ if (mu->mu_datasize > 0) { mc->mc_mailbox[mu->mu_bufptr ] = mc->mc_dataphys & 0xff; mc->mc_mailbox[mu->mu_bufptr + 1] = (mc->mc_dataphys >> 8) & 0xff; mc->mc_mailbox[mu->mu_bufptr + 2] = (mc->mc_dataphys >> 16) & 0xff; mc->mc_mailbox[mu->mu_bufptr + 3] = (mc->mc_dataphys >> 24) & 0xff; } debug(0, "command fixup"); /* submit the command and wait */ if (mlx_wait_command(mc) != 0) return; } /******************************************************************************** * Take a command from user-space and try to run it. * * XXX Note that this can't perform very much in the way of error checking, and * as such, applications _must_ be considered trustworthy. * XXX Commands using S/G for data are not supported. */ static int mlx_user_command(struct mlx_softc *sc, struct mlx_usercommand *mu) { struct mlx_command *mc; void *kbuf; int error; debug_called(0); kbuf = NULL; mc = NULL; error = ENOMEM; /* get ourselves a command and copy in from user space */ MLX_IO_LOCK(sc); if ((mc = mlx_alloccmd(sc)) == NULL) { MLX_IO_UNLOCK(sc); return(error); } bcopy(mu->mu_command, mc->mc_mailbox, sizeof(mc->mc_mailbox)); debug(0, "got command buffer"); /* * if we need a buffer for data transfer, allocate one and copy in its * initial contents */ if (mu->mu_datasize > 0) { if (mu->mu_datasize > MLX_MAXPHYS) { error = EINVAL; goto out; } MLX_IO_UNLOCK(sc); if (((kbuf = malloc(mu->mu_datasize, M_DEVBUF, M_WAITOK)) == NULL) || (error = copyin(mu->mu_buf, kbuf, mu->mu_datasize))) { MLX_IO_LOCK(sc); goto out; } MLX_IO_LOCK(sc); debug(0, "got kernel buffer"); } /* get a command slot */ if (mlx_getslot(mc)) goto out; debug(0, "got a slot"); if (mu->mu_datasize > 0) { /* range check the pointer to physical buffer address */ if ((mu->mu_bufptr < 0) || (mu->mu_bufptr > (sizeof(mu->mu_command) - sizeof(u_int32_t)))) { error = EINVAL; goto out; } } /* map the command so the controller can see it */ mc->mc_data = kbuf; mc->mc_length = mu->mu_datasize; mc->mc_private = mu; error = bus_dmamap_load(sc->mlx_buffer_dmat, mc->mc_dmamap, mc->mc_data, mc->mc_length, mlx_user_cb, mc, BUS_DMA_NOWAIT); if (error) goto out; /* copy out status and data */ mu->mu_status = mc->mc_status; if (mu->mu_datasize > 0) { MLX_IO_UNLOCK(sc); error = copyout(kbuf, mu->mu_buf, mu->mu_datasize); MLX_IO_LOCK(sc); } out: mlx_releasecmd(mc); MLX_IO_UNLOCK(sc); if (kbuf != NULL) free(kbuf, M_DEVBUF); return(error); } /******************************************************************************** ******************************************************************************** Command I/O to Controller ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Find a free command slot for (mc). * * Don't hand out a slot to a normal-priority command unless there are at least * 4 slots free for priority commands. */ static int mlx_getslot(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int slot, limit; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); /* * Enforce slot-usage limit, if we have the required information. */ if (sc->mlx_enq2 != NULL) { limit = sc->mlx_enq2->me_max_commands; } else { limit = 2; } if (sc->mlx_busycmds >= ((mc->mc_flags & MLX_CMD_PRIORITY) ? limit : limit - 4)) return(EBUSY); /* * Allocate an outstanding command slot * * XXX linear search is slow */ for (slot = 0; slot < limit; slot++) { debug(2, "try slot %d", slot); if (sc->mlx_busycmd[slot] == NULL) break; } if (slot < limit) { sc->mlx_busycmd[slot] = mc; sc->mlx_busycmds++; } /* out of slots? */ if (slot >= limit) return(EBUSY); debug(2, "got slot %d", slot); mc->mc_slot = slot; return(0); } /******************************************************************************** * Map/unmap (mc)'s data in the controller's addressable space. */ static void mlx_setup_dmamap(struct mlx_command *mc, bus_dma_segment_t *segs, int nsegments, int error) { struct mlx_softc *sc = mc->mc_sc; struct mlx_sgentry *sg; int i; debug_called(1); /* XXX should be unnecessary */ if (sc->mlx_enq2 && (nsegments > sc->mlx_enq2->me_max_sg)) panic("MLX: too many s/g segments (%d, max %d)", nsegments, sc->mlx_enq2->me_max_sg); /* get base address of s/g table */ sg = sc->mlx_sgtable + (mc->mc_slot * MLX_NSEG); /* save s/g table information in command */ mc->mc_nsgent = nsegments; mc->mc_sgphys = sc->mlx_sgbusaddr + (mc->mc_slot * MLX_NSEG * sizeof(struct mlx_sgentry)); mc->mc_dataphys = segs[0].ds_addr; /* populate s/g table */ for (i = 0; i < nsegments; i++, sg++) { sg->sg_addr = segs[i].ds_addr; sg->sg_count = segs[i].ds_len; } /* Make sure the buffers are visible on the bus. */ if (mc->mc_flags & MLX_CMD_DATAIN) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREREAD); if (mc->mc_flags & MLX_CMD_DATAOUT) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_PREWRITE); } static void mlx_unmapcmd(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); /* if the command involved data at all */ if (mc->mc_data != NULL) { if (mc->mc_flags & MLX_CMD_DATAIN) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTREAD); if (mc->mc_flags & MLX_CMD_DATAOUT) bus_dmamap_sync(sc->mlx_buffer_dmat, mc->mc_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mlx_buffer_dmat, mc->mc_dmamap); } } /******************************************************************************** * Try to deliver (mc) to the controller. * * Can be called at any interrupt level, with or without interrupts enabled. */ static int mlx_start(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; int i; debug_called(1); /* save the slot number as ident so we can handle this command when complete */ mc->mc_mailbox[0x1] = mc->mc_slot; /* mark the command as currently being processed */ mc->mc_status = MLX_STATUS_BUSY; /* set a default 60-second timeout XXX tunable? XXX not currently used */ mc->mc_timeout = time_second + 60; /* spin waiting for the mailbox */ for (i = 100000; i > 0; i--) { if (sc->mlx_tryqueue(sc, mc)) { /* move command to work queue */ TAILQ_INSERT_TAIL(&sc->mlx_work, mc, mc_link); return (0); } else if (i > 1) mlx_done(sc, 0); } /* * We couldn't get the controller to take the command. Revoke the slot * that the command was given and return it with a bad status. */ sc->mlx_busycmd[mc->mc_slot] = NULL; device_printf(sc->mlx_dev, "controller wedged (not taking commands)\n"); mc->mc_status = MLX_STATUS_WEDGED; mlx_complete(sc); return(EIO); } /******************************************************************************** * Poll the controller (sc) for completed commands. * Update command status and free slots for reuse. If any slots were freed, * new commands may be posted. * * Returns nonzero if one or more commands were completed. */ static int mlx_done(struct mlx_softc *sc, int startio) { struct mlx_command *mc; int result; u_int8_t slot; u_int16_t status; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); result = 0; /* loop collecting completed commands */ for (;;) { /* poll for a completed command's identifier and status */ if (sc->mlx_findcomplete(sc, &slot, &status)) { result = 1; mc = sc->mlx_busycmd[slot]; /* find command */ if (mc != NULL) { /* paranoia */ if (mc->mc_status == MLX_STATUS_BUSY) { mc->mc_status = status; /* save status */ /* free slot for reuse */ sc->mlx_busycmd[slot] = NULL; sc->mlx_busycmds--; } else { device_printf(sc->mlx_dev, "duplicate done event for slot %d\n", slot); } } else { device_printf(sc->mlx_dev, "done event for nonbusy slot %d\n", slot); } } else { break; } } /* if we've completed any commands, try posting some more */ if (result && startio) mlx_startio(sc); /* handle completion and timeouts */ mlx_complete(sc); return(result); } /******************************************************************************** * Perform post-completion processing for commands on (sc). */ static void mlx_complete(struct mlx_softc *sc) { struct mlx_command *mc, *nc; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* scan the list of busy/done commands */ mc = TAILQ_FIRST(&sc->mlx_work); while (mc != NULL) { nc = TAILQ_NEXT(mc, mc_link); /* Command has been completed in some fashion */ if (mc->mc_status != MLX_STATUS_BUSY) { /* unmap the command's data buffer */ mlx_unmapcmd(mc); /* * Does the command have a completion handler? */ if (mc->mc_complete != NULL) { /* remove from list and give to handler */ TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); mc->mc_complete(mc); /* * Is there a sleeper waiting on this command? */ } else if (mc->mc_private != NULL) { /* sleeping caller wants to know about it */ /* remove from list and wake up sleeper */ TAILQ_REMOVE(&sc->mlx_work, mc, mc_link); wakeup_one(mc->mc_private); /* * Leave the command for a caller that's polling for it. */ } else { } } mc = nc; } } /******************************************************************************** ******************************************************************************** Command Buffer Management ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Get a new command buffer. * * This may return NULL in low-memory cases. * * Note that using malloc() is expensive (the command buffer is << 1 page) but * necessary if we are to be a loadable module before the zone allocator is fixed. * * If possible, we recycle a command buffer that's been used before. * * XXX Note that command buffers are not cleaned out - it is the caller's * responsibility to ensure that all required fields are filled in before * using a buffer. */ static struct mlx_command * mlx_alloccmd(struct mlx_softc *sc) { struct mlx_command *mc; int error; debug_called(1); MLX_IO_ASSERT_LOCKED(sc); if ((mc = TAILQ_FIRST(&sc->mlx_freecmds)) != NULL) TAILQ_REMOVE(&sc->mlx_freecmds, mc, mc_link); /* allocate a new command buffer? */ if (mc == NULL) { mc = (struct mlx_command *)malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT | M_ZERO); if (mc != NULL) { mc->mc_sc = sc; error = bus_dmamap_create(sc->mlx_buffer_dmat, 0, &mc->mc_dmamap); if (error) { free(mc, M_DEVBUF); return(NULL); } } } return(mc); } /******************************************************************************** * Release a command buffer for recycling. * * XXX It might be a good idea to limit the number of commands we save for reuse * if it's shown that this list bloats out massively. */ static void mlx_releasecmd(struct mlx_command *mc) { debug_called(1); MLX_IO_ASSERT_LOCKED(mc->mc_sc); TAILQ_INSERT_HEAD(&mc->mc_sc->mlx_freecmds, mc, mc_link); } /******************************************************************************** * Permanently discard a command buffer. */ static void mlx_freecmd(struct mlx_command *mc) { struct mlx_softc *sc = mc->mc_sc; debug_called(1); bus_dmamap_destroy(sc->mlx_buffer_dmat, mc->mc_dmamap); free(mc, M_DEVBUF); } /******************************************************************************** ******************************************************************************** Type 3 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v3_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_FULL)) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V3_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* post command */ MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_FULL); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v3_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V3_GET_ODBR(sc) & MLX_V3_ODB_SAVAIL) { *slot = MLX_V3_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V3_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V3_PUT_ODBR(sc, MLX_V3_ODB_SAVAIL); MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. (No acknowledge required) */ static void mlx_v3_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V3_PUT_IER(sc, 0); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V3_PUT_IER(sc, 1); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v3_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V3_PUT_IDBR(sc, MLX_V3_IDB_SACK); DELAY(1000); } /* init in progress? */ if (!(MLX_V3_GET_IDBR(sc) & MLX_V3_IDB_INIT_BUSY)) return(0); /* test error value */ fwerror = MLX_V3_GET_FWERROR(sc); if (!(fwerror & MLX_V3_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V3_FWERROR_PEND; *param1 = MLX_V3_GET_FWERROR_PARAM1(sc); *param2 = MLX_V3_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V3_PUT_FWERROR(sc, 0); return(2); } /******************************************************************************** ******************************************************************************** Type 4 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v4_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_FULL)) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V4_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* memory-mapped controller, so issue a write barrier to ensure the mailbox is filled */ bus_barrier(sc->mlx_mem, MLX_V4_MAILBOX, MLX_V4_MAILBOX_LENGTH, BUS_SPACE_BARRIER_WRITE); /* post command */ MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_HWMBOX_CMD); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v4_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V4_GET_ODBR(sc) & MLX_V4_ODB_HWSAVAIL) { *slot = MLX_V4_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V4_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V4_PUT_ODBR(sc, MLX_V4_ODB_HWMBOX_ACK); MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. */ static void mlx_v4_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK | MLX_V4_IER_DISINT); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V4_PUT_IER(sc, MLX_V4_IER_MASK & ~MLX_V4_IER_DISINT); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v4_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V4_PUT_IDBR(sc, MLX_V4_IDB_SACK); DELAY(1000); } /* init in progress? */ if (!(MLX_V4_GET_IDBR(sc) & MLX_V4_IDB_INIT_BUSY)) return(0); /* test error value */ fwerror = MLX_V4_GET_FWERROR(sc); if (!(fwerror & MLX_V4_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V4_FWERROR_PEND; *param1 = MLX_V4_GET_FWERROR_PARAM1(sc); *param2 = MLX_V4_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V4_PUT_FWERROR(sc, 0); return(2); } /******************************************************************************** ******************************************************************************** Type 5 interface accessor methods ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to give (mc) to the controller. Returns 1 if successful, 0 on failure * (the controller is not ready to take a command). */ static int mlx_v5_tryqueue(struct mlx_softc *sc, struct mlx_command *mc) { int i; debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* ready for our command? */ if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_EMPTY) { /* copy mailbox data to window */ for (i = 0; i < 13; i++) MLX_V5_PUT_MAILBOX(sc, i, mc->mc_mailbox[i]); /* post command */ MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_HWMBOX_CMD); return(1); } return(0); } /******************************************************************************** * See if a command has been completed, if so acknowledge its completion * and recover the slot number and status code. */ static int mlx_v5_findcomplete(struct mlx_softc *sc, u_int8_t *slot, u_int16_t *status) { debug_called(2); MLX_IO_ASSERT_LOCKED(sc); /* status available? */ if (MLX_V5_GET_ODBR(sc) & MLX_V5_ODB_HWSAVAIL) { *slot = MLX_V5_GET_STATUS_IDENT(sc); /* get command identifier */ *status = MLX_V5_GET_STATUS(sc); /* get status */ /* acknowledge completion */ MLX_V5_PUT_ODBR(sc, MLX_V5_ODB_HWMBOX_ACK); MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK); return(1); } return(0); } /******************************************************************************** * Enable/disable interrupts as requested. */ static void mlx_v5_intaction(struct mlx_softc *sc, int action) { debug_called(1); MLX_IO_ASSERT_LOCKED(sc); switch(action) { case MLX_INTACTION_DISABLE: MLX_V5_PUT_IER(sc, 0xff & MLX_V5_IER_DISINT); sc->mlx_state &= ~MLX_STATE_INTEN; break; case MLX_INTACTION_ENABLE: MLX_V5_PUT_IER(sc, 0xff & ~MLX_V5_IER_DISINT); sc->mlx_state |= MLX_STATE_INTEN; break; } } /******************************************************************************** * Poll for firmware error codes during controller initialisation. * Returns 0 if initialisation is complete, 1 if still in progress but no * error has been fetched, 2 if an error has been retrieved. */ static int mlx_v5_fw_handshake(struct mlx_softc *sc, int *error, int *param1, int *param2, int first) { u_int8_t fwerror; debug_called(2); /* first time around, clear any hardware completion status */ if (first) { MLX_V5_PUT_IDBR(sc, MLX_V5_IDB_SACK); DELAY(1000); } /* init in progress? */ if (MLX_V5_GET_IDBR(sc) & MLX_V5_IDB_INIT_DONE) return(0); /* test for error value */ fwerror = MLX_V5_GET_FWERROR(sc); if (!(fwerror & MLX_V5_FWERROR_PEND)) return(1); /* mask status pending bit, fetch status */ *error = fwerror & ~MLX_V5_FWERROR_PEND; *param1 = MLX_V5_GET_FWERROR_PARAM1(sc); *param2 = MLX_V5_GET_FWERROR_PARAM2(sc); /* acknowledge */ MLX_V5_PUT_FWERROR(sc, 0xff); return(2); } /******************************************************************************** ******************************************************************************** Debugging ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Return a status message describing (mc) */ static char *mlx_status_messages[] = { "normal completion", /* 00 */ "irrecoverable data error", /* 01 */ "drive does not exist, or is offline", /* 02 */ "attempt to write beyond end of drive", /* 03 */ "bad data encountered", /* 04 */ "invalid log entry request", /* 05 */ "attempt to rebuild online drive", /* 06 */ "new disk failed during rebuild", /* 07 */ "invalid channel/target", /* 08 */ "rebuild/check already in progress", /* 09 */ "one or more disks are dead", /* 10 */ "invalid or non-redundant drive", /* 11 */ "channel is busy", /* 12 */ "channel is not stopped", /* 13 */ "rebuild successfully terminated", /* 14 */ "unsupported command", /* 15 */ "check condition received", /* 16 */ "device is busy", /* 17 */ "selection or command timeout", /* 18 */ "command terminated abnormally", /* 19 */ "" }; static struct { int command; u_int16_t status; int msg; } mlx_messages[] = { {MLX_CMD_READSG, 0x0001, 1}, {MLX_CMD_READSG, 0x0002, 1}, {MLX_CMD_READSG, 0x0105, 3}, {MLX_CMD_READSG, 0x010c, 4}, {MLX_CMD_WRITESG, 0x0001, 1}, {MLX_CMD_WRITESG, 0x0002, 1}, {MLX_CMD_WRITESG, 0x0105, 3}, {MLX_CMD_READSG_OLD, 0x0001, 1}, {MLX_CMD_READSG_OLD, 0x0002, 1}, {MLX_CMD_READSG_OLD, 0x0105, 3}, {MLX_CMD_WRITESG_OLD, 0x0001, 1}, {MLX_CMD_WRITESG_OLD, 0x0002, 1}, {MLX_CMD_WRITESG_OLD, 0x0105, 3}, {MLX_CMD_LOGOP, 0x0105, 5}, {MLX_CMD_REBUILDASYNC, 0x0002, 6}, {MLX_CMD_REBUILDASYNC, 0x0004, 7}, {MLX_CMD_REBUILDASYNC, 0x0105, 8}, {MLX_CMD_REBUILDASYNC, 0x0106, 9}, {MLX_CMD_REBUILDASYNC, 0x0107, 14}, {MLX_CMD_CHECKASYNC, 0x0002, 10}, {MLX_CMD_CHECKASYNC, 0x0105, 11}, {MLX_CMD_CHECKASYNC, 0x0106, 9}, {MLX_CMD_STOPCHANNEL, 0x0106, 12}, {MLX_CMD_STOPCHANNEL, 0x0105, 8}, {MLX_CMD_STARTCHANNEL, 0x0005, 13}, {MLX_CMD_STARTCHANNEL, 0x0105, 8}, {MLX_CMD_DIRECT_CDB, 0x0002, 16}, {MLX_CMD_DIRECT_CDB, 0x0008, 17}, {MLX_CMD_DIRECT_CDB, 0x000e, 18}, {MLX_CMD_DIRECT_CDB, 0x000f, 19}, {MLX_CMD_DIRECT_CDB, 0x0105, 8}, {0, 0x0104, 14}, {-1, 0, 0} }; static char * mlx_diagnose_command(struct mlx_command *mc) { static char unkmsg[80]; int i; /* look up message in table */ for (i = 0; mlx_messages[i].command != -1; i++) if (((mc->mc_mailbox[0] == mlx_messages[i].command) || (mlx_messages[i].command == 0)) && (mc->mc_status == mlx_messages[i].status)) return(mlx_status_messages[mlx_messages[i].msg]); sprintf(unkmsg, "unknown response 0x%x for command 0x%x", (int)mc->mc_status, (int)mc->mc_mailbox[0]); return(unkmsg); } /******************************************************************************* * Print a string describing the controller (sc) */ static struct { int hwid; char *name; } mlx_controller_names[] = { {0x01, "960P/PD"}, {0x02, "960PL"}, {0x10, "960PG"}, {0x11, "960PJ"}, {0x12, "960PR"}, {0x13, "960PT"}, {0x14, "960PTL0"}, {0x15, "960PRL"}, {0x16, "960PTL1"}, {0x20, "1164PVX"}, {-1, NULL} }; static void mlx_describe_controller(struct mlx_softc *sc) { static char buf[80]; char *model; int i; for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) { if ((sc->mlx_enq2->me_hardware_id & 0xff) == mlx_controller_names[i].hwid) { model = mlx_controller_names[i].name; break; } } if (model == NULL) { sprintf(buf, " model 0x%x", sc->mlx_enq2->me_hardware_id & 0xff); model = buf; } device_printf(sc->mlx_dev, "DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n", model, sc->mlx_enq2->me_actual_channels, sc->mlx_enq2->me_actual_channels > 1 ? "s" : "", sc->mlx_enq2->me_firmware_id & 0xff, (sc->mlx_enq2->me_firmware_id >> 8) & 0xff, (sc->mlx_enq2->me_firmware_id >> 24) & 0xff, (sc->mlx_enq2->me_firmware_id >> 16) & 0xff, sc->mlx_enq2->me_mem_size / (1024 * 1024)); if (bootverbose) { device_printf(sc->mlx_dev, " Hardware ID 0x%08x\n", sc->mlx_enq2->me_hardware_id); device_printf(sc->mlx_dev, " Firmware ID 0x%08x\n", sc->mlx_enq2->me_firmware_id); device_printf(sc->mlx_dev, " Configured/Actual channels %d/%d\n", sc->mlx_enq2->me_configured_channels, sc->mlx_enq2->me_actual_channels); device_printf(sc->mlx_dev, " Max Targets %d\n", sc->mlx_enq2->me_max_targets); device_printf(sc->mlx_dev, " Max Tags %d\n", sc->mlx_enq2->me_max_tags); device_printf(sc->mlx_dev, " Max System Drives %d\n", sc->mlx_enq2->me_max_sys_drives); device_printf(sc->mlx_dev, " Max Arms %d\n", sc->mlx_enq2->me_max_arms); device_printf(sc->mlx_dev, " Max Spans %d\n", sc->mlx_enq2->me_max_spans); device_printf(sc->mlx_dev, " DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", sc->mlx_enq2->me_mem_size, sc->mlx_enq2->me_cache_size, sc->mlx_enq2->me_flash_size, sc->mlx_enq2->me_nvram_size); device_printf(sc->mlx_dev, " DRAM type %d\n", sc->mlx_enq2->me_mem_type); device_printf(sc->mlx_dev, " Clock Speed %dns\n", sc->mlx_enq2->me_clock_speed); device_printf(sc->mlx_dev, " Hardware Speed %dns\n", sc->mlx_enq2->me_hardware_speed); device_printf(sc->mlx_dev, " Max Commands %d\n", sc->mlx_enq2->me_max_commands); device_printf(sc->mlx_dev, " Max SG Entries %d\n", sc->mlx_enq2->me_max_sg); device_printf(sc->mlx_dev, " Max DP %d\n", sc->mlx_enq2->me_max_dp); device_printf(sc->mlx_dev, " Max IOD %d\n", sc->mlx_enq2->me_max_iod); device_printf(sc->mlx_dev, " Max Comb %d\n", sc->mlx_enq2->me_max_comb); device_printf(sc->mlx_dev, " Latency %ds\n", sc->mlx_enq2->me_latency); device_printf(sc->mlx_dev, " SCSI Timeout %ds\n", sc->mlx_enq2->me_scsi_timeout); device_printf(sc->mlx_dev, " Min Free Lines %d\n", sc->mlx_enq2->me_min_freelines); device_printf(sc->mlx_dev, " Rate Constant %d\n", sc->mlx_enq2->me_rate_const); device_printf(sc->mlx_dev, " MAXBLK %d\n", sc->mlx_enq2->me_maxblk); device_printf(sc->mlx_dev, " Blocking Factor %d sectors\n", sc->mlx_enq2->me_blocking_factor); device_printf(sc->mlx_dev, " Cache Line Size %d blocks\n", sc->mlx_enq2->me_cacheline); device_printf(sc->mlx_dev, " SCSI Capability %s%dMHz, %d bit\n", sc->mlx_enq2->me_scsi_cap & (1<<4) ? "differential " : "", (1 << ((sc->mlx_enq2->me_scsi_cap >> 2) & 3)) * 10, 8 << (sc->mlx_enq2->me_scsi_cap & 0x3)); device_printf(sc->mlx_dev, " Firmware Build Number %d\n", sc->mlx_enq2->me_firmware_build); device_printf(sc->mlx_dev, " Fault Management Type %d\n", sc->mlx_enq2->me_fault_mgmt_type); device_printf(sc->mlx_dev, " Features %b\n", sc->mlx_enq2->me_firmware_features, "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n"); } } /******************************************************************************* * Emit a string describing the firmware handshake status code, and return a flag * indicating whether the code represents a fatal error. * * Error code interpretations are from the Linux driver, and don't directly match * the messages printed by Mylex's BIOS. This may change if documentation on the * codes is forthcoming. */ static int mlx_fw_message(struct mlx_softc *sc, int error, int param1, int param2) { switch(error) { case 0x00: device_printf(sc->mlx_dev, "physical drive %d:%d not responding\n", param2, param1); break; case 0x08: /* we could be neater about this and give some indication when we receive more of them */ if (!(sc->mlx_flags & MLX_SPINUP_REPORTED)) { device_printf(sc->mlx_dev, "spinning up drives...\n"); sc->mlx_flags |= MLX_SPINUP_REPORTED; } break; case 0x30: device_printf(sc->mlx_dev, "configuration checksum error\n"); break; case 0x60: device_printf(sc->mlx_dev, "mirror race recovery failed\n"); break; case 0x70: device_printf(sc->mlx_dev, "mirror race recovery in progress\n"); break; case 0x90: device_printf(sc->mlx_dev, "physical drive %d:%d COD mismatch\n", param2, param1); break; case 0xa0: device_printf(sc->mlx_dev, "logical drive installation aborted\n"); break; case 0xb0: device_printf(sc->mlx_dev, "mirror race on a critical system drive\n"); break; case 0xd0: device_printf(sc->mlx_dev, "new controller configuration found\n"); break; case 0xf0: device_printf(sc->mlx_dev, "FATAL MEMORY PARITY ERROR\n"); return(1); default: device_printf(sc->mlx_dev, "unknown firmware initialisation error %02x:%02x:%02x\n", error, param1, param2); break; } return(0); } /******************************************************************************** ******************************************************************************** Utility Functions ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Find the disk whose unit number is (unit) on this controller */ static struct mlx_sysdrive * mlx_findunit(struct mlx_softc *sc, int unit) { int i; /* search system drives */ MLX_CONFIG_ASSERT_LOCKED(sc); for (i = 0; i < MLX_MAXDRIVES; i++) { /* is this one attached? */ if (sc->mlx_sysdrive[i].ms_disk != 0) { /* is this the one? */ if (unit == device_get_unit(sc->mlx_sysdrive[i].ms_disk)) return(&sc->mlx_sysdrive[i]); } } return(NULL); } Index: head/sys/dev/mlx/mlx_pci.c =================================================================== --- head/sys/dev/mlx/mlx_pci.c (revision 280346) +++ head/sys/dev/mlx/mlx_pci.c (revision 280347) @@ -1,218 +1,219 @@ /*- * Copyright (c) 1999 Michael Smith * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int mlx_pci_probe(device_t dev); static int mlx_pci_attach(device_t dev); static device_method_t mlx_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mlx_pci_probe), DEVMETHOD(device_attach, mlx_pci_attach), DEVMETHOD(device_detach, mlx_detach), DEVMETHOD(device_shutdown, mlx_shutdown), DEVMETHOD(device_suspend, mlx_suspend), DEVMETHOD(device_resume, mlx_resume), DEVMETHOD_END }; static driver_t mlx_pci_driver = { "mlx", mlx_methods, sizeof(struct mlx_softc) }; DRIVER_MODULE(mlx, pci, mlx_pci_driver, mlx_devclass, 0, 0); struct mlx_ident { u_int16_t vendor; u_int16_t device; u_int16_t subvendor; u_int16_t subdevice; int iftype; char *desc; } mlx_identifiers[] = { {0x1069, 0x0001, 0x0000, 0x0000, MLX_IFTYPE_2, "Mylex version 2 RAID interface"}, {0x1069, 0x0002, 0x0000, 0x0000, MLX_IFTYPE_3, "Mylex version 3 RAID interface"}, {0x1069, 0x0010, 0x0000, 0x0000, MLX_IFTYPE_4, "Mylex version 4 RAID interface"}, {0x1011, 0x1065, 0x1069, 0x0020, MLX_IFTYPE_5, "Mylex version 5 RAID interface"}, {0, 0, 0, 0, 0, 0} }; static struct mlx_ident * mlx_pci_match(device_t dev) { struct mlx_ident *m; for (m = mlx_identifiers; m->vendor != 0; m++) { if ((m->vendor == pci_get_vendor(dev)) && (m->device == pci_get_device(dev)) && ((m->subvendor == 0) || ((m->subvendor == pci_get_subvendor(dev)) && (m->subdevice == pci_get_subdevice(dev))))) return (m); } return (NULL); } static int mlx_pci_probe(device_t dev) { struct mlx_ident *m; debug_called(1); m = mlx_pci_match(dev); if (m != NULL) { device_set_desc(dev, m->desc); return(BUS_PROBE_DEFAULT); } return(ENXIO); } static int mlx_pci_attach(device_t dev) { struct mlx_softc *sc; struct mlx_ident *m; int error; debug_called(1); pci_enable_busmaster(dev); sc = device_get_softc(dev); sc->mlx_dev = dev; /* * Work out what sort of adapter this is (we need to know this in order * to map the appropriate interface resources). */ m = mlx_pci_match(dev); if (m == NULL) /* shouldn't happen */ return(ENXIO); sc->mlx_iftype = m->iftype; mtx_init(&sc->mlx_io_lock, "mlx I/O", NULL, MTX_DEF); sx_init(&sc->mlx_config_lock, "mlx config"); callout_init_mtx(&sc->mlx_timeout, &sc->mlx_io_lock, 0); /* * Allocate the PCI register window. */ /* type 2/3 adapters have an I/O region we don't prefer at base 0 */ switch(sc->mlx_iftype) { case MLX_IFTYPE_2: case MLX_IFTYPE_3: sc->mlx_mem_type = SYS_RES_MEMORY; sc->mlx_mem_rid = MLX_CFG_BASE1; sc->mlx_mem = bus_alloc_resource_any(dev, sc->mlx_mem_type, &sc->mlx_mem_rid, RF_ACTIVE); if (sc->mlx_mem == NULL) { sc->mlx_mem_type = SYS_RES_IOPORT; sc->mlx_mem_rid = MLX_CFG_BASE0; sc->mlx_mem = bus_alloc_resource_any(dev, sc->mlx_mem_type, &sc->mlx_mem_rid, RF_ACTIVE); } break; case MLX_IFTYPE_4: case MLX_IFTYPE_5: sc->mlx_mem_type = SYS_RES_MEMORY; sc->mlx_mem_rid = MLX_CFG_BASE0; sc->mlx_mem = bus_alloc_resource_any(dev, sc->mlx_mem_type, &sc->mlx_mem_rid, RF_ACTIVE); break; } if (sc->mlx_mem == NULL) { device_printf(sc->mlx_dev, "couldn't allocate mailbox window\n"); mlx_free(sc); return(ENXIO); } /* * Allocate the parent bus DMA tag appropriate for PCI. */ error = bus_dma_tag_create(bus_get_dma_tag(dev), /* PCI parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, MLX_NSEG, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->mlx_parent_dmat); if (error != 0) { device_printf(dev, "can't allocate parent DMA tag\n"); mlx_free(sc); return(ENOMEM); } /* * Do bus-independant initialisation. */ error = mlx_attach(sc); if (error != 0) { mlx_free(sc); return(error); } /* * Start the controller. */ mlx_startup(sc); return(0); } Index: head/sys/dev/mly/mly.c =================================================================== --- head/sys/dev/mly/mly.c (revision 280346) +++ head/sys/dev/mly/mly.c (revision 280347) @@ -1,3012 +1,3014 @@ /*- * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int mly_probe(device_t dev); static int mly_attach(device_t dev); static int mly_pci_attach(struct mly_softc *sc); static int mly_detach(device_t dev); static int mly_shutdown(device_t dev); static void mly_intr(void *arg); static int mly_sg_map(struct mly_softc *sc); static void mly_sg_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int mly_mmbox_map(struct mly_softc *sc); static void mly_mmbox_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error); static void mly_free(struct mly_softc *sc); static int mly_get_controllerinfo(struct mly_softc *sc); static void mly_scan_devices(struct mly_softc *sc); static void mly_rescan_btl(struct mly_softc *sc, int bus, int target); static void mly_complete_rescan(struct mly_command *mc); static int mly_get_eventstatus(struct mly_softc *sc); static int mly_enable_mmbox(struct mly_softc *sc); static int mly_flush(struct mly_softc *sc); static int mly_ioctl(struct mly_softc *sc, struct mly_command_ioctl *ioctl, void **data, size_t datasize, u_int8_t *status, void *sense_buffer, size_t *sense_length); static void mly_check_event(struct mly_softc *sc); static void mly_fetch_event(struct mly_softc *sc); static void mly_complete_event(struct mly_command *mc); static void mly_process_event(struct mly_softc *sc, struct mly_event *me); static void mly_periodic(void *data); static int mly_immediate_command(struct mly_command *mc); static int mly_start(struct mly_command *mc); static void mly_done(struct mly_softc *sc); static void mly_complete(struct mly_softc *sc); static void mly_complete_handler(void *context, int pending); static int mly_alloc_command(struct mly_softc *sc, struct mly_command **mcp); static void mly_release_command(struct mly_command *mc); static void mly_alloc_commands_map(void *arg, bus_dma_segment_t *segs, int nseg, int error); static int mly_alloc_commands(struct mly_softc *sc); static void mly_release_commands(struct mly_softc *sc); static void mly_map_command(struct mly_command *mc); static void mly_unmap_command(struct mly_command *mc); static int mly_cam_attach(struct mly_softc *sc); static void mly_cam_detach(struct mly_softc *sc); static void mly_cam_rescan_btl(struct mly_softc *sc, int bus, int target); static void mly_cam_action(struct cam_sim *sim, union ccb *ccb); static int mly_cam_action_io(struct cam_sim *sim, struct ccb_scsiio *csio); static void mly_cam_poll(struct cam_sim *sim); static void mly_cam_complete(struct mly_command *mc); static struct cam_periph *mly_find_periph(struct mly_softc *sc, int bus, int target); static int mly_name_device(struct mly_softc *sc, int bus, int target); static int mly_fwhandshake(struct mly_softc *sc); static void mly_describe_controller(struct mly_softc *sc); #ifdef MLY_DEBUG static void mly_printstate(struct mly_softc *sc); static void mly_print_command(struct mly_command *mc); static void mly_print_packet(struct mly_command *mc); static void mly_panic(struct mly_softc *sc, char *reason); static void mly_timeout(void *arg); #endif void mly_print_controller(int controller); static d_open_t mly_user_open; static d_close_t mly_user_close; static d_ioctl_t mly_user_ioctl; static int mly_user_command(struct mly_softc *sc, struct mly_user_command *uc); static int mly_user_health(struct mly_softc *sc, struct mly_user_health *uh); #define MLY_CMD_TIMEOUT 20 static device_method_t mly_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mly_probe), DEVMETHOD(device_attach, mly_attach), DEVMETHOD(device_detach, mly_detach), DEVMETHOD(device_shutdown, mly_shutdown), { 0, 0 } }; static driver_t mly_pci_driver = { "mly", mly_methods, sizeof(struct mly_softc) }; static devclass_t mly_devclass; DRIVER_MODULE(mly, pci, mly_pci_driver, mly_devclass, 0, 0); MODULE_DEPEND(mly, pci, 1, 1, 1); MODULE_DEPEND(mly, cam, 1, 1, 1); static struct cdevsw mly_cdevsw = { .d_version = D_VERSION, .d_open = mly_user_open, .d_close = mly_user_close, .d_ioctl = mly_user_ioctl, .d_name = "mly", }; /******************************************************************************** ******************************************************************************** Device Interface ******************************************************************************** ********************************************************************************/ static struct mly_ident { u_int16_t vendor; u_int16_t device; u_int16_t subvendor; u_int16_t subdevice; int hwif; char *desc; } mly_identifiers[] = { {0x1069, 0xba56, 0x1069, 0x0040, MLY_HWIF_STRONGARM, "Mylex eXtremeRAID 2000"}, {0x1069, 0xba56, 0x1069, 0x0030, MLY_HWIF_STRONGARM, "Mylex eXtremeRAID 3000"}, {0x1069, 0x0050, 0x1069, 0x0050, MLY_HWIF_I960RX, "Mylex AcceleRAID 352"}, {0x1069, 0x0050, 0x1069, 0x0052, MLY_HWIF_I960RX, "Mylex AcceleRAID 170"}, {0x1069, 0x0050, 0x1069, 0x0054, MLY_HWIF_I960RX, "Mylex AcceleRAID 160"}, {0, 0, 0, 0, 0, 0} }; /******************************************************************************** * Compare the provided PCI device with the list we support. */ static int mly_probe(device_t dev) { struct mly_ident *m; debug_called(1); for (m = mly_identifiers; m->vendor != 0; m++) { if ((m->vendor == pci_get_vendor(dev)) && (m->device == pci_get_device(dev)) && ((m->subvendor == 0) || ((m->subvendor == pci_get_subvendor(dev)) && (m->subdevice == pci_get_subdevice(dev))))) { device_set_desc(dev, m->desc); return(BUS_PROBE_DEFAULT); /* allow room to be overridden */ } } return(ENXIO); } /******************************************************************************** * Initialise the controller and softc */ static int mly_attach(device_t dev) { struct mly_softc *sc = device_get_softc(dev); int error; debug_called(1); sc->mly_dev = dev; mtx_init(&sc->mly_lock, "mly", NULL, MTX_DEF); callout_init_mtx(&sc->mly_periodic, &sc->mly_lock, 0); #ifdef MLY_DEBUG callout_init_mtx(&sc->mly_timeout, &sc->mly_lock, 0); if (device_get_unit(sc->mly_dev) == 0) mly_softc0 = sc; #endif /* * Do PCI-specific initialisation. */ if ((error = mly_pci_attach(sc)) != 0) goto out; /* * Initialise per-controller queues. */ mly_initq_free(sc); mly_initq_busy(sc); mly_initq_complete(sc); /* * Initialise command-completion task. */ TASK_INIT(&sc->mly_task_complete, 0, mly_complete_handler, sc); /* disable interrupts before we start talking to the controller */ MLY_MASK_INTERRUPTS(sc); /* * Wait for the controller to come ready, handshake with the firmware if required. * This is typically only necessary on platforms where the controller BIOS does not * run. */ if ((error = mly_fwhandshake(sc))) goto out; /* * Allocate initial command buffers. */ if ((error = mly_alloc_commands(sc))) goto out; /* * Obtain controller feature information */ MLY_LOCK(sc); error = mly_get_controllerinfo(sc); MLY_UNLOCK(sc); if (error) goto out; /* * Reallocate command buffers now we know how many we want. */ mly_release_commands(sc); if ((error = mly_alloc_commands(sc))) goto out; /* * Get the current event counter for health purposes, populate the initial * health status buffer. */ MLY_LOCK(sc); error = mly_get_eventstatus(sc); /* * Enable memory-mailbox mode. */ if (error == 0) error = mly_enable_mmbox(sc); MLY_UNLOCK(sc); if (error) goto out; /* * Attach to CAM. */ if ((error = mly_cam_attach(sc))) goto out; /* * Print a little information about the controller */ mly_describe_controller(sc); /* * Mark all attached devices for rescan. */ MLY_LOCK(sc); mly_scan_devices(sc); /* * Instigate the first status poll immediately. Rescan completions won't * happen until interrupts are enabled, which should still be before * the SCSI subsystem gets to us, courtesy of the "SCSI settling delay". */ mly_periodic((void *)sc); MLY_UNLOCK(sc); /* * Create the control device. */ sc->mly_dev_t = make_dev(&mly_cdevsw, 0, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "mly%d", device_get_unit(sc->mly_dev)); sc->mly_dev_t->si_drv1 = sc; /* enable interrupts now */ MLY_UNMASK_INTERRUPTS(sc); #ifdef MLY_DEBUG callout_reset(&sc->mly_timeout, MLY_CMD_TIMEOUT * hz, mly_timeout, sc); #endif out: if (error != 0) mly_free(sc); return(error); } /******************************************************************************** * Perform PCI-specific initialisation. */ static int mly_pci_attach(struct mly_softc *sc) { int i, error; debug_called(1); /* assume failure is 'not configured' */ error = ENXIO; /* * Verify that the adapter is correctly set up in PCI space. */ pci_enable_busmaster(sc->mly_dev); /* * Allocate the PCI register window. */ sc->mly_regs_rid = PCIR_BAR(0); /* first base address register */ if ((sc->mly_regs_resource = bus_alloc_resource_any(sc->mly_dev, SYS_RES_MEMORY, &sc->mly_regs_rid, RF_ACTIVE)) == NULL) { mly_printf(sc, "can't allocate register window\n"); goto fail; } /* * Allocate and connect our interrupt. */ sc->mly_irq_rid = 0; if ((sc->mly_irq = bus_alloc_resource_any(sc->mly_dev, SYS_RES_IRQ, &sc->mly_irq_rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { mly_printf(sc, "can't allocate interrupt\n"); goto fail; } if (bus_setup_intr(sc->mly_dev, sc->mly_irq, INTR_TYPE_CAM | INTR_ENTROPY | INTR_MPSAFE, NULL, mly_intr, sc, &sc->mly_intr)) { mly_printf(sc, "can't set up interrupt\n"); goto fail; } /* assume failure is 'out of memory' */ error = ENOMEM; /* * Allocate the parent bus DMA tag appropriate for our PCI interface. * * Note that all of these controllers are 64-bit capable. */ if (bus_dma_tag_create(bus_get_dma_tag(sc->mly_dev),/* PCI parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, MLY_MAX_SGENTRIES, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->mly_parent_dmat)) { mly_printf(sc, "can't allocate parent DMA tag\n"); goto fail; } /* * Create DMA tag for mapping buffers into controller-addressable space. */ if (bus_dma_tag_create(sc->mly_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, MLY_MAX_SGENTRIES, /* maxsize, nsegments */ + DFLTPHYS, /* maxsize */ + MLY_MAX_SGENTRIES, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->mly_lock, /* lockarg */ &sc->mly_buffer_dmat)) { mly_printf(sc, "can't allocate buffer DMA tag\n"); goto fail; } /* * Initialise the DMA tag for command packets. */ if (bus_dma_tag_create(sc->mly_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sizeof(union mly_command_packet) * MLY_MAX_COMMANDS, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mly_packet_dmat)) { mly_printf(sc, "can't allocate command packet DMA tag\n"); goto fail; } /* * Detect the hardware interface version */ for (i = 0; mly_identifiers[i].vendor != 0; i++) { if ((mly_identifiers[i].vendor == pci_get_vendor(sc->mly_dev)) && (mly_identifiers[i].device == pci_get_device(sc->mly_dev))) { sc->mly_hwif = mly_identifiers[i].hwif; switch(sc->mly_hwif) { case MLY_HWIF_I960RX: debug(1, "set hardware up for i960RX"); sc->mly_doorbell_true = 0x00; sc->mly_command_mailbox = MLY_I960RX_COMMAND_MAILBOX; sc->mly_status_mailbox = MLY_I960RX_STATUS_MAILBOX; sc->mly_idbr = MLY_I960RX_IDBR; sc->mly_odbr = MLY_I960RX_ODBR; sc->mly_error_status = MLY_I960RX_ERROR_STATUS; sc->mly_interrupt_status = MLY_I960RX_INTERRUPT_STATUS; sc->mly_interrupt_mask = MLY_I960RX_INTERRUPT_MASK; break; case MLY_HWIF_STRONGARM: debug(1, "set hardware up for StrongARM"); sc->mly_doorbell_true = 0xff; /* doorbell 'true' is 0 */ sc->mly_command_mailbox = MLY_STRONGARM_COMMAND_MAILBOX; sc->mly_status_mailbox = MLY_STRONGARM_STATUS_MAILBOX; sc->mly_idbr = MLY_STRONGARM_IDBR; sc->mly_odbr = MLY_STRONGARM_ODBR; sc->mly_error_status = MLY_STRONGARM_ERROR_STATUS; sc->mly_interrupt_status = MLY_STRONGARM_INTERRUPT_STATUS; sc->mly_interrupt_mask = MLY_STRONGARM_INTERRUPT_MASK; break; } break; } } /* * Create the scatter/gather mappings. */ if ((error = mly_sg_map(sc))) goto fail; /* * Allocate and map the memory mailbox */ if ((error = mly_mmbox_map(sc))) goto fail; error = 0; fail: return(error); } /******************************************************************************** * Shut the controller down and detach all our resources. */ static int mly_detach(device_t dev) { int error; if ((error = mly_shutdown(dev)) != 0) return(error); mly_free(device_get_softc(dev)); return(0); } /******************************************************************************** * Bring the controller to a state where it can be safely left alone. * * Note that it should not be necessary to wait for any outstanding commands, * as they should be completed prior to calling here. * * XXX this applies for I/O, but not status polls; we should beware of * the case where a status command is running while we detach. */ static int mly_shutdown(device_t dev) { struct mly_softc *sc = device_get_softc(dev); debug_called(1); MLY_LOCK(sc); if (sc->mly_state & MLY_STATE_OPEN) { MLY_UNLOCK(sc); return(EBUSY); } /* kill the periodic event */ callout_stop(&sc->mly_periodic); #ifdef MLY_DEBUG callout_stop(&sc->mly_timeout); #endif /* flush controller */ mly_printf(sc, "flushing cache..."); printf("%s\n", mly_flush(sc) ? "failed" : "done"); MLY_MASK_INTERRUPTS(sc); MLY_UNLOCK(sc); return(0); } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ static void mly_intr(void *arg) { struct mly_softc *sc = (struct mly_softc *)arg; debug_called(2); MLY_LOCK(sc); mly_done(sc); MLY_UNLOCK(sc); }; /******************************************************************************** ******************************************************************************** Bus-dependant Resource Management ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Allocate memory for the scatter/gather tables */ static int mly_sg_map(struct mly_softc *sc) { size_t segsize; debug_called(1); /* * Create a single tag describing a region large enough to hold all of * the s/g lists we will need. */ segsize = sizeof(struct mly_sg_entry) * MLY_MAX_COMMANDS *MLY_MAX_SGENTRIES; if (bus_dma_tag_create(sc->mly_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ segsize, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mly_sg_dmat)) { mly_printf(sc, "can't allocate scatter/gather DMA tag\n"); return(ENOMEM); } /* * Allocate enough s/g maps for all commands and permanently map them into * controller-visible space. * * XXX this assumes we can get enough space for all the s/g maps in one * contiguous slab. */ if (bus_dmamem_alloc(sc->mly_sg_dmat, (void **)&sc->mly_sg_table, BUS_DMA_NOWAIT, &sc->mly_sg_dmamap)) { mly_printf(sc, "can't allocate s/g table\n"); return(ENOMEM); } if (bus_dmamap_load(sc->mly_sg_dmat, sc->mly_sg_dmamap, sc->mly_sg_table, segsize, mly_sg_map_helper, sc, BUS_DMA_NOWAIT) != 0) return (ENOMEM); return(0); } /******************************************************************************** * Save the physical address of the base of the s/g table. */ static void mly_sg_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mly_softc *sc = (struct mly_softc *)arg; debug_called(1); /* save base of s/g table's address in bus space */ sc->mly_sg_busaddr = segs->ds_addr; } /******************************************************************************** * Allocate memory for the memory-mailbox interface */ static int mly_mmbox_map(struct mly_softc *sc) { /* * Create a DMA tag for a single contiguous region large enough for the * memory mailbox structure. */ if (bus_dma_tag_create(sc->mly_parent_dmat, /* parent */ 1, 0, /* alignment,boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sizeof(struct mly_mmbox), 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->mly_mmbox_dmat)) { mly_printf(sc, "can't allocate memory mailbox DMA tag\n"); return(ENOMEM); } /* * Allocate the buffer */ if (bus_dmamem_alloc(sc->mly_mmbox_dmat, (void **)&sc->mly_mmbox, BUS_DMA_NOWAIT, &sc->mly_mmbox_dmamap)) { mly_printf(sc, "can't allocate memory mailbox\n"); return(ENOMEM); } if (bus_dmamap_load(sc->mly_mmbox_dmat, sc->mly_mmbox_dmamap, sc->mly_mmbox, sizeof(struct mly_mmbox), mly_mmbox_map_helper, sc, BUS_DMA_NOWAIT) != 0) return (ENOMEM); bzero(sc->mly_mmbox, sizeof(*sc->mly_mmbox)); return(0); } /******************************************************************************** * Save the physical address of the memory mailbox */ static void mly_mmbox_map_helper(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mly_softc *sc = (struct mly_softc *)arg; debug_called(1); sc->mly_mmbox_busaddr = segs->ds_addr; } /******************************************************************************** * Free all of the resources associated with (sc) * * Should not be called if the controller is active. */ static void mly_free(struct mly_softc *sc) { debug_called(1); /* Remove the management device */ destroy_dev(sc->mly_dev_t); if (sc->mly_intr) bus_teardown_intr(sc->mly_dev, sc->mly_irq, sc->mly_intr); callout_drain(&sc->mly_periodic); #ifdef MLY_DEBUG callout_drain(&sc->mly_timeout); #endif /* detach from CAM */ mly_cam_detach(sc); /* release command memory */ mly_release_commands(sc); /* throw away the controllerinfo structure */ if (sc->mly_controllerinfo != NULL) free(sc->mly_controllerinfo, M_DEVBUF); /* throw away the controllerparam structure */ if (sc->mly_controllerparam != NULL) free(sc->mly_controllerparam, M_DEVBUF); /* destroy data-transfer DMA tag */ if (sc->mly_buffer_dmat) bus_dma_tag_destroy(sc->mly_buffer_dmat); /* free and destroy DMA memory and tag for s/g lists */ if (sc->mly_sg_table) { bus_dmamap_unload(sc->mly_sg_dmat, sc->mly_sg_dmamap); bus_dmamem_free(sc->mly_sg_dmat, sc->mly_sg_table, sc->mly_sg_dmamap); } if (sc->mly_sg_dmat) bus_dma_tag_destroy(sc->mly_sg_dmat); /* free and destroy DMA memory and tag for memory mailbox */ if (sc->mly_mmbox) { bus_dmamap_unload(sc->mly_mmbox_dmat, sc->mly_mmbox_dmamap); bus_dmamem_free(sc->mly_mmbox_dmat, sc->mly_mmbox, sc->mly_mmbox_dmamap); } if (sc->mly_mmbox_dmat) bus_dma_tag_destroy(sc->mly_mmbox_dmat); /* disconnect the interrupt handler */ if (sc->mly_irq != NULL) bus_release_resource(sc->mly_dev, SYS_RES_IRQ, sc->mly_irq_rid, sc->mly_irq); /* destroy the parent DMA tag */ if (sc->mly_parent_dmat) bus_dma_tag_destroy(sc->mly_parent_dmat); /* release the register window mapping */ if (sc->mly_regs_resource != NULL) bus_release_resource(sc->mly_dev, SYS_RES_MEMORY, sc->mly_regs_rid, sc->mly_regs_resource); mtx_destroy(&sc->mly_lock); } /******************************************************************************** ******************************************************************************** Command Wrappers ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Fill in the mly_controllerinfo and mly_controllerparam fields in the softc. */ static int mly_get_controllerinfo(struct mly_softc *sc) { struct mly_command_ioctl mci; u_int8_t status; int error; debug_called(1); if (sc->mly_controllerinfo != NULL) free(sc->mly_controllerinfo, M_DEVBUF); /* build the getcontrollerinfo ioctl and send it */ bzero(&mci, sizeof(mci)); sc->mly_controllerinfo = NULL; mci.sub_ioctl = MDACIOCTL_GETCONTROLLERINFO; if ((error = mly_ioctl(sc, &mci, (void **)&sc->mly_controllerinfo, sizeof(*sc->mly_controllerinfo), &status, NULL, NULL))) return(error); if (status != 0) return(EIO); if (sc->mly_controllerparam != NULL) free(sc->mly_controllerparam, M_DEVBUF); /* build the getcontrollerparameter ioctl and send it */ bzero(&mci, sizeof(mci)); sc->mly_controllerparam = NULL; mci.sub_ioctl = MDACIOCTL_GETCONTROLLERPARAMETER; if ((error = mly_ioctl(sc, &mci, (void **)&sc->mly_controllerparam, sizeof(*sc->mly_controllerparam), &status, NULL, NULL))) return(error); if (status != 0) return(EIO); return(0); } /******************************************************************************** * Schedule all possible devices for a rescan. * */ static void mly_scan_devices(struct mly_softc *sc) { int bus, target; debug_called(1); /* * Clear any previous BTL information. */ bzero(&sc->mly_btl, sizeof(sc->mly_btl)); /* * Mark all devices as requiring a rescan, and let the next * periodic scan collect them. */ for (bus = 0; bus < sc->mly_cam_channels; bus++) if (MLY_BUS_IS_VALID(sc, bus)) for (target = 0; target < MLY_MAX_TARGETS; target++) sc->mly_btl[bus][target].mb_flags = MLY_BTL_RESCAN; } /******************************************************************************** * Rescan a device, possibly as a consequence of getting an event which suggests * that it may have changed. * * If we suffer resource starvation, we can abandon the rescan as we'll be * retried. */ static void mly_rescan_btl(struct mly_softc *sc, int bus, int target) { struct mly_command *mc; struct mly_command_ioctl *mci; debug_called(1); /* check that this bus is valid */ if (!MLY_BUS_IS_VALID(sc, bus)) return; /* get a command */ if (mly_alloc_command(sc, &mc)) return; /* set up the data buffer */ if ((mc->mc_data = malloc(sizeof(union mly_devinfo), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { mly_release_command(mc); return; } mc->mc_flags |= MLY_CMD_DATAIN; mc->mc_complete = mly_complete_rescan; /* * Build the ioctl. */ mci = (struct mly_command_ioctl *)&mc->mc_packet->ioctl; mci->opcode = MDACMD_IOCTL; mci->addr.phys.controller = 0; mci->timeout.value = 30; mci->timeout.scale = MLY_TIMEOUT_SECONDS; if (MLY_BUS_IS_VIRTUAL(sc, bus)) { mc->mc_length = mci->data_size = sizeof(struct mly_ioctl_getlogdevinfovalid); mci->sub_ioctl = MDACIOCTL_GETLOGDEVINFOVALID; mci->addr.log.logdev = MLY_LOGDEV_ID(sc, bus, target); debug(1, "logical device %d", mci->addr.log.logdev); } else { mc->mc_length = mci->data_size = sizeof(struct mly_ioctl_getphysdevinfovalid); mci->sub_ioctl = MDACIOCTL_GETPHYSDEVINFOVALID; mci->addr.phys.lun = 0; mci->addr.phys.target = target; mci->addr.phys.channel = bus; debug(1, "physical device %d:%d", mci->addr.phys.channel, mci->addr.phys.target); } /* * Dispatch the command. If we successfully send the command, clear the rescan * bit. */ if (mly_start(mc) != 0) { mly_release_command(mc); } else { sc->mly_btl[bus][target].mb_flags &= ~MLY_BTL_RESCAN; /* success */ } } /******************************************************************************** * Handle the completion of a rescan operation */ static void mly_complete_rescan(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; struct mly_ioctl_getlogdevinfovalid *ldi; struct mly_ioctl_getphysdevinfovalid *pdi; struct mly_command_ioctl *mci; struct mly_btl btl, *btlp; int bus, target, rescan; debug_called(1); /* * Recover the bus and target from the command. We need these even in * the case where we don't have a useful response. */ mci = (struct mly_command_ioctl *)&mc->mc_packet->ioctl; if (mci->sub_ioctl == MDACIOCTL_GETLOGDEVINFOVALID) { bus = MLY_LOGDEV_BUS(sc, mci->addr.log.logdev); target = MLY_LOGDEV_TARGET(sc, mci->addr.log.logdev); } else { bus = mci->addr.phys.channel; target = mci->addr.phys.target; } /* XXX validate bus/target? */ /* the default result is 'no device' */ bzero(&btl, sizeof(btl)); /* if the rescan completed OK, we have possibly-new BTL data */ if (mc->mc_status == 0) { if (mc->mc_length == sizeof(*ldi)) { ldi = (struct mly_ioctl_getlogdevinfovalid *)mc->mc_data; if ((MLY_LOGDEV_BUS(sc, ldi->logical_device_number) != bus) || (MLY_LOGDEV_TARGET(sc, ldi->logical_device_number) != target)) { mly_printf(sc, "WARNING: BTL rescan for %d:%d returned data for %d:%d instead\n", bus, target, MLY_LOGDEV_BUS(sc, ldi->logical_device_number), MLY_LOGDEV_TARGET(sc, ldi->logical_device_number)); /* XXX what can we do about this? */ } btl.mb_flags = MLY_BTL_LOGICAL; btl.mb_type = ldi->raid_level; btl.mb_state = ldi->state; debug(1, "BTL rescan for %d returns %s, %s", ldi->logical_device_number, mly_describe_code(mly_table_device_type, ldi->raid_level), mly_describe_code(mly_table_device_state, ldi->state)); } else if (mc->mc_length == sizeof(*pdi)) { pdi = (struct mly_ioctl_getphysdevinfovalid *)mc->mc_data; if ((pdi->channel != bus) || (pdi->target != target)) { mly_printf(sc, "WARNING: BTL rescan for %d:%d returned data for %d:%d instead\n", bus, target, pdi->channel, pdi->target); /* XXX what can we do about this? */ } btl.mb_flags = MLY_BTL_PHYSICAL; btl.mb_type = MLY_DEVICE_TYPE_PHYSICAL; btl.mb_state = pdi->state; btl.mb_speed = pdi->speed; btl.mb_width = pdi->width; if (pdi->state != MLY_DEVICE_STATE_UNCONFIGURED) sc->mly_btl[bus][target].mb_flags |= MLY_BTL_PROTECTED; debug(1, "BTL rescan for %d:%d returns %s", bus, target, mly_describe_code(mly_table_device_state, pdi->state)); } else { mly_printf(sc, "BTL rescan result invalid\n"); } } free(mc->mc_data, M_DEVBUF); mly_release_command(mc); /* * Decide whether we need to rescan the device. */ rescan = 0; /* device type changes (usually between 'nothing' and 'something') */ btlp = &sc->mly_btl[bus][target]; if (btl.mb_flags != btlp->mb_flags) { debug(1, "flags changed, rescanning"); rescan = 1; } /* XXX other reasons? */ /* * Update BTL information. */ *btlp = btl; /* * Perform CAM rescan if required. */ if (rescan) mly_cam_rescan_btl(sc, bus, target); } /******************************************************************************** * Get the current health status and set the 'next event' counter to suit. */ static int mly_get_eventstatus(struct mly_softc *sc) { struct mly_command_ioctl mci; struct mly_health_status *mh; u_int8_t status; int error; /* build the gethealthstatus ioctl and send it */ bzero(&mci, sizeof(mci)); mh = NULL; mci.sub_ioctl = MDACIOCTL_GETHEALTHSTATUS; if ((error = mly_ioctl(sc, &mci, (void **)&mh, sizeof(*mh), &status, NULL, NULL))) return(error); if (status != 0) return(EIO); /* get the event counter */ sc->mly_event_change = mh->change_counter; sc->mly_event_waiting = mh->next_event; sc->mly_event_counter = mh->next_event; /* save the health status into the memory mailbox */ bcopy(mh, &sc->mly_mmbox->mmm_health.status, sizeof(*mh)); debug(1, "initial change counter %d, event counter %d", mh->change_counter, mh->next_event); free(mh, M_DEVBUF); return(0); } /******************************************************************************** * Enable the memory mailbox mode. */ static int mly_enable_mmbox(struct mly_softc *sc) { struct mly_command_ioctl mci; u_int8_t *sp, status; int error; debug_called(1); /* build the ioctl and send it */ bzero(&mci, sizeof(mci)); mci.sub_ioctl = MDACIOCTL_SETMEMORYMAILBOX; /* set buffer addresses */ mci.param.setmemorymailbox.command_mailbox_physaddr = sc->mly_mmbox_busaddr + offsetof(struct mly_mmbox, mmm_command); mci.param.setmemorymailbox.status_mailbox_physaddr = sc->mly_mmbox_busaddr + offsetof(struct mly_mmbox, mmm_status); mci.param.setmemorymailbox.health_buffer_physaddr = sc->mly_mmbox_busaddr + offsetof(struct mly_mmbox, mmm_health); /* set buffer sizes - abuse of data_size field is revolting */ sp = (u_int8_t *)&mci.data_size; sp[0] = ((sizeof(union mly_command_packet) * MLY_MMBOX_COMMANDS) / 1024); sp[1] = (sizeof(union mly_status_packet) * MLY_MMBOX_STATUS) / 1024; mci.param.setmemorymailbox.health_buffer_size = sizeof(union mly_health_region) / 1024; debug(1, "memory mailbox at %p (0x%llx/%d 0x%llx/%d 0x%llx/%d", sc->mly_mmbox, mci.param.setmemorymailbox.command_mailbox_physaddr, sp[0], mci.param.setmemorymailbox.status_mailbox_physaddr, sp[1], mci.param.setmemorymailbox.health_buffer_physaddr, mci.param.setmemorymailbox.health_buffer_size); if ((error = mly_ioctl(sc, &mci, NULL, 0, &status, NULL, NULL))) return(error); if (status != 0) return(EIO); sc->mly_state |= MLY_STATE_MMBOX_ACTIVE; debug(1, "memory mailbox active"); return(0); } /******************************************************************************** * Flush all pending I/O from the controller. */ static int mly_flush(struct mly_softc *sc) { struct mly_command_ioctl mci; u_int8_t status; int error; debug_called(1); /* build the ioctl */ bzero(&mci, sizeof(mci)); mci.sub_ioctl = MDACIOCTL_FLUSHDEVICEDATA; mci.param.deviceoperation.operation_device = MLY_OPDEVICE_PHYSICAL_CONTROLLER; /* pass it off to the controller */ if ((error = mly_ioctl(sc, &mci, NULL, 0, &status, NULL, NULL))) return(error); return((status == 0) ? 0 : EIO); } /******************************************************************************** * Perform an ioctl command. * * If (data) is not NULL, the command requires data transfer. If (*data) is NULL * the command requires data transfer from the controller, and we will allocate * a buffer for it. If (*data) is not NULL, the command requires data transfer * to the controller. * * XXX passing in the whole ioctl structure is ugly. Better ideas? * * XXX we don't even try to handle the case where datasize > 4k. We should. */ static int mly_ioctl(struct mly_softc *sc, struct mly_command_ioctl *ioctl, void **data, size_t datasize, u_int8_t *status, void *sense_buffer, size_t *sense_length) { struct mly_command *mc; struct mly_command_ioctl *mci; int error; debug_called(1); MLY_ASSERT_LOCKED(sc); mc = NULL; if (mly_alloc_command(sc, &mc)) { error = ENOMEM; goto out; } /* copy the ioctl structure, but save some important fields and then fixup */ mci = &mc->mc_packet->ioctl; ioctl->sense_buffer_address = mci->sense_buffer_address; ioctl->maximum_sense_size = mci->maximum_sense_size; *mci = *ioctl; mci->opcode = MDACMD_IOCTL; mci->timeout.value = 30; mci->timeout.scale = MLY_TIMEOUT_SECONDS; /* handle the data buffer */ if (data != NULL) { if (*data == NULL) { /* allocate data buffer */ if ((mc->mc_data = malloc(datasize, M_DEVBUF, M_NOWAIT)) == NULL) { error = ENOMEM; goto out; } mc->mc_flags |= MLY_CMD_DATAIN; } else { mc->mc_data = *data; mc->mc_flags |= MLY_CMD_DATAOUT; } mc->mc_length = datasize; mc->mc_packet->generic.data_size = datasize; } /* run the command */ if ((error = mly_immediate_command(mc))) goto out; /* clean up and return any data */ *status = mc->mc_status; if ((mc->mc_sense > 0) && (sense_buffer != NULL)) { bcopy(mc->mc_packet, sense_buffer, mc->mc_sense); *sense_length = mc->mc_sense; goto out; } /* should we return a data pointer? */ if ((data != NULL) && (*data == NULL)) *data = mc->mc_data; /* command completed OK */ error = 0; out: if (mc != NULL) { /* do we need to free a data buffer we allocated? */ if (error && (mc->mc_data != NULL) && (*data == NULL)) free(mc->mc_data, M_DEVBUF); mly_release_command(mc); } return(error); } /******************************************************************************** * Check for event(s) outstanding in the controller. */ static void mly_check_event(struct mly_softc *sc) { /* * The controller may have updated the health status information, * so check for it here. Note that the counters are all in host memory, * so this check is very cheap. Also note that we depend on checking on * completion */ if (sc->mly_mmbox->mmm_health.status.change_counter != sc->mly_event_change) { sc->mly_event_change = sc->mly_mmbox->mmm_health.status.change_counter; debug(1, "event change %d, event status update, %d -> %d", sc->mly_event_change, sc->mly_event_waiting, sc->mly_mmbox->mmm_health.status.next_event); sc->mly_event_waiting = sc->mly_mmbox->mmm_health.status.next_event; /* wake up anyone that might be interested in this */ wakeup(&sc->mly_event_change); } if (sc->mly_event_counter != sc->mly_event_waiting) mly_fetch_event(sc); } /******************************************************************************** * Fetch one event from the controller. * * If we fail due to resource starvation, we'll be retried the next time a * command completes. */ static void mly_fetch_event(struct mly_softc *sc) { struct mly_command *mc; struct mly_command_ioctl *mci; int s; u_int32_t event; debug_called(1); /* get a command */ if (mly_alloc_command(sc, &mc)) return; /* set up the data buffer */ if ((mc->mc_data = malloc(sizeof(struct mly_event), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { mly_release_command(mc); return; } mc->mc_length = sizeof(struct mly_event); mc->mc_flags |= MLY_CMD_DATAIN; mc->mc_complete = mly_complete_event; /* * Get an event number to fetch. It's possible that we've raced with another * context for the last event, in which case there will be no more events. */ s = splcam(); if (sc->mly_event_counter == sc->mly_event_waiting) { mly_release_command(mc); splx(s); return; } event = sc->mly_event_counter++; splx(s); /* * Build the ioctl. * * At this point we are committed to sending this request, as it * will be the only one constructed for this particular event number. */ mci = (struct mly_command_ioctl *)&mc->mc_packet->ioctl; mci->opcode = MDACMD_IOCTL; mci->data_size = sizeof(struct mly_event); mci->addr.phys.lun = (event >> 16) & 0xff; mci->addr.phys.target = (event >> 24) & 0xff; mci->addr.phys.channel = 0; mci->addr.phys.controller = 0; mci->timeout.value = 30; mci->timeout.scale = MLY_TIMEOUT_SECONDS; mci->sub_ioctl = MDACIOCTL_GETEVENT; mci->param.getevent.sequence_number_low = event & 0xffff; debug(1, "fetch event %u", event); /* * Submit the command. * * Note that failure of mly_start() will result in this event never being * fetched. */ if (mly_start(mc) != 0) { mly_printf(sc, "couldn't fetch event %u\n", event); mly_release_command(mc); } } /******************************************************************************** * Handle the completion of an event poll. */ static void mly_complete_event(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; struct mly_event *me = (struct mly_event *)mc->mc_data; debug_called(1); /* * If the event was successfully fetched, process it. */ if (mc->mc_status == SCSI_STATUS_OK) { mly_process_event(sc, me); free(me, M_DEVBUF); } mly_release_command(mc); /* * Check for another event. */ mly_check_event(sc); } /******************************************************************************** * Process a controller event. */ static void mly_process_event(struct mly_softc *sc, struct mly_event *me) { struct scsi_sense_data_fixed *ssd; char *fp, *tp; int bus, target, event, class, action; ssd = (struct scsi_sense_data_fixed *)&me->sense[0]; /* * Errors can be reported using vendor-unique sense data. In this case, the * event code will be 0x1c (Request sense data present), the sense key will * be 0x09 (vendor specific), the MSB of the ASC will be set, and the * actual event code will be a 16-bit value comprised of the ASCQ (low byte) * and low seven bits of the ASC (low seven bits of the high byte). */ if ((me->code == 0x1c) && ((ssd->flags & SSD_KEY) == SSD_KEY_Vendor_Specific) && (ssd->add_sense_code & 0x80)) { event = ((int)(ssd->add_sense_code & ~0x80) << 8) + ssd->add_sense_code_qual; } else { event = me->code; } /* look up event, get codes */ fp = mly_describe_code(mly_table_event, event); debug(1, "Event %d code 0x%x", me->sequence_number, me->code); /* quiet event? */ class = fp[0]; if (isupper(class) && bootverbose) class = tolower(class); /* get action code, text string */ action = fp[1]; tp = &fp[2]; /* * Print some information about the event. * * This code uses a table derived from the corresponding portion of the Linux * driver, and thus the parser is very similar. */ switch(class) { case 'p': /* error on physical device */ mly_printf(sc, "physical device %d:%d %s\n", me->channel, me->target, tp); if (action == 'r') sc->mly_btl[me->channel][me->target].mb_flags |= MLY_BTL_RESCAN; break; case 'l': /* error on logical unit */ case 'm': /* message about logical unit */ bus = MLY_LOGDEV_BUS(sc, me->lun); target = MLY_LOGDEV_TARGET(sc, me->lun); mly_name_device(sc, bus, target); mly_printf(sc, "logical device %d (%s) %s\n", me->lun, sc->mly_btl[bus][target].mb_name, tp); if (action == 'r') sc->mly_btl[bus][target].mb_flags |= MLY_BTL_RESCAN; break; case 's': /* report of sense data */ if (((ssd->flags & SSD_KEY) == SSD_KEY_NO_SENSE) || (((ssd->flags & SSD_KEY) == SSD_KEY_NOT_READY) && (ssd->add_sense_code == 0x04) && ((ssd->add_sense_code_qual == 0x01) || (ssd->add_sense_code_qual == 0x02)))) break; /* ignore NO_SENSE or NOT_READY in one case */ mly_printf(sc, "physical device %d:%d %s\n", me->channel, me->target, tp); mly_printf(sc, " sense key %d asc %02x ascq %02x\n", ssd->flags & SSD_KEY, ssd->add_sense_code, ssd->add_sense_code_qual); mly_printf(sc, " info %4D csi %4D\n", ssd->info, "", ssd->cmd_spec_info, ""); if (action == 'r') sc->mly_btl[me->channel][me->target].mb_flags |= MLY_BTL_RESCAN; break; case 'e': mly_printf(sc, tp, me->target, me->lun); printf("\n"); break; case 'c': mly_printf(sc, "controller %s\n", tp); break; case '?': mly_printf(sc, "%s - %d\n", tp, me->code); break; default: /* probably a 'noisy' event being ignored */ break; } } /******************************************************************************** * Perform periodic activities. */ static void mly_periodic(void *data) { struct mly_softc *sc = (struct mly_softc *)data; int bus, target; debug_called(2); MLY_ASSERT_LOCKED(sc); /* * Scan devices. */ for (bus = 0; bus < sc->mly_cam_channels; bus++) { if (MLY_BUS_IS_VALID(sc, bus)) { for (target = 0; target < MLY_MAX_TARGETS; target++) { /* ignore the controller in this scan */ if (target == sc->mly_controllerparam->initiator_id) continue; /* perform device rescan? */ if (sc->mly_btl[bus][target].mb_flags & MLY_BTL_RESCAN) mly_rescan_btl(sc, bus, target); } } } /* check for controller events */ mly_check_event(sc); /* reschedule ourselves */ callout_schedule(&sc->mly_periodic, MLY_PERIODIC_INTERVAL * hz); } /******************************************************************************** ******************************************************************************** Command Processing ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Run a command and wait for it to complete. * */ static int mly_immediate_command(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; int error; debug_called(1); MLY_ASSERT_LOCKED(sc); if ((error = mly_start(mc))) { return(error); } if (sc->mly_state & MLY_STATE_INTERRUPTS_ON) { /* sleep on the command */ while(!(mc->mc_flags & MLY_CMD_COMPLETE)) { mtx_sleep(mc, &sc->mly_lock, PRIBIO, "mlywait", 0); } } else { /* spin and collect status while we do */ while(!(mc->mc_flags & MLY_CMD_COMPLETE)) { mly_done(mc->mc_sc); } } return(0); } /******************************************************************************** * Deliver a command to the controller. * * XXX it would be good to just queue commands that we can't submit immediately * and send them later, but we probably want a wrapper for that so that * we don't hang on a failed submission for an immediate command. */ static int mly_start(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; union mly_command_packet *pkt; debug_called(2); MLY_ASSERT_LOCKED(sc); /* * Set the command up for delivery to the controller. */ mly_map_command(mc); mc->mc_packet->generic.command_id = mc->mc_slot; #ifdef MLY_DEBUG mc->mc_timestamp = time_second; #endif /* * Do we have to use the hardware mailbox? */ if (!(sc->mly_state & MLY_STATE_MMBOX_ACTIVE)) { /* * Check to see if the controller is ready for us. */ if (MLY_IDBR_TRUE(sc, MLY_HM_CMDSENT)) { return(EBUSY); } mc->mc_flags |= MLY_CMD_BUSY; /* * It's ready, send the command. */ MLY_SET_MBOX(sc, sc->mly_command_mailbox, &mc->mc_packetphys); MLY_SET_REG(sc, sc->mly_idbr, MLY_HM_CMDSENT); } else { /* use memory-mailbox mode */ pkt = &sc->mly_mmbox->mmm_command[sc->mly_mmbox_command_index]; /* check to see if the next index is free yet */ if (pkt->mmbox.flag != 0) { return(EBUSY); } mc->mc_flags |= MLY_CMD_BUSY; /* copy in new command */ bcopy(mc->mc_packet->mmbox.data, pkt->mmbox.data, sizeof(pkt->mmbox.data)); /* barrier to ensure completion of previous write before we write the flag */ bus_barrier(sc->mly_regs_resource, 0, 0, BUS_SPACE_BARRIER_WRITE); /* copy flag last */ pkt->mmbox.flag = mc->mc_packet->mmbox.flag; /* barrier to ensure completion of previous write before we notify the controller */ bus_barrier(sc->mly_regs_resource, 0, 0, BUS_SPACE_BARRIER_WRITE); /* signal controller, update index */ MLY_SET_REG(sc, sc->mly_idbr, MLY_AM_CMDSENT); sc->mly_mmbox_command_index = (sc->mly_mmbox_command_index + 1) % MLY_MMBOX_COMMANDS; } mly_enqueue_busy(mc); return(0); } /******************************************************************************** * Pick up command status from the controller, schedule a completion event */ static void mly_done(struct mly_softc *sc) { struct mly_command *mc; union mly_status_packet *sp; u_int16_t slot; int worked; MLY_ASSERT_LOCKED(sc); worked = 0; /* pick up hardware-mailbox commands */ if (MLY_ODBR_TRUE(sc, MLY_HM_STSREADY)) { slot = MLY_GET_REG2(sc, sc->mly_status_mailbox); if (slot < MLY_SLOT_MAX) { mc = &sc->mly_command[slot - MLY_SLOT_START]; mc->mc_status = MLY_GET_REG(sc, sc->mly_status_mailbox + 2); mc->mc_sense = MLY_GET_REG(sc, sc->mly_status_mailbox + 3); mc->mc_resid = MLY_GET_REG4(sc, sc->mly_status_mailbox + 4); mly_remove_busy(mc); mc->mc_flags &= ~MLY_CMD_BUSY; mly_enqueue_complete(mc); worked = 1; } else { /* slot 0xffff may mean "extremely bogus command" */ mly_printf(sc, "got HM completion for illegal slot %u\n", slot); } /* unconditionally acknowledge status */ MLY_SET_REG(sc, sc->mly_odbr, MLY_HM_STSREADY); MLY_SET_REG(sc, sc->mly_idbr, MLY_HM_STSACK); } /* pick up memory-mailbox commands */ if (MLY_ODBR_TRUE(sc, MLY_AM_STSREADY)) { for (;;) { sp = &sc->mly_mmbox->mmm_status[sc->mly_mmbox_status_index]; /* check for more status */ if (sp->mmbox.flag == 0) break; /* get slot number */ slot = sp->status.command_id; if (slot < MLY_SLOT_MAX) { mc = &sc->mly_command[slot - MLY_SLOT_START]; mc->mc_status = sp->status.status; mc->mc_sense = sp->status.sense_length; mc->mc_resid = sp->status.residue; mly_remove_busy(mc); mc->mc_flags &= ~MLY_CMD_BUSY; mly_enqueue_complete(mc); worked = 1; } else { /* slot 0xffff may mean "extremely bogus command" */ mly_printf(sc, "got AM completion for illegal slot %u at %d\n", slot, sc->mly_mmbox_status_index); } /* clear and move to next index */ sp->mmbox.flag = 0; sc->mly_mmbox_status_index = (sc->mly_mmbox_status_index + 1) % MLY_MMBOX_STATUS; } /* acknowledge that we have collected status value(s) */ MLY_SET_REG(sc, sc->mly_odbr, MLY_AM_STSREADY); } if (worked) { if (sc->mly_state & MLY_STATE_INTERRUPTS_ON) taskqueue_enqueue(taskqueue_thread, &sc->mly_task_complete); else mly_complete(sc); } } /******************************************************************************** * Process completed commands */ static void mly_complete_handler(void *context, int pending) { struct mly_softc *sc = (struct mly_softc *)context; MLY_LOCK(sc); mly_complete(sc); MLY_UNLOCK(sc); } static void mly_complete(struct mly_softc *sc) { struct mly_command *mc; void (* mc_complete)(struct mly_command *mc); debug_called(2); /* * Spin pulling commands off the completed queue and processing them. */ while ((mc = mly_dequeue_complete(sc)) != NULL) { /* * Free controller resources, mark command complete. * * Note that as soon as we mark the command complete, it may be freed * out from under us, so we need to save the mc_complete field in * order to later avoid dereferencing mc. (We would not expect to * have a polling/sleeping consumer with mc_complete != NULL). */ mly_unmap_command(mc); mc_complete = mc->mc_complete; mc->mc_flags |= MLY_CMD_COMPLETE; /* * Call completion handler or wake up sleeping consumer. */ if (mc_complete != NULL) { mc_complete(mc); } else { wakeup(mc); } } /* * XXX if we are deferring commands due to controller-busy status, we should * retry submitting them here. */ } /******************************************************************************** ******************************************************************************** Command Buffer Management ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Allocate a command. */ static int mly_alloc_command(struct mly_softc *sc, struct mly_command **mcp) { struct mly_command *mc; debug_called(3); if ((mc = mly_dequeue_free(sc)) == NULL) return(ENOMEM); *mcp = mc; return(0); } /******************************************************************************** * Release a command back to the freelist. */ static void mly_release_command(struct mly_command *mc) { debug_called(3); /* * Fill in parts of the command that may cause confusion if * a consumer doesn't when we are later allocated. */ mc->mc_data = NULL; mc->mc_flags = 0; mc->mc_complete = NULL; mc->mc_private = NULL; /* * By default, we set up to overwrite the command packet with * sense information. */ mc->mc_packet->generic.sense_buffer_address = mc->mc_packetphys; mc->mc_packet->generic.maximum_sense_size = sizeof(union mly_command_packet); mly_enqueue_free(mc); } /******************************************************************************** * Map helper for command allocation. */ static void mly_alloc_commands_map(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mly_softc *sc = (struct mly_softc *)arg; debug_called(1); sc->mly_packetphys = segs[0].ds_addr; } /******************************************************************************** * Allocate and initialise command and packet structures. * * If the controller supports fewer than MLY_MAX_COMMANDS commands, limit our * allocation to that number. If we don't yet know how many commands the * controller supports, allocate a very small set (suitable for initialisation * purposes only). */ static int mly_alloc_commands(struct mly_softc *sc) { struct mly_command *mc; int i, ncmd; if (sc->mly_controllerinfo == NULL) { ncmd = 4; } else { ncmd = min(MLY_MAX_COMMANDS, sc->mly_controllerinfo->maximum_parallel_commands); } /* * Allocate enough space for all the command packets in one chunk and * map them permanently into controller-visible space. */ if (bus_dmamem_alloc(sc->mly_packet_dmat, (void **)&sc->mly_packet, BUS_DMA_NOWAIT, &sc->mly_packetmap)) { return(ENOMEM); } if (bus_dmamap_load(sc->mly_packet_dmat, sc->mly_packetmap, sc->mly_packet, ncmd * sizeof(union mly_command_packet), mly_alloc_commands_map, sc, BUS_DMA_NOWAIT) != 0) return (ENOMEM); for (i = 0; i < ncmd; i++) { mc = &sc->mly_command[i]; bzero(mc, sizeof(*mc)); mc->mc_sc = sc; mc->mc_slot = MLY_SLOT_START + i; mc->mc_packet = sc->mly_packet + i; mc->mc_packetphys = sc->mly_packetphys + (i * sizeof(union mly_command_packet)); if (!bus_dmamap_create(sc->mly_buffer_dmat, 0, &mc->mc_datamap)) mly_release_command(mc); } return(0); } /******************************************************************************** * Free all the storage held by commands. * * Must be called with all commands on the free list. */ static void mly_release_commands(struct mly_softc *sc) { struct mly_command *mc; /* throw away command buffer DMA maps */ while (mly_alloc_command(sc, &mc) == 0) bus_dmamap_destroy(sc->mly_buffer_dmat, mc->mc_datamap); /* release the packet storage */ if (sc->mly_packet != NULL) { bus_dmamap_unload(sc->mly_packet_dmat, sc->mly_packetmap); bus_dmamem_free(sc->mly_packet_dmat, sc->mly_packet, sc->mly_packetmap); sc->mly_packet = NULL; } } /******************************************************************************** * Command-mapping helper function - populate this command's s/g table * with the s/g entries for its data. */ static void mly_map_command_sg(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mly_command *mc = (struct mly_command *)arg; struct mly_softc *sc = mc->mc_sc; struct mly_command_generic *gen = &(mc->mc_packet->generic); struct mly_sg_entry *sg; int i, tabofs; debug_called(2); /* can we use the transfer structure directly? */ if (nseg <= 2) { sg = &gen->transfer.direct.sg[0]; gen->command_control.extended_sg_table = 0; } else { tabofs = ((mc->mc_slot - MLY_SLOT_START) * MLY_MAX_SGENTRIES); sg = sc->mly_sg_table + tabofs; gen->transfer.indirect.entries[0] = nseg; gen->transfer.indirect.table_physaddr[0] = sc->mly_sg_busaddr + (tabofs * sizeof(struct mly_sg_entry)); gen->command_control.extended_sg_table = 1; } /* copy the s/g table */ for (i = 0; i < nseg; i++) { sg[i].physaddr = segs[i].ds_addr; sg[i].length = segs[i].ds_len; } } #if 0 /******************************************************************************** * Command-mapping helper function - save the cdb's physical address. * * We don't support 'large' SCSI commands at this time, so this is unused. */ static void mly_map_command_cdb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct mly_command *mc = (struct mly_command *)arg; debug_called(2); /* XXX can we safely assume that a CDB will never cross a page boundary? */ if ((segs[0].ds_addr % PAGE_SIZE) > ((segs[0].ds_addr + mc->mc_packet->scsi_large.cdb_length) % PAGE_SIZE)) panic("cdb crosses page boundary"); /* fix up fields in the command packet */ mc->mc_packet->scsi_large.cdb_physaddr = segs[0].ds_addr; } #endif /******************************************************************************** * Map a command into controller-visible space */ static void mly_map_command(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; debug_called(2); /* don't map more than once */ if (mc->mc_flags & MLY_CMD_MAPPED) return; /* does the command have a data buffer? */ if (mc->mc_data != NULL) { if (mc->mc_flags & MLY_CMD_CCB) bus_dmamap_load_ccb(sc->mly_buffer_dmat, mc->mc_datamap, mc->mc_data, mly_map_command_sg, mc, 0); else bus_dmamap_load(sc->mly_buffer_dmat, mc->mc_datamap, mc->mc_data, mc->mc_length, mly_map_command_sg, mc, 0); if (mc->mc_flags & MLY_CMD_DATAIN) bus_dmamap_sync(sc->mly_buffer_dmat, mc->mc_datamap, BUS_DMASYNC_PREREAD); if (mc->mc_flags & MLY_CMD_DATAOUT) bus_dmamap_sync(sc->mly_buffer_dmat, mc->mc_datamap, BUS_DMASYNC_PREWRITE); } mc->mc_flags |= MLY_CMD_MAPPED; } /******************************************************************************** * Unmap a command from controller-visible space */ static void mly_unmap_command(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; debug_called(2); if (!(mc->mc_flags & MLY_CMD_MAPPED)) return; /* does the command have a data buffer? */ if (mc->mc_data != NULL) { if (mc->mc_flags & MLY_CMD_DATAIN) bus_dmamap_sync(sc->mly_buffer_dmat, mc->mc_datamap, BUS_DMASYNC_POSTREAD); if (mc->mc_flags & MLY_CMD_DATAOUT) bus_dmamap_sync(sc->mly_buffer_dmat, mc->mc_datamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->mly_buffer_dmat, mc->mc_datamap); } mc->mc_flags &= ~MLY_CMD_MAPPED; } /******************************************************************************** ******************************************************************************** CAM interface ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Attach the physical and virtual SCSI busses to CAM. * * Physical bus numbering starts from 0, virtual bus numbering from one greater * than the highest physical bus. Physical busses are only registered if * the kernel environment variable "hw.mly.register_physical_channels" is set. * * When we refer to a "bus", we are referring to the bus number registered with * the SIM, wheras a "channel" is a channel number given to the adapter. In order * to keep things simple, we map these 1:1, so "bus" and "channel" may be used * interchangeably. */ static int mly_cam_attach(struct mly_softc *sc) { struct cam_devq *devq; int chn, i; debug_called(1); /* * Allocate a devq for all our channels combined. */ if ((devq = cam_simq_alloc(sc->mly_controllerinfo->maximum_parallel_commands)) == NULL) { mly_printf(sc, "can't allocate CAM SIM queue\n"); return(ENOMEM); } /* * If physical channel registration has been requested, register these first. * Note that we enable tagged command queueing for physical channels. */ if (testenv("hw.mly.register_physical_channels")) { chn = 0; for (i = 0; i < sc->mly_controllerinfo->physical_channels_present; i++, chn++) { if ((sc->mly_cam_sim[chn] = cam_sim_alloc(mly_cam_action, mly_cam_poll, "mly", sc, device_get_unit(sc->mly_dev), &sc->mly_lock, sc->mly_controllerinfo->maximum_parallel_commands, 1, devq)) == NULL) { return(ENOMEM); } MLY_LOCK(sc); if (xpt_bus_register(sc->mly_cam_sim[chn], sc->mly_dev, chn)) { MLY_UNLOCK(sc); mly_printf(sc, "CAM XPT phsyical channel registration failed\n"); return(ENXIO); } MLY_UNLOCK(sc); debug(1, "registered physical channel %d", chn); } } /* * Register our virtual channels, with bus numbers matching channel numbers. */ chn = sc->mly_controllerinfo->physical_channels_present; for (i = 0; i < sc->mly_controllerinfo->virtual_channels_present; i++, chn++) { if ((sc->mly_cam_sim[chn] = cam_sim_alloc(mly_cam_action, mly_cam_poll, "mly", sc, device_get_unit(sc->mly_dev), &sc->mly_lock, sc->mly_controllerinfo->maximum_parallel_commands, 0, devq)) == NULL) { return(ENOMEM); } MLY_LOCK(sc); if (xpt_bus_register(sc->mly_cam_sim[chn], sc->mly_dev, chn)) { MLY_UNLOCK(sc); mly_printf(sc, "CAM XPT virtual channel registration failed\n"); return(ENXIO); } MLY_UNLOCK(sc); debug(1, "registered virtual channel %d", chn); } /* * This is the total number of channels that (might have been) registered with * CAM. Some may not have been; check the mly_cam_sim array to be certain. */ sc->mly_cam_channels = sc->mly_controllerinfo->physical_channels_present + sc->mly_controllerinfo->virtual_channels_present; return(0); } /******************************************************************************** * Detach from CAM */ static void mly_cam_detach(struct mly_softc *sc) { int i; debug_called(1); MLY_LOCK(sc); for (i = 0; i < sc->mly_cam_channels; i++) { if (sc->mly_cam_sim[i] != NULL) { xpt_bus_deregister(cam_sim_path(sc->mly_cam_sim[i])); cam_sim_free(sc->mly_cam_sim[i], 0); } } MLY_UNLOCK(sc); if (sc->mly_cam_devq != NULL) cam_simq_free(sc->mly_cam_devq); } /************************************************************************ * Rescan a device. */ static void mly_cam_rescan_btl(struct mly_softc *sc, int bus, int target) { union ccb *ccb; debug_called(1); if ((ccb = xpt_alloc_ccb()) == NULL) { mly_printf(sc, "rescan failed (can't allocate CCB)\n"); return; } if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(sc->mly_cam_sim[bus]), target, 0) != CAM_REQ_CMP) { mly_printf(sc, "rescan failed (can't create path)\n"); xpt_free_ccb(ccb); return; } debug(1, "rescan target %d:%d", bus, target); xpt_rescan(ccb); } /******************************************************************************** * Handle an action requested by CAM */ static void mly_cam_action(struct cam_sim *sim, union ccb *ccb) { struct mly_softc *sc = cam_sim_softc(sim); debug_called(2); MLY_ASSERT_LOCKED(sc); switch (ccb->ccb_h.func_code) { /* perform SCSI I/O */ case XPT_SCSI_IO: if (!mly_cam_action_io(sim, (struct ccb_scsiio *)&ccb->csio)) return; break; /* perform geometry calculations */ case XPT_CALC_GEOMETRY: { struct ccb_calc_geometry *ccg = &ccb->ccg; u_int32_t secs_per_cylinder; debug(2, "XPT_CALC_GEOMETRY %d:%d:%d", cam_sim_bus(sim), ccb->ccb_h.target_id, ccb->ccb_h.target_lun); if (sc->mly_controllerparam->bios_geometry == MLY_BIOSGEOM_8G) { ccg->heads = 255; ccg->secs_per_track = 63; } else { /* MLY_BIOSGEOM_2G */ ccg->heads = 128; ccg->secs_per_track = 32; } secs_per_cylinder = ccg->heads * ccg->secs_per_track; ccg->cylinders = ccg->volume_size / secs_per_cylinder; ccb->ccb_h.status = CAM_REQ_CMP; break; } /* handle path attribute inquiry */ case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; debug(2, "XPT_PATH_INQ %d:%d:%d", cam_sim_bus(sim), ccb->ccb_h.target_id, ccb->ccb_h.target_lun); cpi->version_num = 1; cpi->hba_inquiry = PI_TAG_ABLE; /* XXX extra flags for physical channels? */ cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->max_target = MLY_MAX_TARGETS - 1; cpi->max_lun = MLY_MAX_LUNS - 1; cpi->initiator_id = sc->mly_controllerparam->initiator_id; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "FreeBSD", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 132 * 1024; /* XXX what to set this to? */ cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; int bus, target; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; scsi->flags = 0; scsi->valid = 0; spi->flags = 0; spi->valid = 0; bus = cam_sim_bus(sim); target = cts->ccb_h.target_id; debug(2, "XPT_GET_TRAN_SETTINGS %d:%d", bus, target); /* logical device? */ if (sc->mly_btl[bus][target].mb_flags & MLY_BTL_LOGICAL) { /* nothing special for these */ /* physical device? */ } else if (sc->mly_btl[bus][target].mb_flags & MLY_BTL_PHYSICAL) { /* allow CAM to try tagged transactions */ scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; scsi->valid |= CTS_SCSI_VALID_TQ; /* convert speed (MHz) to usec */ if (sc->mly_btl[bus][target].mb_speed == 0) { spi->sync_period = 1000000 / 5; } else { spi->sync_period = 1000000 / sc->mly_btl[bus][target].mb_speed; } /* convert bus width to CAM internal encoding */ switch (sc->mly_btl[bus][target].mb_width) { case 32: spi->bus_width = MSG_EXT_WDTR_BUS_32_BIT; break; case 16: spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT; break; case 8: default: spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT; break; } spi->valid |= CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_BUS_WIDTH; /* not a device, bail out */ } else { cts->ccb_h.status = CAM_REQ_CMP_ERR; break; } /* disconnect always OK */ spi->flags |= CTS_SPI_FLAGS_DISC_ENB; spi->valid |= CTS_SPI_VALID_DISC; cts->ccb_h.status = CAM_REQ_CMP; break; } default: /* we can't do this */ debug(2, "unspported func_code = 0x%x", ccb->ccb_h.func_code); ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); } /******************************************************************************** * Handle an I/O operation requested by CAM */ static int mly_cam_action_io(struct cam_sim *sim, struct ccb_scsiio *csio) { struct mly_softc *sc = cam_sim_softc(sim); struct mly_command *mc; struct mly_command_scsi_small *ss; int bus, target; int error; bus = cam_sim_bus(sim); target = csio->ccb_h.target_id; debug(2, "XPT_SCSI_IO %d:%d:%d", bus, target, csio->ccb_h.target_lun); /* validate bus number */ if (!MLY_BUS_IS_VALID(sc, bus)) { debug(0, " invalid bus %d", bus); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* check for I/O attempt to a protected device */ if (sc->mly_btl[bus][target].mb_flags & MLY_BTL_PROTECTED) { debug(2, " device protected"); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* check for I/O attempt to nonexistent device */ if (!(sc->mly_btl[bus][target].mb_flags & (MLY_BTL_LOGICAL | MLY_BTL_PHYSICAL))) { debug(2, " device %d:%d does not exist", bus, target); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* XXX increase if/when we support large SCSI commands */ if (csio->cdb_len > MLY_CMD_SCSI_SMALL_CDB) { debug(0, " command too large (%d > %d)", csio->cdb_len, MLY_CMD_SCSI_SMALL_CDB); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* check that the CDB pointer is not to a physical address */ if ((csio->ccb_h.flags & CAM_CDB_POINTER) && (csio->ccb_h.flags & CAM_CDB_PHYS)) { debug(0, " CDB pointer is to physical address"); csio->ccb_h.status = CAM_REQ_CMP_ERR; } /* abandon aborted ccbs or those that have failed validation */ if ((csio->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { debug(2, "abandoning CCB due to abort/validation failure"); return(EINVAL); } /* * Get a command, or push the ccb back to CAM and freeze the queue. */ if ((error = mly_alloc_command(sc, &mc))) { xpt_freeze_simq(sim, 1); csio->ccb_h.status |= CAM_REQUEUE_REQ; sc->mly_qfrzn_cnt++; return(error); } /* build the command */ mc->mc_data = csio; mc->mc_length = csio->dxfer_len; mc->mc_complete = mly_cam_complete; mc->mc_private = csio; mc->mc_flags |= MLY_CMD_CCB; /* XXX This code doesn't set the data direction in mc_flags. */ /* save the bus number in the ccb for later recovery XXX should be a better way */ csio->ccb_h.sim_priv.entries[0].field = bus; /* build the packet for the controller */ ss = &mc->mc_packet->scsi_small; ss->opcode = MDACMD_SCSI; if (csio->ccb_h.flags & CAM_DIS_DISCONNECT) ss->command_control.disable_disconnect = 1; if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) ss->command_control.data_direction = MLY_CCB_WRITE; ss->data_size = csio->dxfer_len; ss->addr.phys.lun = csio->ccb_h.target_lun; ss->addr.phys.target = csio->ccb_h.target_id; ss->addr.phys.channel = bus; if (csio->ccb_h.timeout < (60 * 1000)) { ss->timeout.value = csio->ccb_h.timeout / 1000; ss->timeout.scale = MLY_TIMEOUT_SECONDS; } else if (csio->ccb_h.timeout < (60 * 60 * 1000)) { ss->timeout.value = csio->ccb_h.timeout / (60 * 1000); ss->timeout.scale = MLY_TIMEOUT_MINUTES; } else { ss->timeout.value = csio->ccb_h.timeout / (60 * 60 * 1000); /* overflow? */ ss->timeout.scale = MLY_TIMEOUT_HOURS; } ss->maximum_sense_size = csio->sense_len; ss->cdb_length = csio->cdb_len; if (csio->ccb_h.flags & CAM_CDB_POINTER) { bcopy(csio->cdb_io.cdb_ptr, ss->cdb, csio->cdb_len); } else { bcopy(csio->cdb_io.cdb_bytes, ss->cdb, csio->cdb_len); } /* give the command to the controller */ if ((error = mly_start(mc))) { xpt_freeze_simq(sim, 1); csio->ccb_h.status |= CAM_REQUEUE_REQ; sc->mly_qfrzn_cnt++; return(error); } return(0); } /******************************************************************************** * Check for possibly-completed commands. */ static void mly_cam_poll(struct cam_sim *sim) { struct mly_softc *sc = cam_sim_softc(sim); debug_called(2); mly_done(sc); } /******************************************************************************** * Handle completion of a command - pass results back through the CCB */ static void mly_cam_complete(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; struct ccb_scsiio *csio = (struct ccb_scsiio *)mc->mc_private; struct scsi_inquiry_data *inq = (struct scsi_inquiry_data *)csio->data_ptr; struct mly_btl *btl; u_int8_t cmd; int bus, target; debug_called(2); csio->scsi_status = mc->mc_status; switch(mc->mc_status) { case SCSI_STATUS_OK: /* * In order to report logical device type and status, we overwrite * the result of the INQUIRY command to logical devices. */ bus = csio->ccb_h.sim_priv.entries[0].field; target = csio->ccb_h.target_id; /* XXX validate bus/target? */ if (sc->mly_btl[bus][target].mb_flags & MLY_BTL_LOGICAL) { if (csio->ccb_h.flags & CAM_CDB_POINTER) { cmd = *csio->cdb_io.cdb_ptr; } else { cmd = csio->cdb_io.cdb_bytes[0]; } if (cmd == INQUIRY) { btl = &sc->mly_btl[bus][target]; padstr(inq->vendor, mly_describe_code(mly_table_device_type, btl->mb_type), 8); padstr(inq->product, mly_describe_code(mly_table_device_state, btl->mb_state), 16); padstr(inq->revision, "", 4); } } debug(2, "SCSI_STATUS_OK"); csio->ccb_h.status = CAM_REQ_CMP; break; case SCSI_STATUS_CHECK_COND: debug(1, "SCSI_STATUS_CHECK_COND sense %d resid %d", mc->mc_sense, mc->mc_resid); csio->ccb_h.status = CAM_SCSI_STATUS_ERROR; bzero(&csio->sense_data, SSD_FULL_SIZE); bcopy(mc->mc_packet, &csio->sense_data, mc->mc_sense); csio->sense_len = mc->mc_sense; csio->ccb_h.status |= CAM_AUTOSNS_VALID; csio->resid = mc->mc_resid; /* XXX this is a signed value... */ break; case SCSI_STATUS_BUSY: debug(1, "SCSI_STATUS_BUSY"); csio->ccb_h.status = CAM_SCSI_BUSY; break; default: debug(1, "unknown status 0x%x", csio->scsi_status); csio->ccb_h.status = CAM_REQ_CMP_ERR; break; } if (sc->mly_qfrzn_cnt) { csio->ccb_h.status |= CAM_RELEASE_SIMQ; sc->mly_qfrzn_cnt--; } xpt_done((union ccb *)csio); mly_release_command(mc); } /******************************************************************************** * Find a peripheral attahed at (bus),(target) */ static struct cam_periph * mly_find_periph(struct mly_softc *sc, int bus, int target) { struct cam_periph *periph; struct cam_path *path; int status; status = xpt_create_path(&path, NULL, cam_sim_path(sc->mly_cam_sim[bus]), target, 0); if (status == CAM_REQ_CMP) { periph = cam_periph_find(path, NULL); xpt_free_path(path); } else { periph = NULL; } return(periph); } /******************************************************************************** * Name the device at (bus)(target) */ static int mly_name_device(struct mly_softc *sc, int bus, int target) { struct cam_periph *periph; if ((periph = mly_find_periph(sc, bus, target)) != NULL) { sprintf(sc->mly_btl[bus][target].mb_name, "%s%d", periph->periph_name, periph->unit_number); return(0); } sc->mly_btl[bus][target].mb_name[0] = 0; return(ENOENT); } /******************************************************************************** ******************************************************************************** Hardware Control ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Handshake with the firmware while the card is being initialised. */ static int mly_fwhandshake(struct mly_softc *sc) { u_int8_t error, param0, param1; int spinup = 0; debug_called(1); /* set HM_STSACK and let the firmware initialise */ MLY_SET_REG(sc, sc->mly_idbr, MLY_HM_STSACK); DELAY(1000); /* too short? */ /* if HM_STSACK is still true, the controller is initialising */ if (!MLY_IDBR_TRUE(sc, MLY_HM_STSACK)) return(0); mly_printf(sc, "controller initialisation started\n"); /* spin waiting for initialisation to finish, or for a message to be delivered */ while (MLY_IDBR_TRUE(sc, MLY_HM_STSACK)) { /* check for a message */ if (MLY_ERROR_VALID(sc)) { error = MLY_GET_REG(sc, sc->mly_error_status) & ~MLY_MSG_EMPTY; param0 = MLY_GET_REG(sc, sc->mly_command_mailbox); param1 = MLY_GET_REG(sc, sc->mly_command_mailbox + 1); switch(error) { case MLY_MSG_SPINUP: if (!spinup) { mly_printf(sc, "drive spinup in progress\n"); spinup = 1; /* only print this once (should print drive being spun?) */ } break; case MLY_MSG_RACE_RECOVERY_FAIL: mly_printf(sc, "mirror race recovery failed, one or more drives offline\n"); break; case MLY_MSG_RACE_IN_PROGRESS: mly_printf(sc, "mirror race recovery in progress\n"); break; case MLY_MSG_RACE_ON_CRITICAL: mly_printf(sc, "mirror race recovery on a critical drive\n"); break; case MLY_MSG_PARITY_ERROR: mly_printf(sc, "FATAL MEMORY PARITY ERROR\n"); return(ENXIO); default: mly_printf(sc, "unknown initialisation code 0x%x\n", error); } } } return(0); } /******************************************************************************** ******************************************************************************** Debugging and Diagnostics ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Print some information about the controller. */ static void mly_describe_controller(struct mly_softc *sc) { struct mly_ioctl_getcontrollerinfo *mi = sc->mly_controllerinfo; mly_printf(sc, "%16s, %d channel%s, firmware %d.%02d-%d-%02d (%02d%02d%02d%02d), %dMB RAM\n", mi->controller_name, mi->physical_channels_present, (mi->physical_channels_present) > 1 ? "s" : "", mi->fw_major, mi->fw_minor, mi->fw_turn, mi->fw_build, /* XXX turn encoding? */ mi->fw_century, mi->fw_year, mi->fw_month, mi->fw_day, mi->memory_size); if (bootverbose) { mly_printf(sc, "%s %s (%x), %dMHz %d-bit %.16s\n", mly_describe_code(mly_table_oemname, mi->oem_information), mly_describe_code(mly_table_controllertype, mi->controller_type), mi->controller_type, mi->interface_speed, mi->interface_width, mi->interface_name); mly_printf(sc, "%dMB %dMHz %d-bit %s%s%s, cache %dMB\n", mi->memory_size, mi->memory_speed, mi->memory_width, mly_describe_code(mly_table_memorytype, mi->memory_type), mi->memory_parity ? "+parity": "",mi->memory_ecc ? "+ECC": "", mi->cache_size); mly_printf(sc, "CPU: %s @ %dMHz\n", mly_describe_code(mly_table_cputype, mi->cpu[0].type), mi->cpu[0].speed); if (mi->l2cache_size != 0) mly_printf(sc, "%dKB L2 cache\n", mi->l2cache_size); if (mi->exmemory_size != 0) mly_printf(sc, "%dMB %dMHz %d-bit private %s%s%s\n", mi->exmemory_size, mi->exmemory_speed, mi->exmemory_width, mly_describe_code(mly_table_memorytype, mi->exmemory_type), mi->exmemory_parity ? "+parity": "",mi->exmemory_ecc ? "+ECC": ""); mly_printf(sc, "battery backup %s\n", mi->bbu_present ? "present" : "not installed"); mly_printf(sc, "maximum data transfer %d blocks, maximum sg entries/command %d\n", mi->maximum_block_count, mi->maximum_sg_entries); mly_printf(sc, "logical devices present/critical/offline %d/%d/%d\n", mi->logical_devices_present, mi->logical_devices_critical, mi->logical_devices_offline); mly_printf(sc, "physical devices present %d\n", mi->physical_devices_present); mly_printf(sc, "physical disks present/offline %d/%d\n", mi->physical_disks_present, mi->physical_disks_offline); mly_printf(sc, "%d physical channel%s, %d virtual channel%s of %d possible\n", mi->physical_channels_present, mi->physical_channels_present == 1 ? "" : "s", mi->virtual_channels_present, mi->virtual_channels_present == 1 ? "" : "s", mi->virtual_channels_possible); mly_printf(sc, "%d parallel commands supported\n", mi->maximum_parallel_commands); mly_printf(sc, "%dMB flash ROM, %d of %d maximum cycles\n", mi->flash_size, mi->flash_age, mi->flash_maximum_age); } } #ifdef MLY_DEBUG /******************************************************************************** * Print some controller state */ static void mly_printstate(struct mly_softc *sc) { mly_printf(sc, "IDBR %02x ODBR %02x ERROR %02x (%x %x %x)\n", MLY_GET_REG(sc, sc->mly_idbr), MLY_GET_REG(sc, sc->mly_odbr), MLY_GET_REG(sc, sc->mly_error_status), sc->mly_idbr, sc->mly_odbr, sc->mly_error_status); mly_printf(sc, "IMASK %02x ISTATUS %02x\n", MLY_GET_REG(sc, sc->mly_interrupt_mask), MLY_GET_REG(sc, sc->mly_interrupt_status)); mly_printf(sc, "COMMAND %02x %02x %02x %02x %02x %02x %02x %02x\n", MLY_GET_REG(sc, sc->mly_command_mailbox), MLY_GET_REG(sc, sc->mly_command_mailbox + 1), MLY_GET_REG(sc, sc->mly_command_mailbox + 2), MLY_GET_REG(sc, sc->mly_command_mailbox + 3), MLY_GET_REG(sc, sc->mly_command_mailbox + 4), MLY_GET_REG(sc, sc->mly_command_mailbox + 5), MLY_GET_REG(sc, sc->mly_command_mailbox + 6), MLY_GET_REG(sc, sc->mly_command_mailbox + 7)); mly_printf(sc, "STATUS %02x %02x %02x %02x %02x %02x %02x %02x\n", MLY_GET_REG(sc, sc->mly_status_mailbox), MLY_GET_REG(sc, sc->mly_status_mailbox + 1), MLY_GET_REG(sc, sc->mly_status_mailbox + 2), MLY_GET_REG(sc, sc->mly_status_mailbox + 3), MLY_GET_REG(sc, sc->mly_status_mailbox + 4), MLY_GET_REG(sc, sc->mly_status_mailbox + 5), MLY_GET_REG(sc, sc->mly_status_mailbox + 6), MLY_GET_REG(sc, sc->mly_status_mailbox + 7)); mly_printf(sc, " %04x %08x\n", MLY_GET_REG2(sc, sc->mly_status_mailbox), MLY_GET_REG4(sc, sc->mly_status_mailbox + 4)); } struct mly_softc *mly_softc0 = NULL; void mly_printstate0(void) { if (mly_softc0 != NULL) mly_printstate(mly_softc0); } /******************************************************************************** * Print a command */ static void mly_print_command(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; mly_printf(sc, "COMMAND @ %p\n", mc); mly_printf(sc, " slot %d\n", mc->mc_slot); mly_printf(sc, " status 0x%x\n", mc->mc_status); mly_printf(sc, " sense len %d\n", mc->mc_sense); mly_printf(sc, " resid %d\n", mc->mc_resid); mly_printf(sc, " packet %p/0x%llx\n", mc->mc_packet, mc->mc_packetphys); if (mc->mc_packet != NULL) mly_print_packet(mc); mly_printf(sc, " data %p/%d\n", mc->mc_data, mc->mc_length); mly_printf(sc, " flags %b\n", mc->mc_flags, "\20\1busy\2complete\3slotted\4mapped\5datain\6dataout\n"); mly_printf(sc, " complete %p\n", mc->mc_complete); mly_printf(sc, " private %p\n", mc->mc_private); } /******************************************************************************** * Print a command packet */ static void mly_print_packet(struct mly_command *mc) { struct mly_softc *sc = mc->mc_sc; struct mly_command_generic *ge = (struct mly_command_generic *)mc->mc_packet; struct mly_command_scsi_small *ss = (struct mly_command_scsi_small *)mc->mc_packet; struct mly_command_scsi_large *sl = (struct mly_command_scsi_large *)mc->mc_packet; struct mly_command_ioctl *io = (struct mly_command_ioctl *)mc->mc_packet; int transfer; mly_printf(sc, " command_id %d\n", ge->command_id); mly_printf(sc, " opcode %d\n", ge->opcode); mly_printf(sc, " command_control fua %d dpo %d est %d dd %s nas %d ddis %d\n", ge->command_control.force_unit_access, ge->command_control.disable_page_out, ge->command_control.extended_sg_table, (ge->command_control.data_direction == MLY_CCB_WRITE) ? "WRITE" : "READ", ge->command_control.no_auto_sense, ge->command_control.disable_disconnect); mly_printf(sc, " data_size %d\n", ge->data_size); mly_printf(sc, " sense_buffer_address 0x%llx\n", ge->sense_buffer_address); mly_printf(sc, " lun %d\n", ge->addr.phys.lun); mly_printf(sc, " target %d\n", ge->addr.phys.target); mly_printf(sc, " channel %d\n", ge->addr.phys.channel); mly_printf(sc, " logical device %d\n", ge->addr.log.logdev); mly_printf(sc, " controller %d\n", ge->addr.phys.controller); mly_printf(sc, " timeout %d %s\n", ge->timeout.value, (ge->timeout.scale == MLY_TIMEOUT_SECONDS) ? "seconds" : ((ge->timeout.scale == MLY_TIMEOUT_MINUTES) ? "minutes" : "hours")); mly_printf(sc, " maximum_sense_size %d\n", ge->maximum_sense_size); switch(ge->opcode) { case MDACMD_SCSIPT: case MDACMD_SCSI: mly_printf(sc, " cdb length %d\n", ss->cdb_length); mly_printf(sc, " cdb %*D\n", ss->cdb_length, ss->cdb, " "); transfer = 1; break; case MDACMD_SCSILC: case MDACMD_SCSILCPT: mly_printf(sc, " cdb length %d\n", sl->cdb_length); mly_printf(sc, " cdb 0x%llx\n", sl->cdb_physaddr); transfer = 1; break; case MDACMD_IOCTL: mly_printf(sc, " sub_ioctl 0x%x\n", io->sub_ioctl); switch(io->sub_ioctl) { case MDACIOCTL_SETMEMORYMAILBOX: mly_printf(sc, " health_buffer_size %d\n", io->param.setmemorymailbox.health_buffer_size); mly_printf(sc, " health_buffer_phys 0x%llx\n", io->param.setmemorymailbox.health_buffer_physaddr); mly_printf(sc, " command_mailbox 0x%llx\n", io->param.setmemorymailbox.command_mailbox_physaddr); mly_printf(sc, " status_mailbox 0x%llx\n", io->param.setmemorymailbox.status_mailbox_physaddr); transfer = 0; break; case MDACIOCTL_SETREALTIMECLOCK: case MDACIOCTL_GETHEALTHSTATUS: case MDACIOCTL_GETCONTROLLERINFO: case MDACIOCTL_GETLOGDEVINFOVALID: case MDACIOCTL_GETPHYSDEVINFOVALID: case MDACIOCTL_GETPHYSDEVSTATISTICS: case MDACIOCTL_GETLOGDEVSTATISTICS: case MDACIOCTL_GETCONTROLLERSTATISTICS: case MDACIOCTL_GETBDT_FOR_SYSDRIVE: case MDACIOCTL_CREATENEWCONF: case MDACIOCTL_ADDNEWCONF: case MDACIOCTL_GETDEVCONFINFO: case MDACIOCTL_GETFREESPACELIST: case MDACIOCTL_MORE: case MDACIOCTL_SETPHYSDEVPARAMETER: case MDACIOCTL_GETPHYSDEVPARAMETER: case MDACIOCTL_GETLOGDEVPARAMETER: case MDACIOCTL_SETLOGDEVPARAMETER: mly_printf(sc, " param %10D\n", io->param.data.param, " "); transfer = 1; break; case MDACIOCTL_GETEVENT: mly_printf(sc, " event %d\n", io->param.getevent.sequence_number_low + ((u_int32_t)io->addr.log.logdev << 16)); transfer = 1; break; case MDACIOCTL_SETRAIDDEVSTATE: mly_printf(sc, " state %d\n", io->param.setraiddevstate.state); transfer = 0; break; case MDACIOCTL_XLATEPHYSDEVTORAIDDEV: mly_printf(sc, " raid_device %d\n", io->param.xlatephysdevtoraiddev.raid_device); mly_printf(sc, " controller %d\n", io->param.xlatephysdevtoraiddev.controller); mly_printf(sc, " channel %d\n", io->param.xlatephysdevtoraiddev.channel); mly_printf(sc, " target %d\n", io->param.xlatephysdevtoraiddev.target); mly_printf(sc, " lun %d\n", io->param.xlatephysdevtoraiddev.lun); transfer = 0; break; case MDACIOCTL_GETGROUPCONFINFO: mly_printf(sc, " group %d\n", io->param.getgroupconfinfo.group); transfer = 1; break; case MDACIOCTL_GET_SUBSYSTEM_DATA: case MDACIOCTL_SET_SUBSYSTEM_DATA: case MDACIOCTL_STARTDISOCVERY: case MDACIOCTL_INITPHYSDEVSTART: case MDACIOCTL_INITPHYSDEVSTOP: case MDACIOCTL_INITRAIDDEVSTART: case MDACIOCTL_INITRAIDDEVSTOP: case MDACIOCTL_REBUILDRAIDDEVSTART: case MDACIOCTL_REBUILDRAIDDEVSTOP: case MDACIOCTL_MAKECONSISTENTDATASTART: case MDACIOCTL_MAKECONSISTENTDATASTOP: case MDACIOCTL_CONSISTENCYCHECKSTART: case MDACIOCTL_CONSISTENCYCHECKSTOP: case MDACIOCTL_RESETDEVICE: case MDACIOCTL_FLUSHDEVICEDATA: case MDACIOCTL_PAUSEDEVICE: case MDACIOCTL_UNPAUSEDEVICE: case MDACIOCTL_LOCATEDEVICE: case MDACIOCTL_SETMASTERSLAVEMODE: case MDACIOCTL_DELETERAIDDEV: case MDACIOCTL_REPLACEINTERNALDEV: case MDACIOCTL_CLEARCONF: case MDACIOCTL_GETCONTROLLERPARAMETER: case MDACIOCTL_SETCONTRLLERPARAMETER: case MDACIOCTL_CLEARCONFSUSPMODE: case MDACIOCTL_STOREIMAGE: case MDACIOCTL_READIMAGE: case MDACIOCTL_FLASHIMAGES: case MDACIOCTL_RENAMERAIDDEV: default: /* no idea what to print */ transfer = 0; break; } break; case MDACMD_IOCTLCHECK: case MDACMD_MEMCOPY: default: transfer = 0; break; /* print nothing */ } if (transfer) { if (ge->command_control.extended_sg_table) { mly_printf(sc, " sg table 0x%llx/%d\n", ge->transfer.indirect.table_physaddr[0], ge->transfer.indirect.entries[0]); } else { mly_printf(sc, " 0000 0x%llx/%lld\n", ge->transfer.direct.sg[0].physaddr, ge->transfer.direct.sg[0].length); mly_printf(sc, " 0001 0x%llx/%lld\n", ge->transfer.direct.sg[1].physaddr, ge->transfer.direct.sg[1].length); } } } /******************************************************************************** * Panic in a slightly informative fashion */ static void mly_panic(struct mly_softc *sc, char *reason) { mly_printstate(sc); panic(reason); } /******************************************************************************** * Print queue statistics, callable from DDB. */ void mly_print_controller(int controller) { struct mly_softc *sc; if ((sc = devclass_get_softc(devclass_find("mly"), controller)) == NULL) { printf("mly: controller %d invalid\n", controller); } else { device_printf(sc->mly_dev, "queue curr max\n"); device_printf(sc->mly_dev, "free %04d/%04d\n", sc->mly_qstat[MLYQ_FREE].q_length, sc->mly_qstat[MLYQ_FREE].q_max); device_printf(sc->mly_dev, "busy %04d/%04d\n", sc->mly_qstat[MLYQ_BUSY].q_length, sc->mly_qstat[MLYQ_BUSY].q_max); device_printf(sc->mly_dev, "complete %04d/%04d\n", sc->mly_qstat[MLYQ_COMPLETE].q_length, sc->mly_qstat[MLYQ_COMPLETE].q_max); } } #endif /******************************************************************************** ******************************************************************************** Control device interface ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Accept an open operation on the control device. */ static int mly_user_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mly_softc *sc = dev->si_drv1; MLY_LOCK(sc); sc->mly_state |= MLY_STATE_OPEN; MLY_UNLOCK(sc); return(0); } /******************************************************************************** * Accept the last close on the control device. */ static int mly_user_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct mly_softc *sc = dev->si_drv1; MLY_LOCK(sc); sc->mly_state &= ~MLY_STATE_OPEN; MLY_UNLOCK(sc); return (0); } /******************************************************************************** * Handle controller-specific control operations. */ static int mly_user_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { struct mly_softc *sc = (struct mly_softc *)dev->si_drv1; struct mly_user_command *uc = (struct mly_user_command *)addr; struct mly_user_health *uh = (struct mly_user_health *)addr; switch(cmd) { case MLYIO_COMMAND: return(mly_user_command(sc, uc)); case MLYIO_HEALTH: return(mly_user_health(sc, uh)); default: return(ENOIOCTL); } } /******************************************************************************** * Execute a command passed in from userspace. * * The control structure contains the actual command for the controller, as well * as the user-space data pointer and data size, and an optional sense buffer * size/pointer. On completion, the data size is adjusted to the command * residual, and the sense buffer size to the size of the returned sense data. * */ static int mly_user_command(struct mly_softc *sc, struct mly_user_command *uc) { struct mly_command *mc; int error; /* allocate a command */ MLY_LOCK(sc); if (mly_alloc_command(sc, &mc)) { MLY_UNLOCK(sc); error = ENOMEM; goto out; /* XXX Linux version will wait for a command */ } MLY_UNLOCK(sc); /* handle data size/direction */ mc->mc_length = (uc->DataTransferLength >= 0) ? uc->DataTransferLength : -uc->DataTransferLength; if (mc->mc_length > 0) { if ((mc->mc_data = malloc(mc->mc_length, M_DEVBUF, M_NOWAIT)) == NULL) { error = ENOMEM; goto out; } } if (uc->DataTransferLength > 0) { mc->mc_flags |= MLY_CMD_DATAIN; bzero(mc->mc_data, mc->mc_length); } if (uc->DataTransferLength < 0) { mc->mc_flags |= MLY_CMD_DATAOUT; if ((error = copyin(uc->DataTransferBuffer, mc->mc_data, mc->mc_length)) != 0) goto out; } /* copy the controller command */ bcopy(&uc->CommandMailbox, mc->mc_packet, sizeof(uc->CommandMailbox)); /* clear command completion handler so that we get woken up */ mc->mc_complete = NULL; /* execute the command */ MLY_LOCK(sc); if ((error = mly_start(mc)) != 0) { MLY_UNLOCK(sc); goto out; } while (!(mc->mc_flags & MLY_CMD_COMPLETE)) mtx_sleep(mc, &sc->mly_lock, PRIBIO, "mlyioctl", 0); MLY_UNLOCK(sc); /* return the data to userspace */ if (uc->DataTransferLength > 0) if ((error = copyout(mc->mc_data, uc->DataTransferBuffer, mc->mc_length)) != 0) goto out; /* return the sense buffer to userspace */ if ((uc->RequestSenseLength > 0) && (mc->mc_sense > 0)) { if ((error = copyout(mc->mc_packet, uc->RequestSenseBuffer, min(uc->RequestSenseLength, mc->mc_sense))) != 0) goto out; } /* return command results to userspace (caller will copy out) */ uc->DataTransferLength = mc->mc_resid; uc->RequestSenseLength = min(uc->RequestSenseLength, mc->mc_sense); uc->CommandStatus = mc->mc_status; error = 0; out: if (mc->mc_data != NULL) free(mc->mc_data, M_DEVBUF); if (mc != NULL) { MLY_LOCK(sc); mly_release_command(mc); MLY_UNLOCK(sc); } return(error); } /******************************************************************************** * Return health status to userspace. If the health change index in the user * structure does not match that currently exported by the controller, we * return the current status immediately. Otherwise, we block until either * interrupted or new status is delivered. */ static int mly_user_health(struct mly_softc *sc, struct mly_user_health *uh) { struct mly_health_status mh; int error; /* fetch the current health status from userspace */ if ((error = copyin(uh->HealthStatusBuffer, &mh, sizeof(mh))) != 0) return(error); /* spin waiting for a status update */ MLY_LOCK(sc); error = EWOULDBLOCK; while ((error != 0) && (sc->mly_event_change == mh.change_counter)) error = mtx_sleep(&sc->mly_event_change, &sc->mly_lock, PRIBIO | PCATCH, "mlyhealth", 0); mh = sc->mly_mmbox->mmm_health.status; MLY_UNLOCK(sc); /* copy the controller's health status buffer out */ error = copyout(&mh, uh->HealthStatusBuffer, sizeof(mh)); return(error); } #ifdef MLY_DEBUG static void mly_timeout(void *arg) { struct mly_softc *sc; struct mly_command *mc; int deadline; sc = arg; MLY_ASSERT_LOCKED(sc); deadline = time_second - MLY_CMD_TIMEOUT; TAILQ_FOREACH(mc, &sc->mly_busy, mc_link) { if ((mc->mc_timestamp < deadline)) { device_printf(sc->mly_dev, "COMMAND %p TIMEOUT AFTER %d SECONDS\n", mc, (int)(time_second - mc->mc_timestamp)); } } callout_reset(&sc->mly_timeout, MLY_CMD_TIMEOUT * hz, mly_timeout, sc); } #endif Index: head/sys/dev/trm/trm.c =================================================================== --- head/sys/dev/trm/trm.c (revision 280346) +++ head/sys/dev/trm/trm.c (revision 280347) @@ -1,3703 +1,3703 @@ /* * O.S : FreeBSD CAM * FILE NAME : trm.c * BY : C.L. Huang (ching@tekram.com.tw) * Erich Chen (erich@tekram.com.tw) * Description: Device Driver for Tekram SCSI adapters * DC395U/UW/F ,DC315/U(TRM-S1040) * DC395U2D/U2W(TRM-S2080) * PCI SCSI Bus Master Host Adapter * (SCSI chip set used Tekram ASIC TRM-S1040,TRM-S2080) */ #include __FBSDID("$FreeBSD$"); /* * HISTORY: * * REV# DATE NAME DESCRIPTION * 1.05 05/01/1999 ERICH CHEN First released for 3.x.x (CAM) * 1.06 07/29/1999 ERICH CHEN Modify for NEW PCI * 1.07 12/12/1999 ERICH CHEN Modify for 3.3.x ,DCB no free * 1.08 06/12/2000 ERICH CHEN Modify for 4.x.x * 1.09 11/03/2000 ERICH CHEN Modify for 4.1.R ,new sim * 1.10 10/10/2001 Oscar Feng Fixed CAM rescan hang up bug. * 1.11 10/13/2001 Oscar Feng Fixed wrong Async speed display bug. */ /*- * (C)Copyright 1995-2001 Tekram Technology Co.,Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /* * Imported into FreeBSD source repository, and updated to compile under * FreeBSD-3.0-DEVELOPMENT, by Stefan Esser , 1996-12-17 */ /* * Updated to compile under FreeBSD 5.0-CURRENT by Olivier Houchard * , 2002-03-04 */ #include #include #include #include #if __FreeBSD_version >= 500000 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define trm_reg_read8(reg) bus_space_read_1(pACB->tag, pACB->bsh, reg) #define trm_reg_read16(reg) bus_space_read_2(pACB->tag, pACB->bsh, reg) #define trm_reg_read32(reg) bus_space_read_4(pACB->tag, pACB->bsh, reg) #define trm_reg_write8(value,reg) bus_space_write_1(pACB->tag, pACB->bsh,\ reg, value) #define trm_reg_write16(value,reg) bus_space_write_2(pACB->tag, pACB->bsh,\ reg, value) #define trm_reg_write32(value,reg) bus_space_write_4(pACB->tag, pACB->bsh,\ reg, value) #define PCI_Vendor_ID_TEKRAM 0x1DE1 #define PCI_Device_ID_TRM_S1040 0x0391 #define PCI_DEVICEID_TRMS1040 0x03911DE1 #define PCI_DEVICEID_TRMS2080 0x03921DE1 #ifdef trm_DEBUG1 #define TRM_DPRINTF(fmt, arg...) printf("trm: " fmt, ##arg) #else #define TRM_DPRINTF(fmt, arg...) {} #endif /* TRM_DEBUG */ static void trm_check_eeprom(PNVRAMTYPE pEEpromBuf,PACB pACB); static void NVRAM_trm_read_all(PNVRAMTYPE pEEpromBuf,PACB pACB); static u_int8_t NVRAM_trm_get_data(PACB pACB, u_int8_t bAddr); static void NVRAM_trm_write_all(PNVRAMTYPE pEEpromBuf,PACB pACB); static void NVRAM_trm_set_data(PACB pACB, u_int8_t bAddr, u_int8_t bData); static void NVRAM_trm_write_cmd(PACB pACB, u_int8_t bCmd, u_int8_t bAddr); static void NVRAM_trm_wait_30us(PACB pACB); static void trm_Interrupt(void *vpACB); static void trm_DataOutPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_DataInPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_CommandPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_StatusPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_MsgOutPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_MsgInPhase0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_DataOutPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_DataInPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_CommandPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_StatusPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_MsgOutPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_MsgInPhase1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_Nop0(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_Nop1(PACB pACB, PSRB pSRB, u_int16_t * pscsi_status); static void trm_SetXferRate(PACB pACB, PSRB pSRB,PDCB pDCB); static void trm_DataIO_transfer(PACB pACB, PSRB pSRB, u_int16_t ioDir); static void trm_Disconnect(PACB pACB); static void trm_Reselect(PACB pACB); static void trm_SRBdone(PACB pACB, PDCB pDCB, PSRB pSRB); static void trm_DoingSRB_Done(PACB pACB); static void trm_ScsiRstDetect(PACB pACB); static void trm_ResetSCSIBus(PACB pACB); static void trm_RequestSense(PACB pACB, PDCB pDCB, PSRB pSRB); static void trm_EnableMsgOutAbort2(PACB pACB, PSRB pSRB); static void trm_EnableMsgOutAbort1(PACB pACB, PSRB pSRB); static void trm_SendSRB(PACB pACB, PSRB pSRB); static int trm_probe(device_t tag); static int trm_attach(device_t tag); static void trm_reset(PACB pACB); static u_int16_t trm_StartSCSI(PACB pACB, PDCB pDCB, PSRB pSRB); static int trm_initAdapter(PACB pACB, u_int16_t unit); static void trm_initDCB(PACB pACB, PDCB pDCB, u_int16_t unit, u_int32_t i, u_int32_t j); static int trm_initSRB(PACB pACB); static void trm_initACB(PACB pACB, u_int8_t adaptType, u_int16_t unit); /* CAM SIM entry points */ #define ccb_trmsrb_ptr spriv_ptr0 #define ccb_trmacb_ptr spriv_ptr1 static void trm_action(struct cam_sim *psim, union ccb *pccb); static void trm_poll(struct cam_sim *psim); static void * trm_SCSI_phase0[] = { trm_DataOutPhase0, /* phase:0 */ trm_DataInPhase0, /* phase:1 */ trm_CommandPhase0, /* phase:2 */ trm_StatusPhase0, /* phase:3 */ trm_Nop0, /* phase:4 */ trm_Nop1, /* phase:5 */ trm_MsgOutPhase0, /* phase:6 */ trm_MsgInPhase0, /* phase:7 */ }; /* * * stateV = (void *) trm_SCSI_phase1[phase] * */ static void * trm_SCSI_phase1[] = { trm_DataOutPhase1, /* phase:0 */ trm_DataInPhase1, /* phase:1 */ trm_CommandPhase1, /* phase:2 */ trm_StatusPhase1, /* phase:3 */ trm_Nop0, /* phase:4 */ trm_Nop1, /* phase:5 */ trm_MsgOutPhase1, /* phase:6 */ trm_MsgInPhase1, /* phase:7 */ }; NVRAMTYPE trm_eepromBuf[TRM_MAX_ADAPTER_NUM]; /* *Fast20: 000 50ns, 20.0 Mbytes/s * 001 75ns, 13.3 Mbytes/s * 010 100ns, 10.0 Mbytes/s * 011 125ns, 8.0 Mbytes/s * 100 150ns, 6.6 Mbytes/s * 101 175ns, 5.7 Mbytes/s * 110 200ns, 5.0 Mbytes/s * 111 250ns, 4.0 Mbytes/s * *Fast40: 000 25ns, 40.0 Mbytes/s * 001 50ns, 20.0 Mbytes/s * 010 75ns, 13.3 Mbytes/s * 011 100ns, 10.0 Mbytes/s * 100 125ns, 8.0 Mbytes/s * 101 150ns, 6.6 Mbytes/s * 110 175ns, 5.7 Mbytes/s * 111 200ns, 5.0 Mbytes/s */ /* real period: */ u_int8_t dc395x_clock_period[] = { 12,/* 48 ns 20 MB/sec */ 18,/* 72 ns 13.3 MB/sec */ 25,/* 100 ns 10.0 MB/sec */ 31,/* 124 ns 8.0 MB/sec */ 37,/* 148 ns 6.6 MB/sec */ 43,/* 172 ns 5.7 MB/sec */ 50,/* 200 ns 5.0 MB/sec */ 62 /* 248 ns 4.0 MB/sec */ }; u_int8_t dc395u2x_clock_period[]={ 10,/* 25 ns 40.0 MB/sec */ 12,/* 48 ns 20.0 MB/sec */ 18,/* 72 ns 13.3 MB/sec */ 25,/* 100 ns 10.0 MB/sec */ 31,/* 124 ns 8.0 MB/sec */ 37,/* 148 ns 6.6 MB/sec */ 43,/* 172 ns 5.7 MB/sec */ 50,/* 200 ns 5.0 MB/sec */ }; #define dc395x_tinfo_period dc395x_clock_period #define dc395u2x_tinfo_period dc395u2x_clock_period static PSRB trm_GetSRB(PACB pACB) { int intflag; PSRB pSRB; intflag = splcam(); pSRB = pACB->pFreeSRB; if (pSRB) { pACB->pFreeSRB = pSRB->pNextSRB; pSRB->pNextSRB = NULL; } splx(intflag); return (pSRB); } static void trm_RewaitSRB0(PDCB pDCB, PSRB pSRB) { PSRB psrb1; int intflag; intflag = splcam(); if ((psrb1 = pDCB->pWaitingSRB)) { pSRB->pNextSRB = psrb1; pDCB->pWaitingSRB = pSRB; } else { pSRB->pNextSRB = NULL; pDCB->pWaitingSRB = pSRB; pDCB->pWaitingLastSRB = pSRB; } splx(intflag); } static void trm_RewaitSRB(PDCB pDCB, PSRB pSRB) { PSRB psrb1; int intflag; intflag = splcam(); pDCB->GoingSRBCnt--; psrb1 = pDCB->pGoingSRB; if (pSRB == psrb1) /* * if this SRB is GoingSRB * remove this SRB from GoingSRB Q */ pDCB->pGoingSRB = psrb1->pNextSRB; else { /* * if this SRB is not current GoingSRB * remove this SRB from GoingSRB Q */ while (pSRB != psrb1->pNextSRB) psrb1 = psrb1->pNextSRB; psrb1->pNextSRB = pSRB->pNextSRB; if (pSRB == pDCB->pGoingLastSRB) pDCB->pGoingLastSRB = psrb1; } if ((psrb1 = pDCB->pWaitingSRB)) { /* * if WaitingSRB Q is not NULL * Q back this SRB into WaitingSRB */ pSRB->pNextSRB = psrb1; pDCB->pWaitingSRB = pSRB; } else { pSRB->pNextSRB = NULL; pDCB->pWaitingSRB = pSRB; pDCB->pWaitingLastSRB = pSRB; } splx(intflag); } static void trm_DoWaitingSRB(PACB pACB) { int intflag; PDCB ptr, ptr1; PSRB pSRB; intflag = splcam(); if (!(pACB->pActiveDCB) && !(pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV))) { ptr = pACB->pDCBRunRobin; if (!ptr) { ptr = pACB->pLinkDCB; pACB->pDCBRunRobin = ptr; } ptr1 = ptr; for (;ptr1 ;) { pACB->pDCBRunRobin = ptr1->pNextDCB; if (!(ptr1->MaxActiveCommandCnt > ptr1->GoingSRBCnt) || !(pSRB = ptr1->pWaitingSRB)) { if (pACB->pDCBRunRobin == ptr) break; ptr1 = ptr1->pNextDCB; } else { if (!trm_StartSCSI(pACB, ptr1, pSRB)) { /* * If trm_StartSCSI return 0 : * current interrupt status is interrupt enable * It's said that SCSI processor is unoccupied */ ptr1->GoingSRBCnt++; if (ptr1->pWaitingLastSRB == pSRB) { ptr1->pWaitingSRB = NULL; ptr1->pWaitingLastSRB = NULL; } else ptr1->pWaitingSRB = pSRB->pNextSRB; pSRB->pNextSRB = NULL; if (ptr1->pGoingSRB) ptr1->pGoingLastSRB->pNextSRB = pSRB; else ptr1->pGoingSRB = pSRB; ptr1->pGoingLastSRB = pSRB; } break; } } } splx(intflag); return; } static void trm_SRBwaiting(PDCB pDCB, PSRB pSRB) { if (pDCB->pWaitingSRB) { pDCB->pWaitingLastSRB->pNextSRB = pSRB; pDCB->pWaitingLastSRB = pSRB; pSRB->pNextSRB = NULL; } else { pDCB->pWaitingSRB = pSRB; pDCB->pWaitingLastSRB = pSRB; } } static u_int32_t trm_get_sense_bufaddr(PACB pACB, PSRB pSRB) { int offset; offset = pSRB->TagNumber; return (pACB->sense_busaddr + (offset * sizeof(struct scsi_sense_data))); } static struct scsi_sense_data * trm_get_sense_buf(PACB pACB, PSRB pSRB) { int offset; offset = pSRB->TagNumber; return (&pACB->sense_buffers[offset]); } static void trm_ExecuteSRB(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) { int flags; PACB pACB; PSRB pSRB; union ccb *ccb; u_long totalxferlen=0; flags = splcam(); pSRB = (PSRB)arg; ccb = pSRB->pccb; pACB = (PACB)ccb->ccb_h.ccb_trmacb_ptr; TRM_DPRINTF("trm_ExecuteSRB..........\n"); if (nseg != 0) { PSEG psg; bus_dma_segment_t *end_seg; int op; /* Copy the segments into our SG list */ end_seg = dm_segs + nseg; psg = pSRB->pSRBSGL; while (dm_segs < end_seg) { psg->address = dm_segs->ds_addr; psg->length = (u_long)dm_segs->ds_len; totalxferlen += dm_segs->ds_len; psg++; dm_segs++; } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { op = BUS_DMASYNC_PREREAD; } else { op = BUS_DMASYNC_PREWRITE; } bus_dmamap_sync(pACB->buffer_dmat, pSRB->dmamap, op); } pSRB->RetryCnt = 0; pSRB->SRBTotalXferLength = totalxferlen; pSRB->SRBSGCount = nseg; pSRB->SRBSGIndex = 0; pSRB->AdaptStatus = 0; pSRB->TargetStatus = 0; pSRB->MsgCnt = 0; pSRB->SRBStatus = 0; pSRB->SRBFlag = 0; pSRB->SRBState = 0; pSRB->ScsiPhase = PH_BUS_FREE; /* SCSI bus free Phase */ if (ccb->ccb_h.status != CAM_REQ_INPROG) { if (nseg != 0) bus_dmamap_unload(pACB->buffer_dmat, pSRB->dmamap); pSRB->pNextSRB = pACB->pFreeSRB; pACB->pFreeSRB = pSRB; xpt_done(ccb); splx(flags); return; } ccb->ccb_h.status |= CAM_SIM_QUEUED; trm_SendSRB(pACB, pSRB); splx(flags); return; } static void trm_SendSRB(PACB pACB, PSRB pSRB) { PDCB pDCB; pDCB = pSRB->pSRBDCB; if (!(pDCB->MaxActiveCommandCnt > pDCB->GoingSRBCnt) || (pACB->pActiveDCB) || (pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV))) { TRM_DPRINTF("pDCB->MaxCommand=%d \n",pDCB->MaxActiveCommandCnt); TRM_DPRINTF("pDCB->GoingSRBCnt=%d \n",pDCB->GoingSRBCnt); TRM_DPRINTF("pACB->pActiveDCB=%8x \n",(u_int)pACB->pActiveDCB); TRM_DPRINTF("pACB->ACBFlag=%x \n",pACB->ACBFlag); trm_SRBwaiting(pDCB, pSRB); goto SND_EXIT; } if (pDCB->pWaitingSRB) { trm_SRBwaiting(pDCB, pSRB); pSRB = pDCB->pWaitingSRB; pDCB->pWaitingSRB = pSRB->pNextSRB; pSRB->pNextSRB = NULL; } if (!trm_StartSCSI(pACB, pDCB, pSRB)) { /* * If trm_StartSCSI return 0 : * current interrupt status is interrupt enable * It's said that SCSI processor is unoccupied */ pDCB->GoingSRBCnt++; /* stack waiting SRB*/ if (pDCB->pGoingSRB) { pDCB->pGoingLastSRB->pNextSRB = pSRB; pDCB->pGoingLastSRB = pSRB; } else { pDCB->pGoingSRB = pSRB; pDCB->pGoingLastSRB = pSRB; } } else { /* * If trm_StartSCSI return 1 : * current interrupt status is interrupt disreenable * It's said that SCSI processor has more one SRB need to do */ trm_RewaitSRB0(pDCB, pSRB); } SND_EXIT: return; } static void trm_action(struct cam_sim *psim, union ccb *pccb) { PACB pACB; int actionflags; u_int target_id,target_lun; CAM_DEBUG(pccb->ccb_h.path, CAM_DEBUG_TRACE, ("trm_action\n")); actionflags = splcam(); pACB = (PACB) cam_sim_softc(psim); target_id = pccb->ccb_h.target_id; target_lun = pccb->ccb_h.target_lun; switch (pccb->ccb_h.func_code) { case XPT_NOOP: TRM_DPRINTF(" XPT_NOOP \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Execute the requested I/O operation */ case XPT_SCSI_IO: { PDCB pDCB = NULL; PSRB pSRB; struct ccb_scsiio *pcsio; int error; pcsio = &pccb->csio; TRM_DPRINTF(" XPT_SCSI_IO \n"); TRM_DPRINTF("trm: target_id= %d target_lun= %d \n" ,target_id, target_lun); TRM_DPRINTF( "pACB->scan_devices[target_id][target_lun]= %d \n" ,pACB->scan_devices[target_id][target_lun]); if ((pccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG) { xpt_done(pccb); splx(actionflags); return; } pDCB = &pACB->DCBarray[target_id][target_lun]; if (!(pDCB->DCBstatus & DS_IN_QUEUE)) { pACB->scan_devices[target_id][target_lun] = 1; trm_initDCB(pACB, pDCB, pACB->AdapterUnit, target_id, target_lun); } /* * Assign an SRB and connect it with this ccb. */ pSRB = trm_GetSRB(pACB); if (!pSRB) { /* Freeze SIMQ */ pccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(pccb); splx(actionflags); return; } pSRB->pSRBDCB = pDCB; pccb->ccb_h.ccb_trmsrb_ptr = pSRB; pccb->ccb_h.ccb_trmacb_ptr = pACB; pSRB->pccb = pccb; pSRB->ScsiCmdLen = pcsio->cdb_len; /* * move layer of CAM command block to layer of SCSI * Request Block for SCSI processor command doing */ if ((pccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((pccb->ccb_h.flags & CAM_CDB_PHYS) == 0) { bcopy(pcsio->cdb_io.cdb_ptr,pSRB->CmdBlock ,pcsio->cdb_len); } else { pccb->ccb_h.status = CAM_REQ_INVALID; pSRB->pNextSRB = pACB->pFreeSRB; pACB->pFreeSRB= pSRB; xpt_done(pccb); splx(actionflags); return; } } else bcopy(pcsio->cdb_io.cdb_bytes, pSRB->CmdBlock, pcsio->cdb_len); error = bus_dmamap_load_ccb(pACB->buffer_dmat, pSRB->dmamap, pccb, trm_ExecuteSRB, pSRB, 0); if (error == EINPROGRESS) { xpt_freeze_simq(pACB->psim, 1); pccb->ccb_h.status |= CAM_RELEASE_SIMQ; } break; } case XPT_GDEV_TYPE: TRM_DPRINTF(" XPT_GDEV_TYPE \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; case XPT_GDEVLIST: TRM_DPRINTF(" XPT_GDEVLIST \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Path routing inquiry * Path Inquiry CCB */ case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &pccb->cpi; TRM_DPRINTF(" XPT_PATH_INQ \n"); cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16; cpi->target_sprt = 0; cpi->hba_misc = 0; cpi->hba_eng_cnt = 0; cpi->max_target = 15 ; cpi->max_lun = pACB->max_lun; /* 7 or 0 */ cpi->initiator_id = pACB->AdaptSCSIID; cpi->bus_id = cam_sim_bus(psim); cpi->base_transfer_speed = 3300; strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Tekram_TRM", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(psim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(psim); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(pccb); } break; /* * Release a frozen SIM queue * Release SIM Queue */ case XPT_REL_SIMQ: TRM_DPRINTF(" XPT_REL_SIMQ \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Set Asynchronous Callback Parameters * Set Asynchronous Callback CCB */ case XPT_SASYNC_CB: TRM_DPRINTF(" XPT_SASYNC_CB \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Set device type information * Set Device Type CCB */ case XPT_SDEV_TYPE: TRM_DPRINTF(" XPT_SDEV_TYPE \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Get EDT entries matching the given pattern */ case XPT_DEV_MATCH: TRM_DPRINTF(" XPT_DEV_MATCH \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Turn on debugging for a bus, target or lun */ case XPT_DEBUG: TRM_DPRINTF(" XPT_DEBUG \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * XPT_ABORT = 0x10, Abort the specified CCB * Abort XPT request CCB */ case XPT_ABORT: TRM_DPRINTF(" XPT_ABORT \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Reset the specified SCSI bus * Reset SCSI Bus CCB */ case XPT_RESET_BUS: { int i; TRM_DPRINTF(" XPT_RESET_BUS \n"); trm_reset(pACB); pACB->ACBFlag=0; for (i=0; i<500; i++) DELAY(1000); pccb->ccb_h.status = CAM_REQ_CMP; xpt_done(pccb); } break; /* * Bus Device Reset the specified SCSI device * Reset SCSI Device CCB */ case XPT_RESET_DEV: /* * Don't (yet?) support vendor * specific commands. */ TRM_DPRINTF(" XPT_RESET_DEV \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Terminate the I/O process * Terminate I/O Process Request CCB */ case XPT_TERM_IO: TRM_DPRINTF(" XPT_TERM_IO \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Get/Set transfer rate/width/disconnection/tag queueing * settings * (GET) default/user transfer settings for the target */ case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &pccb->cts; int intflag; struct trm_transinfo *tinfo; PDCB pDCB; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_2; cts->transport = XPORT_SPI; cts->transport_version = 2; TRM_DPRINTF(" XPT_GET_TRAN_SETTINGS \n"); pDCB = &pACB->DCBarray[target_id][target_lun]; intflag = splcam(); /* * disable interrupt */ if (cts->type == CTS_TYPE_CURRENT_SETTINGS) { /* current transfer settings */ if (pDCB->tinfo.disc_tag & TRM_CUR_DISCENB) spi->flags = CTS_SPI_FLAGS_DISC_ENB; else spi->flags = 0;/* no tag & disconnect */ if (pDCB->tinfo.disc_tag & TRM_CUR_TAGENB) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; tinfo = &pDCB->tinfo.current; TRM_DPRINTF("CURRENT: cts->flags= %2x \n", cts->flags); } else { /* default(user) transfer settings */ if (pDCB->tinfo.disc_tag & TRM_USR_DISCENB) spi->flags = CTS_SPI_FLAGS_DISC_ENB; else spi->flags = 0; if (pDCB->tinfo.disc_tag & TRM_USR_TAGENB) scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB; tinfo = &pDCB->tinfo.user; TRM_DPRINTF("USER: cts->flags= %2x \n", cts->flags); } spi->sync_period = tinfo->period; spi->sync_offset = tinfo->offset; spi->bus_width = tinfo->width; TRM_DPRINTF("pDCB->SyncPeriod: %d \n", pDCB->SyncPeriod); TRM_DPRINTF("period: %d \n", tinfo->period); TRM_DPRINTF("offset: %d \n", tinfo->offset); TRM_DPRINTF("width: %d \n", tinfo->width); splx(intflag); spi->valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET | CTS_SPI_VALID_BUS_WIDTH | CTS_SPI_VALID_DISC; scsi->valid = CTS_SCSI_VALID_TQ; pccb->ccb_h.status = CAM_REQ_CMP; xpt_done(pccb); } break; /* * Get/Set transfer rate/width/disconnection/tag queueing * settings * (Set) transfer rate/width negotiation settings */ case XPT_SET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &pccb->cts; u_int update_type; int intflag; PDCB pDCB; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_spi *spi = &cts->xport_specific.spi; TRM_DPRINTF(" XPT_SET_TRAN_SETTINGS \n"); update_type = 0; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) update_type |= TRM_TRANS_GOAL; if (cts->type == CTS_TYPE_USER_SETTINGS) update_type |= TRM_TRANS_USER; intflag = splcam(); pDCB = &pACB->DCBarray[target_id][target_lun]; if ((spi->valid & CTS_SPI_VALID_DISC) != 0) { /*ccb disc enables */ if (update_type & TRM_TRANS_GOAL) { if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0) pDCB->tinfo.disc_tag |= TRM_CUR_DISCENB; else pDCB->tinfo.disc_tag &= ~TRM_CUR_DISCENB; } if (update_type & TRM_TRANS_USER) { if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0) pDCB->tinfo.disc_tag |= TRM_USR_DISCENB; else pDCB->tinfo.disc_tag &= ~TRM_USR_DISCENB; } } if ((scsi->valid & CTS_SCSI_VALID_TQ) != 0) { /* if ccb tag q active */ if (update_type & TRM_TRANS_GOAL) { if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0) pDCB->tinfo.disc_tag |= TRM_CUR_TAGENB; else pDCB->tinfo.disc_tag &= ~TRM_CUR_TAGENB; } if (update_type & TRM_TRANS_USER) { if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0) pDCB->tinfo.disc_tag |= TRM_USR_TAGENB; else pDCB->tinfo.disc_tag &= ~TRM_USR_TAGENB; } } /* Minimum sync period factor */ if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) { /* if ccb sync active */ /* TRM-S1040 MinSyncPeriod = 4 clocks/byte */ if ((spi->sync_period != 0) && (spi->sync_period < 125)) spi->sync_period = 125; /* 1/(125*4) minsync 2 MByte/sec */ if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) { if (spi->sync_offset == 0) spi->sync_period = 0; /* TRM-S1040 MaxSyncOffset = 15 bytes*/ if (spi->sync_offset > 15) spi->sync_offset = 15; } } if ((update_type & TRM_TRANS_USER) != 0) { pDCB->tinfo.user.period = spi->sync_period; pDCB->tinfo.user.offset = spi->sync_offset; pDCB->tinfo.user.width = spi->bus_width; } if ((update_type & TRM_TRANS_GOAL) != 0) { pDCB->tinfo.goal.period = spi->sync_period; pDCB->tinfo.goal.offset = spi->sync_offset; pDCB->tinfo.goal.width = spi->bus_width; } splx(intflag); pccb->ccb_h.status = CAM_REQ_CMP; xpt_done(pccb); break; } /* * Calculate the geometry parameters for a device give * the sector size and volume size. */ case XPT_CALC_GEOMETRY: TRM_DPRINTF(" XPT_CALC_GEOMETRY \n"); cam_calc_geometry(&pccb->ccg, /*extended*/1); xpt_done(pccb); break; case XPT_ENG_INQ: TRM_DPRINTF(" XPT_ENG_INQ \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * HBA execute engine request * This structure must match SCSIIO size */ case XPT_ENG_EXEC: TRM_DPRINTF(" XPT_ENG_EXEC \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * XPT_EN_LUN = 0x30, Enable LUN as a target * Target mode structures. */ case XPT_EN_LUN: /* * Don't (yet?) support vendor * specific commands. */ TRM_DPRINTF(" XPT_EN_LUN \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Execute target I/O request */ case XPT_TARGET_IO: /* * Don't (yet?) support vendor * specific commands. */ TRM_DPRINTF(" XPT_TARGET_IO \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Accept Host Target Mode CDB */ case XPT_ACCEPT_TARGET_IO: /* * Don't (yet?) support vendor * specific commands. */ TRM_DPRINTF(" XPT_ACCEPT_TARGET_IO \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Continue Host Target I/O Connection */ case XPT_CONT_TARGET_IO: /* * Don't (yet?) support vendor * specific commands. */ TRM_DPRINTF(" XPT_CONT_TARGET_IO \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Notify Host Target driver of event */ case XPT_IMMED_NOTIFY: TRM_DPRINTF(" XPT_IMMED_NOTIFY \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * Acknowledgement of event */ case XPT_NOTIFY_ACK: TRM_DPRINTF(" XPT_NOTIFY_ACK \n"); pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; /* * XPT_VUNIQUE = 0x80 */ case XPT_VUNIQUE: pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; default: pccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(pccb); break; } splx(actionflags); } static void trm_poll(struct cam_sim *psim) { trm_Interrupt(cam_sim_softc(psim)); } static void trm_ResetDevParam(PACB pACB) { PDCB pDCB, pdcb; PNVRAMTYPE pEEpromBuf; u_int8_t PeriodIndex; pDCB = pACB->pLinkDCB; if (pDCB == NULL) return; pdcb = pDCB; do { pDCB->SyncMode &= ~(SYNC_NEGO_DONE+ WIDE_NEGO_DONE); pDCB->SyncPeriod = 0; pDCB->SyncOffset = 0; pEEpromBuf = &trm_eepromBuf[pACB->AdapterUnit]; pDCB->DevMode = pEEpromBuf->NvramTarget[pDCB->TargetID].NvmTarCfg0; pDCB->AdpMode = pEEpromBuf->NvramChannelCfg; PeriodIndex = pEEpromBuf->NvramTarget[pDCB->TargetID].NvmTarPeriod & 0x07; if (pACB->AdaptType == 1) /* is U2? */ pDCB->MaxNegoPeriod = dc395u2x_clock_period[PeriodIndex]; else pDCB->MaxNegoPeriod = dc395x_clock_period[PeriodIndex]; if ((pDCB->DevMode & NTC_DO_WIDE_NEGO) && (pACB->Config & HCC_WIDE_CARD)) pDCB->SyncMode |= WIDE_NEGO_ENABLE; pDCB = pDCB->pNextDCB; } while (pdcb != pDCB); } static void trm_RecoverSRB(PACB pACB) { PDCB pDCB, pdcb; PSRB psrb, psrb2; u_int16_t cnt, i; pDCB = pACB->pLinkDCB; if (pDCB == NULL) return; pdcb = pDCB; do { cnt = pdcb->GoingSRBCnt; psrb = pdcb->pGoingSRB; for (i = 0; i < cnt; i++) { psrb2 = psrb; psrb = psrb->pNextSRB; if (pdcb->pWaitingSRB) { psrb2->pNextSRB = pdcb->pWaitingSRB; pdcb->pWaitingSRB = psrb2; } else { pdcb->pWaitingSRB = psrb2; pdcb->pWaitingLastSRB = psrb2; psrb2->pNextSRB = NULL; } } pdcb->GoingSRBCnt = 0; pdcb->pGoingSRB = NULL; pdcb = pdcb->pNextDCB; } while (pdcb != pDCB); } static void trm_reset(PACB pACB) { int intflag; u_int16_t i; TRM_DPRINTF("trm: RESET"); intflag = splcam(); trm_reg_write8(0x00, TRMREG_DMA_INTEN); trm_reg_write8(0x00, TRMREG_SCSI_INTEN); trm_ResetSCSIBus(pACB); for (i = 0; i < 500; i++) DELAY(1000); trm_reg_write8(0x7F, TRMREG_SCSI_INTEN); /* Enable DMA interrupt */ trm_reg_write8(EN_SCSIINTR, TRMREG_DMA_INTEN); /* Clear DMA FIFO */ trm_reg_write8(CLRXFIFO, TRMREG_DMA_CONTROL); /* Clear SCSI FIFO */ trm_reg_write16(DO_CLRFIFO,TRMREG_SCSI_CONTROL); trm_ResetDevParam(pACB); trm_DoingSRB_Done(pACB); pACB->pActiveDCB = NULL; pACB->ACBFlag = 0;/* RESET_DETECT, RESET_DONE ,RESET_DEV */ trm_DoWaitingSRB(pACB); /* Tell the XPT layer that a bus reset occured */ if (pACB->ppath != NULL) xpt_async(AC_BUS_RESET, pACB->ppath, NULL); splx(intflag); return; } static u_int16_t trm_StartSCSI(PACB pACB, PDCB pDCB, PSRB pSRB) { u_int16_t return_code; u_int8_t scsicommand, i,command,identify_message; u_int8_t * ptr; union ccb *pccb; struct ccb_scsiio *pcsio; pccb = pSRB->pccb; pcsio = &pccb->csio; trm_reg_write8(pACB->AdaptSCSIID, TRMREG_SCSI_HOSTID); trm_reg_write8(pDCB->TargetID, TRMREG_SCSI_TARGETID); trm_reg_write8(pDCB->SyncPeriod, TRMREG_SCSI_SYNC); trm_reg_write8(pDCB->SyncOffset, TRMREG_SCSI_OFFSET); pSRB->ScsiPhase = PH_BUS_FREE;/* initial phase */ /* Flush FIFO */ trm_reg_write16(DO_CLRFIFO, TRMREG_SCSI_CONTROL); identify_message = pDCB->IdentifyMsg; if ((pSRB->CmdBlock[0] == INQUIRY) || (pSRB->CmdBlock[0] == REQUEST_SENSE) || (pSRB->SRBFlag & AUTO_REQSENSE)) { if (((pDCB->SyncMode & WIDE_NEGO_ENABLE) && !(pDCB->SyncMode & WIDE_NEGO_DONE)) || ((pDCB->SyncMode & SYNC_NEGO_ENABLE) && !(pDCB->SyncMode & SYNC_NEGO_DONE))) { if (!(pDCB->IdentifyMsg & 7) || (pSRB->CmdBlock[0] != INQUIRY)) { scsicommand = SCMD_SEL_ATNSTOP; pSRB->SRBState = SRB_MSGOUT; goto polling; } } /* * Send identify message */ trm_reg_write8((identify_message & 0xBF) ,TRMREG_SCSI_FIFO); scsicommand = SCMD_SEL_ATN; pSRB->SRBState = SRB_START_; } else { /* not inquiry,request sense,auto request sense */ /* * Send identify message */ trm_reg_write8(identify_message,TRMREG_SCSI_FIFO); scsicommand = SCMD_SEL_ATN; pSRB->SRBState = SRB_START_; if (pDCB->SyncMode & EN_TAG_QUEUING) { /* Send Tag message */ trm_reg_write8(MSG_SIMPLE_QTAG, TRMREG_SCSI_FIFO); trm_reg_write8(pSRB->TagNumber, TRMREG_SCSI_FIFO); scsicommand = SCMD_SEL_ATN3; } } polling: /* * Send CDB ..command block ......... */ if (pSRB->SRBFlag & AUTO_REQSENSE) { trm_reg_write8(REQUEST_SENSE, TRMREG_SCSI_FIFO); trm_reg_write8((pDCB->IdentifyMsg << 5), TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); trm_reg_write8(pcsio->sense_len, TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); } else { ptr = (u_int8_t *) pSRB->CmdBlock; for (i = 0; i < pSRB->ScsiCmdLen ; i++) { command = *ptr++; trm_reg_write8(command,TRMREG_SCSI_FIFO); } } if (trm_reg_read16(TRMREG_SCSI_STATUS) & SCSIINTERRUPT) { /* * If trm_StartSCSI return 1 : * current interrupt status is interrupt disreenable * It's said that SCSI processor has more one SRB need to do, * SCSI processor has been occupied by one SRB. */ pSRB->SRBState = SRB_READY; return_code = 1; } else { /* * If trm_StartSCSI return 0 : * current interrupt status is interrupt enable * It's said that SCSI processor is unoccupied */ pSRB->ScsiPhase = SCSI_NOP1; /* SCSI bus free Phase */ pACB->pActiveDCB = pDCB; pDCB->pActiveSRB = pSRB; return_code = 0; trm_reg_write16(DO_DATALATCH | DO_HWRESELECT, TRMREG_SCSI_CONTROL);/* it's important for atn stop*/ /* * SCSI cammand */ trm_reg_write8(scsicommand,TRMREG_SCSI_COMMAND); } return (return_code); } static void trm_Interrupt(vpACB) void *vpACB; { PACB pACB; PDCB pDCB; PSRB pSRB; u_int16_t phase; void (*stateV)(PACB, PSRB, u_int16_t *); u_int16_t scsi_status=0; u_int8_t scsi_intstatus; pACB = vpACB; scsi_status = trm_reg_read16(TRMREG_SCSI_STATUS); if (!(scsi_status & SCSIINTERRUPT)) { TRM_DPRINTF("trm_Interrupt: TRMREG_SCSI_STATUS scsi_status = NULL ,return......"); return; } TRM_DPRINTF("scsi_status=%2x,",scsi_status); scsi_intstatus = trm_reg_read8(TRMREG_SCSI_INTSTATUS); TRM_DPRINTF("scsi_intstatus=%2x,",scsi_intstatus); if (scsi_intstatus & (INT_SELTIMEOUT | INT_DISCONNECT)) { trm_Disconnect(pACB); return; } if (scsi_intstatus & INT_RESELECTED) { trm_Reselect(pACB); return; } if (scsi_intstatus & INT_SCSIRESET) { trm_ScsiRstDetect(pACB); return; } if (scsi_intstatus & (INT_BUSSERVICE | INT_CMDDONE)) { pDCB = pACB->pActiveDCB; KASSERT(pDCB != NULL, ("no active DCB")); pSRB = pDCB->pActiveSRB; if (pDCB->DCBFlag & ABORT_DEV_) trm_EnableMsgOutAbort1(pACB, pSRB); phase = (u_int16_t) pSRB->ScsiPhase; /* phase: */ stateV = (void *) trm_SCSI_phase0[phase]; stateV(pACB, pSRB, &scsi_status); pSRB->ScsiPhase = scsi_status & PHASEMASK; /* phase:0,1,2,3,4,5,6,7 */ phase = (u_int16_t) scsi_status & PHASEMASK; stateV = (void *) trm_SCSI_phase1[phase]; stateV(pACB, pSRB, &scsi_status); } } static void trm_MsgOutPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { if (pSRB->SRBState & (SRB_UNEXPECT_RESEL+SRB_ABORT_SENT)) *pscsi_status = PH_BUS_FREE; /*.. initial phase*/ } static void trm_MsgOutPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { u_int8_t bval; u_int16_t i, cnt; u_int8_t * ptr; PDCB pDCB; trm_reg_write16(DO_CLRFIFO, TRMREG_SCSI_CONTROL); pDCB = pACB->pActiveDCB; if (!(pSRB->SRBState & SRB_MSGOUT)) { cnt = pSRB->MsgCnt; if (cnt) { ptr = (u_int8_t *) pSRB->MsgOutBuf; for (i = 0; i < cnt; i++) { trm_reg_write8(*ptr, TRMREG_SCSI_FIFO); ptr++; } pSRB->MsgCnt = 0; if ((pDCB->DCBFlag & ABORT_DEV_) && (pSRB->MsgOutBuf[0] == MSG_ABORT)) { pSRB->SRBState = SRB_ABORT_SENT; } } else { bval = MSG_ABORT; if ((pSRB->CmdBlock[0] == INQUIRY) || (pSRB->CmdBlock[0] == REQUEST_SENSE) || (pSRB->SRBFlag & AUTO_REQSENSE)) { if (pDCB->SyncMode & SYNC_NEGO_ENABLE) { goto mop1; } } trm_reg_write8(bval, TRMREG_SCSI_FIFO); } } else { mop1: /* message out phase */ if (!(pSRB->SRBState & SRB_DO_WIDE_NEGO) && (pDCB->SyncMode & WIDE_NEGO_ENABLE)) { /* * WIDE DATA TRANSFER REQUEST code (03h) */ pDCB->SyncMode &= ~(SYNC_NEGO_DONE | EN_ATN_STOP); trm_reg_write8((pDCB->IdentifyMsg & 0xBF), TRMREG_SCSI_FIFO); trm_reg_write8(MSG_EXTENDED,TRMREG_SCSI_FIFO); /* (01h) */ trm_reg_write8(2,TRMREG_SCSI_FIFO); /* Message length (02h) */ trm_reg_write8(3,TRMREG_SCSI_FIFO); /* wide data xfer (03h) */ trm_reg_write8(1,TRMREG_SCSI_FIFO); /* width:0(8bit),1(16bit),2(32bit) */ pSRB->SRBState |= SRB_DO_WIDE_NEGO; } else if (!(pSRB->SRBState & SRB_DO_SYNC_NEGO) && (pDCB->SyncMode & SYNC_NEGO_ENABLE)) { /* * SYNCHRONOUS DATA TRANSFER REQUEST code (01h) */ if (!(pDCB->SyncMode & WIDE_NEGO_DONE)) trm_reg_write8((pDCB->IdentifyMsg & 0xBF), TRMREG_SCSI_FIFO); trm_reg_write8(MSG_EXTENDED,TRMREG_SCSI_FIFO); /* (01h) */ trm_reg_write8(3,TRMREG_SCSI_FIFO); /* Message length (03h) */ trm_reg_write8(1,TRMREG_SCSI_FIFO); /* SYNCHRONOUS DATA TRANSFER REQUEST code (01h) */ trm_reg_write8(pDCB->MaxNegoPeriod,TRMREG_SCSI_FIFO); /* Transfer peeriod factor */ trm_reg_write8((pACB->AdaptType == 1) ? 31 : 15, TRMREG_SCSI_FIFO); /* REQ/ACK offset */ pSRB->SRBState |= SRB_DO_SYNC_NEGO; } } trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_FIFO_OUT, TRMREG_SCSI_COMMAND); } static void trm_CommandPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { } static void trm_CommandPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { PDCB pDCB; u_int8_t * ptr; u_int16_t i, cnt; union ccb *pccb; struct ccb_scsiio *pcsio; pccb = pSRB->pccb; pcsio = &pccb->csio; trm_reg_write16(DO_CLRATN | DO_CLRFIFO , TRMREG_SCSI_CONTROL); if (!(pSRB->SRBFlag & AUTO_REQSENSE)) { cnt = (u_int16_t) pSRB->ScsiCmdLen; ptr = (u_int8_t *) pSRB->CmdBlock; for (i = 0; i < cnt; i++) { trm_reg_write8(*ptr, TRMREG_SCSI_FIFO); ptr++; } } else { trm_reg_write8(REQUEST_SENSE, TRMREG_SCSI_FIFO); pDCB = pACB->pActiveDCB; /* target id */ trm_reg_write8((pDCB->IdentifyMsg << 5), TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); /* sizeof(struct scsi_sense_data) */ trm_reg_write8(pcsio->sense_len, TRMREG_SCSI_FIFO); trm_reg_write8(0, TRMREG_SCSI_FIFO); } pSRB->SRBState = SRB_COMMAND; trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop*/ /* * SCSI cammand */ trm_reg_write8(SCMD_FIFO_OUT, TRMREG_SCSI_COMMAND); } static void trm_DataOutPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { PDCB pDCB; u_int8_t TempDMAstatus,SGIndexTemp; u_int16_t scsi_status; PSEG pseg; u_long TempSRBXferredLength,dLeftCounter=0; pDCB = pSRB->pSRBDCB; scsi_status = *pscsi_status; if (!(pSRB->SRBState & SRB_XFERPAD)) { if (scsi_status & PARITYERROR) pSRB->SRBStatus |= PARITY_ERROR; if (!(scsi_status & SCSIXFERDONE)) { /* * when data transfer from DMA FIFO to SCSI FIFO * if there was some data left in SCSI FIFO */ dLeftCounter = (u_long) (trm_reg_read8(TRMREG_SCSI_FIFOCNT) & 0x3F); if (pDCB->SyncPeriod & WIDE_SYNC) { /* * if WIDE scsi SCSI FIFOCNT unit is word * so need to * 2 */ dLeftCounter <<= 1; } } /* * caculate all the residue data that not yet tranfered * SCSI transfer counter + left in SCSI FIFO data * * .....TRM_SCSI_COUNTER (24bits) * The counter always decrement by one for every SCSI byte *transfer. * .....TRM_SCSI_FIFOCNT (5bits) * The counter is SCSI FIFO offset counter */ dLeftCounter += trm_reg_read32(TRMREG_SCSI_COUNTER); if (dLeftCounter == 1) { dLeftCounter = 0; trm_reg_write16(DO_CLRFIFO,TRMREG_SCSI_CONTROL); } if ((dLeftCounter == 0) || (scsi_status & SCSIXFERCNT_2_ZERO)) { TempDMAstatus = trm_reg_read8(TRMREG_DMA_STATUS); while (!(TempDMAstatus & DMAXFERCOMP)) { TempDMAstatus = trm_reg_read8(TRMREG_DMA_STATUS); } pSRB->SRBTotalXferLength = 0; } else { /* Update SG list */ /* * if transfer not yet complete * there were some data residue in SCSI FIFO or * SCSI transfer counter not empty */ if (pSRB->SRBTotalXferLength != dLeftCounter) { /* * data that had transferred length */ TempSRBXferredLength = pSRB->SRBTotalXferLength - dLeftCounter; /* * next time to be transferred length */ pSRB->SRBTotalXferLength = dLeftCounter; /* * parsing from last time disconnect SRBSGIndex */ pseg = pSRB->pSRBSGL + pSRB->SRBSGIndex; for (SGIndexTemp = pSRB->SRBSGIndex; SGIndexTemp < pSRB->SRBSGCount; SGIndexTemp++) { /* * find last time which SG transfer be * disconnect */ if (TempSRBXferredLength >= pseg->length) TempSRBXferredLength -= pseg->length; else { /* * update last time disconnected SG * list */ pseg->length -= TempSRBXferredLength; /* residue data length */ pseg->address += TempSRBXferredLength; /* residue data pointer */ pSRB->SRBSGIndex = SGIndexTemp; break; } pseg++; } } } } trm_reg_write8(STOPDMAXFER ,TRMREG_DMA_CONTROL); } static void trm_DataOutPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { u_int16_t ioDir; /* * do prepare befor transfer when data out phase */ ioDir = XFERDATAOUT; trm_DataIO_transfer(pACB, pSRB, ioDir); } static void trm_DataInPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { u_int8_t TempDMAstatus, SGIndexTemp; u_int16_t scsi_status; PSEG pseg; u_long TempSRBXferredLength,dLeftCounter = 0; scsi_status = *pscsi_status; if (!(pSRB->SRBState & SRB_XFERPAD)) { if (scsi_status & PARITYERROR) pSRB->SRBStatus |= PARITY_ERROR; dLeftCounter += trm_reg_read32(TRMREG_SCSI_COUNTER); if ((dLeftCounter == 0) || (scsi_status & SCSIXFERCNT_2_ZERO)) { TempDMAstatus = trm_reg_read8(TRMREG_DMA_STATUS); while (!(TempDMAstatus & DMAXFERCOMP)) TempDMAstatus = trm_reg_read8(TRMREG_DMA_STATUS); pSRB->SRBTotalXferLength = 0; } else { /* * parsing the case: * when a transfer not yet complete * but be disconnected by uper layer * if transfer not yet complete * there were some data residue in SCSI FIFO or * SCSI transfer counter not empty */ if (pSRB->SRBTotalXferLength != dLeftCounter) { /* * data that had transferred length */ TempSRBXferredLength = pSRB->SRBTotalXferLength - dLeftCounter; /* * next time to be transferred length */ pSRB->SRBTotalXferLength = dLeftCounter; /* * parsing from last time disconnect SRBSGIndex */ pseg = pSRB->pSRBSGL + pSRB->SRBSGIndex; for (SGIndexTemp = pSRB->SRBSGIndex; SGIndexTemp < pSRB->SRBSGCount; SGIndexTemp++) { /* * find last time which SG transfer be disconnect */ if (TempSRBXferredLength >= pseg->length) TempSRBXferredLength -= pseg->length; else { /* * update last time disconnected SG list */ pseg->length -= TempSRBXferredLength; /* residue data length */ pseg->address += TempSRBXferredLength; /* residue data pointer */ pSRB->SRBSGIndex = SGIndexTemp; break; } pseg++; } } } } } static void trm_DataInPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { u_int16_t ioDir; /* * do prepare befor transfer when data in phase */ ioDir = XFERDATAIN; trm_DataIO_transfer(pACB, pSRB, ioDir); } static void trm_DataIO_transfer(PACB pACB, PSRB pSRB, u_int16_t ioDir) { u_int8_t bval; PDCB pDCB; pDCB = pSRB->pSRBDCB; if (pSRB->SRBSGIndex < pSRB->SRBSGCount) { if (pSRB->SRBTotalXferLength != 0) { /* * load what physical address of Scatter/Gather list table want to be transfer */ TRM_DPRINTF(" SG->address=%8x \n",pSRB->pSRBSGL->address); TRM_DPRINTF(" SG->length=%8x \n",pSRB->pSRBSGL->length); TRM_DPRINTF(" pDCB->SyncPeriod=%x \n",pDCB->SyncPeriod); TRM_DPRINTF(" pSRB->pSRBSGL=%8x \n",(unsigned int)pSRB->pSRBSGL); TRM_DPRINTF(" pSRB->SRBSGPhyAddr=%8x \n",pSRB->SRBSGPhyAddr); TRM_DPRINTF(" pSRB->SRBSGIndex=%d \n",pSRB->SRBSGIndex); TRM_DPRINTF(" pSRB->SRBSGCount=%d \n",pSRB->SRBSGCount); TRM_DPRINTF(" pSRB->SRBTotalXferLength=%d \n",pSRB->SRBTotalXferLength); pSRB->SRBState = SRB_DATA_XFER; trm_reg_write32(0, TRMREG_DMA_XHIGHADDR); trm_reg_write32( (pSRB->SRBSGPhyAddr + ((u_long)pSRB->SRBSGIndex << 3)), TRMREG_DMA_XLOWADDR); /* * load how many bytes in the Scatter/Gather * list table */ trm_reg_write32( ((u_long)(pSRB->SRBSGCount - pSRB->SRBSGIndex) << 3), TRMREG_DMA_XCNT); /* * load total transfer length (24bits) max value * 16Mbyte */ trm_reg_write32(pSRB->SRBTotalXferLength, TRMREG_SCSI_COUNTER); /* Start DMA transfer */ trm_reg_write16(ioDir, TRMREG_DMA_COMMAND); /* Start SCSI transfer */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ bval = (ioDir == XFERDATAOUT) ? SCMD_DMA_OUT : SCMD_DMA_IN; trm_reg_write8(bval, TRMREG_SCSI_COMMAND); } else { /* xfer pad */ if (pSRB->SRBSGCount) { pSRB->AdaptStatus = H_OVER_UNDER_RUN; pSRB->SRBStatus |= OVER_RUN; } if (pDCB->SyncPeriod & WIDE_SYNC) trm_reg_write32(2,TRMREG_SCSI_COUNTER); else trm_reg_write32(1,TRMREG_SCSI_COUNTER); if (ioDir == XFERDATAOUT) trm_reg_write16(0, TRMREG_SCSI_FIFO); else trm_reg_read16(TRMREG_SCSI_FIFO); pSRB->SRBState |= SRB_XFERPAD; trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ bval = (ioDir == XFERDATAOUT) ? SCMD_FIFO_OUT : SCMD_FIFO_IN; trm_reg_write8(bval, TRMREG_SCSI_COMMAND); } } } static void trm_StatusPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { pSRB->TargetStatus = trm_reg_read8(TRMREG_SCSI_FIFO); pSRB->SRBState = SRB_COMPLETED; *pscsi_status = PH_BUS_FREE; /*.. initial phase*/ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); } static void trm_StatusPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { if (trm_reg_read16(TRMREG_DMA_COMMAND) & 0x0001) { if (!(trm_reg_read8(TRMREG_SCSI_FIFOCNT) & 0x40)) trm_reg_write16(DO_CLRFIFO, TRMREG_SCSI_CONTROL); if (!(trm_reg_read16(TRMREG_DMA_FIFOCNT) & 0x8000)) trm_reg_write8(CLRXFIFO, TRMREG_DMA_CONTROL); } else { if (!(trm_reg_read16(TRMREG_DMA_FIFOCNT) & 0x8000)) trm_reg_write8(CLRXFIFO, TRMREG_DMA_CONTROL); if (!(trm_reg_read8(TRMREG_SCSI_FIFOCNT) & 0x40)) trm_reg_write16(DO_CLRFIFO, TRMREG_SCSI_CONTROL); } pSRB->SRBState = SRB_STATUS; trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_COMP, TRMREG_SCSI_COMMAND); } /* *scsiiom * trm_MsgInPhase0: one of trm_SCSI_phase0[] vectors * stateV = (void *) trm_SCSI_phase0[phase] * if phase =7 * extended message codes: * * code description * * 02h Reserved * 00h MODIFY DATA POINTER * 01h SYNCHRONOUS DATA TRANSFER REQUEST * 03h WIDE DATA TRANSFER REQUEST * 04h - 7Fh Reserved * 80h - FFh Vendor specific * */ static void trm_MsgInPhase0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { u_int8_t message_in_code,bIndex,message_in_tag_id; PDCB pDCB; PSRB pSRBTemp; pDCB = pACB->pActiveDCB; message_in_code = trm_reg_read8(TRMREG_SCSI_FIFO); if (!(pSRB->SRBState & SRB_EXTEND_MSGIN)) { if (message_in_code == MSG_DISCONNECT) { pSRB->SRBState = SRB_DISCONNECT; *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if (message_in_code == MSG_SAVE_PTR) { *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if ((message_in_code == MSG_EXTENDED) || ((message_in_code >= MSG_SIMPLE_QTAG) && (message_in_code <= MSG_ORDER_QTAG))) { pSRB->SRBState |= SRB_EXTEND_MSGIN; pSRB->MsgInBuf[0] = message_in_code; /* extended message (01h) */ pSRB->MsgCnt = 1; pSRB->pMsgPtr = &pSRB->MsgInBuf[1]; /* extended message length (n) */ *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if (message_in_code == MSG_REJECT_) { /* Reject message */ if (pDCB->SyncMode & WIDE_NEGO_ENABLE) { /* do wide nego reject */ pDCB = pSRB->pSRBDCB; pDCB->SyncMode |= WIDE_NEGO_DONE; pDCB->SyncMode &= ~(SYNC_NEGO_DONE | EN_ATN_STOP | WIDE_NEGO_ENABLE); pSRB->SRBState &= ~(SRB_DO_WIDE_NEGO+SRB_MSGIN); if ((pDCB->SyncMode & SYNC_NEGO_ENABLE) && !(pDCB->SyncMode & SYNC_NEGO_DONE)) { /* Set ATN, in case ATN was clear */ pSRB->SRBState |= SRB_MSGOUT; trm_reg_write16( DO_SETATN, TRMREG_SCSI_CONTROL); } else { /* Clear ATN */ trm_reg_write16( DO_CLRATN, TRMREG_SCSI_CONTROL); } } else if (pDCB->SyncMode & SYNC_NEGO_ENABLE) { /* do sync nego reject */ trm_reg_write16(DO_CLRATN,TRMREG_SCSI_CONTROL); if (pSRB->SRBState & SRB_DO_SYNC_NEGO) { pDCB = pSRB->pSRBDCB; pDCB->SyncMode &= ~(SYNC_NEGO_ENABLE+SYNC_NEGO_DONE); pDCB->SyncPeriod = 0; pDCB->SyncOffset = 0; /* * * program SCSI control register * */ trm_reg_write8(pDCB->SyncPeriod, TRMREG_SCSI_SYNC); trm_reg_write8(pDCB->SyncOffset, TRMREG_SCSI_OFFSET); trm_SetXferRate(pACB,pSRB,pDCB); } } *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if (message_in_code == MSG_IGNOREWIDE) { trm_reg_write32(1, TRMREG_SCSI_COUNTER); trm_reg_read8(TRMREG_SCSI_FIFO); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else { /* Restore data pointer message */ /* Save data pointer message */ /* Completion message */ /* NOP message */ *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } } else { /* * Parsing incomming extented messages */ *pSRB->pMsgPtr = message_in_code; pSRB->MsgCnt++; pSRB->pMsgPtr++; TRM_DPRINTF("pSRB->MsgInBuf[0]=%2x \n ",pSRB->MsgInBuf[0]); TRM_DPRINTF("pSRB->MsgInBuf[1]=%2x \n ",pSRB->MsgInBuf[1]); TRM_DPRINTF("pSRB->MsgInBuf[2]=%2x \n ",pSRB->MsgInBuf[2]); TRM_DPRINTF("pSRB->MsgInBuf[3]=%2x \n ",pSRB->MsgInBuf[3]); TRM_DPRINTF("pSRB->MsgInBuf[4]=%2x \n ",pSRB->MsgInBuf[4]); if ((pSRB->MsgInBuf[0] >= MSG_SIMPLE_QTAG) && (pSRB->MsgInBuf[0] <= MSG_ORDER_QTAG)) { /* * is QUEUE tag message : * * byte 0: * HEAD QUEUE TAG (20h) * ORDERED QUEUE TAG (21h) * SIMPLE QUEUE TAG (22h) * byte 1: * Queue tag (00h - FFh) */ if (pSRB->MsgCnt == 2) { pSRB->SRBState = 0; message_in_tag_id = pSRB->MsgInBuf[1]; pSRB = pDCB->pGoingSRB; pSRBTemp = pDCB->pGoingLastSRB; if (pSRB) { for (;;) { if (pSRB->TagNumber != message_in_tag_id) { if (pSRB == pSRBTemp) { goto mingx0; } pSRB = pSRB->pNextSRB; } else break; } if (pDCB->DCBFlag & ABORT_DEV_) { pSRB->SRBState = SRB_ABORT_SENT; trm_EnableMsgOutAbort1( pACB, pSRB); } if (!(pSRB->SRBState & SRB_DISCONNECT)) { TRM_DPRINTF("SRB not yet disconnect........ \n "); goto mingx0; } pDCB->pActiveSRB = pSRB; pSRB->SRBState = SRB_DATA_XFER; } else { mingx0: pSRB = &pACB->TmpSRB; pSRB->SRBState = SRB_UNEXPECT_RESEL; pDCB->pActiveSRB = pSRB; pSRB->MsgOutBuf[0] = MSG_ABORT_TAG; trm_EnableMsgOutAbort2( pACB, pSRB); } } *pscsi_status = PH_BUS_FREE; /* .. initial phase */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if ((pSRB->MsgInBuf[0] == MSG_EXTENDED) && (pSRB->MsgInBuf[2] == 3) && (pSRB->MsgCnt == 4)) { /* * is Wide data xfer Extended message : * ====================================== * WIDE DATA TRANSFER REQUEST * ====================================== * byte 0 : Extended message (01h) * byte 1 : Extended message length (02h) * byte 2 : WIDE DATA TRANSFER code (03h) * byte 3 : Transfer width exponent */ pDCB = pSRB->pSRBDCB; pSRB->SRBState &= ~(SRB_EXTEND_MSGIN+SRB_DO_WIDE_NEGO); if ((pSRB->MsgInBuf[1] != 2)) { /* Length is wrong, reject it */ pDCB->SyncMode &= ~(WIDE_NEGO_ENABLE+WIDE_NEGO_DONE); pSRB->MsgCnt = 1; pSRB->MsgInBuf[0] = MSG_REJECT_; trm_reg_write16(DO_SETATN, TRMREG_SCSI_CONTROL); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } if (pDCB->SyncMode & WIDE_NEGO_ENABLE) { /* Do wide negoniation */ if (pSRB->MsgInBuf[3] > 2) { /* > 32 bit */ /* reject_msg: */ pDCB->SyncMode &= ~(WIDE_NEGO_ENABLE+WIDE_NEGO_DONE); pSRB->MsgCnt = 1; pSRB->MsgInBuf[0] = MSG_REJECT_; trm_reg_write16(DO_SETATN, TRMREG_SCSI_CONTROL); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } if (pSRB->MsgInBuf[3] == 2) { pSRB->MsgInBuf[3] = 1; /* do 16 bits */ } else { if (!(pDCB->SyncMode & WIDE_NEGO_DONE)) { pSRB->SRBState &= ~(SRB_DO_WIDE_NEGO+SRB_MSGIN); pDCB->SyncMode |= WIDE_NEGO_DONE; pDCB->SyncMode &= ~(SYNC_NEGO_DONE | EN_ATN_STOP | WIDE_NEGO_ENABLE); if (pSRB->MsgInBuf[3] != 0) { /* is Wide data xfer */ pDCB->SyncPeriod |= WIDE_SYNC; pDCB->tinfo.current.width = MSG_EXT_WDTR_BUS_16_BIT; pDCB->tinfo.goal.width = MSG_EXT_WDTR_BUS_16_BIT; } } } } else pSRB->MsgInBuf[3] = 0; pSRB->SRBState |= SRB_MSGOUT; trm_reg_write16(DO_SETATN,TRMREG_SCSI_CONTROL); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ /* it's important for atn stop */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* * SCSI command */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if ((pSRB->MsgInBuf[0] == MSG_EXTENDED) && (pSRB->MsgInBuf[2] == 1) && (pSRB->MsgCnt == 5)) { /* * is 8bit transfer Extended message : * ================================= * SYNCHRONOUS DATA TRANSFER REQUEST * ================================= * byte 0 : Extended message (01h) * byte 1 : Extended message length (03) * byte 2 : SYNCHRONOUS DATA TRANSFER code (01h) * byte 3 : Transfer period factor * byte 4 : REQ/ACK offset */ pSRB->SRBState &= ~(SRB_EXTEND_MSGIN+SRB_DO_SYNC_NEGO); if ((pSRB->MsgInBuf[1] != 3) || (pSRB->MsgInBuf[2] != 1)) { /* reject_msg: */ pSRB->MsgCnt = 1; pSRB->MsgInBuf[0] = MSG_REJECT_; trm_reg_write16(DO_SETATN, TRMREG_SCSI_CONTROL); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else if (!(pSRB->MsgInBuf[3]) || !(pSRB->MsgInBuf[4])) { /* set async */ pDCB = pSRB->pSRBDCB; /* disable sync & sync nego */ pDCB->SyncMode &= ~(SYNC_NEGO_ENABLE+SYNC_NEGO_DONE); pDCB->SyncPeriod = 0; pDCB->SyncOffset = 0; pDCB->tinfo.goal.period = 0; pDCB->tinfo.goal.offset = 0; pDCB->tinfo.current.period = 0; pDCB->tinfo.current.offset = 0; pDCB->tinfo.current.width = MSG_EXT_WDTR_BUS_8_BIT; /* * * program SCSI control register * */ trm_reg_write8(pDCB->SyncPeriod,TRMREG_SCSI_SYNC); trm_reg_write8(pDCB->SyncOffset,TRMREG_SCSI_OFFSET); trm_SetXferRate(pACB,pSRB,pDCB); *pscsi_status = PH_BUS_FREE; /* .. initial phase */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); return; } else { /* set sync */ pDCB = pSRB->pSRBDCB; pDCB->SyncMode |= SYNC_NEGO_ENABLE+SYNC_NEGO_DONE; pDCB->MaxNegoPeriod = pSRB->MsgInBuf[3]; /* Transfer period factor */ pDCB->SyncOffset = pSRB->MsgInBuf[4]; /* REQ/ACK offset */ if (pACB->AdaptType == 1) { for(bIndex = 0; bIndex < 7; bIndex++) { if (pSRB->MsgInBuf[3] <= dc395u2x_clock_period[bIndex]) { pDCB->tinfo.goal.period = dc395u2x_tinfo_period[bIndex]; pDCB->tinfo.current.period = dc395u2x_tinfo_period[bIndex]; pDCB->tinfo.goal.offset = pDCB->SyncOffset; pDCB->tinfo.current.offset = pDCB->SyncOffset; pDCB->SyncPeriod |= (bIndex|LVDS_SYNC); break; } } } else { for(bIndex = 0; bIndex < 7; bIndex++) { if (pSRB->MsgInBuf[3] <= dc395x_clock_period[bIndex]) { pDCB->tinfo.goal.period = dc395x_tinfo_period[bIndex]; pDCB->tinfo.current.period = dc395x_tinfo_period[bIndex]; pDCB->tinfo.goal.offset = pDCB->SyncOffset; pDCB->tinfo.current.offset = pDCB->SyncOffset; pDCB->SyncPeriod |= (bIndex|ALT_SYNC); break; } } } /* * * program SCSI control register * */ trm_reg_write8(pDCB->SyncPeriod, TRMREG_SCSI_SYNC); trm_reg_write8(pDCB->SyncOffset, TRMREG_SCSI_OFFSET); trm_SetXferRate(pACB,pSRB,pDCB); *pscsi_status=PH_BUS_FREE;/*.. initial phase*/ trm_reg_write16(DO_DATALATCH,TRMREG_SCSI_CONTROL);/* it's important for atn stop*/ /* ** SCSI command */ trm_reg_write8(SCMD_MSGACCEPT,TRMREG_SCSI_COMMAND); return; } } *pscsi_status = PH_BUS_FREE; /* .. initial phase */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop */ /* * SCSI cammand */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); } } static void trm_MsgInPhase1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { trm_reg_write16(DO_CLRFIFO, TRMREG_SCSI_CONTROL); trm_reg_write32(1,TRMREG_SCSI_COUNTER); if (!(pSRB->SRBState & SRB_MSGIN)) { pSRB->SRBState &= SRB_DISCONNECT; pSRB->SRBState |= SRB_MSGIN; } trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop*/ /* * SCSI cammand */ trm_reg_write8(SCMD_FIFO_IN, TRMREG_SCSI_COMMAND); } static void trm_Nop0(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { } static void trm_Nop1(PACB pACB, PSRB pSRB, u_int16_t *pscsi_status) { } static void trm_SetXferRate(PACB pACB,PSRB pSRB, PDCB pDCB) { union ccb *pccb; struct ccb_trans_settings neg; u_int16_t cnt, i; u_int8_t bval; PDCB pDCBTemp; /* * set all lun device's period , offset */ TRM_DPRINTF("trm_SetXferRate\n"); pccb = pSRB->pccb; memset(&neg, 0, sizeof (neg)); neg.xport_specific.spi.sync_period = pDCB->tinfo.goal.period; neg.xport_specific.spi.sync_offset = pDCB->tinfo.goal.offset; neg.xport_specific.spi.valid = CTS_SPI_VALID_SYNC_RATE | CTS_SPI_VALID_SYNC_OFFSET; xpt_setup_ccb(&neg.ccb_h, pccb->ccb_h.path, /* priority */1); xpt_async(AC_TRANSFER_NEG, pccb->ccb_h.path, &neg); if (!(pDCB->IdentifyMsg & 0x07)) { pDCBTemp = pACB->pLinkDCB; cnt = pACB->DeviceCnt; bval = pDCB->TargetID; for (i = 0; i < cnt; i++) { if (pDCBTemp->TargetID == bval) { pDCBTemp->SyncPeriod = pDCB->SyncPeriod; pDCBTemp->SyncOffset = pDCB->SyncOffset; pDCBTemp->SyncMode = pDCB->SyncMode; } pDCBTemp = pDCBTemp->pNextDCB; } } return; } /* * scsiiom * trm_Interrupt * * * ---SCSI bus phase * * PH_DATA_OUT 0x00 Data out phase * PH_DATA_IN 0x01 Data in phase * PH_COMMAND 0x02 Command phase * PH_STATUS 0x03 Status phase * PH_BUS_FREE 0x04 Invalid phase used as bus free * PH_BUS_FREE 0x05 Invalid phase used as bus free * PH_MSG_OUT 0x06 Message out phase * PH_MSG_IN 0x07 Message in phase * */ static void trm_Disconnect(PACB pACB) { PDCB pDCB; PSRB pSRB, psrb; u_int16_t i,j, cnt; u_int target_id,target_lun; TRM_DPRINTF("trm_Disconnect...............\n "); pDCB = pACB->pActiveDCB; if (!pDCB) { TRM_DPRINTF(" Exception Disconnect DCB=NULL..............\n "); j = 400; while (--j) DELAY(1); /* 1 msec */ trm_reg_write16((DO_CLRFIFO | DO_HWRESELECT), TRMREG_SCSI_CONTROL); return; } pSRB = pDCB->pActiveSRB; /* bug pSRB=0 */ target_id = pSRB->pccb->ccb_h.target_id; target_lun = pSRB->pccb->ccb_h.target_lun; TRM_DPRINTF(":pDCB->pActiveSRB= %8x \n ",(u_int) pDCB->pActiveSRB); pACB->pActiveDCB = 0; pSRB->ScsiPhase = PH_BUS_FREE; /* SCSI bus free Phase */ trm_reg_write16((DO_CLRFIFO | DO_HWRESELECT), TRMREG_SCSI_CONTROL); if (pSRB->SRBState & SRB_UNEXPECT_RESEL) { pSRB->SRBState = 0; trm_DoWaitingSRB(pACB); } else if (pSRB->SRBState & SRB_ABORT_SENT) { pDCB->DCBFlag = 0; cnt = pDCB->GoingSRBCnt; pDCB->GoingSRBCnt = 0; pSRB = pDCB->pGoingSRB; for (i = 0; i < cnt; i++) { psrb = pSRB->pNextSRB; pSRB->pNextSRB = pACB->pFreeSRB; pACB->pFreeSRB = pSRB; pSRB = psrb; } pDCB->pGoingSRB = 0; trm_DoWaitingSRB(pACB); } else { if ((pSRB->SRBState & (SRB_START_+SRB_MSGOUT)) || !(pSRB->SRBState & (SRB_DISCONNECT+SRB_COMPLETED))) { /* Selection time out */ if (!(pACB->scan_devices[target_id][target_lun]) && pSRB->CmdBlock[0] != 0x00 && /* TEST UNIT READY */ pSRB->CmdBlock[0] != INQUIRY) { pSRB->SRBState = SRB_READY; trm_RewaitSRB(pDCB, pSRB); } else { pSRB->TargetStatus = SCSI_STAT_SEL_TIMEOUT; goto disc1; } } else if (pSRB->SRBState & SRB_DISCONNECT) { /* * SRB_DISCONNECT */ trm_DoWaitingSRB(pACB); } else if (pSRB->SRBState & SRB_COMPLETED) { disc1: /* * SRB_COMPLETED */ pDCB->pActiveSRB = 0; pSRB->SRBState = SRB_FREE; trm_SRBdone(pACB, pDCB, pSRB); } } return; } static void trm_Reselect(PACB pACB) { PDCB pDCB; PSRB pSRB; u_int16_t RselTarLunId; TRM_DPRINTF("trm_Reselect................. \n"); pDCB = pACB->pActiveDCB; if (pDCB) { /* Arbitration lost but Reselection win */ pSRB = pDCB->pActiveSRB; pSRB->SRBState = SRB_READY; trm_RewaitSRB(pDCB, pSRB); } /* Read Reselected Target Id and LUN */ RselTarLunId = trm_reg_read16(TRMREG_SCSI_TARGETID) & 0x1FFF; pDCB = pACB->pLinkDCB; while (RselTarLunId != *((u_int16_t *) &pDCB->TargetID)) { /* get pDCB of the reselect id */ pDCB = pDCB->pNextDCB; } pACB->pActiveDCB = pDCB; if (pDCB->SyncMode & EN_TAG_QUEUING) { pSRB = &pACB->TmpSRB; pDCB->pActiveSRB = pSRB; } else { pSRB = pDCB->pActiveSRB; if (!pSRB || !(pSRB->SRBState & SRB_DISCONNECT)) { /* * abort command */ pSRB = &pACB->TmpSRB; pSRB->SRBState = SRB_UNEXPECT_RESEL; pDCB->pActiveSRB = pSRB; trm_EnableMsgOutAbort1(pACB, pSRB); } else { if (pDCB->DCBFlag & ABORT_DEV_) { pSRB->SRBState = SRB_ABORT_SENT; trm_EnableMsgOutAbort1(pACB, pSRB); } else pSRB->SRBState = SRB_DATA_XFER; } } pSRB->ScsiPhase = PH_BUS_FREE; /* SCSI bus free Phase */ /* * Program HA ID, target ID, period and offset */ trm_reg_write8((u_int8_t) RselTarLunId,TRMREG_SCSI_TARGETID); /* target ID */ trm_reg_write8(pACB->AdaptSCSIID,TRMREG_SCSI_HOSTID); /* host ID */ trm_reg_write8(pDCB->SyncPeriod,TRMREG_SCSI_SYNC); /* period */ trm_reg_write8(pDCB->SyncOffset,TRMREG_SCSI_OFFSET); /* offset */ trm_reg_write16(DO_DATALATCH, TRMREG_SCSI_CONTROL); /* it's important for atn stop*/ /* * SCSI cammand */ trm_reg_write8(SCMD_MSGACCEPT, TRMREG_SCSI_COMMAND); /* to rls the /ACK signal */ } static void trm_SRBdone(PACB pACB, PDCB pDCB, PSRB pSRB) { PSRB psrb; u_int8_t bval, bval1,status; union ccb *pccb; struct ccb_scsiio *pcsio; PSCSI_INQDATA ptr; int intflag; u_int target_id,target_lun; PDCB pTempDCB; pccb = pSRB->pccb; if (pccb == NULL) return; pcsio = &pccb->csio; target_id = pSRB->pccb->ccb_h.target_id; target_lun = pSRB->pccb->ccb_h.target_lun; if ((pccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmasync_op_t op; if ((pccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) op = BUS_DMASYNC_POSTREAD; else op = BUS_DMASYNC_POSTWRITE; bus_dmamap_sync(pACB->buffer_dmat, pSRB->dmamap, op); bus_dmamap_unload(pACB->buffer_dmat, pSRB->dmamap); } /* * * target status * */ status = pSRB->TargetStatus; pcsio->scsi_status=SCSI_STAT_GOOD; pccb->ccb_h.status = CAM_REQ_CMP; if (pSRB->SRBFlag & AUTO_REQSENSE) { /* * status of auto request sense */ pSRB->SRBFlag &= ~AUTO_REQSENSE; pSRB->AdaptStatus = 0; pSRB->TargetStatus = SCSI_STATUS_CHECK_COND; if (status == SCSI_STATUS_CHECK_COND) { pccb->ccb_h.status = CAM_SEL_TIMEOUT; goto ckc_e; } *((u_long *) &(pSRB->CmdBlock[0])) = pSRB->Segment0[0]; *((u_long *) &(pSRB->CmdBlock[4])) = pSRB->Segment0[1]; pSRB->SRBTotalXferLength = pSRB->Segment1[1]; pSRB->pSRBSGL->address = pSRB->SgSenseTemp.address; pSRB->pSRBSGL->length = pSRB->SgSenseTemp.length; pcsio->scsi_status = SCSI_STATUS_CHECK_COND; bcopy(trm_get_sense_buf(pACB, pSRB), &pcsio->sense_data, pcsio->sense_len); pcsio->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID; goto ckc_e; } /* * target status */ if (status) { if (status == SCSI_STATUS_CHECK_COND) { if ((pcsio->ccb_h.flags & CAM_DIS_AUTOSENSE) == 0) { TRM_DPRINTF("trm_RequestSense..................\n"); trm_RequestSense(pACB, pDCB, pSRB); return; } pcsio->scsi_status = SCSI_STATUS_CHECK_COND; pccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; goto ckc_e; } else if (status == SCSI_STAT_QUEUEFULL) { bval = (u_int8_t) pDCB->GoingSRBCnt; bval--; pDCB->MaxActiveCommandCnt = bval; trm_RewaitSRB(pDCB, pSRB); pSRB->AdaptStatus = 0; pSRB->TargetStatus = 0; return; } else if (status == SCSI_STAT_SEL_TIMEOUT) { pSRB->AdaptStatus = H_SEL_TIMEOUT; pSRB->TargetStatus = 0; pcsio->scsi_status = SCSI_STAT_SEL_TIMEOUT; pccb->ccb_h.status = CAM_SEL_TIMEOUT; } else if (status == SCSI_STAT_BUSY) { TRM_DPRINTF("trm: target busy at %s %d\n", __FILE__, __LINE__); pcsio->scsi_status = SCSI_STAT_BUSY; pccb->ccb_h.status = CAM_SCSI_BUSY; return; /* The device busy, try again later? */ } else if (status == SCSI_STAT_RESCONFLICT) { TRM_DPRINTF("trm: target reserved at %s %d\n", __FILE__, __LINE__); pcsio->scsi_status = SCSI_STAT_RESCONFLICT; pccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; /*XXX*/ return; } else { pSRB->AdaptStatus = 0; if (pSRB->RetryCnt) { pSRB->RetryCnt--; pSRB->TargetStatus = 0; pSRB->SRBSGIndex = 0; if (trm_StartSCSI(pACB, pDCB, pSRB)) { /* * If trm_StartSCSI return 1 : * current interrupt status is interrupt * disreenable * It's said that SCSI processor has more * one SRB need to do */ trm_RewaitSRB(pDCB, pSRB); } return; } else { TRM_DPRINTF("trm: driver stuffup at %s %d\n", __FILE__, __LINE__); pccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; } } } else { /* * process initiator status.......................... * Adapter (initiator) status */ status = pSRB->AdaptStatus; if (status & H_OVER_UNDER_RUN) { pSRB->TargetStatus = 0; pccb->ccb_h.status = CAM_DATA_RUN_ERR; /* Illegal length (over/under run) */ } else if (pSRB->SRBStatus & PARITY_ERROR) { TRM_DPRINTF("trm: driver stuffup %s %d\n", __FILE__, __LINE__); pDCB->tinfo.goal.period = 0; pDCB->tinfo.goal.offset = 0; /* Driver failed to perform operation */ pccb->ccb_h.status = CAM_UNCOR_PARITY; } else { /* no error */ pSRB->AdaptStatus = 0; pSRB->TargetStatus = 0; pccb->ccb_h.status = CAM_REQ_CMP; /* there is no error, (sense is invalid) */ } } ckc_e: if (pACB->scan_devices[target_id][target_lun]) { /* * if SCSI command in "scan devices" duty */ if (pSRB->CmdBlock[0] == TEST_UNIT_READY) pACB->scan_devices[target_id][target_lun] = 0; /* SCSI command phase :test unit ready */ else if (pSRB->CmdBlock[0] == INQUIRY) { /* * SCSI command phase :inquiry scsi device data * (type,capacity,manufacture.... */ if (pccb->ccb_h.status == CAM_SEL_TIMEOUT) goto NO_DEV; ptr = (PSCSI_INQDATA) pcsio->data_ptr; /* page fault */ TRM_DPRINTF("trm_SRBdone..PSCSI_INQDATA:%2x \n", ptr->DevType); bval1 = ptr->DevType & SCSI_DEVTYPE; if (bval1 == SCSI_NODEV) { NO_DEV: TRM_DPRINTF("trm_SRBdone NO Device:target_id= %d ,target_lun= %d \n", target_id, target_lun); intflag = splcam(); pACB->scan_devices[target_id][target_lun] = 0; /* no device set scan device flag =0*/ /* pDCB Q link */ /* move the head of DCB to tempDCB*/ pTempDCB=pACB->pLinkDCB; /* search current DCB for pass link */ while (pTempDCB->pNextDCB != pDCB) { pTempDCB = pTempDCB->pNextDCB; } /* * when the current DCB found than connect * current DCB tail */ /* to the DCB tail that before current DCB */ pTempDCB->pNextDCB = pDCB->pNextDCB; /* * if there was only one DCB ,connect his tail * to his head */ if (pACB->pLinkDCB == pDCB) pACB->pLinkDCB = pTempDCB->pNextDCB; if (pACB->pDCBRunRobin == pDCB) pACB->pDCBRunRobin = pTempDCB->pNextDCB; pDCB->DCBstatus &= ~DS_IN_QUEUE; pACB->DeviceCnt--; if (pACB->DeviceCnt == 0) { pACB->pLinkDCB = NULL; pACB->pDCBRunRobin = NULL; } splx(intflag); } else { #ifdef trm_DEBUG1 int j; for (j = 0; j < 28; j++) { TRM_DPRINTF("ptr=%2x ", ((u_int8_t *)ptr)[j]); } #endif pDCB->DevType = bval1; if (bval1 == SCSI_DASD || bval1 == SCSI_OPTICAL) { if ((((ptr->Vers & 0x07) >= 2) || ((ptr->RDF & 0x0F) == 2)) && (ptr->Flags & SCSI_INQ_CMDQUEUE) && (pDCB->DevMode & TAG_QUEUING_) && (pDCB->DevMode & EN_DISCONNECT_)) { if (pDCB->DevMode & TAG_QUEUING_) { pDCB-> MaxActiveCommandCnt = pACB->TagMaxNum; pDCB->SyncMode |= EN_TAG_QUEUING; pDCB->tinfo.disc_tag |= TRM_CUR_TAGENB; } else { pDCB->SyncMode |= EN_ATN_STOP; pDCB->tinfo.disc_tag &= ~TRM_CUR_TAGENB; } } } } /* pSRB->CmdBlock[0] == INQUIRY */ } /* pACB->scan_devices[target_id][target_lun] */ } intflag = splcam(); /* ReleaseSRB(pDCB, pSRB); */ if (pSRB == pDCB->pGoingSRB) pDCB->pGoingSRB = pSRB->pNextSRB; else { psrb = pDCB->pGoingSRB; while (psrb->pNextSRB != pSRB) { psrb = psrb->pNextSRB; } psrb->pNextSRB = pSRB->pNextSRB; if (pSRB == pDCB->pGoingLastSRB) { pDCB->pGoingLastSRB = psrb; } } pSRB->pNextSRB = pACB->pFreeSRB; pACB->pFreeSRB = pSRB; pDCB->GoingSRBCnt--; trm_DoWaitingSRB(pACB); splx(intflag); /* Notify cmd done */ xpt_done (pccb); } static void trm_DoingSRB_Done(PACB pACB) { PDCB pDCB, pdcb; PSRB psrb, psrb2; u_int16_t cnt, i; union ccb *pccb; pDCB = pACB->pLinkDCB; if (pDCB == NULL) return; pdcb = pDCB; do { cnt = pdcb->GoingSRBCnt; psrb = pdcb->pGoingSRB; for (i = 0; i < cnt; i++) { psrb2 = psrb->pNextSRB; pccb = psrb->pccb; pccb->ccb_h.status = CAM_SEL_TIMEOUT; /* ReleaseSRB(pDCB, pSRB); */ psrb->pNextSRB = pACB->pFreeSRB; pACB->pFreeSRB = psrb; xpt_done(pccb); psrb = psrb2; } pdcb->GoingSRBCnt = 0; pdcb->pGoingSRB = NULL; pdcb = pdcb->pNextDCB; } while (pdcb != pDCB); } static void trm_ResetSCSIBus(PACB pACB) { int intflag; intflag = splcam(); pACB->ACBFlag |= RESET_DEV; trm_reg_write16(DO_RSTSCSI,TRMREG_SCSI_CONTROL); while (!(trm_reg_read16(TRMREG_SCSI_INTSTATUS) & INT_SCSIRESET)); splx(intflag); return; } static void trm_ScsiRstDetect(PACB pACB) { int intflag; u_long wlval; TRM_DPRINTF("trm_ScsiRstDetect \n"); wlval = 1000; while (--wlval) DELAY(1000); intflag = splcam(); trm_reg_write8(STOPDMAXFER,TRMREG_DMA_CONTROL); trm_reg_write16(DO_CLRFIFO,TRMREG_SCSI_CONTROL); if (pACB->ACBFlag & RESET_DEV) pACB->ACBFlag |= RESET_DONE; else { pACB->ACBFlag |= RESET_DETECT; trm_ResetDevParam(pACB); /* trm_DoingSRB_Done(pACB); ???? */ trm_RecoverSRB(pACB); pACB->pActiveDCB = NULL; pACB->ACBFlag = 0; trm_DoWaitingSRB(pACB); } splx(intflag); return; } static void trm_RequestSense(PACB pACB, PDCB pDCB, PSRB pSRB) { union ccb *pccb; struct ccb_scsiio *pcsio; pccb = pSRB->pccb; pcsio = &pccb->csio; pSRB->SRBFlag |= AUTO_REQSENSE; pSRB->Segment0[0] = *((u_long *) &(pSRB->CmdBlock[0])); pSRB->Segment0[1] = *((u_long *) &(pSRB->CmdBlock[4])); pSRB->Segment1[0] = (u_long) ((pSRB->ScsiCmdLen << 8) + pSRB->SRBSGCount); pSRB->Segment1[1] = pSRB->SRBTotalXferLength; /* ?????????? */ /* $$$$$$ Status of initiator/target $$$$$$$$ */ pSRB->AdaptStatus = 0; pSRB->TargetStatus = 0; /* $$$$$$ Status of initiator/target $$$$$$$$ */ pSRB->SRBTotalXferLength = sizeof(pcsio->sense_data); pSRB->SgSenseTemp.address = pSRB->pSRBSGL->address; pSRB->SgSenseTemp.length = pSRB->pSRBSGL->length; pSRB->pSRBSGL->address = trm_get_sense_bufaddr(pACB, pSRB); pSRB->pSRBSGL->length = (u_long) sizeof(struct scsi_sense_data); pSRB->SRBSGCount = 1; pSRB->SRBSGIndex = 0; *((u_long *) &(pSRB->CmdBlock[0])) = 0x00000003; pSRB->CmdBlock[1] = pDCB->IdentifyMsg << 5; *((u_int16_t *) &(pSRB->CmdBlock[4])) = pcsio->sense_len; pSRB->ScsiCmdLen = 6; if (trm_StartSCSI(pACB, pDCB, pSRB)) /* * If trm_StartSCSI return 1 : * current interrupt status is interrupt disreenable * It's said that SCSI processor has more one SRB need to do */ trm_RewaitSRB(pDCB, pSRB); } static void trm_EnableMsgOutAbort2(PACB pACB, PSRB pSRB) { pSRB->MsgCnt = 1; trm_reg_write16(DO_SETATN, TRMREG_SCSI_CONTROL); } static void trm_EnableMsgOutAbort1(PACB pACB, PSRB pSRB) { pSRB->MsgOutBuf[0] = MSG_ABORT; trm_EnableMsgOutAbort2(pACB, pSRB); } static void trm_initDCB(PACB pACB, PDCB pDCB, u_int16_t unit,u_int32_t i,u_int32_t j) { PNVRAMTYPE pEEpromBuf; u_int8_t bval,PeriodIndex; u_int target_id,target_lun; PDCB pTempDCB; int intflag; target_id = i; target_lun = j; /* * Using the lun 0 device to init other DCB first, if the device * has been initialized. * I don't want init sync arguments one by one, it is the same. */ if (target_lun != 0 && (pACB->DCBarray[target_id][0].DCBstatus & DS_IN_QUEUE)) bcopy(&pACB->DCBarray[target_id][0], pDCB, sizeof(TRM_DCB)); intflag = splcam(); if (pACB->pLinkDCB == 0) { pACB->pLinkDCB = pDCB; /* * RunRobin impersonate the role * that let each device had good proportion * about SCSI command proceeding */ pACB->pDCBRunRobin = pDCB; pDCB->pNextDCB = pDCB; } else { pTempDCB=pACB->pLinkDCB; /* search the last nod of DCB link */ while (pTempDCB->pNextDCB != pACB->pLinkDCB) pTempDCB = pTempDCB->pNextDCB; /* connect current DCB with last DCB tail */ pTempDCB->pNextDCB = pDCB; /* connect current DCB tail to this DCB Q head */ pDCB->pNextDCB=pACB->pLinkDCB; } splx(intflag); pACB->DeviceCnt++; pDCB->TargetID = target_id; pDCB->TargetLUN = target_lun; pDCB->pWaitingSRB = NULL; pDCB->pGoingSRB = NULL; pDCB->GoingSRBCnt = 0; pDCB->pActiveSRB = NULL; pDCB->MaxActiveCommandCnt = 1; pDCB->DCBFlag = 0; pDCB->DCBstatus |= DS_IN_QUEUE; /* $$$$$$$ */ pEEpromBuf = &trm_eepromBuf[unit]; pDCB->DevMode = pEEpromBuf->NvramTarget[target_id].NvmTarCfg0; pDCB->AdpMode = pEEpromBuf->NvramChannelCfg; /* $$$$$$$ */ /* * disconnect enable ? */ if (pDCB->DevMode & NTC_DO_DISCONNECT) { bval = 0xC0; pDCB->tinfo.disc_tag |= TRM_USR_DISCENB ; } else { bval = 0x80; pDCB->tinfo.disc_tag &= ~(TRM_USR_DISCENB); } bval |= target_lun; pDCB->IdentifyMsg = bval; if (target_lun != 0 && (pACB->DCBarray[target_id][0].DCBstatus & DS_IN_QUEUE)) return; /* $$$$$$$ */ /* * tag Qing enable ? */ if (pDCB->DevMode & TAG_QUEUING_) { pDCB->tinfo.disc_tag |= TRM_USR_TAGENB ; } else pDCB->tinfo.disc_tag &= ~(TRM_USR_TAGENB); /* $$$$$$$ */ /* * wide nego ,sync nego enable ? */ pDCB->SyncPeriod = 0; pDCB->SyncOffset = 0; PeriodIndex = pEEpromBuf->NvramTarget[target_id].NvmTarPeriod & 0x07; if (pACB->AdaptType==1) {/* is U2? */ pDCB->MaxNegoPeriod=dc395u2x_clock_period[ PeriodIndex ]; pDCB->tinfo.user.period=pDCB->MaxNegoPeriod; pDCB->tinfo.user.offset=(pDCB->SyncMode & SYNC_NEGO_ENABLE) ? 31 : 0; } else { pDCB->MaxNegoPeriod=dc395x_clock_period[ PeriodIndex ]; pDCB->tinfo.user.period=pDCB->MaxNegoPeriod; pDCB->tinfo.user.offset=(pDCB->SyncMode & SYNC_NEGO_ENABLE) ? 15 : 0; } pDCB->SyncMode = 0; if ((pDCB->DevMode & NTC_DO_WIDE_NEGO) && (pACB->Config & HCC_WIDE_CARD)) pDCB->SyncMode |= WIDE_NEGO_ENABLE; /* enable wide nego */ if (pDCB->DevMode & NTC_DO_SYNC_NEGO) pDCB->SyncMode |= SYNC_NEGO_ENABLE; /* enable sync nego */ /* $$$$$$$ */ /* * Fill in tinfo structure. */ pDCB->tinfo.user.width = (pDCB->SyncMode & WIDE_NEGO_ENABLE) ? MSG_EXT_WDTR_BUS_16_BIT : MSG_EXT_WDTR_BUS_8_BIT; pDCB->tinfo.current.period = 0; pDCB->tinfo.current.offset = 0; pDCB->tinfo.current.width = MSG_EXT_WDTR_BUS_8_BIT; } static void trm_srbmapSG(void *arg, bus_dma_segment_t *segs, int nseg, int error) { PSRB pSRB; pSRB=(PSRB) arg; pSRB->SRBSGPhyAddr=segs->ds_addr; return; } static void trm_destroySRB(PACB pACB) { PSRB pSRB; pSRB = pACB->pFreeSRB; while (pSRB) { if (pSRB->SRBSGPhyAddr) bus_dmamap_unload(pACB->sg_dmat, pSRB->sg_dmamap); if (pSRB->pSRBSGL) bus_dmamem_free(pACB->sg_dmat, pSRB->pSRBSGL, pSRB->sg_dmamap); if (pSRB->dmamap) bus_dmamap_destroy(pACB->buffer_dmat, pSRB->dmamap); pSRB = pSRB->pNextSRB; } } static int trm_initSRB(PACB pACB) { u_int16_t i; PSRB pSRB; int error; for (i = 0; i < TRM_MAX_SRB_CNT; i++) { pSRB = (PSRB)&pACB->pFreeSRB[i]; if (bus_dmamem_alloc(pACB->sg_dmat, (void **)&pSRB->pSRBSGL, BUS_DMA_NOWAIT, &pSRB->sg_dmamap) !=0 ) { return ENXIO; } bus_dmamap_load(pACB->sg_dmat, pSRB->sg_dmamap, pSRB->pSRBSGL, TRM_MAX_SG_LISTENTRY * sizeof(SGentry), trm_srbmapSG, pSRB, /*flags*/0); if (i != TRM_MAX_SRB_CNT - 1) { /* * link all SRB */ pSRB->pNextSRB = &pACB->pFreeSRB[i+1]; } else { /* * load NULL to NextSRB of the last SRB */ pSRB->pNextSRB = NULL; } pSRB->TagNumber = i; /* * Create the dmamap. This is no longer optional! */ if ((error = bus_dmamap_create(pACB->buffer_dmat, 0, &pSRB->dmamap)) != 0) return (error); } return (0); } static void trm_initACB(PACB pACB, u_int8_t adaptType, u_int16_t unit) { PNVRAMTYPE pEEpromBuf; pEEpromBuf = &trm_eepromBuf[unit]; pACB->max_id = 15; if (pEEpromBuf->NvramChannelCfg & NAC_SCANLUN) pACB->max_lun = 7; else pACB->max_lun = 0; TRM_DPRINTF("trm: pACB->max_id= %d pACB->max_lun= %d \n", pACB->max_id, pACB->max_lun); pACB->pLinkDCB = NULL; pACB->pDCBRunRobin = NULL; pACB->pActiveDCB = NULL; pACB->AdapterUnit = (u_int8_t)unit; pACB->AdaptSCSIID = pEEpromBuf->NvramScsiId; pACB->AdaptSCSILUN = 0; pACB->DeviceCnt = 0; pACB->AdaptType = adaptType; pACB->TagMaxNum = 2 << pEEpromBuf->NvramMaxTag; pACB->ACBFlag = 0; return; } static void NVRAM_trm_write_all(PNVRAMTYPE pEEpromBuf,PACB pACB) { u_int8_t *bpEeprom = (u_int8_t *) pEEpromBuf; u_int8_t bAddr; /* Enable SEEPROM */ trm_reg_write8((trm_reg_read8(TRMREG_GEN_CONTROL) | EN_EEPROM), TRMREG_GEN_CONTROL); /* * Write enable */ NVRAM_trm_write_cmd(pACB, 0x04, 0xFF); trm_reg_write8(0, TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); for (bAddr = 0; bAddr < 128; bAddr++, bpEeprom++) { NVRAM_trm_set_data(pACB, bAddr, *bpEeprom); } /* * Write disable */ NVRAM_trm_write_cmd(pACB, 0x04, 0x00); trm_reg_write8(0 , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); /* Disable SEEPROM */ trm_reg_write8((trm_reg_read8(TRMREG_GEN_CONTROL) & ~EN_EEPROM), TRMREG_GEN_CONTROL); return; } static void NVRAM_trm_set_data(PACB pACB, u_int8_t bAddr, u_int8_t bData) { int i; u_int8_t bSendData; /* * Send write command & address */ NVRAM_trm_write_cmd(pACB, 0x05, bAddr); /* * Write data */ for (i = 0; i < 8; i++, bData <<= 1) { bSendData = NVR_SELECT; if (bData & 0x80) /* Start from bit 7 */ bSendData |= NVR_BITOUT; trm_reg_write8(bSendData , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8((bSendData | NVR_CLOCK), TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); } trm_reg_write8(NVR_SELECT , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); /* * Disable chip select */ trm_reg_write8(0 , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8(NVR_SELECT ,TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); /* * Wait for write ready */ while (1) { trm_reg_write8((NVR_SELECT | NVR_CLOCK), TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8(NVR_SELECT, TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); if (trm_reg_read8(TRMREG_GEN_NVRAM) & NVR_BITIN) { break; } } /* * Disable chip select */ trm_reg_write8(0, TRMREG_GEN_NVRAM); return; } static void NVRAM_trm_read_all(PNVRAMTYPE pEEpromBuf, PACB pACB) { u_int8_t *bpEeprom = (u_int8_t*) pEEpromBuf; u_int8_t bAddr; /* * Enable SEEPROM */ trm_reg_write8((trm_reg_read8(TRMREG_GEN_CONTROL) | EN_EEPROM), TRMREG_GEN_CONTROL); for (bAddr = 0; bAddr < 128; bAddr++, bpEeprom++) *bpEeprom = NVRAM_trm_get_data(pACB, bAddr); /* * Disable SEEPROM */ trm_reg_write8((trm_reg_read8(TRMREG_GEN_CONTROL) & ~EN_EEPROM), TRMREG_GEN_CONTROL); return; } static u_int8_t NVRAM_trm_get_data(PACB pACB, u_int8_t bAddr) { int i; u_int8_t bReadData, bData = 0; /* * Send read command & address */ NVRAM_trm_write_cmd(pACB, 0x06, bAddr); for (i = 0; i < 8; i++) { /* * Read data */ trm_reg_write8((NVR_SELECT | NVR_CLOCK) , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8(NVR_SELECT , TRMREG_GEN_NVRAM); /* * Get data bit while falling edge */ bReadData = trm_reg_read8(TRMREG_GEN_NVRAM); bData <<= 1; if (bReadData & NVR_BITIN) { bData |= 1; } NVRAM_trm_wait_30us(pACB); } /* * Disable chip select */ trm_reg_write8(0, TRMREG_GEN_NVRAM); return (bData); } static void NVRAM_trm_wait_30us(PACB pACB) { /* ScsiPortStallExecution(30); wait 30 us */ trm_reg_write8(5, TRMREG_GEN_TIMER); while (!(trm_reg_read8(TRMREG_GEN_STATUS) & GTIMEOUT)); return; } static void NVRAM_trm_write_cmd(PACB pACB, u_int8_t bCmd, u_int8_t bAddr) { int i; u_int8_t bSendData; for (i = 0; i < 3; i++, bCmd <<= 1) { /* * Program SB+OP code */ bSendData = NVR_SELECT; if (bCmd & 0x04) bSendData |= NVR_BITOUT; /* start from bit 2 */ trm_reg_write8(bSendData, TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8((bSendData | NVR_CLOCK), TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); } for (i = 0; i < 7; i++, bAddr <<= 1) { /* * Program address */ bSendData = NVR_SELECT; if (bAddr & 0x40) /* Start from bit 6 */ bSendData |= NVR_BITOUT; trm_reg_write8(bSendData , TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); trm_reg_write8((bSendData | NVR_CLOCK), TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); } trm_reg_write8(NVR_SELECT, TRMREG_GEN_NVRAM); NVRAM_trm_wait_30us(pACB); } static void trm_check_eeprom(PNVRAMTYPE pEEpromBuf, PACB pACB) { u_int16_t *wpEeprom = (u_int16_t *) pEEpromBuf; u_int16_t wAddr, wCheckSum; u_long dAddr, *dpEeprom; NVRAM_trm_read_all(pEEpromBuf,pACB); wCheckSum = 0; for (wAddr = 0, wpEeprom = (u_int16_t *) pEEpromBuf; wAddr < 64; wAddr++, wpEeprom++) { wCheckSum += *wpEeprom; } if (wCheckSum != 0x1234) { /* * Checksum error, load default */ pEEpromBuf->NvramSubVendorID[0] = (u_int8_t) PCI_Vendor_ID_TEKRAM; pEEpromBuf->NvramSubVendorID[1] = (u_int8_t) (PCI_Vendor_ID_TEKRAM >> 8); pEEpromBuf->NvramSubSysID[0] = (u_int8_t) PCI_Device_ID_TRM_S1040; pEEpromBuf->NvramSubSysID[1] = (u_int8_t) (PCI_Device_ID_TRM_S1040 >> 8); pEEpromBuf->NvramSubClass = 0x00; pEEpromBuf->NvramVendorID[0] = (u_int8_t) PCI_Vendor_ID_TEKRAM; pEEpromBuf->NvramVendorID[1] = (u_int8_t) (PCI_Vendor_ID_TEKRAM >> 8); pEEpromBuf->NvramDeviceID[0] = (u_int8_t) PCI_Device_ID_TRM_S1040; pEEpromBuf->NvramDeviceID[1] = (u_int8_t) (PCI_Device_ID_TRM_S1040 >> 8); pEEpromBuf->NvramReserved = 0x00; for (dAddr = 0, dpEeprom = (u_long *) pEEpromBuf->NvramTarget; dAddr < 16; dAddr++, dpEeprom++) { *dpEeprom = 0x00000077; /* NvmTarCfg3,NvmTarCfg2,NvmTarPeriod,NvmTarCfg0 */ } *dpEeprom++ = 0x04000F07; /* NvramMaxTag,NvramDelayTime,NvramChannelCfg,NvramScsiId */ *dpEeprom++ = 0x00000015; /* NvramReserved1,NvramBootLun,NvramBootTarget,NvramReserved0 */ for (dAddr = 0; dAddr < 12; dAddr++, dpEeprom++) *dpEeprom = 0x00; pEEpromBuf->NvramCheckSum = 0x00; for (wAddr = 0, wCheckSum = 0, wpEeprom = (u_int16_t *) pEEpromBuf; wAddr < 63; wAddr++, wpEeprom++) wCheckSum += *wpEeprom; *wpEeprom = 0x1234 - wCheckSum; NVRAM_trm_write_all(pEEpromBuf,pACB); } return; } static int trm_initAdapter(PACB pACB, u_int16_t unit) { PNVRAMTYPE pEEpromBuf; u_int16_t wval; u_int8_t bval; pEEpromBuf = &trm_eepromBuf[unit]; /* 250ms selection timeout */ trm_reg_write8(SEL_TIMEOUT, TRMREG_SCSI_TIMEOUT); /* Mask all the interrupt */ trm_reg_write8(0x00, TRMREG_DMA_INTEN); trm_reg_write8(0x00, TRMREG_SCSI_INTEN); /* Reset SCSI module */ trm_reg_write16(DO_RSTMODULE, TRMREG_SCSI_CONTROL); /* program configuration 0 */ pACB->Config = HCC_AUTOTERM | HCC_PARITY; if (trm_reg_read8(TRMREG_GEN_STATUS) & WIDESCSI) pACB->Config |= HCC_WIDE_CARD; if (pEEpromBuf->NvramChannelCfg & NAC_POWERON_SCSI_RESET) pACB->Config |= HCC_SCSI_RESET; if (pACB->Config & HCC_PARITY) bval = PHASELATCH | INITIATOR | BLOCKRST | PARITYCHECK; else bval = PHASELATCH | INITIATOR | BLOCKRST ; trm_reg_write8(bval,TRMREG_SCSI_CONFIG0); /* program configuration 1 */ trm_reg_write8(0x13, TRMREG_SCSI_CONFIG1); /* program Host ID */ bval = pEEpromBuf->NvramScsiId; trm_reg_write8(bval, TRMREG_SCSI_HOSTID); /* set ansynchronous transfer */ trm_reg_write8(0x00, TRMREG_SCSI_OFFSET); /* Trun LED control off*/ wval = trm_reg_read16(TRMREG_GEN_CONTROL) & 0x7F; trm_reg_write16(wval, TRMREG_GEN_CONTROL); /* DMA config */ wval = trm_reg_read16(TRMREG_DMA_CONFIG) | DMA_ENHANCE; trm_reg_write16(wval, TRMREG_DMA_CONFIG); /* Clear pending interrupt status */ trm_reg_read8(TRMREG_SCSI_INTSTATUS); /* Enable SCSI interrupt */ trm_reg_write8(0x7F, TRMREG_SCSI_INTEN); trm_reg_write8(EN_SCSIINTR, TRMREG_DMA_INTEN); return (0); } static void trm_mapSRB(void *arg, bus_dma_segment_t *segs, int nseg, int error) { PACB pACB; pACB = (PACB)arg; pACB->srb_physbase = segs->ds_addr; } static void trm_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *baddr; baddr = (bus_addr_t *)arg; *baddr = segs->ds_addr; } static PACB trm_init(u_int16_t unit, device_t dev) { PACB pACB; int rid = PCIR_BAR(0), i = 0, j = 0; u_int16_t adaptType = 0; pACB = (PACB) device_get_softc(dev); if (!pACB) { printf("trm%d: cannot allocate ACB !\n", unit); return (NULL); } pACB->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (pACB->iores == NULL) { printf("trm_init: bus_alloc_resource failed!\n"); return (NULL); } switch (pci_get_devid(dev)) { case PCI_DEVICEID_TRMS1040: adaptType = 0; break; case PCI_DEVICEID_TRMS2080: adaptType = 1; break; default: printf("trm_init %d: unknown adapter type!\n", unit); goto bad; } pACB->dev = dev; pACB->tag = rman_get_bustag(pACB->iores); pACB->bsh = rman_get_bushandle(pACB->iores); if (bus_dma_tag_create( /*parent_dmat*/ bus_get_dma_tag(dev), /*alignment*/ 1, /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR, /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, /*maxsize*/ BUS_SPACE_MAXSIZE_32BIT, /*nsegments*/ BUS_SPACE_UNRESTRICTED, /*maxsegsz*/ BUS_SPACE_MAXSIZE_32BIT, /*flags*/ 0, /*lockfunc*/ NULL, /*lockarg*/ NULL, /* dmat */ &pACB->parent_dmat) != 0) goto bad; if (bus_dma_tag_create( /*parent_dmat*/ pACB->parent_dmat, /*alignment*/ 1, /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR, /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, - /*maxsize*/ MAXBSIZE, + /*maxsize*/ TRM_MAXPHYS, /*nsegments*/ TRM_NSEG, /*maxsegsz*/ TRM_MAXTRANSFER_SIZE, /*flags*/ BUS_DMA_ALLOCNOW, /*lockfunc*/ busdma_lock_mutex, /*lockarg*/ &Giant, /* dmat */ &pACB->buffer_dmat) != 0) goto bad; /* DMA tag for our ccb structures */ if (bus_dma_tag_create( /*parent_dmat*/pACB->parent_dmat, /*alignment*/ 1, /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR, /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, /*maxsize*/ TRM_MAX_SRB_CNT * sizeof(TRM_SRB), /*nsegments*/ 1, /*maxsegsz*/ TRM_MAXTRANSFER_SIZE, /*flags*/ 0, /*lockfunc*/ busdma_lock_mutex, /*lockarg*/ &Giant, /*dmat*/ &pACB->srb_dmat) != 0) { printf("trm_init %d: bus_dma_tag_create SRB failure\n", unit); goto bad; } if (bus_dmamem_alloc(pACB->srb_dmat, (void **)&pACB->pFreeSRB, BUS_DMA_NOWAIT, &pACB->srb_dmamap) != 0) { printf("trm_init %d: bus_dmamem_alloc SRB failure\n", unit); goto bad; } bus_dmamap_load(pACB->srb_dmat, pACB->srb_dmamap, pACB->pFreeSRB, TRM_MAX_SRB_CNT * sizeof(TRM_SRB), trm_mapSRB, pACB, /* flags */0); /* Create, allocate, and map DMA buffers for autosense data */ if (bus_dma_tag_create( /*parent_dmat*/pACB->parent_dmat, /*alignment*/1, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, sizeof(struct scsi_sense_data) * TRM_MAX_SRB_CNT, /*nsegments*/1, /*maxsegsz*/TRM_MAXTRANSFER_SIZE, /*flags*/0, /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, &pACB->sense_dmat) != 0) { if (bootverbose) device_printf(dev, "cannot create sense buffer dmat\n"); goto bad; } if (bus_dmamem_alloc(pACB->sense_dmat, (void **)&pACB->sense_buffers, BUS_DMA_NOWAIT, &pACB->sense_dmamap) != 0) goto bad; bus_dmamap_load(pACB->sense_dmat, pACB->sense_dmamap, pACB->sense_buffers, sizeof(struct scsi_sense_data) * TRM_MAX_SRB_CNT, trm_dmamap_cb, &pACB->sense_busaddr, /*flags*/0); trm_check_eeprom(&trm_eepromBuf[unit],pACB); trm_initACB(pACB, adaptType, unit); for (i = 0; i < (pACB->max_id + 1); i++) { if (pACB->AdaptSCSIID == i) continue; for(j = 0; j < (pACB->max_lun + 1); j++) { pACB->scan_devices[i][j] = 1; /* we assume we need to scan all devices */ trm_initDCB(pACB, &pACB->DCBarray[i][j], unit, i, j); } } bzero(pACB->pFreeSRB, TRM_MAX_SRB_CNT * sizeof(TRM_SRB)); if (bus_dma_tag_create( /*parent_dmat*/pACB->parent_dmat, /*alignment*/ 1, /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR, /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, /*maxsize*/ TRM_MAX_SG_LISTENTRY * sizeof(SGentry), /*nsegments*/ 1, /*maxsegsz*/ TRM_MAXTRANSFER_SIZE, /*flags*/ 0, /*lockfunc*/ busdma_lock_mutex, /*lockarg*/ &Giant, /*dmat*/ &pACB->sg_dmat) != 0) goto bad; if (trm_initSRB(pACB)) { printf("trm_initSRB: error\n"); goto bad; } if (trm_initAdapter(pACB, unit)) { printf("trm_initAdapter: initial ERROR\n"); goto bad; } return (pACB); bad: if (pACB->iores) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), pACB->iores); if (pACB->sense_dmamap) { bus_dmamap_unload(pACB->sense_dmat, pACB->sense_dmamap); bus_dmamem_free(pACB->sense_dmat, pACB->sense_buffers, pACB->sense_dmamap); } if (pACB->sense_dmat) bus_dma_tag_destroy(pACB->sense_dmat); if (pACB->sg_dmat) { trm_destroySRB(pACB); bus_dma_tag_destroy(pACB->sg_dmat); } if (pACB->pFreeSRB) { bus_dmamap_unload(pACB->srb_dmat, pACB->srb_dmamap); bus_dmamem_free(pACB->srb_dmat, pACB->pFreeSRB, pACB->srb_dmamap); } if (pACB->srb_dmat) bus_dma_tag_destroy(pACB->srb_dmat); if (pACB->buffer_dmat) bus_dma_tag_destroy(pACB->buffer_dmat); if (pACB->parent_dmat) bus_dma_tag_destroy(pACB->parent_dmat); return (NULL); } static int trm_attach(device_t dev) { struct cam_devq *device_Q; u_long device_id; PACB pACB = 0; int rid = 0; int unit = device_get_unit(dev); device_id = pci_get_devid(dev); /* * These cards do not allow memory mapped accesses */ if ((pACB = trm_init((u_int16_t) unit, dev)) == NULL) { printf("trm%d: trm_init error!\n",unit); return (ENXIO); } /* After setting up the adapter, map our interrupt */ /* * Now let the CAM generic SCSI layer find the SCSI devices on the bus * start queue to reset to the idle loop. * Create device queue of SIM(s) * (MAX_START_JOB - 1) : max_sim_transactions */ pACB->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (pACB->irq == NULL || bus_setup_intr(dev, pACB->irq, INTR_TYPE_CAM, NULL, trm_Interrupt, pACB, &pACB->ih)) { printf("trm%d: register Interrupt handler error!\n", unit); goto bad; } device_Q = cam_simq_alloc(TRM_MAX_START_JOB); if (device_Q == NULL){ printf("trm%d: device_Q == NULL !\n",unit); goto bad; } /* * Now tell the generic SCSI layer * about our bus. * If this is the xpt layer creating a sim, then it's OK * to wait for an allocation. * XXX Should we pass in a flag to indicate that wait is OK? * * SIM allocation * * SCSI Interface Modules * The sim driver creates a sim for each controller. The sim device * queue is separately created in order to allow resource sharing betwee * sims. For instance, a driver may create one sim for each channel of * a multi-channel controller and use the same queue for each channel. * In this way, the queue resources are shared across all the channels * of the multi-channel controller. * trm_action : sim_action_func * trm_poll : sim_poll_func * "trm" : sim_name ,if sim_name = "xpt" ..M_DEVBUF,M_WAITOK * pACB : *softc if sim_name <> "xpt" ..M_DEVBUF,M_NOWAIT * pACB->unit : unit * 1 : max_dev_transactions * MAX_TAGS : max_tagged_dev_transactions * * *******Construct our first channel SIM entry */ pACB->psim = cam_sim_alloc(trm_action, trm_poll, "trm", pACB, unit, &Giant, 1, TRM_MAX_TAGS_CMD_QUEUE, device_Q); if (pACB->psim == NULL) { printf("trm%d: SIM allocate fault !\n",unit); cam_simq_free(device_Q); /* SIM allocate fault*/ goto bad; } if (xpt_bus_register(pACB->psim, dev, 0) != CAM_SUCCESS) { printf("trm%d: xpt_bus_register fault !\n",unit); goto bad; } if (xpt_create_path(&pACB->ppath, NULL, cam_sim_path(pACB->psim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { printf("trm%d: xpt_create_path fault !\n",unit); xpt_bus_deregister(cam_sim_path(pACB->psim)); goto bad; } return (0); bad: if (pACB->iores) bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), pACB->iores); if (pACB->sg_dmat) { trm_destroySRB(pACB); bus_dma_tag_destroy(pACB->sg_dmat); } if (pACB->pFreeSRB) { bus_dmamap_unload(pACB->srb_dmat, pACB->srb_dmamap); bus_dmamem_free(pACB->srb_dmat, pACB->pFreeSRB, pACB->srb_dmamap); } if (pACB->srb_dmat) bus_dma_tag_destroy(pACB->srb_dmat); if (pACB->sense_buffers) { bus_dmamap_unload(pACB->sense_dmat, pACB->sense_dmamap); bus_dmamem_free(pACB->sense_dmat, pACB->sense_buffers, pACB->sense_dmamap); } if (pACB->sense_dmat) bus_dma_tag_destroy(pACB->sense_dmat); if (pACB->buffer_dmat) bus_dma_tag_destroy(pACB->buffer_dmat); if (pACB->ih) bus_teardown_intr(dev, pACB->irq, pACB->ih); if (pACB->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, pACB->irq); if (pACB->psim) cam_sim_free(pACB->psim, TRUE); return (ENXIO); } /* * pci_device * trm_probe (device_t tag, pcidi_t type) * */ static int trm_probe(device_t dev) { switch (pci_get_devid(dev)) { case PCI_DEVICEID_TRMS1040: device_set_desc(dev, "Tekram DC395U/UW/F DC315/U Fast20 Wide SCSI Adapter"); return (BUS_PROBE_DEFAULT); case PCI_DEVICEID_TRMS2080: device_set_desc(dev, "Tekram DC395U2D/U2W Fast40 Wide SCSI Adapter"); return (BUS_PROBE_DEFAULT); default: return (ENXIO); } } static int trm_detach(device_t dev) { PACB pACB = device_get_softc(dev); bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), pACB->iores); trm_destroySRB(pACB); bus_dma_tag_destroy(pACB->sg_dmat); bus_dmamap_unload(pACB->srb_dmat, pACB->srb_dmamap); bus_dmamem_free(pACB->srb_dmat, pACB->pFreeSRB, pACB->srb_dmamap); bus_dma_tag_destroy(pACB->srb_dmat); bus_dmamap_unload(pACB->sense_dmat, pACB->sense_dmamap); bus_dmamem_free(pACB->sense_dmat, pACB->sense_buffers, pACB->sense_dmamap); bus_dma_tag_destroy(pACB->sense_dmat); bus_dma_tag_destroy(pACB->buffer_dmat); bus_teardown_intr(dev, pACB->irq, pACB->ih); bus_release_resource(dev, SYS_RES_IRQ, 0, pACB->irq); xpt_async(AC_LOST_DEVICE, pACB->ppath, NULL); xpt_free_path(pACB->ppath); xpt_bus_deregister(cam_sim_path(pACB->psim)); cam_sim_free(pACB->psim, TRUE); return (0); } static device_method_t trm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, trm_probe), DEVMETHOD(device_attach, trm_attach), DEVMETHOD(device_detach, trm_detach), { 0, 0 } }; static driver_t trm_driver = { "trm", trm_methods, sizeof(struct _ACB) }; static devclass_t trm_devclass; DRIVER_MODULE(trm, pci, trm_driver, trm_devclass, 0, 0); MODULE_DEPEND(trm, pci, 1, 1, 1); MODULE_DEPEND(trm, cam, 1, 1, 1); Index: head/sys/dev/twe/twe.c =================================================================== --- head/sys/dev/twe/twe.c (revision 280346) +++ head/sys/dev/twe/twe.c (revision 280347) @@ -1,1966 +1,1966 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2003 Paul Saab * Copyright (c) 2003 Vinod Kashyap * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Driver for the 3ware Escalade family of IDE RAID controllers. */ #include #include #include #include #define TWE_DEFINE_TABLES #include /* * Command submission. */ static int twe_get_param_1(struct twe_softc *sc, int table_id, int param_id, u_int8_t *result); static int twe_get_param_2(struct twe_softc *sc, int table_id, int param_id, u_int16_t *result); static int twe_get_param_4(struct twe_softc *sc, int table_id, int param_id, u_int32_t *result); static void *twe_get_param(struct twe_softc *sc, int table_id, int parameter_id, size_t size, void (* func)(struct twe_request *tr)); #ifdef TWE_SHUTDOWN_NOTIFICATION static int twe_set_param_1(struct twe_softc *sc, int table_id, int param_id, u_int8_t value); #endif #if 0 static int twe_set_param_2(struct twe_softc *sc, int table_id, int param_id, u_int16_t value); static int twe_set_param_4(struct twe_softc *sc, int table_id, int param_id, u_int32_t value); #endif static int twe_set_param(struct twe_softc *sc, int table_id, int param_id, int param_size, void *data); static int twe_init_connection(struct twe_softc *sc, int mode); static int twe_wait_request(struct twe_request *tr); static int twe_immediate_request(struct twe_request *tr, int usetmp); static void twe_completeio(struct twe_request *tr); static void twe_reset(struct twe_softc *sc); static int twe_add_unit(struct twe_softc *sc, int unit); static int twe_del_unit(struct twe_softc *sc, int unit); /* * Command I/O to controller. */ static void twe_done(struct twe_softc *sc, int startio); static void twe_complete(struct twe_softc *sc); static int twe_wait_status(struct twe_softc *sc, u_int32_t status, int timeout); static int twe_drain_response_queue(struct twe_softc *sc); static int twe_check_bits(struct twe_softc *sc, u_int32_t status_reg); static int twe_soft_reset(struct twe_softc *sc); /* * Interrupt handling. */ static void twe_host_intr(struct twe_softc *sc); static void twe_attention_intr(struct twe_softc *sc); static void twe_command_intr(struct twe_softc *sc); /* * Asynchronous event handling. */ static int twe_fetch_aen(struct twe_softc *sc); static void twe_handle_aen(struct twe_request *tr); static void twe_enqueue_aen(struct twe_softc *sc, u_int16_t aen); static u_int16_t twe_dequeue_aen(struct twe_softc *sc); static int twe_drain_aen_queue(struct twe_softc *sc); static int twe_find_aen(struct twe_softc *sc, u_int16_t aen); /* * Command buffer management. */ static int twe_get_request(struct twe_softc *sc, struct twe_request **tr); static void twe_release_request(struct twe_request *tr); /* * Debugging. */ static char *twe_format_aen(struct twe_softc *sc, u_int16_t aen); static int twe_report_request(struct twe_request *tr); static void twe_panic(struct twe_softc *sc, char *reason); /******************************************************************************** ******************************************************************************** Public Interfaces ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Initialise the controller, set up driver data structures. */ int twe_setup(struct twe_softc *sc) { struct twe_request *tr; TWE_Command *cmd; u_int32_t status_reg; int i; debug_called(4); /* * Initialise request queues. */ twe_initq_free(sc); twe_initq_bio(sc); twe_initq_ready(sc); twe_initq_busy(sc); twe_initq_complete(sc); sc->twe_wait_aen = -1; /* * Allocate request structures up front. */ for (i = 0; i < TWE_Q_LENGTH; i++) { if ((tr = twe_allocate_request(sc, i)) == NULL) return(ENOMEM); /* * Set global defaults that won't change. */ cmd = TWE_FIND_COMMAND(tr); cmd->generic.host_id = sc->twe_host_id; /* controller-assigned host ID */ cmd->generic.request_id = i; /* our index number */ sc->twe_lookup[i] = tr; /* * Put command onto the freelist. */ TWE_IO_LOCK(sc); twe_release_request(tr); TWE_IO_UNLOCK(sc); } TWE_IO_LOCK(sc); /* * Check status register for errors, clear them. */ status_reg = TWE_STATUS(sc); twe_check_bits(sc, status_reg); /* * Wait for the controller to come ready. */ if (twe_wait_status(sc, TWE_STATUS_MICROCONTROLLER_READY, 60)) { TWE_IO_UNLOCK(sc); twe_printf(sc, "microcontroller not ready\n"); return(ENXIO); } /* * Disable interrupts from the card. */ twe_disable_interrupts(sc); /* * Soft reset the controller, look for the AEN acknowledging the reset, * check for errors, drain the response queue. */ for (i = 0; i < TWE_MAX_RESET_TRIES; i++) { if (i > 0) twe_printf(sc, "reset %d failed, trying again\n", i); if (!twe_soft_reset(sc)) break; /* reset process complete */ } TWE_IO_UNLOCK(sc); /* did we give up? */ if (i >= TWE_MAX_RESET_TRIES) { twe_printf(sc, "can't initialise controller, giving up\n"); return(ENXIO); } return(0); } static int twe_add_unit(struct twe_softc *sc, int unit) { struct twe_drive *dr; int table, error = 0; u_int16_t dsize; TWE_Param *drives = NULL, *param = NULL; TWE_Array_Descriptor *ud; TWE_CONFIG_ASSERT_LOCKED(sc); if (unit < 0 || unit > TWE_MAX_UNITS) return (EINVAL); /* * The controller is in a safe state, so try to find drives attached to it. */ TWE_IO_LOCK(sc); if ((drives = twe_get_param(sc, TWE_PARAM_UNITSUMMARY, TWE_PARAM_UNITSUMMARY_Status, TWE_MAX_UNITS, NULL)) == NULL) { TWE_IO_UNLOCK(sc); twe_printf(sc, "can't detect attached units\n"); return (EIO); } dr = &sc->twe_drive[unit]; /* check that the drive is online */ if (!(drives->data[unit] & TWE_PARAM_UNITSTATUS_Online)) { TWE_IO_UNLOCK(sc); error = ENXIO; goto out; } table = TWE_PARAM_UNITINFO + unit; if (twe_get_param_4(sc, table, TWE_PARAM_UNITINFO_Capacity, &dr->td_size)) { TWE_IO_UNLOCK(sc); twe_printf(sc, "error fetching capacity for unit %d\n", unit); error = EIO; goto out; } if (twe_get_param_1(sc, table, TWE_PARAM_UNITINFO_Status, &dr->td_state)) { TWE_IO_UNLOCK(sc); twe_printf(sc, "error fetching state for unit %d\n", unit); error = EIO; goto out; } if (twe_get_param_2(sc, table, TWE_PARAM_UNITINFO_DescriptorSize, &dsize)) { TWE_IO_UNLOCK(sc); twe_printf(sc, "error fetching descriptor size for unit %d\n", unit); error = EIO; goto out; } if ((param = twe_get_param(sc, table, TWE_PARAM_UNITINFO_Descriptor, dsize - 3, NULL)) == NULL) { TWE_IO_UNLOCK(sc); twe_printf(sc, "error fetching descriptor for unit %d\n", unit); error = EIO; goto out; } ud = (TWE_Array_Descriptor *)param->data; dr->td_type = ud->configuration; dr->td_stripe = ud->stripe_size; /* build synthetic geometry as per controller internal rules */ if (dr->td_size > 0x200000) { dr->td_heads = 255; dr->td_sectors = 63; } else { dr->td_heads = 64; dr->td_sectors = 32; } dr->td_cylinders = dr->td_size / (dr->td_heads * dr->td_sectors); dr->td_twe_unit = unit; TWE_IO_UNLOCK(sc); error = twe_attach_drive(sc, dr); out: if (param != NULL) free(param, M_DEVBUF); if (drives != NULL) free(drives, M_DEVBUF); return (error); } static int twe_del_unit(struct twe_softc *sc, int unit) { int error; TWE_CONFIG_ASSERT_LOCKED(sc); if (unit < 0 || unit >= TWE_MAX_UNITS) return (ENXIO); if (sc->twe_drive[unit].td_disk == NULL) return (ENXIO); error = twe_detach_drive(sc, unit); return (error); } /******************************************************************************** * Locate disk devices and attach children to them. */ void twe_init(struct twe_softc *sc) { int i; /* * Scan for drives */ TWE_CONFIG_LOCK(sc); for (i = 0; i < TWE_MAX_UNITS; i++) twe_add_unit(sc, i); TWE_CONFIG_UNLOCK(sc); /* * Initialise connection with controller. */ TWE_IO_LOCK(sc); twe_init_connection(sc, TWE_INIT_MESSAGE_CREDITS); #ifdef TWE_SHUTDOWN_NOTIFICATION /* * Tell the controller we support shutdown notification. */ twe_set_param_1(sc, TWE_PARAM_FEATURES, TWE_PARAM_FEATURES_DriverShutdown, 1); #endif /* * Mark controller up and ready to run. */ sc->twe_state &= ~TWE_STATE_SHUTDOWN; /* * Finally enable interrupts. */ twe_enable_interrupts(sc); TWE_IO_UNLOCK(sc); } /******************************************************************************** * Stop the controller */ void twe_deinit(struct twe_softc *sc) { /* * Mark the controller as shutting down, and disable any further interrupts. */ TWE_IO_ASSERT_LOCKED(sc); sc->twe_state |= TWE_STATE_SHUTDOWN; twe_disable_interrupts(sc); #ifdef TWE_SHUTDOWN_NOTIFICATION /* * Disconnect from the controller */ twe_init_connection(sc, TWE_SHUTDOWN_MESSAGE_CREDITS); #endif } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ void twe_intr(struct twe_softc *sc) { u_int32_t status_reg; debug_called(4); /* * Collect current interrupt status. */ status_reg = TWE_STATUS(sc); twe_check_bits(sc, status_reg); /* * Dispatch based on interrupt status */ if (status_reg & TWE_STATUS_HOST_INTERRUPT) twe_host_intr(sc); if (status_reg & TWE_STATUS_ATTENTION_INTERRUPT) twe_attention_intr(sc); if (status_reg & TWE_STATUS_COMMAND_INTERRUPT) twe_command_intr(sc); if (status_reg & TWE_STATUS_RESPONSE_INTERRUPT) twe_done(sc, 1); }; /******************************************************************************** * Pull as much work off the softc's work queue as possible and give it to the * controller. */ void twe_startio(struct twe_softc *sc) { struct twe_request *tr; TWE_Command *cmd; struct bio *bp; int error; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); if (sc->twe_state & (TWE_STATE_CTLR_BUSY | TWE_STATE_FRZN)) return; /* spin until something prevents us from doing any work */ for (;;) { /* try to get a command that's already ready to go */ tr = twe_dequeue_ready(sc); /* build a command from an outstanding bio */ if (tr == NULL) { /* get a command to handle the bio with */ if (twe_get_request(sc, &tr)) break; /* see if there's work to be done */ if ((bp = twe_dequeue_bio(sc)) == NULL) { twe_release_request(tr); break; } /* connect the bio to the command */ tr->tr_complete = twe_completeio; tr->tr_private = bp; tr->tr_data = bp->bio_data; tr->tr_length = bp->bio_bcount; cmd = TWE_FIND_COMMAND(tr); if (bp->bio_cmd == BIO_READ) { tr->tr_flags |= TWE_CMD_DATAIN; cmd->io.opcode = TWE_OP_READ; } else { tr->tr_flags |= TWE_CMD_DATAOUT; cmd->io.opcode = TWE_OP_WRITE; } /* build a suitable I/O command (assumes 512-byte rounded transfers) */ cmd->io.size = 3; cmd->io.unit = *(int *)(bp->bio_driver1); cmd->io.block_count = (tr->tr_length + TWE_BLOCK_SIZE - 1) / TWE_BLOCK_SIZE; cmd->io.lba = bp->bio_pblkno; } /* did we find something to do? */ if (tr == NULL) break; /* try to map and submit the command to controller */ error = twe_map_request(tr); if (error != 0) { if (error == EBUSY) break; tr->tr_status = TWE_CMD_ERROR; if (tr->tr_private != NULL) { bp = (struct bio *)(tr->tr_private); bp->bio_error = error; bp->bio_flags |= BIO_ERROR; tr->tr_private = NULL; twed_intr(bp); twe_release_request(tr); } else if (tr->tr_flags & TWE_CMD_SLEEPER) wakeup_one(tr); /* wakeup the sleeping owner */ } } } /******************************************************************************** * Write blocks from memory to disk, for system crash dumps. */ int twe_dump_blocks(struct twe_softc *sc, int unit, u_int32_t lba, void *data, int nblks) { struct twe_request *tr; TWE_Command *cmd; int error; if (twe_get_request(sc, &tr)) return(ENOMEM); tr->tr_data = data; tr->tr_status = TWE_CMD_SETUP; tr->tr_length = nblks * TWE_BLOCK_SIZE; tr->tr_flags = TWE_CMD_DATAOUT; cmd = TWE_FIND_COMMAND(tr); cmd->io.opcode = TWE_OP_WRITE; cmd->io.size = 3; cmd->io.unit = unit; cmd->io.block_count = nblks; cmd->io.lba = lba; error = twe_immediate_request(tr, 0); if (error == 0) if (twe_report_request(tr)) error = EIO; twe_release_request(tr); return(error); } /******************************************************************************** * Handle controller-specific control operations. */ int twe_ioctl(struct twe_softc *sc, u_long ioctlcmd, void *addr) { struct twe_usercommand *tu = (struct twe_usercommand *)addr; struct twe_paramcommand *tp = (struct twe_paramcommand *)addr; struct twe_drivecommand *td = (struct twe_drivecommand *)addr; union twe_statrequest *ts = (union twe_statrequest *)addr; TWE_Param *param; TWE_Command *cmd; void *data; u_int16_t *aen_code = (u_int16_t *)addr; struct twe_request *tr; u_int8_t srid; int error; size_t tr_length; error = 0; switch(ioctlcmd) { /* handle a command from userspace */ case TWEIO_COMMAND: /* * if there's a data buffer, allocate and copy it in. * Must be in multipled of 512 bytes. */ tr_length = roundup2(tu->tu_size, 512); if (tr_length > 0) { data = malloc(tr_length, M_DEVBUF, M_WAITOK); error = copyin(tu->tu_data, data, tu->tu_size); if (error) { free(data, M_DEVBUF); break; } } else data = NULL; /* get a request */ TWE_IO_LOCK(sc); while (twe_get_request(sc, &tr)) mtx_sleep(sc, &sc->twe_io_lock, PPAUSE, "twioctl", hz); /* * Save the command's request ID, copy the user-supplied command in, * restore the request ID. */ cmd = TWE_FIND_COMMAND(tr); srid = cmd->generic.request_id; bcopy(&tu->tu_command, cmd, sizeof(TWE_Command)); cmd->generic.request_id = srid; tr->tr_length = tr_length; tr->tr_data = data; if (tr->tr_length > 0) { tr->tr_flags |= TWE_CMD_DATAIN | TWE_CMD_DATAOUT; } /* run the command */ error = twe_wait_request(tr); TWE_IO_UNLOCK(sc); if (error) goto cmd_done; /* copy the command out again */ bcopy(cmd, &tu->tu_command, sizeof(TWE_Command)); /* if there was a data buffer, copy it out */ if (tr->tr_length > 0) error = copyout(tr->tr_data, tu->tu_data, tu->tu_size); cmd_done: /* free resources */ if (tr->tr_data != NULL) free(tr->tr_data, M_DEVBUF); TWE_IO_LOCK(sc); twe_release_request(tr); TWE_IO_UNLOCK(sc); break; /* fetch statistics counter */ case TWEIO_STATS: switch (ts->ts_item) { #ifdef TWE_PERFORMANCE_MONITOR case TWEQ_FREE: case TWEQ_BIO: case TWEQ_READY: case TWEQ_BUSY: case TWEQ_COMPLETE: TWE_IO_LOCK(sc); bcopy(&sc->twe_qstat[ts->ts_item], &ts->ts_qstat, sizeof(struct twe_qstat)); TWE_IO_UNLOCK(sc); break; #endif default: error = ENOENT; break; } break; /* poll for an AEN */ case TWEIO_AEN_POLL: TWE_IO_LOCK(sc); *aen_code = twe_dequeue_aen(sc); TWE_IO_UNLOCK(sc); break; /* wait for another AEN to show up */ case TWEIO_AEN_WAIT: TWE_IO_LOCK(sc); while ((*aen_code = twe_dequeue_aen(sc)) == TWE_AEN_QUEUE_EMPTY) { error = mtx_sleep(&sc->twe_aen_queue, &sc->twe_io_lock, PRIBIO | PCATCH, "tweaen", 0); if (error == EINTR) break; } TWE_IO_UNLOCK(sc); break; case TWEIO_GET_PARAM: TWE_IO_LOCK(sc); param = twe_get_param(sc, tp->tp_table_id, tp->tp_param_id, tp->tp_size, NULL); TWE_IO_UNLOCK(sc); if (param == NULL) { twe_printf(sc, "TWEIO_GET_PARAM failed for 0x%x/0x%x/%d\n", tp->tp_table_id, tp->tp_param_id, tp->tp_size); error = EINVAL; } else { if (param->parameter_size_bytes > tp->tp_size) { twe_printf(sc, "TWEIO_GET_PARAM parameter too large (%d > %d)\n", param->parameter_size_bytes, tp->tp_size); error = EFAULT; } else { error = copyout(param->data, tp->tp_data, param->parameter_size_bytes); } free(param, M_DEVBUF); } break; case TWEIO_SET_PARAM: data = malloc(tp->tp_size, M_DEVBUF, M_WAITOK); error = copyin(tp->tp_data, data, tp->tp_size); if (error == 0) { TWE_IO_LOCK(sc); error = twe_set_param(sc, tp->tp_table_id, tp->tp_param_id, tp->tp_size, data); TWE_IO_UNLOCK(sc); } free(data, M_DEVBUF); break; case TWEIO_RESET: TWE_IO_LOCK(sc); twe_reset(sc); TWE_IO_UNLOCK(sc); break; case TWEIO_ADD_UNIT: TWE_CONFIG_LOCK(sc); error = twe_add_unit(sc, td->td_unit); TWE_CONFIG_UNLOCK(sc); break; case TWEIO_DEL_UNIT: TWE_CONFIG_LOCK(sc); error = twe_del_unit(sc, td->td_unit); TWE_CONFIG_UNLOCK(sc); break; /* XXX implement ATA PASSTHROUGH */ /* nothing we understand */ default: error = ENOTTY; } return(error); } /******************************************************************************** * Enable the useful interrupts from the controller. */ void twe_enable_interrupts(struct twe_softc *sc) { sc->twe_state |= TWE_STATE_INTEN; TWE_CONTROL(sc, TWE_CONTROL_CLEAR_ATTENTION_INTERRUPT | TWE_CONTROL_UNMASK_RESPONSE_INTERRUPT | TWE_CONTROL_ENABLE_INTERRUPTS); } /******************************************************************************** * Disable interrupts from the controller. */ void twe_disable_interrupts(struct twe_softc *sc) { TWE_CONTROL(sc, TWE_CONTROL_DISABLE_INTERRUPTS); sc->twe_state &= ~TWE_STATE_INTEN; } /******************************************************************************** ******************************************************************************** Command Submission ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Read integer parameter table entries. */ static int twe_get_param_1(struct twe_softc *sc, int table_id, int param_id, u_int8_t *result) { TWE_Param *param; if ((param = twe_get_param(sc, table_id, param_id, 1, NULL)) == NULL) return(ENOENT); *result = *(u_int8_t *)param->data; free(param, M_DEVBUF); return(0); } static int twe_get_param_2(struct twe_softc *sc, int table_id, int param_id, u_int16_t *result) { TWE_Param *param; if ((param = twe_get_param(sc, table_id, param_id, 2, NULL)) == NULL) return(ENOENT); *result = *(u_int16_t *)param->data; free(param, M_DEVBUF); return(0); } static int twe_get_param_4(struct twe_softc *sc, int table_id, int param_id, u_int32_t *result) { TWE_Param *param; if ((param = twe_get_param(sc, table_id, param_id, 4, NULL)) == NULL) return(ENOENT); *result = *(u_int32_t *)param->data; free(param, M_DEVBUF); return(0); } /******************************************************************************** * Perform a TWE_OP_GET_PARAM command. If a callback function is provided, it * will be called with the command when it's completed. If no callback is * provided, we will wait for the command to complete and then return just the data. * The caller is responsible for freeing the data when done with it. */ static void * twe_get_param(struct twe_softc *sc, int table_id, int param_id, size_t param_size, void (* func)(struct twe_request *tr)) { struct twe_request *tr; TWE_Command *cmd; TWE_Param *param; int error; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); tr = NULL; param = NULL; /* get a command */ if (twe_get_request(sc, &tr)) goto err; /* get a buffer */ if ((param = (TWE_Param *)malloc(TWE_SECTOR_SIZE, M_DEVBUF, M_NOWAIT)) == NULL) goto err; tr->tr_data = param; tr->tr_length = TWE_SECTOR_SIZE; tr->tr_flags = TWE_CMD_DATAIN | TWE_CMD_DATAOUT; /* build the command for the controller */ cmd = TWE_FIND_COMMAND(tr); cmd->param.opcode = TWE_OP_GET_PARAM; cmd->param.size = 2; cmd->param.unit = 0; cmd->param.param_count = 1; /* fill in the outbound parameter data */ param->table_id = table_id; param->parameter_id = param_id; param->parameter_size_bytes = param_size; /* submit the command and either wait or let the callback handle it */ if (func == NULL) { /* XXX could use twe_wait_request here if interrupts were enabled? */ error = twe_immediate_request(tr, 1 /* usetmp */); if (error == 0) { if (twe_report_request(tr)) goto err; } else { goto err; } twe_release_request(tr); return(param); } else { tr->tr_complete = func; error = twe_map_request(tr); if ((error == 0) || (error == EBUSY)) return(func); } /* something failed */ err: debug(1, "failed"); if (tr != NULL) twe_release_request(tr); if (param != NULL) free(param, M_DEVBUF); return(NULL); } /******************************************************************************** * Set integer parameter table entries. */ #ifdef TWE_SHUTDOWN_NOTIFICATION static int twe_set_param_1(struct twe_softc *sc, int table_id, int param_id, u_int8_t value) { return(twe_set_param(sc, table_id, param_id, sizeof(value), &value)); } #endif #if 0 static int twe_set_param_2(struct twe_softc *sc, int table_id, int param_id, u_int16_t value) { return(twe_set_param(sc, table_id, param_id, sizeof(value), &value)); } static int twe_set_param_4(struct twe_softc *sc, int table_id, int param_id, u_int32_t value) { return(twe_set_param(sc, table_id, param_id, sizeof(value), &value)); } #endif /******************************************************************************** * Perform a TWE_OP_SET_PARAM command, returns nonzero on error. */ static int twe_set_param(struct twe_softc *sc, int table_id, int param_id, int param_size, void *data) { struct twe_request *tr; TWE_Command *cmd; TWE_Param *param; int error; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); tr = NULL; param = NULL; error = ENOMEM; /* get a command */ if (twe_get_request(sc, &tr)) goto out; /* get a buffer */ if ((param = (TWE_Param *)malloc(TWE_SECTOR_SIZE, M_DEVBUF, M_NOWAIT)) == NULL) goto out; tr->tr_data = param; tr->tr_length = TWE_SECTOR_SIZE; tr->tr_flags = TWE_CMD_DATAIN | TWE_CMD_DATAOUT; /* build the command for the controller */ cmd = TWE_FIND_COMMAND(tr); cmd->param.opcode = TWE_OP_SET_PARAM; cmd->param.size = 2; cmd->param.unit = 0; cmd->param.param_count = 1; /* fill in the outbound parameter data */ param->table_id = table_id; param->parameter_id = param_id; param->parameter_size_bytes = param_size; bcopy(data, param->data, param_size); /* XXX could use twe_wait_request here if interrupts were enabled? */ error = twe_immediate_request(tr, 1 /* usetmp */); if (error == 0) { if (twe_report_request(tr)) error = EIO; } out: if (tr != NULL) twe_release_request(tr); if (param != NULL) free(param, M_DEVBUF); return(error); } /******************************************************************************** * Perform a TWE_OP_INIT_CONNECTION command, returns nonzero on error. * * Typically called with interrupts disabled. */ static int twe_init_connection(struct twe_softc *sc, int mode) { struct twe_request *tr; TWE_Command *cmd; int error; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); /* get a command */ if (twe_get_request(sc, &tr)) return(0); /* build the command */ cmd = TWE_FIND_COMMAND(tr); cmd->initconnection.opcode = TWE_OP_INIT_CONNECTION; cmd->initconnection.size = 3; cmd->initconnection.host_id = 0; cmd->initconnection.message_credits = mode; cmd->initconnection.response_queue_pointer = 0; /* submit the command */ error = twe_immediate_request(tr, 0 /* usetmp */); twe_release_request(tr); if (mode == TWE_INIT_MESSAGE_CREDITS) sc->twe_host_id = cmd->initconnection.host_id; return(error); } /******************************************************************************** * Start the command (tr) and sleep waiting for it to complete. * * Successfully completed commands are dequeued. */ static int twe_wait_request(struct twe_request *tr) { debug_called(4); TWE_IO_ASSERT_LOCKED(tr->tr_sc); tr->tr_flags |= TWE_CMD_SLEEPER; tr->tr_status = TWE_CMD_BUSY; twe_enqueue_ready(tr); twe_startio(tr->tr_sc); while (tr->tr_status == TWE_CMD_BUSY) mtx_sleep(tr, &tr->tr_sc->twe_io_lock, PRIBIO, "twewait", 0); return(tr->tr_status != TWE_CMD_COMPLETE); } /******************************************************************************** * Start the command (tr) and busy-wait for it to complete. * This should only be used when interrupts are actually disabled (although it * will work if they are not). */ static int twe_immediate_request(struct twe_request *tr, int usetmp) { struct twe_softc *sc; int error; int count = 0; debug_called(4); sc = tr->tr_sc; if (usetmp && (tr->tr_data != NULL)) { tr->tr_flags |= TWE_CMD_IMMEDIATE; - if (tr->tr_length > MAXBSIZE) + if (tr->tr_length > DFLTPHYS) return (EINVAL); bcopy(tr->tr_data, sc->twe_immediate, tr->tr_length); } tr->tr_status = TWE_CMD_BUSY; if ((error = twe_map_request(tr)) != 0) if (error != EBUSY) return(error); /* Wait up to 5 seconds for the command to complete */ while ((count++ < 5000) && (tr->tr_status == TWE_CMD_BUSY)){ DELAY(1000); twe_done(sc, 1); } if (usetmp && (tr->tr_data != NULL)) bcopy(sc->twe_immediate, tr->tr_data, tr->tr_length); return(tr->tr_status != TWE_CMD_COMPLETE); } /******************************************************************************** * Handle completion of an I/O command. */ static void twe_completeio(struct twe_request *tr) { TWE_Command *cmd = TWE_FIND_COMMAND(tr); struct twe_softc *sc = tr->tr_sc; struct bio *bp = tr->tr_private; debug_called(4); if (tr->tr_status == TWE_CMD_COMPLETE) { if (cmd->generic.status) if (twe_report_request(tr)) { bp->bio_error = EIO; bp->bio_flags |= BIO_ERROR; } } else { twe_panic(sc, "twe_completeio on incomplete command"); } tr->tr_private = NULL; twed_intr(bp); twe_release_request(tr); } /******************************************************************************** * Reset the controller and pull all the active commands back onto the ready * queue. Used to restart a controller that's exhibiting bad behaviour. */ static void twe_reset(struct twe_softc *sc) { struct twe_request *tr; int i; /* * Sleep for a short period to allow AENs to be signalled. */ mtx_sleep(sc, &sc->twe_io_lock, PRIBIO, "twereset", hz); /* * Disable interrupts from the controller, and mask any accidental entry * into our interrupt handler. */ twe_printf(sc, "controller reset in progress...\n"); twe_disable_interrupts(sc); /* * Try to soft-reset the controller. */ for (i = 0; i < TWE_MAX_RESET_TRIES; i++) { if (i > 0) twe_printf(sc, "reset %d failed, trying again\n", i); if (!twe_soft_reset(sc)) break; /* reset process complete */ } /* did we give up? */ if (i >= TWE_MAX_RESET_TRIES) { twe_printf(sc, "can't reset controller, giving up\n"); goto out; } /* * Move all of the commands that were busy back to the ready queue. */ i = 0; while ((tr = twe_dequeue_busy(sc)) != NULL) { twe_enqueue_ready(tr); i++; } /* * Kick the controller to start things going again, then re-enable interrupts. */ twe_startio(sc); twe_printf(sc, "controller reset done, %d commands restarted\n", i); out: twe_enable_interrupts(sc); } /******************************************************************************** ******************************************************************************** Command I/O to Controller ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Try to deliver (tr) to the controller. * * Can be called at any interrupt level, with or without interrupts enabled. */ int twe_start(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; TWE_Command *cmd; int i; u_int32_t status_reg; debug_called(4); if (!dumping) TWE_IO_ASSERT_LOCKED(sc); /* mark the command as currently being processed */ tr->tr_status = TWE_CMD_BUSY; cmd = TWE_FIND_COMMAND(tr); /* * Spin briefly waiting for the controller to come ready * * XXX it might be more efficient to return EBUSY immediately * and let the command be rescheduled. */ for (i = 100000; (i > 0); i--) { /* check to see if we can post a command */ status_reg = TWE_STATUS(sc); twe_check_bits(sc, status_reg); if (!(status_reg & TWE_STATUS_COMMAND_QUEUE_FULL)) { twe_enqueue_busy(tr); TWE_COMMAND_QUEUE(sc, TWE_FIND_COMMANDPHYS(tr)); /* move command to work queue */ #ifdef TWE_DEBUG if (tr->tr_complete != NULL) { debug(3, "queued request %d with callback %p", cmd->generic.request_id, tr->tr_complete); } else if (tr->tr_flags & TWE_CMD_SLEEPER) { debug(3, "queued request %d with wait channel %p", cmd->generic.request_id, tr); } else { debug(3, "queued request %d for polling caller", cmd->generic.request_id); } #endif return(0); } else if (!(status_reg & TWE_STATUS_RESPONSE_QUEUE_EMPTY) && i > 1) twe_done(sc, 0); } /* * We couldn't get the controller to take the command; try submitting it again later. * This should only happen if something is wrong with the controller, or if we have * overestimated the number of commands it can accept. (Should we actually reject * the command at this point?) */ return(EBUSY); } /******************************************************************************** * Poll the controller (sc) for completed commands. * * Can be called at any interrupt level, with or without interrupts enabled. */ static void twe_done(struct twe_softc *sc, int startio) { TWE_Response_Queue rq; TWE_Command *cmd; struct twe_request *tr; int found; u_int32_t status_reg; debug_called(5); /* loop collecting completed commands */ found = 0; for (;;) { status_reg = TWE_STATUS(sc); twe_check_bits(sc, status_reg); /* XXX should this fail? */ if (!(status_reg & TWE_STATUS_RESPONSE_QUEUE_EMPTY)) { found = 1; rq = TWE_RESPONSE_QUEUE(sc); tr = sc->twe_lookup[rq.u.response_id]; /* find command */ cmd = TWE_FIND_COMMAND(tr); if (tr->tr_status != TWE_CMD_BUSY) twe_printf(sc, "completion event for nonbusy command\n"); tr->tr_status = TWE_CMD_COMPLETE; debug(3, "completed request id %d with status %d", cmd->generic.request_id, cmd->generic.status); /* move to completed queue */ twe_remove_busy(tr); twe_enqueue_complete(tr); sc->twe_state &= ~TWE_STATE_CTLR_BUSY; } else { break; /* no response ready */ } } /* if we've completed any commands, try posting some more */ if (found && startio) twe_startio(sc); /* handle completion and timeouts */ twe_complete(sc); /* XXX use deferred completion? */ } /******************************************************************************** * Perform post-completion processing for commands on (sc). * * This is split from twe_done as it can be safely deferred and run at a lower * priority level should facilities for such a thing become available. */ static void twe_complete(struct twe_softc *sc) { struct twe_request *tr; debug_called(5); /* * Pull commands off the completed list, dispatch them appropriately */ while ((tr = twe_dequeue_complete(sc)) != NULL) { /* unmap the command's data buffer */ twe_unmap_request(tr); /* dispatch to suit command originator */ if (tr->tr_complete != NULL) { /* completion callback */ debug(2, "call completion handler %p", tr->tr_complete); tr->tr_complete(tr); } else if (tr->tr_flags & TWE_CMD_SLEEPER) { /* caller is asleep waiting */ debug(2, "wake up command owner on %p", tr); wakeup_one(tr); } else { /* caller is polling command */ debug(2, "command left for owner"); } } } /******************************************************************************** * Wait for (status) to be set in the controller status register for up to * (timeout) seconds. Returns 0 if found, nonzero if we time out. * * Note: this busy-waits, rather than sleeping, since we may be called with * eg. clock interrupts masked. */ static int twe_wait_status(struct twe_softc *sc, u_int32_t status, int timeout) { time_t expiry; u_int32_t status_reg; debug_called(4); expiry = time_second + timeout; do { status_reg = TWE_STATUS(sc); if (status_reg & status) /* got the required bit(s)? */ return(0); DELAY(100000); } while (time_second <= expiry); return(1); } /******************************************************************************** * Drain the response queue, which may contain responses to commands we know * nothing about. */ static int twe_drain_response_queue(struct twe_softc *sc) { TWE_Response_Queue rq; u_int32_t status_reg; debug_called(4); for (;;) { /* XXX give up eventually? */ status_reg = TWE_STATUS(sc); if (twe_check_bits(sc, status_reg)) return(1); if (status_reg & TWE_STATUS_RESPONSE_QUEUE_EMPTY) return(0); rq = TWE_RESPONSE_QUEUE(sc); } } /******************************************************************************** * Soft-reset the controller */ static int twe_soft_reset(struct twe_softc *sc) { u_int32_t status_reg; debug_called(2); TWE_IO_ASSERT_LOCKED(sc); TWE_SOFT_RESET(sc); if (twe_wait_status(sc, TWE_STATUS_ATTENTION_INTERRUPT, 30)) { twe_printf(sc, "no attention interrupt\n"); return(1); } TWE_CONTROL(sc, TWE_CONTROL_CLEAR_ATTENTION_INTERRUPT); if (twe_drain_aen_queue(sc)) { twe_printf(sc, "can't drain AEN queue\n"); return(1); } if (twe_find_aen(sc, TWE_AEN_SOFT_RESET)) { twe_printf(sc, "reset not reported\n"); return(1); } status_reg = TWE_STATUS(sc); if (TWE_STATUS_ERRORS(status_reg) || twe_check_bits(sc, status_reg)) { twe_printf(sc, "controller errors detected\n"); return(1); } if (twe_drain_response_queue(sc)) { twe_printf(sc, "can't drain response queue\n"); return(1); } return(0); } /******************************************************************************** ******************************************************************************** Interrupt Handling ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Host interrupt. * * XXX what does this mean? */ static void twe_host_intr(struct twe_softc *sc) { debug_called(4); twe_printf(sc, "host interrupt\n"); TWE_CONTROL(sc, TWE_CONTROL_CLEAR_HOST_INTERRUPT); } /******************************************************************************** * Attention interrupt. * * Signalled when the controller has one or more AENs for us. */ static void twe_attention_intr(struct twe_softc *sc) { debug_called(4); /* instigate a poll for AENs */ if (twe_fetch_aen(sc)) { twe_printf(sc, "error polling for signalled AEN\n"); } else { TWE_CONTROL(sc, TWE_CONTROL_CLEAR_ATTENTION_INTERRUPT); } } /******************************************************************************** * Command interrupt. * * Signalled when the controller can handle more commands. */ static void twe_command_intr(struct twe_softc *sc) { debug_called(4); /* * We don't use this, rather we try to submit commands when we receive * them, and when other commands have completed. Mask it so we don't get * another one. */ TWE_CONTROL(sc, TWE_CONTROL_MASK_COMMAND_INTERRUPT); } /******************************************************************************** ******************************************************************************** Asynchronous Event Handling ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Request an AEN from the controller. */ static int twe_fetch_aen(struct twe_softc *sc) { debug_called(4); if ((twe_get_param(sc, TWE_PARAM_AEN, TWE_PARAM_AEN_UnitCode, 2, twe_handle_aen)) == NULL) return(EIO); return(0); } /******************************************************************************** * Handle an AEN returned by the controller. */ static void twe_handle_aen(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; TWE_Param *param; u_int16_t aen; debug_called(4); /* XXX check for command success somehow? */ param = (TWE_Param *)tr->tr_data; aen = *(u_int16_t *)(param->data); free(tr->tr_data, M_DEVBUF); twe_release_request(tr); twe_enqueue_aen(sc, aen); /* XXX poll for more AENs? */ } /******************************************************************************** * Pull AENs out of the controller and park them in the queue, in a context where * interrupts aren't active. Return nonzero if we encounter any errors in the * process of obtaining all the available AENs. */ static int twe_drain_aen_queue(struct twe_softc *sc) { u_int16_t aen; TWE_IO_ASSERT_LOCKED(sc); for (;;) { if (twe_get_param_2(sc, TWE_PARAM_AEN, TWE_PARAM_AEN_UnitCode, &aen)) return(1); if (aen == TWE_AEN_QUEUE_EMPTY) return(0); twe_enqueue_aen(sc, aen); } } /******************************************************************************** * Push an AEN that we've received onto the queue. * * Note that we have to lock this against reentrance, since it may be called * from both interrupt and non-interrupt context. * * If someone is waiting for the AEN we have, wake them up. */ static void twe_enqueue_aen(struct twe_softc *sc, u_int16_t aen) { char *msg; int next, nextnext; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); if ((msg = twe_format_aen(sc, aen)) != NULL) twe_printf(sc, "AEN: <%s>\n", msg); /* enqueue the AEN */ next = ((sc->twe_aen_head + 1) % TWE_Q_LENGTH); nextnext = ((sc->twe_aen_head + 2) % TWE_Q_LENGTH); /* check to see if this is the last free slot, and subvert the AEN if it is */ if (nextnext == sc->twe_aen_tail) aen = TWE_AEN_QUEUE_FULL; /* look to see if there's room for this AEN */ if (next != sc->twe_aen_tail) { sc->twe_aen_queue[sc->twe_aen_head] = aen; sc->twe_aen_head = next; } /* wake up anyone asleep on the queue */ wakeup(&sc->twe_aen_queue); /* anyone looking for this AEN? */ if (sc->twe_wait_aen == aen) { sc->twe_wait_aen = -1; wakeup(&sc->twe_wait_aen); } } /******************************************************************************** * Pop an AEN off the queue, or return -1 if there are none left. * * We are more or less interrupt-safe, so don't block interrupts. */ static u_int16_t twe_dequeue_aen(struct twe_softc *sc) { u_int16_t result; debug_called(4); TWE_IO_ASSERT_LOCKED(sc); if (sc->twe_aen_tail == sc->twe_aen_head) { result = TWE_AEN_QUEUE_EMPTY; } else { result = sc->twe_aen_queue[sc->twe_aen_tail]; sc->twe_aen_tail = ((sc->twe_aen_tail + 1) % TWE_Q_LENGTH); } return(result); } /******************************************************************************** * Check to see if the requested AEN is in the queue. * * XXX we could probably avoid masking interrupts here */ static int twe_find_aen(struct twe_softc *sc, u_int16_t aen) { int i, missing; missing = 1; for (i = sc->twe_aen_tail; (i != sc->twe_aen_head) && missing; i = (i + 1) % TWE_Q_LENGTH) { if (sc->twe_aen_queue[i] == aen) missing = 0; } return(missing); } #if 0 /* currently unused */ /******************************************************************************** * Sleep waiting for at least (timeout) seconds until we see (aen) as * requested. Returns nonzero on timeout or failure. * * XXX: this should not be used in cases where there may be more than one sleeper * without a mechanism for registering multiple sleepers. */ static int twe_wait_aen(struct twe_softc *sc, int aen, int timeout) { time_t expiry; int found; debug_called(4); expiry = time_second + timeout; found = 0; sc->twe_wait_aen = aen; do { twe_fetch_aen(sc); mtx_sleep(&sc->twe_wait_aen, &sc->twe_io_lock, PZERO, "twewaen", hz); if (sc->twe_wait_aen == -1) found = 1; } while ((time_second <= expiry) && !found); return(!found); } #endif /******************************************************************************** ******************************************************************************** Command Buffer Management ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Get a new command buffer. * * This will return NULL if all command buffers are in use. */ static int twe_get_request(struct twe_softc *sc, struct twe_request **tr) { TWE_Command *cmd; debug_called(4); if (!dumping) TWE_IO_ASSERT_LOCKED(sc); /* try to reuse an old buffer */ *tr = twe_dequeue_free(sc); /* initialise some fields to their defaults */ if (*tr != NULL) { cmd = TWE_FIND_COMMAND(*tr); (*tr)->tr_data = NULL; (*tr)->tr_private = NULL; (*tr)->tr_status = TWE_CMD_SETUP; /* command is in setup phase */ (*tr)->tr_flags = 0; (*tr)->tr_complete = NULL; cmd->generic.status = 0; /* before submission to controller */ cmd->generic.flags = 0; /* not used */ } return(*tr == NULL); } /******************************************************************************** * Release a command buffer for reuse. * */ static void twe_release_request(struct twe_request *tr) { debug_called(4); if (!dumping) TWE_IO_ASSERT_LOCKED(tr->tr_sc); if (tr->tr_private != NULL) twe_panic(tr->tr_sc, "tr_private != NULL"); twe_enqueue_free(tr); } /******************************************************************************** ******************************************************************************** Debugging ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Print some information about the controller */ void twe_describe_controller(struct twe_softc *sc) { TWE_Param *p[6]; u_int8_t ports; u_int32_t size; int i; debug_called(2); TWE_IO_LOCK(sc); /* get the port count */ twe_get_param_1(sc, TWE_PARAM_CONTROLLER, TWE_PARAM_CONTROLLER_PortCount, &ports); /* get version strings */ p[0] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_FW, 16, NULL); p[1] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_BIOS, 16, NULL); if (p[0] && p[1]) twe_printf(sc, "%d ports, Firmware %.16s, BIOS %.16s\n", ports, p[0]->data, p[1]->data); if (bootverbose) { p[2] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_Mon, 16, NULL); p[3] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_PCB, 8, NULL); p[4] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_ATA, 8, NULL); p[5] = twe_get_param(sc, TWE_PARAM_VERSION, TWE_PARAM_VERSION_PCI, 8, NULL); if (p[2] && p[3] && p[4] && p[5]) twe_printf(sc, "Monitor %.16s, PCB %.8s, Achip %.8s, Pchip %.8s\n", p[2]->data, p[3]->data, p[4]->data, p[5]->data); if (p[2]) free(p[2], M_DEVBUF); if (p[3]) free(p[3], M_DEVBUF); if (p[4]) free(p[4], M_DEVBUF); if (p[5]) free(p[5], M_DEVBUF); } if (p[0]) free(p[0], M_DEVBUF); if (p[1]) free(p[1], M_DEVBUF); /* print attached drives */ if (bootverbose) { p[0] = twe_get_param(sc, TWE_PARAM_DRIVESUMMARY, TWE_PARAM_DRIVESUMMARY_Status, 16, NULL); for (i = 0; i < ports; i++) { if (p[0]->data[i] != TWE_PARAM_DRIVESTATUS_Present) continue; twe_get_param_4(sc, TWE_PARAM_DRIVEINFO + i, TWE_PARAM_DRIVEINFO_Size, &size); p[1] = twe_get_param(sc, TWE_PARAM_DRIVEINFO + i, TWE_PARAM_DRIVEINFO_Model, 40, NULL); if (p[1] != NULL) { twe_printf(sc, "port %d: %.40s %dMB\n", i, p[1]->data, size / 2048); free(p[1], M_DEVBUF); } else { twe_printf(sc, "port %d, drive status unavailable\n", i); } } if (p[0]) free(p[0], M_DEVBUF); } TWE_IO_UNLOCK(sc); } /******************************************************************************** * Look up a text description of a numeric code and return a pointer to same. */ char * twe_describe_code(struct twe_code_lookup *table, u_int32_t code) { int i; for (i = 0; table[i].string != NULL; i++) if (table[i].code == code) return(table[i].string); return(table[i+1].string); } /******************************************************************************** * Complain if the status bits aren't what we're expecting. * * Rate-limit the complaints to at most one of each every five seconds, but * always return the correct status. */ static int twe_check_bits(struct twe_softc *sc, u_int32_t status_reg) { int result; static time_t lastwarn[2] = {0, 0}; /* * This can be a little problematic, as twe_panic may call twe_reset if * TWE_DEBUG is not set, which will call us again as part of the soft reset. */ if ((status_reg & TWE_STATUS_PANIC_BITS) != 0) { twe_printf(sc, "FATAL STATUS BIT(S) %b\n", status_reg & TWE_STATUS_PANIC_BITS, TWE_STATUS_BITS_DESCRIPTION); twe_panic(sc, "fatal status bits"); } result = 0; if ((status_reg & TWE_STATUS_EXPECTED_BITS) != TWE_STATUS_EXPECTED_BITS) { if (time_second > (lastwarn[0] + 5)) { twe_printf(sc, "missing expected status bit(s) %b\n", ~status_reg & TWE_STATUS_EXPECTED_BITS, TWE_STATUS_BITS_DESCRIPTION); lastwarn[0] = time_second; } result = 1; } if ((status_reg & TWE_STATUS_UNEXPECTED_BITS) != 0) { if (time_second > (lastwarn[1] + 5)) { twe_printf(sc, "unexpected status bit(s) %b\n", status_reg & TWE_STATUS_UNEXPECTED_BITS, TWE_STATUS_BITS_DESCRIPTION); lastwarn[1] = time_second; } result = 1; if (status_reg & TWE_STATUS_PCI_PARITY_ERROR) { twe_printf(sc, "PCI parity error: Reseat card, move card or buggy device present.\n"); twe_clear_pci_parity_error(sc); } if (status_reg & TWE_STATUS_PCI_ABORT) { twe_printf(sc, "PCI abort, clearing.\n"); twe_clear_pci_abort(sc); } } return(result); } /******************************************************************************** * Return a string describing (aen). * * The low 8 bits of the aen are the code, the high 8 bits give the unit number * where an AEN is specific to a unit. * * Note that we could expand this routine to handle eg. up/downgrading the status * of a drive if we had some idea of what the drive's initial status was. */ static char * twe_format_aen(struct twe_softc *sc, u_int16_t aen) { device_t child; char *code, *msg; code = twe_describe_code(twe_table_aen, TWE_AEN_CODE(aen)); msg = code + 2; switch (*code) { case 'q': if (!bootverbose) return(NULL); /* FALLTHROUGH */ case 'a': return(msg); case 'c': if ((child = sc->twe_drive[TWE_AEN_UNIT(aen)].td_disk) != NULL) { snprintf(sc->twe_aen_buf, sizeof(sc->twe_aen_buf), "twed%d: %s", device_get_unit(child), msg); } else { snprintf(sc->twe_aen_buf, sizeof(sc->twe_aen_buf), "twe%d: %s for unknown unit %d", device_get_unit(sc->twe_dev), msg, TWE_AEN_UNIT(aen)); } return(sc->twe_aen_buf); case 'p': snprintf(sc->twe_aen_buf, sizeof(sc->twe_aen_buf), "twe%d: port %d: %s", device_get_unit(sc->twe_dev), TWE_AEN_UNIT(aen), msg); return(sc->twe_aen_buf); case 'x': default: break; } snprintf(sc->twe_aen_buf, sizeof(sc->twe_aen_buf), "unknown AEN 0x%x", aen); return(sc->twe_aen_buf); } /******************************************************************************** * Print a diagnostic if the status of the command warrants it, and return * either zero (command was ok) or nonzero (command failed). */ static int twe_report_request(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; TWE_Command *cmd = TWE_FIND_COMMAND(tr); int result = 0; /* * Check the command status value and handle accordingly. */ if (cmd->generic.status == TWE_STATUS_RESET) { /* * The status code 0xff requests a controller reset. */ twe_printf(sc, "command returned with controller reset request\n"); twe_reset(sc); result = 1; } else if (cmd->generic.status > TWE_STATUS_FATAL) { /* * Fatal errors that don't require controller reset. * * We know a few special flags values. */ switch (cmd->generic.flags) { case 0x1b: device_printf(sc->twe_drive[cmd->generic.unit].td_disk, "drive timeout"); break; case 0x51: device_printf(sc->twe_drive[cmd->generic.unit].td_disk, "unrecoverable drive error"); break; default: device_printf(sc->twe_drive[cmd->generic.unit].td_disk, "controller error - %s (flags = 0x%x)\n", twe_describe_code(twe_table_status, cmd->generic.status), cmd->generic.flags); result = 1; } } else if (cmd->generic.status > TWE_STATUS_WARNING) { /* * Warning level status. */ device_printf(sc->twe_drive[cmd->generic.unit].td_disk, "warning - %s (flags = 0x%x)\n", twe_describe_code(twe_table_status, cmd->generic.status), cmd->generic.flags); } else if (cmd->generic.status > 0x40) { /* * Info level status. */ device_printf(sc->twe_drive[cmd->generic.unit].td_disk, "attention - %s (flags = 0x%x)\n", twe_describe_code(twe_table_status, cmd->generic.status), cmd->generic.flags); } return(result); } /******************************************************************************** * Print some controller state to aid in debugging error/panic conditions */ void twe_print_controller(struct twe_softc *sc) { u_int32_t status_reg; status_reg = TWE_STATUS(sc); twe_printf(sc, "status %b\n", status_reg, TWE_STATUS_BITS_DESCRIPTION); twe_printf(sc, " current max min\n"); twe_printf(sc, "free %04d %04d %04d\n", sc->twe_qstat[TWEQ_FREE].q_length, sc->twe_qstat[TWEQ_FREE].q_max, sc->twe_qstat[TWEQ_FREE].q_min); twe_printf(sc, "ready %04d %04d %04d\n", sc->twe_qstat[TWEQ_READY].q_length, sc->twe_qstat[TWEQ_READY].q_max, sc->twe_qstat[TWEQ_READY].q_min); twe_printf(sc, "busy %04d %04d %04d\n", sc->twe_qstat[TWEQ_BUSY].q_length, sc->twe_qstat[TWEQ_BUSY].q_max, sc->twe_qstat[TWEQ_BUSY].q_min); twe_printf(sc, "complete %04d %04d %04d\n", sc->twe_qstat[TWEQ_COMPLETE].q_length, sc->twe_qstat[TWEQ_COMPLETE].q_max, sc->twe_qstat[TWEQ_COMPLETE].q_min); twe_printf(sc, "bioq %04d %04d %04d\n", sc->twe_qstat[TWEQ_BIO].q_length, sc->twe_qstat[TWEQ_BIO].q_max, sc->twe_qstat[TWEQ_BIO].q_min); twe_printf(sc, "AEN queue head %d tail %d\n", sc->twe_aen_head, sc->twe_aen_tail); } static void twe_panic(struct twe_softc *sc, char *reason) { twe_print_controller(sc); #ifdef TWE_DEBUG panic(reason); #else twe_reset(sc); #endif } #if 0 /******************************************************************************** * Print a request/command in human-readable format. */ static void twe_print_request(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; TWE_Command *cmd = TWE_FIND_COMMAND(tr); int i; twe_printf(sc, "CMD: request_id %d opcode <%s> size %d unit %d host_id %d\n", cmd->generic.request_id, twe_describe_code(twe_table_opcode, cmd->generic.opcode), cmd->generic.size, cmd->generic.unit, cmd->generic.host_id); twe_printf(sc, " status %d flags 0x%x count %d sgl_offset %d\n", cmd->generic.status, cmd->generic.flags, cmd->generic.count, cmd->generic.sgl_offset); switch(cmd->generic.opcode) { /* XXX add more opcodes? */ case TWE_OP_READ: case TWE_OP_WRITE: twe_printf(sc, " lba %d\n", cmd->io.lba); for (i = 0; (i < TWE_MAX_SGL_LENGTH) && (cmd->io.sgl[i].length != 0); i++) twe_printf(sc, " %d: 0x%x/%d\n", i, cmd->io.sgl[i].address, cmd->io.sgl[i].length); break; case TWE_OP_GET_PARAM: case TWE_OP_SET_PARAM: for (i = 0; (i < TWE_MAX_SGL_LENGTH) && (cmd->param.sgl[i].length != 0); i++) twe_printf(sc, " %d: 0x%x/%d\n", i, cmd->param.sgl[i].address, cmd->param.sgl[i].length); break; case TWE_OP_INIT_CONNECTION: twe_printf(sc, " response queue pointer 0x%x\n", cmd->initconnection.response_queue_pointer); break; default: break; } twe_printf(sc, " tr_command %p/0x%x tr_data %p/0x%x,%d\n", tr, TWE_FIND_COMMANDPHYS(tr), tr->tr_data, tr->tr_dataphys, tr->tr_length); twe_printf(sc, " tr_status %d tr_flags 0x%x tr_complete %p tr_private %p\n", tr->tr_status, tr->tr_flags, tr->tr_complete, tr->tr_private); } #endif Index: head/sys/dev/twe/twe_freebsd.c =================================================================== --- head/sys/dev/twe/twe_freebsd.c (revision 280346) +++ head/sys/dev/twe/twe_freebsd.c (revision 280347) @@ -1,1170 +1,1172 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2003 Paul Saab * Copyright (c) 2003 Vinod Kashyap * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * FreeBSD-specific code. */ #include #include #include #include #include #include static devclass_t twe_devclass; #ifdef TWE_DEBUG static u_int32_t twed_bio_in; #define TWED_BIO_IN twed_bio_in++ static u_int32_t twed_bio_out; #define TWED_BIO_OUT twed_bio_out++ #else #define TWED_BIO_IN #define TWED_BIO_OUT #endif static void twe_setup_data_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error); static void twe_setup_request_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error); /******************************************************************************** ******************************************************************************** Control device interface ******************************************************************************** ********************************************************************************/ static d_open_t twe_open; static d_close_t twe_close; static d_ioctl_t twe_ioctl_wrapper; static struct cdevsw twe_cdevsw = { .d_version = D_VERSION, .d_open = twe_open, .d_close = twe_close, .d_ioctl = twe_ioctl_wrapper, .d_name = "twe", }; /******************************************************************************** * Accept an open operation on the control device. */ static int twe_open(struct cdev *dev, int flags, int fmt, struct thread *td) { struct twe_softc *sc = (struct twe_softc *)dev->si_drv1; TWE_IO_LOCK(sc); if (sc->twe_state & TWE_STATE_DETACHING) { TWE_IO_UNLOCK(sc); return (ENXIO); } sc->twe_state |= TWE_STATE_OPEN; TWE_IO_UNLOCK(sc); return(0); } /******************************************************************************** * Accept the last close on the control device. */ static int twe_close(struct cdev *dev, int flags, int fmt, struct thread *td) { struct twe_softc *sc = (struct twe_softc *)dev->si_drv1; TWE_IO_LOCK(sc); sc->twe_state &= ~TWE_STATE_OPEN; TWE_IO_UNLOCK(sc); return (0); } /******************************************************************************** * Handle controller-specific control operations. */ static int twe_ioctl_wrapper(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td) { struct twe_softc *sc = (struct twe_softc *)dev->si_drv1; return(twe_ioctl(sc, cmd, addr)); } /******************************************************************************** ******************************************************************************** PCI device interface ******************************************************************************** ********************************************************************************/ static int twe_probe(device_t dev); static int twe_attach(device_t dev); static void twe_free(struct twe_softc *sc); static int twe_detach(device_t dev); static int twe_shutdown(device_t dev); static int twe_suspend(device_t dev); static int twe_resume(device_t dev); static void twe_pci_intr(void *arg); static void twe_intrhook(void *arg); static device_method_t twe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, twe_probe), DEVMETHOD(device_attach, twe_attach), DEVMETHOD(device_detach, twe_detach), DEVMETHOD(device_shutdown, twe_shutdown), DEVMETHOD(device_suspend, twe_suspend), DEVMETHOD(device_resume, twe_resume), DEVMETHOD_END }; static driver_t twe_pci_driver = { "twe", twe_methods, sizeof(struct twe_softc) }; DRIVER_MODULE(twe, pci, twe_pci_driver, twe_devclass, 0, 0); /******************************************************************************** * Match a 3ware Escalade ATA RAID controller. */ static int twe_probe(device_t dev) { debug_called(4); if ((pci_get_vendor(dev) == TWE_VENDOR_ID) && ((pci_get_device(dev) == TWE_DEVICE_ID) || (pci_get_device(dev) == TWE_DEVICE_ID_ASIC))) { device_set_desc_copy(dev, TWE_DEVICE_NAME ". Driver version " TWE_DRIVER_VERSION_STRING); return(BUS_PROBE_DEFAULT); } return(ENXIO); } /******************************************************************************** * Allocate resources, initialise the controller. */ static int twe_attach(device_t dev) { struct twe_softc *sc; struct sysctl_oid *sysctl_tree; int rid, error; debug_called(4); /* * Initialise the softc structure. */ sc = device_get_softc(dev); sc->twe_dev = dev; mtx_init(&sc->twe_io_lock, "twe I/O", NULL, MTX_DEF); sx_init(&sc->twe_config_lock, "twe config"); /* * XXX: This sysctl tree must stay at hw.tweX rather than using * the device_get_sysctl_tree() created by new-bus because * existing 3rd party binary tools such as tw_cli and 3dm2 use the * existence of this sysctl node to discover controllers. */ sysctl_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev), SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, device_get_nameunit(dev), CTLFLAG_RD, 0, ""); if (sysctl_tree == NULL) { twe_printf(sc, "cannot add sysctl tree node\n"); return (ENXIO); } SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(sysctl_tree), OID_AUTO, "driver_version", CTLFLAG_RD, TWE_DRIVER_VERSION_STRING, 0, "TWE driver version"); /* * Force the busmaster enable bit on, in case the BIOS forgot. */ pci_enable_busmaster(dev); /* * Allocate the PCI register window. */ rid = TWE_IO_CONFIG_REG; if ((sc->twe_io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE)) == NULL) { twe_printf(sc, "can't allocate register window\n"); twe_free(sc); return(ENXIO); } /* * Allocate the parent bus DMA tag appropriate for PCI. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), /* PCI parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, TWE_MAX_SGL_LENGTH, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsize */ + BUS_SPACE_UNRESTRICTED, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->twe_parent_dmat)) { twe_printf(sc, "can't allocate parent DMA tag\n"); twe_free(sc); return(ENOMEM); } /* * Allocate and connect our interrupt. */ rid = 0; if ((sc->twe_irq = bus_alloc_resource_any(sc->twe_dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { twe_printf(sc, "can't allocate interrupt\n"); twe_free(sc); return(ENXIO); } if (bus_setup_intr(sc->twe_dev, sc->twe_irq, INTR_TYPE_BIO | INTR_ENTROPY | INTR_MPSAFE, NULL, twe_pci_intr, sc, &sc->twe_intr)) { twe_printf(sc, "can't set up interrupt\n"); twe_free(sc); return(ENXIO); } /* * Create DMA tag for mapping command's into controller-addressable space. */ if (bus_dma_tag_create(sc->twe_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sizeof(TWE_Command) * TWE_Q_LENGTH, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->twe_cmd_dmat)) { twe_printf(sc, "can't allocate data buffer DMA tag\n"); twe_free(sc); return(ENOMEM); } /* * Allocate memory and make it available for DMA. */ if (bus_dmamem_alloc(sc->twe_cmd_dmat, (void **)&sc->twe_cmd, BUS_DMA_NOWAIT, &sc->twe_cmdmap)) { twe_printf(sc, "can't allocate command memory\n"); return(ENOMEM); } bus_dmamap_load(sc->twe_cmd_dmat, sc->twe_cmdmap, sc->twe_cmd, sizeof(TWE_Command) * TWE_Q_LENGTH, twe_setup_request_dmamap, sc, 0); bzero(sc->twe_cmd, sizeof(TWE_Command) * TWE_Q_LENGTH); /* * Create DMA tag for mapping objects into controller-addressable space. */ if (bus_dma_tag_create(sc->twe_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, TWE_MAX_SGL_LENGTH,/* maxsize, nsegments */ + (TWE_MAX_SGL_LENGTH - 1) * PAGE_SIZE,/* maxsize */ + TWE_MAX_SGL_LENGTH, /* nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ busdma_lock_mutex, /* lockfunc */ &sc->twe_io_lock, /* lockarg */ &sc->twe_buffer_dmat)) { twe_printf(sc, "can't allocate data buffer DMA tag\n"); twe_free(sc); return(ENOMEM); } /* * Create DMA tag for mapping objects into controller-addressable space. */ if (bus_dma_tag_create(sc->twe_parent_dmat, /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ - MAXBSIZE, 1, /* maxsize, nsegments */ + DFLTPHYS, 1, /* maxsize, nsegments */ BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->twe_immediate_dmat)) { twe_printf(sc, "can't allocate data buffer DMA tag\n"); twe_free(sc); return(ENOMEM); } /* * Allocate memory for requests which cannot sleep or support continuation. */ if (bus_dmamem_alloc(sc->twe_immediate_dmat, (void **)&sc->twe_immediate, BUS_DMA_NOWAIT, &sc->twe_immediate_map)) { twe_printf(sc, "can't allocate memory for immediate requests\n"); return(ENOMEM); } /* * Initialise the controller and driver core. */ if ((error = twe_setup(sc))) { twe_free(sc); return(error); } /* * Print some information about the controller and configuration. */ twe_describe_controller(sc); /* * Create the control device. */ sc->twe_dev_t = make_dev(&twe_cdevsw, device_get_unit(sc->twe_dev), UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "twe%d", device_get_unit(sc->twe_dev)); sc->twe_dev_t->si_drv1 = sc; /* * Schedule ourselves to bring the controller up once interrupts are available. * This isn't strictly necessary, since we disable interrupts while probing the * controller, but it is more in keeping with common practice for other disk * devices. */ sc->twe_ich.ich_func = twe_intrhook; sc->twe_ich.ich_arg = sc; if (config_intrhook_establish(&sc->twe_ich) != 0) { twe_printf(sc, "can't establish configuration hook\n"); twe_free(sc); return(ENXIO); } return(0); } /******************************************************************************** * Free all of the resources associated with (sc). * * Should not be called if the controller is active. */ static void twe_free(struct twe_softc *sc) { struct twe_request *tr; debug_called(4); /* throw away any command buffers */ while ((tr = twe_dequeue_free(sc)) != NULL) twe_free_request(tr); if (sc->twe_cmd != NULL) { bus_dmamap_unload(sc->twe_cmd_dmat, sc->twe_cmdmap); bus_dmamem_free(sc->twe_cmd_dmat, sc->twe_cmd, sc->twe_cmdmap); } if (sc->twe_immediate != NULL) { bus_dmamap_unload(sc->twe_immediate_dmat, sc->twe_immediate_map); bus_dmamem_free(sc->twe_immediate_dmat, sc->twe_immediate, sc->twe_immediate_map); } if (sc->twe_immediate_dmat) bus_dma_tag_destroy(sc->twe_immediate_dmat); /* destroy the data-transfer DMA tag */ if (sc->twe_buffer_dmat) bus_dma_tag_destroy(sc->twe_buffer_dmat); /* disconnect the interrupt handler */ if (sc->twe_intr) bus_teardown_intr(sc->twe_dev, sc->twe_irq, sc->twe_intr); if (sc->twe_irq != NULL) bus_release_resource(sc->twe_dev, SYS_RES_IRQ, 0, sc->twe_irq); /* destroy the parent DMA tag */ if (sc->twe_parent_dmat) bus_dma_tag_destroy(sc->twe_parent_dmat); /* release the register window mapping */ if (sc->twe_io != NULL) bus_release_resource(sc->twe_dev, SYS_RES_IOPORT, TWE_IO_CONFIG_REG, sc->twe_io); /* destroy control device */ if (sc->twe_dev_t != (struct cdev *)NULL) destroy_dev(sc->twe_dev_t); sx_destroy(&sc->twe_config_lock); mtx_destroy(&sc->twe_io_lock); } /******************************************************************************** * Disconnect from the controller completely, in preparation for unload. */ static int twe_detach(device_t dev) { struct twe_softc *sc = device_get_softc(dev); debug_called(4); TWE_IO_LOCK(sc); if (sc->twe_state & TWE_STATE_OPEN) { TWE_IO_UNLOCK(sc); return (EBUSY); } sc->twe_state |= TWE_STATE_DETACHING; TWE_IO_UNLOCK(sc); /* * Shut the controller down. */ if (twe_shutdown(dev)) { TWE_IO_LOCK(sc); sc->twe_state &= ~TWE_STATE_DETACHING; TWE_IO_UNLOCK(sc); return (EBUSY); } twe_free(sc); return(0); } /******************************************************************************** * Bring the controller down to a dormant state and detach all child devices. * * Note that we can assume that the bioq on the controller is empty, as we won't * allow shutdown if any device is open. */ static int twe_shutdown(device_t dev) { struct twe_softc *sc = device_get_softc(dev); int i, error = 0; debug_called(4); /* * Delete all our child devices. */ TWE_CONFIG_LOCK(sc); for (i = 0; i < TWE_MAX_UNITS; i++) { if (sc->twe_drive[i].td_disk != 0) { if ((error = twe_detach_drive(sc, i)) != 0) { TWE_CONFIG_UNLOCK(sc); return (error); } } } TWE_CONFIG_UNLOCK(sc); /* * Bring the controller down. */ TWE_IO_LOCK(sc); twe_deinit(sc); TWE_IO_UNLOCK(sc); return(0); } /******************************************************************************** * Bring the controller to a quiescent state, ready for system suspend. */ static int twe_suspend(device_t dev) { struct twe_softc *sc = device_get_softc(dev); debug_called(4); TWE_IO_LOCK(sc); sc->twe_state |= TWE_STATE_SUSPEND; twe_disable_interrupts(sc); TWE_IO_UNLOCK(sc); return(0); } /******************************************************************************** * Bring the controller back to a state ready for operation. */ static int twe_resume(device_t dev) { struct twe_softc *sc = device_get_softc(dev); debug_called(4); TWE_IO_LOCK(sc); sc->twe_state &= ~TWE_STATE_SUSPEND; twe_enable_interrupts(sc); TWE_IO_UNLOCK(sc); return(0); } /******************************************************************************* * Take an interrupt, or be poked by other code to look for interrupt-worthy * status. */ static void twe_pci_intr(void *arg) { struct twe_softc *sc = arg; TWE_IO_LOCK(sc); twe_intr(sc); TWE_IO_UNLOCK(sc); } /******************************************************************************** * Delayed-startup hook */ static void twe_intrhook(void *arg) { struct twe_softc *sc = (struct twe_softc *)arg; /* pull ourselves off the intrhook chain */ config_intrhook_disestablish(&sc->twe_ich); /* call core startup routine */ twe_init(sc); } /******************************************************************************** * Given a detected drive, attach it to the bio interface. * * This is called from twe_add_unit. */ int twe_attach_drive(struct twe_softc *sc, struct twe_drive *dr) { char buf[80]; int error; mtx_lock(&Giant); dr->td_disk = device_add_child(sc->twe_dev, NULL, -1); if (dr->td_disk == NULL) { mtx_unlock(&Giant); twe_printf(sc, "Cannot add unit\n"); return (EIO); } device_set_ivars(dr->td_disk, dr); /* * XXX It would make sense to test the online/initialising bits, but they seem to be * always set... */ sprintf(buf, "Unit %d, %s, %s", dr->td_twe_unit, twe_describe_code(twe_table_unittype, dr->td_type), twe_describe_code(twe_table_unitstate, dr->td_state & TWE_PARAM_UNITSTATUS_MASK)); device_set_desc_copy(dr->td_disk, buf); error = device_probe_and_attach(dr->td_disk); mtx_unlock(&Giant); if (error != 0) { twe_printf(sc, "Cannot attach unit to controller. error = %d\n", error); return (EIO); } return (0); } /******************************************************************************** * Detach the specified unit if it exsists * * This is called from twe_del_unit. */ int twe_detach_drive(struct twe_softc *sc, int unit) { int error = 0; TWE_CONFIG_ASSERT_LOCKED(sc); mtx_lock(&Giant); error = device_delete_child(sc->twe_dev, sc->twe_drive[unit].td_disk); mtx_unlock(&Giant); if (error != 0) { twe_printf(sc, "failed to delete unit %d\n", unit); return(error); } bzero(&sc->twe_drive[unit], sizeof(sc->twe_drive[unit])); return(error); } /******************************************************************************** * Clear a PCI parity error. */ void twe_clear_pci_parity_error(struct twe_softc *sc) { TWE_CONTROL(sc, TWE_CONTROL_CLEAR_PARITY_ERROR); pci_write_config(sc->twe_dev, PCIR_STATUS, TWE_PCI_CLEAR_PARITY_ERROR, 2); } /******************************************************************************** * Clear a PCI abort. */ void twe_clear_pci_abort(struct twe_softc *sc) { TWE_CONTROL(sc, TWE_CONTROL_CLEAR_PCI_ABORT); pci_write_config(sc->twe_dev, PCIR_STATUS, TWE_PCI_CLEAR_PCI_ABORT, 2); } /******************************************************************************** ******************************************************************************** Disk device ******************************************************************************** ********************************************************************************/ /* * Disk device softc */ struct twed_softc { device_t twed_dev; struct twe_softc *twed_controller; /* parent device softc */ struct twe_drive *twed_drive; /* drive data in parent softc */ struct disk *twed_disk; /* generic disk handle */ }; /* * Disk device bus interface */ static int twed_probe(device_t dev); static int twed_attach(device_t dev); static int twed_detach(device_t dev); static device_method_t twed_methods[] = { DEVMETHOD(device_probe, twed_probe), DEVMETHOD(device_attach, twed_attach), DEVMETHOD(device_detach, twed_detach), { 0, 0 } }; static driver_t twed_driver = { "twed", twed_methods, sizeof(struct twed_softc) }; static devclass_t twed_devclass; DRIVER_MODULE(twed, twe, twed_driver, twed_devclass, 0, 0); /* * Disk device control interface. */ /******************************************************************************** * Handle open from generic layer. * * Note that this is typically only called by the diskslice code, and not * for opens on subdevices (eg. slices, partitions). */ static int twed_open(struct disk *dp) { struct twed_softc *sc = (struct twed_softc *)dp->d_drv1; debug_called(4); if (sc == NULL) return (ENXIO); /* check that the controller is up and running */ if (sc->twed_controller->twe_state & TWE_STATE_SHUTDOWN) return(ENXIO); return (0); } /******************************************************************************** * Handle an I/O request. */ static void twed_strategy(struct bio *bp) { struct twed_softc *sc = bp->bio_disk->d_drv1; debug_called(4); bp->bio_driver1 = &sc->twed_drive->td_twe_unit; TWED_BIO_IN; /* bogus disk? */ if (sc == NULL || sc->twed_drive->td_disk == NULL) { bp->bio_error = EINVAL; bp->bio_flags |= BIO_ERROR; printf("twe: bio for invalid disk!\n"); biodone(bp); TWED_BIO_OUT; return; } /* queue the bio on the controller */ TWE_IO_LOCK(sc->twed_controller); twe_enqueue_bio(sc->twed_controller, bp); /* poke the controller to start I/O */ twe_startio(sc->twed_controller); TWE_IO_UNLOCK(sc->twed_controller); return; } /******************************************************************************** * System crashdump support */ static int twed_dump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t length) { struct twed_softc *twed_sc; struct twe_softc *twe_sc; int error; struct disk *dp; dp = arg; twed_sc = (struct twed_softc *)dp->d_drv1; if (twed_sc == NULL) return(ENXIO); twe_sc = (struct twe_softc *)twed_sc->twed_controller; if (length > 0) { if ((error = twe_dump_blocks(twe_sc, twed_sc->twed_drive->td_twe_unit, offset / TWE_BLOCK_SIZE, virtual, length / TWE_BLOCK_SIZE)) != 0) return(error); } return(0); } /******************************************************************************** * Handle completion of an I/O request. */ void twed_intr(struct bio *bp) { debug_called(4); /* if no error, transfer completed */ if (!(bp->bio_flags & BIO_ERROR)) bp->bio_resid = 0; biodone(bp); TWED_BIO_OUT; } /******************************************************************************** * Default probe stub. */ static int twed_probe(device_t dev) { return (0); } /******************************************************************************** * Attach a unit to the controller. */ static int twed_attach(device_t dev) { struct twed_softc *sc; device_t parent; debug_called(4); /* initialise our softc */ sc = device_get_softc(dev); parent = device_get_parent(dev); sc->twed_controller = (struct twe_softc *)device_get_softc(parent); sc->twed_drive = device_get_ivars(dev); sc->twed_dev = dev; /* report the drive */ twed_printf(sc, "%uMB (%u sectors)\n", sc->twed_drive->td_size / ((1024 * 1024) / TWE_BLOCK_SIZE), sc->twed_drive->td_size); /* attach a generic disk device to ourselves */ sc->twed_drive->td_sys_unit = device_get_unit(dev); sc->twed_disk = disk_alloc(); sc->twed_disk->d_open = twed_open; sc->twed_disk->d_strategy = twed_strategy; sc->twed_disk->d_dump = (dumper_t *)twed_dump; sc->twed_disk->d_name = "twed"; sc->twed_disk->d_drv1 = sc; sc->twed_disk->d_maxsize = (TWE_MAX_SGL_LENGTH - 1) * PAGE_SIZE; sc->twed_disk->d_sectorsize = TWE_BLOCK_SIZE; sc->twed_disk->d_mediasize = TWE_BLOCK_SIZE * (off_t)sc->twed_drive->td_size; if (sc->twed_drive->td_type == TWE_UD_CONFIG_RAID0 || sc->twed_drive->td_type == TWE_UD_CONFIG_RAID5 || sc->twed_drive->td_type == TWE_UD_CONFIG_RAID10) { sc->twed_disk->d_stripesize = TWE_BLOCK_SIZE << sc->twed_drive->td_stripe; sc->twed_disk->d_stripeoffset = 0; } sc->twed_disk->d_fwsectors = sc->twed_drive->td_sectors; sc->twed_disk->d_fwheads = sc->twed_drive->td_heads; sc->twed_disk->d_unit = sc->twed_drive->td_sys_unit; disk_create(sc->twed_disk, DISK_VERSION); /* set the maximum I/O size to the theoretical maximum allowed by the S/G list size */ return (0); } /******************************************************************************** * Disconnect ourselves from the system. */ static int twed_detach(device_t dev) { struct twed_softc *sc = (struct twed_softc *)device_get_softc(dev); debug_called(4); if (sc->twed_disk->d_flags & DISKFLAG_OPEN) return(EBUSY); disk_destroy(sc->twed_disk); return(0); } /******************************************************************************** ******************************************************************************** Misc ******************************************************************************** ********************************************************************************/ /******************************************************************************** * Allocate a command buffer */ static MALLOC_DEFINE(TWE_MALLOC_CLASS, "twe_commands", "twe commands"); struct twe_request * twe_allocate_request(struct twe_softc *sc, int tag) { struct twe_request *tr; tr = malloc(sizeof(struct twe_request), TWE_MALLOC_CLASS, M_WAITOK | M_ZERO); tr->tr_sc = sc; tr->tr_tag = tag; if (bus_dmamap_create(sc->twe_buffer_dmat, 0, &tr->tr_dmamap)) { twe_free_request(tr); twe_printf(sc, "unable to allocate dmamap for tag %d\n", tag); return(NULL); } return(tr); } /******************************************************************************** * Permanently discard a command buffer. */ void twe_free_request(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; debug_called(4); bus_dmamap_destroy(sc->twe_buffer_dmat, tr->tr_dmamap); free(tr, TWE_MALLOC_CLASS); } /******************************************************************************** * Map/unmap (tr)'s command and data in the controller's addressable space. * * These routines ensure that the data which the controller is going to try to * access is actually visible to the controller, in a machine-independant * fashion. Due to a hardware limitation, I/O buffers must be 512-byte aligned * and we take care of that here as well. */ static void twe_fillin_sgl(TWE_SG_Entry *sgl, bus_dma_segment_t *segs, int nsegments, int max_sgl) { int i; for (i = 0; i < nsegments; i++) { sgl[i].address = segs[i].ds_addr; sgl[i].length = segs[i].ds_len; } for (; i < max_sgl; i++) { /* XXX necessary? */ sgl[i].address = 0; sgl[i].length = 0; } } static void twe_setup_data_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct twe_request *tr = (struct twe_request *)arg; struct twe_softc *sc = tr->tr_sc; TWE_Command *cmd = TWE_FIND_COMMAND(tr); debug_called(4); if (tr->tr_flags & TWE_CMD_MAPPED) panic("already mapped command"); tr->tr_flags |= TWE_CMD_MAPPED; if (tr->tr_flags & TWE_CMD_IN_PROGRESS) sc->twe_state &= ~TWE_STATE_FRZN; /* save base of first segment in command (applicable if there only one segment) */ tr->tr_dataphys = segs[0].ds_addr; /* correct command size for s/g list size */ cmd->generic.size += 2 * nsegments; /* * Due to the fact that parameter and I/O commands have the scatter/gather list in * different places, we need to determine which sort of command this actually is * before we can populate it correctly. */ switch(cmd->generic.opcode) { case TWE_OP_GET_PARAM: case TWE_OP_SET_PARAM: cmd->generic.sgl_offset = 2; twe_fillin_sgl(&cmd->param.sgl[0], segs, nsegments, TWE_MAX_SGL_LENGTH); break; case TWE_OP_READ: case TWE_OP_WRITE: cmd->generic.sgl_offset = 3; twe_fillin_sgl(&cmd->io.sgl[0], segs, nsegments, TWE_MAX_SGL_LENGTH); break; case TWE_OP_ATA_PASSTHROUGH: cmd->generic.sgl_offset = 5; twe_fillin_sgl(&cmd->ata.sgl[0], segs, nsegments, TWE_MAX_ATA_SGL_LENGTH); break; default: /* * Fall back to what the linux driver does. * Do this because the API may send an opcode * the driver knows nothing about and this will * at least stop PCIABRT's from hosing us. */ switch (cmd->generic.sgl_offset) { case 2: twe_fillin_sgl(&cmd->param.sgl[0], segs, nsegments, TWE_MAX_SGL_LENGTH); break; case 3: twe_fillin_sgl(&cmd->io.sgl[0], segs, nsegments, TWE_MAX_SGL_LENGTH); break; case 5: twe_fillin_sgl(&cmd->ata.sgl[0], segs, nsegments, TWE_MAX_ATA_SGL_LENGTH); break; } } if (tr->tr_flags & TWE_CMD_DATAIN) { if (tr->tr_flags & TWE_CMD_IMMEDIATE) { bus_dmamap_sync(sc->twe_immediate_dmat, sc->twe_immediate_map, BUS_DMASYNC_PREREAD); } else { bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_PREREAD); } } if (tr->tr_flags & TWE_CMD_DATAOUT) { /* * if we're using an alignment buffer, and we're writing data * copy the real data out */ if (tr->tr_flags & TWE_CMD_ALIGNBUF) bcopy(tr->tr_realdata, tr->tr_data, tr->tr_length); if (tr->tr_flags & TWE_CMD_IMMEDIATE) { bus_dmamap_sync(sc->twe_immediate_dmat, sc->twe_immediate_map, BUS_DMASYNC_PREWRITE); } else { bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_PREWRITE); } } if (twe_start(tr) == EBUSY) { tr->tr_sc->twe_state |= TWE_STATE_CTLR_BUSY; twe_requeue_ready(tr); } } static void twe_setup_request_dmamap(void *arg, bus_dma_segment_t *segs, int nsegments, int error) { struct twe_softc *sc = (struct twe_softc *)arg; debug_called(4); /* command can't cross a page boundary */ sc->twe_cmdphys = segs[0].ds_addr; } int twe_map_request(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; int error = 0; debug_called(4); if (!dumping) TWE_IO_ASSERT_LOCKED(sc); if (sc->twe_state & (TWE_STATE_CTLR_BUSY | TWE_STATE_FRZN)) { twe_requeue_ready(tr); return (EBUSY); } bus_dmamap_sync(sc->twe_cmd_dmat, sc->twe_cmdmap, BUS_DMASYNC_PREWRITE); /* * If the command involves data, map that too. */ if (tr->tr_data != NULL && ((tr->tr_flags & TWE_CMD_MAPPED) == 0)) { /* * Data must be 64-byte aligned; allocate a fixup buffer if it's not. */ if (((vm_offset_t)tr->tr_data % TWE_ALIGNMENT) != 0) { tr->tr_realdata = tr->tr_data; /* save pointer to 'real' data */ tr->tr_flags |= TWE_CMD_ALIGNBUF; tr->tr_data = malloc(tr->tr_length, TWE_MALLOC_CLASS, M_NOWAIT); if (tr->tr_data == NULL) { twe_printf(sc, "%s: malloc failed\n", __func__); tr->tr_data = tr->tr_realdata; /* restore original data pointer */ return(ENOMEM); } } /* * Map the data buffer into bus space and build the s/g list. */ if (tr->tr_flags & TWE_CMD_IMMEDIATE) { error = bus_dmamap_load(sc->twe_immediate_dmat, sc->twe_immediate_map, sc->twe_immediate, tr->tr_length, twe_setup_data_dmamap, tr, BUS_DMA_NOWAIT); } else { error = bus_dmamap_load(sc->twe_buffer_dmat, tr->tr_dmamap, tr->tr_data, tr->tr_length, twe_setup_data_dmamap, tr, 0); } if (error == EINPROGRESS) { tr->tr_flags |= TWE_CMD_IN_PROGRESS; sc->twe_state |= TWE_STATE_FRZN; error = 0; } } else if ((error = twe_start(tr)) == EBUSY) { sc->twe_state |= TWE_STATE_CTLR_BUSY; twe_requeue_ready(tr); } return(error); } void twe_unmap_request(struct twe_request *tr) { struct twe_softc *sc = tr->tr_sc; debug_called(4); if (!dumping) TWE_IO_ASSERT_LOCKED(sc); bus_dmamap_sync(sc->twe_cmd_dmat, sc->twe_cmdmap, BUS_DMASYNC_POSTWRITE); /* * If the command involved data, unmap that too. */ if (tr->tr_data != NULL) { if (tr->tr_flags & TWE_CMD_DATAIN) { if (tr->tr_flags & TWE_CMD_IMMEDIATE) { bus_dmamap_sync(sc->twe_immediate_dmat, sc->twe_immediate_map, BUS_DMASYNC_POSTREAD); } else { bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_POSTREAD); } /* if we're using an alignment buffer, and we're reading data, copy the real data in */ if (tr->tr_flags & TWE_CMD_ALIGNBUF) bcopy(tr->tr_data, tr->tr_realdata, tr->tr_length); } if (tr->tr_flags & TWE_CMD_DATAOUT) { if (tr->tr_flags & TWE_CMD_IMMEDIATE) { bus_dmamap_sync(sc->twe_immediate_dmat, sc->twe_immediate_map, BUS_DMASYNC_POSTWRITE); } else { bus_dmamap_sync(sc->twe_buffer_dmat, tr->tr_dmamap, BUS_DMASYNC_POSTWRITE); } } if (tr->tr_flags & TWE_CMD_IMMEDIATE) { bus_dmamap_unload(sc->twe_immediate_dmat, sc->twe_immediate_map); } else { bus_dmamap_unload(sc->twe_buffer_dmat, tr->tr_dmamap); } } /* free alignment buffer if it was used */ if (tr->tr_flags & TWE_CMD_ALIGNBUF) { free(tr->tr_data, TWE_MALLOC_CLASS); tr->tr_data = tr->tr_realdata; /* restore 'real' data pointer */ } } #ifdef TWE_DEBUG void twe_report(void); /******************************************************************************** * Print current controller status, call from DDB. */ void twe_report(void) { struct twe_softc *sc; int i; for (i = 0; (sc = devclass_get_softc(twe_devclass, i)) != NULL; i++) twe_print_controller(sc); printf("twed: total bio count in %u out %u\n", twed_bio_in, twed_bio_out); } #endif