#include <sys/types.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <dev/ips/ipsreg.h>
#include <dev/ips/ips.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_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)",
"ServeRAID 7t",
"ServeRAID 7k",
"ServeRAID 7M"
};
static int ips_open(struct cdev *dev, int flags, int fmt, struct thread *td)
{
ips_softc_t *sc = dev->si_drv1;
mtx_lock(&sc->queue_mtx);
sc->state |= IPS_DEV_OPEN;
mtx_unlock(&sc->queue_mtx);
return 0;
}
static int ips_close(struct cdev *dev, int flags, int fmt, struct thread *td)
{
ips_softc_t *sc = dev->si_drv1;
mtx_lock(&sc->queue_mtx);
sc->state &= ~IPS_DEV_OPEN;
mtx_unlock(&sc->queue_mtx);
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;
}
static int ips_cmdqueue_free(ips_softc_t *sc)
{
int i, error = -1;
ips_command_t *command;
if(!sc->used_commands){
for(i = 0; i < sc->max_cmds; i++){
command = &sc->commandarray[i];
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);
if (command->data_dmamap != NULL)
bus_dmamap_destroy(command->data_dmatag,
command->data_dmamap);
}
error = 0;
sc->state |= IPS_OFFLINE;
}
sc->staticcmd = NULL;
free(sc->commandarray, M_DEVBUF);
return error;
}
static int ips_cmdqueue_init(ips_softc_t *sc)
{
int i;
ips_command_t *command;
sc->commandarray = (ips_command_t *)malloc(sizeof(ips_command_t) *
sc->max_cmds, M_DEVBUF, M_NOWAIT|M_ZERO);
if (sc->commandarray == NULL)
return (ENOMEM);
SLIST_INIT(&sc->free_cmd_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;
}
if (i != 0) {
command->data_dmatag = sc->sg_dmatag;
if (bus_dmamap_create(command->data_dmatag, 0,
&command->data_dmamap))
goto error;
SLIST_INSERT_HEAD(&sc->free_cmd_list, command, next);
} else
sc->staticcmd = command;
}
sc->state &= ~IPS_OFFLINE;
return 0;
error:
ips_cmdqueue_free(sc);
return ENOMEM;
}
int ips_get_free_cmd(ips_softc_t *sc, ips_command_t **cmd, unsigned long flags)
{
ips_command_t *command;
if(sc->state & IPS_OFFLINE){
return EIO;
}
if ((flags & IPS_STATIC_FLAG) == 0) {
command = SLIST_FIRST(&sc->free_cmd_list);
if(!command || (sc->state & IPS_TIMEOUT)){
return EBUSY;
}
SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
(sc->used_commands)++;
} else {
if (sc->state & IPS_STATIC_BUSY)
return EAGAIN;
command = sc->staticcmd;
sc->state |= IPS_STATIC_BUSY;
}
clear_ips_command(command);
bzero(command->command_buffer, IPS_COMMAND_LEN);
*cmd = command;
return 0;
}
void ips_insert_free_cmd(ips_softc_t *sc, ips_command_t *command)
{
if (sema_value(&sc->cmd_sema) != 0)
panic("ips: command returned non-zero semaphore");
if (command != sc->staticcmd) {
SLIST_INSERT_HEAD(&sc->free_cmd_list, command, next);
(sc->used_commands)--;
} else {
sc->state &= ~IPS_STATIC_BUSY;
}
}
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, DEVICE_UNIT_ANY);
device_set_ivars(sc->diskdev[i],(void *)(uintptr_t) i);
}
}
bus_attach_children(sc->dev);
return 0;
}
static int ips_diskdev_free(ips_softc_t *sc)
{
return (bus_generic_detach(sc->dev));
}
static void ips_timeout(void *arg)
{
ips_softc_t *sc = arg;
int i, state = 0;
ips_command_t *command;
mtx_assert(&sc->queue_mtx, MA_OWNED);
command = &sc->commandarray[0];
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");
}
ips_set_error(&command[i], ETIMEDOUT);
command[i].callback(&command[i]);
} 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;
} else
sc->state &= ~IPS_TIMEOUT;
}
if (sc->state != IPS_OFFLINE)
callout_reset(&sc->timer, 10 * hz, ips_timeout, sc);
}
int ips_adapter_init(ips_softc_t *sc)
{
int i;
DEVICE_PRINTF(1,sc->dev, "initializing\n");
if (bus_dma_tag_create( sc->adapter_dmatag,
1,
0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL,
NULL,
IPS_COMMAND_LEN +
IPS_MAX_SG_LEN,
1,
IPS_COMMAND_LEN +
IPS_MAX_SG_LEN,
0,
NULL,
NULL,
&sc->command_dmatag) != 0) {
device_printf(sc->dev, "can't alloc command dma tag\n");
goto error;
}
if (bus_dma_tag_create( sc->adapter_dmatag,
1,
0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL,
NULL,
IPS_MAX_IOBUF_SIZE,
IPS_MAX_SG_ELEMENTS,
IPS_MAX_IOBUF_SIZE,
0,
busdma_lock_mutex,
&sc->queue_mtx,
&sc->sg_dmatag) != 0) {
device_printf(sc->dev, "can't alloc SG dma tag\n");
goto error;
}
sc->max_cmds = 1;
ips_cmdqueue_init(sc);
if(sc->ips_adapter_reinit(sc, 0))
goto error;
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);
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);
callout_reset(&sc->timer, 10 * hz, ips_timeout, sc);
return 0;
error:
ips_adapter_free(sc);
return ENXIO;
}
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);
ips_read_4(sc, MORPHEUS_REG_OIMR);
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;
}
int ips_adapter_free(ips_softc_t *sc)
{
int error = 0;
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");
callout_drain(&sc->timer);
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;
}
static __inline int ips_morpheus_check_intr(ips_softc_t *sc)
{
int cmdnumber;
ips_cmd_status_t status;
ips_command_t *command;
int found = 0;
u_int32_t oisr;
oisr = ips_read_4(sc, MORPHEUS_REG_OISR);
PRINTF(9, "interrupt registers out:%x\n", oisr);
if(!(oisr & MORPHEUS_BIT_CMD_IRQ)){
DEVICE_PRINTF(2,sc->dev, "got a non-command irq\n");
return (0);
}
while((status.value = ips_read_4(sc, MORPHEUS_REG_OQPR)) != 0xffffffff){
cmdnumber = status.fields.command_id;
command = &sc->commandarray[cmdnumber];
command->status.value = status.value;
command->timeout = 0;
command->callback(command);
found = 1;
}
return (found);
}
void ips_morpheus_intr(void *void_sc)
{
ips_softc_t *sc = void_sc;
mtx_lock(&sc->queue_mtx);
ips_morpheus_check_intr(sc);
mtx_unlock(&sc->queue_mtx);
}
void ips_morpheus_poll(ips_command_t *command)
{
uint32_t ts;
ts = time_second + command->timeout;
while ((command->timeout != 0)
&& (ips_morpheus_check_intr(command->sc) == 0)
&& (ts > time_second))
DELAY(1000);
}
void ips_issue_morpheus_cmd(ips_command_t *command)
{
if(command->sc->state & IPS_OFFLINE){
ips_set_error(command, EINVAL);
command->callback(command);
return;
}
command->timeout = 10;
ips_write_4(command->sc, MORPHEUS_REG_IQPR, command->command_phys_addr);
}
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( sc->adapter_dmatag,
1,
0,
BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR,
NULL,
NULL,
sizeof(ips_copper_queue_t),
1,
sizeof(ips_copper_queue_t),
0,
NULL,
NULL,
&dmatag) != 0) {
device_printf(sc->dev, "can't alloc dma tag for statue queue\n");
error = ENOMEM;
return error;
}
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:
if (sc->copper_queue != NULL)
bus_dmamem_free(dmatag, sc->copper_queue, dmamap);
bus_dma_tag_destroy(dmatag);
return error;
}
int ips_copperhead_reinit(ips_softc_t *sc, int force)
{
int i, j;
u_int32_t 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++){
for(i = 0; i < 45; i++){
if(ips_read_1(sc, COPPER_REG_HISR) & COPPER_GHI_BIT){
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)
{
u_int32_t value;
int statnum = sc->copper_queue->nextstatus++;
if(sc->copper_queue->nextstatus == IPS_MAX_CMD_NUM)
sc->copper_queue->nextstatus = 0;
value = sc->copper_queue->status[statnum];
ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr +
4 * statnum);
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;
mtx_lock(&sc->queue_mtx);
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);
}
mtx_unlock(&sc->queue_mtx);
return;
}
void ips_issue_copperhead_cmd(ips_command_t *command)
{
int i;
if(command->sc->state & IPS_OFFLINE){
ips_set_error(command, EINVAL);
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");
return;
}
DELAY(500);
}
ips_write_4(command->sc, COPPER_REG_CCSAR, command->command_phys_addr);
ips_write_2(command->sc, COPPER_REG_CCCR, COPPER_CMD_START);
}
void ips_copperhead_poll(ips_command_t *command)
{
printf("ips: cmd polling not implemented for copperhead devices\n");
}