mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-17 10:26:15 +00:00
89c9c53da0
Bump __FreeBSD_version accordingly.
782 lines
22 KiB
C
782 lines
22 KiB
C
/*-
|
|
* Written by: David Jeffery
|
|
* Copyright (c) 2002 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <dev/ips/ips.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <machine/clock.h>
|
|
|
|
static d_open_t ips_open;
|
|
static d_close_t ips_close;
|
|
static d_ioctl_t ips_ioctl;
|
|
|
|
MALLOC_DEFINE(M_IPSBUF, "ipsbuf","IPS driver buffer");
|
|
|
|
static struct cdevsw ips_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_flags = D_NEEDGIANT,
|
|
.d_open = ips_open,
|
|
.d_close = ips_close,
|
|
.d_ioctl = ips_ioctl,
|
|
.d_name = "ips",
|
|
};
|
|
|
|
static const char* ips_adapter_name[] = {
|
|
"N/A",
|
|
"ServeRAID (copperhead)",
|
|
"ServeRAID II (copperhead refresh)",
|
|
"ServeRAID onboard (copperhead)",
|
|
"ServeRAID onboard (copperhead)",
|
|
"ServeRAID 3H (clarinet)",
|
|
"ServeRAID 3L (clarinet lite)",
|
|
"ServeRAID 4H (trombone)",
|
|
"ServeRAID 4M (morpheus)",
|
|
"ServeRAID 4L (morpheus lite)",
|
|
"ServeRAID 4Mx (neo)",
|
|
"ServeRAID 4Lx (neo lite)",
|
|
"ServeRAID 5i II (sarasota)",
|
|
"ServeRAID 5i (sarasota)",
|
|
"ServeRAID 6M (marco)",
|
|
"ServeRAID 6i (sebring)"
|
|
};
|
|
|
|
|
|
static int ips_open(struct cdev *dev, int flags, int fmt, struct thread *td)
|
|
{
|
|
ips_softc_t *sc = dev->si_drv1;
|
|
sc->state |= IPS_DEV_OPEN;
|
|
return 0;
|
|
}
|
|
|
|
static int ips_close(struct cdev *dev, int flags, int fmt, struct thread *td)
|
|
{
|
|
ips_softc_t *sc = dev->si_drv1;
|
|
sc->state &= ~IPS_DEV_OPEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ips_ioctl(struct cdev *dev, u_long command, caddr_t addr, int32_t flags, struct thread *td)
|
|
{
|
|
ips_softc_t *sc;
|
|
|
|
sc = dev->si_drv1;
|
|
return ips_ioctl_request(sc, command, addr, flags);
|
|
}
|
|
|
|
static void ips_cmd_dmaload(void *cmdptr, bus_dma_segment_t *segments,int segnum, int error)
|
|
{
|
|
ips_command_t *command = cmdptr;
|
|
PRINTF(10, "ips: in ips_cmd_dmaload\n");
|
|
if(!error)
|
|
command->command_phys_addr = segments[0].ds_addr;
|
|
|
|
}
|
|
|
|
/* is locking needed? what locking guarentees are there on removal? */
|
|
static __inline__ int ips_cmdqueue_free(ips_softc_t *sc)
|
|
{
|
|
int i, error = -1;
|
|
ips_command_t *command;
|
|
intrmask_t mask;
|
|
|
|
mask = splbio();
|
|
if(!sc->used_commands){
|
|
for(i = 0; i < sc->max_cmds; i++){
|
|
|
|
command = &sc->commandarray[i];
|
|
sema_destroy(&command->cmd_sema);
|
|
|
|
if(command->command_phys_addr == 0)
|
|
continue;
|
|
bus_dmamap_unload(sc->command_dmatag,
|
|
command->command_dmamap);
|
|
bus_dmamem_free(sc->command_dmatag,
|
|
command->command_buffer,
|
|
command->command_dmamap);
|
|
}
|
|
error = 0;
|
|
sc->state |= IPS_OFFLINE;
|
|
}
|
|
splx(mask);
|
|
return error;
|
|
}
|
|
|
|
/* places all ips command structs on the free command queue. No locking as if someone else tries
|
|
* to access this during init, we have bigger problems */
|
|
static __inline__ int ips_cmdqueue_init(ips_softc_t *sc)
|
|
{
|
|
int i;
|
|
ips_command_t *command;
|
|
SLIST_INIT(&sc->free_cmd_list);
|
|
STAILQ_INIT(&sc->cmd_wait_list);
|
|
for(i = 0; i < sc->max_cmds; i++){
|
|
command = &sc->commandarray[i];
|
|
command->id = i;
|
|
command->sc = sc;
|
|
|
|
if(bus_dmamem_alloc(sc->command_dmatag,&command->command_buffer,
|
|
BUS_DMA_NOWAIT, &command->command_dmamap))
|
|
goto error;
|
|
bus_dmamap_load(sc->command_dmatag, command->command_dmamap,
|
|
command->command_buffer,IPS_COMMAND_LEN,
|
|
ips_cmd_dmaload, command, BUS_DMA_NOWAIT);
|
|
if(!command->command_phys_addr){
|
|
bus_dmamem_free(sc->command_dmatag,
|
|
command->command_buffer, command->command_dmamap);
|
|
goto error;
|
|
}
|
|
|
|
sema_init(&command->cmd_sema, 0, "IPS Command Semaphore");
|
|
SLIST_INSERT_HEAD(&sc->free_cmd_list, command, next);
|
|
}
|
|
sc->state &= ~IPS_OFFLINE;
|
|
return 0;
|
|
error:
|
|
ips_cmdqueue_free(sc);
|
|
return ENOMEM;
|
|
}
|
|
|
|
static int ips_add_waiting_command(ips_softc_t *sc, int (*callback)(ips_command_t *), void *data, unsigned long flags)
|
|
{
|
|
intrmask_t mask;
|
|
ips_command_t *command;
|
|
ips_wait_list_t *waiter;
|
|
unsigned long memflags = 0;
|
|
if(IPS_NOWAIT_FLAG & flags)
|
|
memflags = M_NOWAIT;
|
|
waiter = malloc(sizeof(ips_wait_list_t), M_IPSBUF, memflags);
|
|
if(!waiter)
|
|
return ENOMEM;
|
|
mask = splbio();
|
|
if(sc->state & IPS_OFFLINE){
|
|
splx(mask);
|
|
free(waiter, M_IPSBUF);
|
|
return EIO;
|
|
}
|
|
command = SLIST_FIRST(&sc->free_cmd_list);
|
|
if(command && !(sc->state & IPS_TIMEOUT)){
|
|
SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
|
|
(sc->used_commands)++;
|
|
splx(mask);
|
|
clear_ips_command(command);
|
|
bzero(command->command_buffer, IPS_COMMAND_LEN);
|
|
free(waiter, M_IPSBUF);
|
|
command->arg = data;
|
|
return callback(command);
|
|
}
|
|
DEVICE_PRINTF(1, sc->dev, "adding command to the wait queue\n");
|
|
waiter->callback = callback;
|
|
waiter->data = data;
|
|
STAILQ_INSERT_TAIL(&sc->cmd_wait_list, waiter, next);
|
|
splx(mask);
|
|
return 0;
|
|
}
|
|
|
|
static void ips_run_waiting_command(ips_softc_t *sc)
|
|
{
|
|
ips_wait_list_t *waiter;
|
|
ips_command_t *command;
|
|
int (*callback)(ips_command_t*);
|
|
intrmask_t mask;
|
|
|
|
mask = splbio();
|
|
waiter = STAILQ_FIRST(&sc->cmd_wait_list);
|
|
command = SLIST_FIRST(&sc->free_cmd_list);
|
|
if(!waiter || !command){
|
|
splx(mask);
|
|
return;
|
|
}
|
|
DEVICE_PRINTF(1, sc->dev, "removing command from wait queue\n");
|
|
SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
|
|
STAILQ_REMOVE_HEAD(&sc->cmd_wait_list, next);
|
|
(sc->used_commands)++;
|
|
splx(mask);
|
|
clear_ips_command(command);
|
|
bzero(command->command_buffer, IPS_COMMAND_LEN);
|
|
command->arg = waiter->data;
|
|
callback = waiter->callback;
|
|
free(waiter, M_IPSBUF);
|
|
callback(command);
|
|
return;
|
|
}
|
|
/* returns a free command struct if one is available.
|
|
* It also blanks out anything that may be a wild pointer/value.
|
|
* Also, command buffers are not freed. They are
|
|
* small so they are saved and kept dmamapped and loaded.
|
|
*/
|
|
int ips_get_free_cmd(ips_softc_t *sc, int (*callback)(ips_command_t *), void *data, unsigned long flags)
|
|
{
|
|
intrmask_t mask;
|
|
ips_command_t *command;
|
|
mask = splbio();
|
|
|
|
if(sc->state & IPS_OFFLINE){
|
|
splx(mask);
|
|
return EIO;
|
|
}
|
|
command = SLIST_FIRST(&sc->free_cmd_list);
|
|
if(!command || (sc->state & IPS_TIMEOUT)){
|
|
splx(mask);
|
|
if(flags & IPS_NOWAIT_FLAG)
|
|
return EAGAIN;
|
|
return ips_add_waiting_command(sc, callback, data, flags);
|
|
}
|
|
SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
|
|
(sc->used_commands)++;
|
|
splx(mask);
|
|
clear_ips_command(command);
|
|
bzero(command->command_buffer, IPS_COMMAND_LEN);
|
|
command->arg = data;
|
|
return callback(command);
|
|
}
|
|
|
|
/* adds a command back to the free command queue */
|
|
void ips_insert_free_cmd(ips_softc_t *sc, ips_command_t *command)
|
|
{
|
|
intrmask_t mask;
|
|
mask = splbio();
|
|
|
|
if (sema_value(&command->cmd_sema) != 0)
|
|
panic("ips: command returned non-zero semaphore");
|
|
|
|
SLIST_INSERT_HEAD(&sc->free_cmd_list, command, next);
|
|
(sc->used_commands)--;
|
|
splx(mask);
|
|
if(!(sc->state & IPS_TIMEOUT))
|
|
ips_run_waiting_command(sc);
|
|
}
|
|
static const char* ips_diskdev_statename(u_int8_t state)
|
|
{
|
|
static char statebuf[20];
|
|
switch(state){
|
|
case IPS_LD_OFFLINE:
|
|
return("OFFLINE");
|
|
break;
|
|
case IPS_LD_OKAY:
|
|
return("OK");
|
|
break;
|
|
case IPS_LD_DEGRADED:
|
|
return("DEGRADED");
|
|
break;
|
|
case IPS_LD_FREE:
|
|
return("FREE");
|
|
break;
|
|
case IPS_LD_SYS:
|
|
return("SYS");
|
|
break;
|
|
case IPS_LD_CRS:
|
|
return("CRS");
|
|
break;
|
|
}
|
|
sprintf(statebuf,"UNKNOWN(0x%02x)", state);
|
|
return(statebuf);
|
|
}
|
|
|
|
static int ips_diskdev_init(ips_softc_t *sc)
|
|
{
|
|
int i;
|
|
for(i=0; i < IPS_MAX_NUM_DRIVES; i++){
|
|
if(sc->drives[i].state == IPS_LD_FREE) continue;
|
|
device_printf(sc->dev, "Logical Drive %d: RAID%d sectors: %u, state %s\n",
|
|
i, sc->drives[i].raid_lvl,
|
|
sc->drives[i].sector_count,
|
|
ips_diskdev_statename(sc->drives[i].state));
|
|
if(sc->drives[i].state == IPS_LD_OKAY ||
|
|
sc->drives[i].state == IPS_LD_DEGRADED){
|
|
sc->diskdev[i] = device_add_child(sc->dev, NULL, -1);
|
|
device_set_ivars(sc->diskdev[i],(void *)(uintptr_t) i);
|
|
}
|
|
}
|
|
if(bus_generic_attach(sc->dev)){
|
|
device_printf(sc->dev, "Attaching bus failed\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ips_diskdev_free(ips_softc_t *sc)
|
|
{
|
|
int i;
|
|
int error = 0;
|
|
for(i = 0; i < IPS_MAX_NUM_DRIVES; i++){
|
|
if(sc->diskdev[i])
|
|
error = device_delete_child(sc->dev, sc->diskdev[i]);
|
|
if(error)
|
|
return error;
|
|
}
|
|
bus_generic_detach(sc->dev);
|
|
return 0;
|
|
}
|
|
|
|
/* ips_timeout is periodically called to make sure no commands sent
|
|
* to the card have become stuck. If it finds a stuck command, it
|
|
* sets a flag so the driver won't start any more commands and then
|
|
* is periodically called to see if all outstanding commands have
|
|
* either finished or timed out. Once timed out, an attempt to
|
|
* reinitialize the card is made. If that fails, the driver gives
|
|
* up and declares the card dead. */
|
|
static void ips_timeout(void *arg)
|
|
{
|
|
intrmask_t mask;
|
|
ips_softc_t *sc = arg;
|
|
int i, state = 0;
|
|
ips_command_t *command;
|
|
command = &sc->commandarray[0];
|
|
mask = splbio();
|
|
for(i = 0; i < sc->max_cmds; i++){
|
|
if(!command[i].timeout){
|
|
continue;
|
|
}
|
|
command[i].timeout--;
|
|
if(!command[i].timeout){
|
|
if(!(sc->state & IPS_TIMEOUT)){
|
|
sc->state |= IPS_TIMEOUT;
|
|
device_printf(sc->dev, "WARNING: command timeout. Adapter is in toaster mode, resetting to known state\n");
|
|
}
|
|
command[i].status.value = IPS_ERROR_STATUS;
|
|
command[i].callback(&command[i]);
|
|
/* hmm, this should be enough cleanup */
|
|
} else
|
|
state = 1;
|
|
}
|
|
if(!state && (sc->state & IPS_TIMEOUT)){
|
|
if(sc->ips_adapter_reinit(sc, 1)){
|
|
device_printf(sc->dev, "AIEE! adapter reset failed, giving up and going home! Have a nice day.\n");
|
|
sc->state |= IPS_OFFLINE;
|
|
sc->state &= ~IPS_TIMEOUT;
|
|
/* Grr, I hate this solution. I run waiting commands
|
|
one at a time and error them out just before they
|
|
would go to the card. This sucks. */
|
|
} else
|
|
sc->state &= ~IPS_TIMEOUT;
|
|
ips_run_waiting_command(sc);
|
|
}
|
|
if (sc->state != IPS_OFFLINE)
|
|
sc->timer = timeout(ips_timeout, sc, 10*hz);
|
|
splx(mask);
|
|
}
|
|
|
|
/* check card and initialize it */
|
|
int ips_adapter_init(ips_softc_t *sc)
|
|
{
|
|
int i;
|
|
DEVICE_PRINTF(1,sc->dev, "initializing\n");
|
|
|
|
if (bus_dma_tag_create( /* parent */ sc->adapter_dmatag,
|
|
/* alignemnt */ 1,
|
|
/* boundary */ 0,
|
|
/* lowaddr */ BUS_SPACE_MAXADDR_32BIT,
|
|
/* highaddr */ BUS_SPACE_MAXADDR,
|
|
/* filter */ NULL,
|
|
/* filterarg */ NULL,
|
|
/* maxsize */ IPS_COMMAND_LEN +
|
|
IPS_MAX_SG_LEN,
|
|
/* numsegs */ 1,
|
|
/* maxsegsize*/ IPS_COMMAND_LEN +
|
|
IPS_MAX_SG_LEN,
|
|
/* flags */ 0,
|
|
/* lockfunc */ busdma_lock_mutex,
|
|
/* lockarg */ &Giant,
|
|
&sc->command_dmatag) != 0) {
|
|
device_printf(sc->dev, "can't alloc command dma tag\n");
|
|
goto error;
|
|
}
|
|
if (bus_dma_tag_create( /* parent */ sc->adapter_dmatag,
|
|
/* alignemnt */ 1,
|
|
/* boundary */ 0,
|
|
/* lowaddr */ BUS_SPACE_MAXADDR_32BIT,
|
|
/* highaddr */ BUS_SPACE_MAXADDR,
|
|
/* filter */ NULL,
|
|
/* filterarg */ NULL,
|
|
/* maxsize */ IPS_MAX_IOBUF_SIZE,
|
|
/* numsegs */ IPS_MAX_SG_ELEMENTS,
|
|
/* maxsegsize*/ IPS_MAX_IOBUF_SIZE,
|
|
/* flags */ 0,
|
|
/* lockfunc */ busdma_lock_mutex,
|
|
/* lockarg */ &Giant,
|
|
&sc->sg_dmatag) != 0) {
|
|
device_printf(sc->dev, "can't alloc SG dma tag\n");
|
|
goto error;
|
|
}
|
|
/* create one command buffer until we know how many commands this card
|
|
can handle */
|
|
sc->max_cmds = 1;
|
|
ips_cmdqueue_init(sc);
|
|
callout_handle_init(&sc->timer);
|
|
|
|
if(sc->ips_adapter_reinit(sc, 0))
|
|
goto error;
|
|
|
|
/* initialize ffdc values */
|
|
microtime(&sc->ffdc_resettime);
|
|
sc->ffdc_resetcount = 1;
|
|
if ((i = ips_ffdc_reset(sc)) != 0) {
|
|
device_printf(sc->dev, "failed to send ffdc reset to device (%d)\n", i);
|
|
goto error;
|
|
}
|
|
if ((i = ips_get_adapter_info(sc)) != 0) {
|
|
device_printf(sc->dev, "failed to get adapter configuration data from device (%d)\n", i);
|
|
goto error;
|
|
}
|
|
ips_update_nvram(sc); /* no error check as failure doesn't matter */
|
|
if(sc->adapter_type > 0 && sc->adapter_type <= IPS_ADAPTER_MAX_T){
|
|
device_printf(sc->dev, "adapter type: %s\n", ips_adapter_name[sc->adapter_type]);
|
|
}
|
|
if ((i = ips_get_drive_info(sc)) != 0) {
|
|
device_printf(sc->dev, "failed to get drive configuration data from device (%d)\n", i);
|
|
goto error;
|
|
}
|
|
|
|
ips_cmdqueue_free(sc);
|
|
if(sc->adapter_info.max_concurrent_cmds)
|
|
sc->max_cmds = min(128, sc->adapter_info.max_concurrent_cmds);
|
|
else
|
|
sc->max_cmds = 32;
|
|
if(ips_cmdqueue_init(sc)){
|
|
device_printf(sc->dev, "failed to initialize command buffers\n");
|
|
goto error;
|
|
}
|
|
sc->device_file = make_dev(&ips_cdevsw, device_get_unit(sc->dev), UID_ROOT, GID_OPERATOR,
|
|
S_IRUSR | S_IWUSR, "ips%d", device_get_unit(sc->dev));
|
|
sc->device_file->si_drv1 = sc;
|
|
ips_diskdev_init(sc);
|
|
sc->timer = timeout(ips_timeout, sc, 10*hz);
|
|
return 0;
|
|
|
|
error:
|
|
ips_adapter_free(sc);
|
|
return ENXIO;
|
|
}
|
|
|
|
/* see if we should reinitialize the card and wait for it to timeout or complete initialization */
|
|
int ips_morpheus_reinit(ips_softc_t *sc, int force)
|
|
{
|
|
u_int32_t tmp;
|
|
int i;
|
|
|
|
tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
|
|
if(!force && (ips_read_4(sc, MORPHEUS_REG_OMR0) >= IPS_POST1_OK) &&
|
|
(ips_read_4(sc, MORPHEUS_REG_OMR1) != 0xdeadbeef) && !tmp){
|
|
ips_write_4(sc, MORPHEUS_REG_OIMR, 0);
|
|
return 0;
|
|
}
|
|
ips_write_4(sc, MORPHEUS_REG_OIMR, 0xff);
|
|
ips_read_4(sc, MORPHEUS_REG_OIMR);
|
|
|
|
device_printf(sc->dev, "resetting adapter, this may take up to 5 minutes\n");
|
|
ips_write_4(sc, MORPHEUS_REG_IDR, 0x80000000);
|
|
DELAY(5000000);
|
|
pci_read_config(sc->dev, 0, 4);
|
|
|
|
tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
|
|
for(i = 0; i < 45 && !(tmp & MORPHEUS_BIT_POST1); i++){
|
|
DELAY(1000000);
|
|
DEVICE_PRINTF(2, sc->dev, "post1: %d\n", i);
|
|
tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
|
|
}
|
|
if(tmp & MORPHEUS_BIT_POST1)
|
|
ips_write_4(sc, MORPHEUS_REG_OISR, MORPHEUS_BIT_POST1);
|
|
|
|
if( i == 45 || ips_read_4(sc, MORPHEUS_REG_OMR0) < IPS_POST1_OK){
|
|
device_printf(sc->dev,"Adapter error during initialization.\n");
|
|
return 1;
|
|
}
|
|
for(i = 0; i < 240 && !(tmp & MORPHEUS_BIT_POST2); i++){
|
|
DELAY(1000000);
|
|
DEVICE_PRINTF(2, sc->dev, "post2: %d\n", i);
|
|
tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
|
|
}
|
|
if(tmp & MORPHEUS_BIT_POST2)
|
|
ips_write_4(sc, MORPHEUS_REG_OISR, MORPHEUS_BIT_POST2);
|
|
|
|
if(i == 240 || !ips_read_4(sc, MORPHEUS_REG_OMR1)){
|
|
device_printf(sc->dev, "adapter failed config check\n");
|
|
return 1;
|
|
}
|
|
ips_write_4(sc, MORPHEUS_REG_OIMR, 0);
|
|
if(force && ips_clear_adapter(sc)){
|
|
device_printf(sc->dev, "adapter clear failed\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* clean up so we can unload the driver. */
|
|
int ips_adapter_free(ips_softc_t *sc)
|
|
{
|
|
int error = 0;
|
|
intrmask_t mask;
|
|
if(sc->state & IPS_DEV_OPEN)
|
|
return EBUSY;
|
|
if((error = ips_diskdev_free(sc)))
|
|
return error;
|
|
if(ips_cmdqueue_free(sc)){
|
|
device_printf(sc->dev,
|
|
"trying to exit when command queue is not empty!\n");
|
|
return EBUSY;
|
|
}
|
|
DEVICE_PRINTF(1, sc->dev, "free\n");
|
|
mask = splbio();
|
|
untimeout(ips_timeout, sc, sc->timer);
|
|
splx(mask);
|
|
|
|
if(sc->sg_dmatag)
|
|
bus_dma_tag_destroy(sc->sg_dmatag);
|
|
if(sc->command_dmatag)
|
|
bus_dma_tag_destroy(sc->command_dmatag);
|
|
if(sc->device_file)
|
|
destroy_dev(sc->device_file);
|
|
return 0;
|
|
}
|
|
|
|
void ips_morpheus_intr(void *void_sc)
|
|
{
|
|
ips_softc_t *sc = (ips_softc_t *)void_sc;
|
|
u_int32_t oisr, iisr;
|
|
int cmdnumber;
|
|
ips_cmd_status_t status;
|
|
|
|
iisr =ips_read_4(sc, MORPHEUS_REG_IISR);
|
|
oisr =ips_read_4(sc, MORPHEUS_REG_OISR);
|
|
PRINTF(9,"interrupt registers in:%x out:%x\n",iisr, oisr);
|
|
if(!(oisr & MORPHEUS_BIT_CMD_IRQ)){
|
|
DEVICE_PRINTF(2,sc->dev, "got a non-command irq\n");
|
|
return;
|
|
}
|
|
while((status.value = ips_read_4(sc, MORPHEUS_REG_OQPR)) != 0xffffffff){
|
|
cmdnumber = status.fields.command_id;
|
|
sc->commandarray[cmdnumber].status.value = status.value;
|
|
sc->commandarray[cmdnumber].timeout = 0;
|
|
sc->commandarray[cmdnumber].callback(&(sc->commandarray[cmdnumber]));
|
|
|
|
|
|
DEVICE_PRINTF(9,sc->dev, "got command %d\n", cmdnumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void ips_issue_morpheus_cmd(ips_command_t *command)
|
|
{
|
|
intrmask_t mask = splbio();
|
|
/* hmmm, is there a cleaner way to do this? */
|
|
if(command->sc->state & IPS_OFFLINE){
|
|
splx(mask);
|
|
command->status.value = IPS_ERROR_STATUS;
|
|
command->callback(command);
|
|
return;
|
|
}
|
|
command->timeout = 10;
|
|
ips_write_4(command->sc, MORPHEUS_REG_IQPR, command->command_phys_addr);
|
|
splx(mask);
|
|
}
|
|
|
|
static void ips_copperhead_queue_callback(void *queueptr, bus_dma_segment_t *segments,int segnum, int error)
|
|
{
|
|
ips_copper_queue_t *queue = queueptr;
|
|
if(error){
|
|
return;
|
|
}
|
|
queue->base_phys_addr = segments[0].ds_addr;
|
|
}
|
|
|
|
static int ips_copperhead_queue_init(ips_softc_t *sc)
|
|
{
|
|
int error;
|
|
bus_dma_tag_t dmatag;
|
|
bus_dmamap_t dmamap;
|
|
if (bus_dma_tag_create( /* parent */ sc->adapter_dmatag,
|
|
/* alignemnt */ 1,
|
|
/* boundary */ 0,
|
|
/* lowaddr */ BUS_SPACE_MAXADDR_32BIT,
|
|
/* highaddr */ BUS_SPACE_MAXADDR,
|
|
/* filter */ NULL,
|
|
/* filterarg */ NULL,
|
|
/* maxsize */ sizeof(ips_copper_queue_t),
|
|
/* numsegs */ 1,
|
|
/* maxsegsize*/ sizeof(ips_copper_queue_t),
|
|
/* flags */ 0,
|
|
/* lockfunc */ busdma_lock_mutex,
|
|
/* lockarg */ &Giant,
|
|
&dmatag) != 0) {
|
|
device_printf(sc->dev, "can't alloc dma tag for statue queue\n");
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
if(bus_dmamem_alloc(dmatag, (void *)&(sc->copper_queue),
|
|
BUS_DMA_NOWAIT, &dmamap)){
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
bzero(sc->copper_queue, sizeof(ips_copper_queue_t));
|
|
sc->copper_queue->dmatag = dmatag;
|
|
sc->copper_queue->dmamap = dmamap;
|
|
sc->copper_queue->nextstatus = 1;
|
|
bus_dmamap_load(dmatag, dmamap,
|
|
&(sc->copper_queue->status[0]), IPS_MAX_CMD_NUM * 4,
|
|
ips_copperhead_queue_callback, sc->copper_queue,
|
|
BUS_DMA_NOWAIT);
|
|
if(sc->copper_queue->base_phys_addr == 0){
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
ips_write_4(sc, COPPER_REG_SQSR, sc->copper_queue->base_phys_addr);
|
|
ips_write_4(sc, COPPER_REG_SQER, sc->copper_queue->base_phys_addr +
|
|
IPS_MAX_CMD_NUM * 4);
|
|
ips_write_4(sc, COPPER_REG_SQHR, sc->copper_queue->base_phys_addr + 4);
|
|
ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr);
|
|
|
|
|
|
return 0;
|
|
exit:
|
|
bus_dmamem_free(dmatag, sc->copper_queue, dmamap);
|
|
bus_dma_tag_destroy(dmatag);
|
|
return error;
|
|
}
|
|
|
|
/* see if we should reinitialize the card and wait for it to timeout or complete initialization FIXME */
|
|
int ips_copperhead_reinit(ips_softc_t *sc, int force)
|
|
{
|
|
int i, j;
|
|
u_int32_t postcode = 0, configstatus = 0;
|
|
ips_write_1(sc, COPPER_REG_SCPR, 0x80);
|
|
ips_write_1(sc, COPPER_REG_SCPR, 0);
|
|
device_printf(sc->dev, "reinitializing adapter, this could take several minutes.\n");
|
|
for(j = 0; j < 2; j++){
|
|
postcode <<= 8;
|
|
for(i = 0; i < 45; i++){
|
|
if(ips_read_1(sc, COPPER_REG_HISR) & COPPER_GHI_BIT){
|
|
postcode |= ips_read_1(sc, COPPER_REG_ISPR);
|
|
ips_write_1(sc, COPPER_REG_HISR,
|
|
COPPER_GHI_BIT);
|
|
break;
|
|
} else
|
|
DELAY(1000000);
|
|
}
|
|
if(i == 45)
|
|
return 1;
|
|
}
|
|
for(j = 0; j < 2; j++){
|
|
configstatus <<= 8;
|
|
for(i = 0; i < 240; i++){
|
|
if(ips_read_1(sc, COPPER_REG_HISR) & COPPER_GHI_BIT){
|
|
configstatus |= ips_read_1(sc, COPPER_REG_ISPR);
|
|
ips_write_1(sc, COPPER_REG_HISR,
|
|
COPPER_GHI_BIT);
|
|
break;
|
|
} else
|
|
DELAY(1000000);
|
|
}
|
|
if(i == 240)
|
|
return 1;
|
|
}
|
|
for(i = 0; i < 240; i++){
|
|
if(!(ips_read_1(sc, COPPER_REG_CBSP) & COPPER_OP_BIT)){
|
|
break;
|
|
} else
|
|
DELAY(1000000);
|
|
}
|
|
if(i == 240)
|
|
return 1;
|
|
ips_write_2(sc, COPPER_REG_CCCR, 0x1000 | COPPER_ILE_BIT);
|
|
ips_write_1(sc, COPPER_REG_SCPR, COPPER_EBM_BIT);
|
|
ips_copperhead_queue_init(sc);
|
|
ips_write_1(sc, COPPER_REG_HISR, COPPER_GHI_BIT);
|
|
i = ips_read_1(sc, COPPER_REG_SCPR);
|
|
ips_write_1(sc, COPPER_REG_HISR, COPPER_EI_BIT);
|
|
if(!configstatus){
|
|
device_printf(sc->dev, "adapter initialization failed\n");
|
|
return 1;
|
|
}
|
|
if(force && ips_clear_adapter(sc)){
|
|
device_printf(sc->dev, "adapter clear failed\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
static u_int32_t ips_copperhead_cmd_status(ips_softc_t *sc)
|
|
{
|
|
intrmask_t mask;
|
|
u_int32_t value;
|
|
int statnum = sc->copper_queue->nextstatus++;
|
|
if(sc->copper_queue->nextstatus == IPS_MAX_CMD_NUM)
|
|
sc->copper_queue->nextstatus = 0;
|
|
mask = splbio();
|
|
value = sc->copper_queue->status[statnum];
|
|
ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr +
|
|
4 * statnum);
|
|
splx(mask);
|
|
return value;
|
|
}
|
|
|
|
|
|
void ips_copperhead_intr(void *void_sc)
|
|
{
|
|
ips_softc_t *sc = (ips_softc_t *)void_sc;
|
|
int cmdnumber;
|
|
ips_cmd_status_t status;
|
|
|
|
while(ips_read_1(sc, COPPER_REG_HISR) & COPPER_SCE_BIT){
|
|
status.value = ips_copperhead_cmd_status(sc);
|
|
cmdnumber = status.fields.command_id;
|
|
sc->commandarray[cmdnumber].status.value = status.value;
|
|
sc->commandarray[cmdnumber].timeout = 0;
|
|
sc->commandarray[cmdnumber].callback(&(sc->commandarray[cmdnumber]));
|
|
PRINTF(9, "ips: got command %d\n", cmdnumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void ips_issue_copperhead_cmd(ips_command_t *command)
|
|
{
|
|
int i;
|
|
intrmask_t mask = splbio();
|
|
/* hmmm, is there a cleaner way to do this? */
|
|
if(command->sc->state & IPS_OFFLINE){
|
|
splx(mask);
|
|
command->status.value = IPS_ERROR_STATUS;
|
|
command->callback(command);
|
|
return;
|
|
}
|
|
command->timeout = 10;
|
|
for(i = 0; ips_read_4(command->sc, COPPER_REG_CCCR) & COPPER_SEM_BIT;
|
|
i++ ){
|
|
if( i == 20){
|
|
printf("sem bit still set, can't send a command\n");
|
|
splx(mask);
|
|
return;
|
|
}
|
|
DELAY(500);/* need to do a delay here */
|
|
}
|
|
ips_write_4(command->sc, COPPER_REG_CCSAR, command->command_phys_addr);
|
|
ips_write_2(command->sc, COPPER_REG_CCCR, COPPER_CMD_START);
|
|
splx(mask);
|
|
}
|
|
|