Many improvements that have been collected over time:

- Improve error handling for load operations.
- Fix a memory corruption bug when using certain linux management apps.
- Allocate all commands up front to avoid OOM deadlocks later on.
This commit is contained in:
Scott Long 2008-01-24 07:26:53 +00:00
parent 47dd70868b
commit 74018414bf
3 changed files with 146 additions and 43 deletions

View File

@ -139,10 +139,11 @@ static int amr_wait_command(struct amr_command *ac) __unused;
static int amr_mapcmd(struct amr_command *ac);
static void amr_unmapcmd(struct amr_command *ac);
static int amr_start(struct amr_command *ac);
static void amr_complete(void *context, int pending);
static void amr_complete(void *context, ac_qhead_t *head);
static void amr_setup_sg(void *arg, bus_dma_segment_t *segs, int nsegments, int error);
static void amr_setup_data(void *arg, bus_dma_segment_t *segs, int nsegments, int error);
static void amr_setup_ccb(void *arg, bus_dma_segment_t *segs, int nsegments, int error);
static void amr_abort_load(struct amr_command *ac);
/*
* Status monitoring
@ -207,10 +208,9 @@ amr_attach(struct amr_softc *sc)
/*
* Initialise per-controller queues.
*/
TAILQ_INIT(&sc->amr_completed);
TAILQ_INIT(&sc->amr_freecmds);
amr_init_qhead(&sc->amr_freecmds);
amr_init_qhead(&sc->amr_ready);
TAILQ_INIT(&sc->amr_cmd_clusters);
TAILQ_INIT(&sc->amr_ready);
bioq_init(&sc->amr_bioq);
debug(2, "queue init done");
@ -235,6 +235,11 @@ amr_attach(struct amr_softc *sc)
return(ENXIO);
#endif
/*
* Allocate initial commands.
*/
amr_alloccmd_cluster(sc);
/*
* Quiz controller for features and limits.
*/
@ -243,6 +248,12 @@ amr_attach(struct amr_softc *sc)
debug(2, "controller query complete");
/*
* preallocate the remaining commands.
*/
while (sc->amr_nextslot < sc->amr_maxio)
amr_alloccmd_cluster(sc);
/*
* Setup sysctls.
*/
@ -357,6 +368,18 @@ amr_init_sysctl(struct amr_softc *sc)
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->amr_dev)),
OID_AUTO, "allow_volume_configure", CTLFLAG_RW, &sc->amr_allow_vol_config, 0,
"");
SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->amr_dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->amr_dev)),
OID_AUTO, "nextslot", CTLFLAG_RD, &sc->amr_nextslot, 0,
"");
SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->amr_dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->amr_dev)),
OID_AUTO, "busyslots", CTLFLAG_RD, &sc->amr_busyslots, 0,
"");
SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->amr_dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->amr_dev)),
OID_AUTO, "maxio", CTLFLAG_RD, &sc->amr_maxio, 0,
"");
}
@ -646,8 +669,18 @@ amr_linux_ioctl_int(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag,
error = ENOIOCTL;
break;
} else {
if (len)
dp = malloc(len, M_AMR, M_WAITOK | M_ZERO);
/*
* Bug-for-bug compatibility with Linux!
* Some apps will send commands with inlen and outlen set to 0,
* even though they expect data to be transfered to them from the
* card. Linux accidentally allows this by allocating a 4KB
* buffer for the transfer anyways, but it then throws it away
* without copying it back to the app.
*/
if (!len)
len = 4096;
dp = malloc(len, M_AMR, M_WAITOK | M_ZERO);
if (ali.inlen) {
error = copyin((void *)(uintptr_t)mb->mb_physaddr, dp, len);
@ -793,8 +826,7 @@ amr_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, d_thread_t *
if (sc == NULL)
return (ENOENT);
return (amr_linux_ioctl_int(sc->amr_dev_t, cmd,
addr, 0, td));
return (amr_linux_ioctl_int(sc->amr_dev_t, cmd, addr, 0, td));
}
default:
debug(1, "unknown ioctl 0x%lx", cmd);
@ -956,13 +988,6 @@ amr_query_controller(struct amr_softc *sc)
int ldrv;
int status;
/*
* If we haven't found the real limit yet, let us have a couple of commands in
* order to be able to probe.
*/
if (sc->amr_maxio == 0)
sc->amr_maxio = 2;
/*
* Greater than 10 byte cdb support
*/
@ -1278,7 +1303,7 @@ amr_bio_command(struct amr_softc *sc, struct amr_command **acp)
int driveno;
int cmd;
*acp = NULL;
ac = NULL;
error = 0;
/* get a command */
@ -1340,6 +1365,7 @@ amr_bio_command(struct amr_softc *sc, struct amr_command **acp)
/* we fill in the s/g related data when the command is mapped */
*acp = ac;
return(error);
}
@ -1411,6 +1437,12 @@ amr_setup_polled_dmamap(void *arg, bus_dma_segment_t *segs, int nsegs, int err)
struct amr_softc *sc = ac->ac_sc;
int mb_channel;
if (err) {
device_printf(sc->amr_dev, "error %d in %s", err, __FUNCTION__);
ac->ac_status = AMR_STATUS_ABORTED;
return;
}
amr_setup_sg(arg, segs, nsegs, err);
/* for AMR_CMD_CONFIG Read/Write the s/g count goes elsewhere */
@ -1565,9 +1597,6 @@ amr_setup_sg(void *arg, bus_dma_segment_t *segs, int nsegments, int error)
debug_called(3);
if (error)
printf("amr_setup_sg: error %d\n", error);
/* get base address of s/g table */
sg = ac->ac_sg.sg32;
sg64 = ac->ac_sg.sg64;
@ -1610,6 +1639,12 @@ amr_setup_data(void *arg, bus_dma_segment_t *segs, int nsegs, int err)
struct amr_softc *sc = ac->ac_sc;
int mb_channel;
if (err) {
device_printf(sc->amr_dev, "error %d in %s", err, __FUNCTION__);
amr_abort_load(ac);
return;
}
amr_setup_sg(arg, segs, nsegs, err);
/* for AMR_CMD_CONFIG Read/Write the s/g count goes elsewhere */
@ -1640,6 +1675,12 @@ amr_setup_ccb(void *arg, bus_dma_segment_t *segs, int nsegs, int err)
struct amr_passthrough *ap = &ac->ac_ccb->ccb_pthru;
struct amr_ext_passthrough *aep = &ac->ac_ccb->ccb_epthru;
if (err) {
device_printf(sc->amr_dev, "error %d in %s", err, __FUNCTION__);
amr_abort_load(ac);
return;
}
/* Set up the mailbox portion of the command to point at the ccb */
ac->ac_mailbox.mb_nsgelem = 0;
ac->ac_mailbox.mb_physaddr = ac->ac_ccb_busaddr;
@ -1729,6 +1770,23 @@ amr_unmapcmd(struct amr_command *ac)
}
}
static void
amr_abort_load(struct amr_command *ac)
{
ac_qhead_t head;
struct amr_softc *sc = ac->ac_sc;
mtx_assert(&sc->amr_list_lock, MA_OWNED);
ac->ac_status = AMR_STATUS_ABORTED;
amr_init_qhead(&head);
amr_enqueue_completed(ac, &head);
mtx_unlock(&sc->amr_list_lock);
amr_complete(sc, &head);
mtx_lock(&sc->amr_list_lock);
}
/********************************************************************************
* Take a command and give it to the controller, returns 0 if successful, or
* EBUSY if the command should be retried later.
@ -1774,6 +1832,7 @@ amr_start(struct amr_command *ac)
int
amr_done(struct amr_softc *sc)
{
ac_qhead_t head;
struct amr_command *ac;
struct amr_mailbox mbox;
int i, idx, result;
@ -1782,6 +1841,7 @@ amr_done(struct amr_softc *sc)
/* See if there's anything for us to do */
result = 0;
amr_init_qhead(&head);
/* loop collecting completed commands */
for (;;) {
@ -1803,7 +1863,7 @@ amr_done(struct amr_softc *sc)
/* save status for later use */
ac->ac_status = mbox.mb_status;
amr_enqueue_completed(ac);
amr_enqueue_completed(ac, &head);
debug(3, "completed command with status %x", mbox.mb_status);
} else {
device_printf(sc->amr_dev, "bad slot %d completed\n", idx);
@ -1814,7 +1874,7 @@ amr_done(struct amr_softc *sc)
}
/* handle completion and timeouts */
amr_complete(sc, 0);
amr_complete(sc, &head);
return(result);
}
@ -1824,7 +1884,7 @@ amr_done(struct amr_softc *sc)
*/
static void
amr_complete(void *context, int pending)
amr_complete(void *context, ac_qhead_t *head)
{
struct amr_softc *sc = (struct amr_softc *)context;
struct amr_command *ac;
@ -1833,7 +1893,7 @@ amr_complete(void *context, int pending)
/* pull completed commands off the queue */
for (;;) {
ac = amr_dequeue_completed(sc);
ac = amr_dequeue_completed(sc, head);
if (ac == NULL)
break;
@ -1893,10 +1953,6 @@ amr_alloccmd(struct amr_softc *sc)
debug_called(3);
ac = amr_dequeue_free(sc);
if (ac == NULL) {
amr_alloccmd_cluster(sc);
ac = amr_dequeue_free(sc);
}
if (ac == NULL) {
sc->amr_state |= AMR_STATE_QUEUE_FRZN;
return(NULL);
@ -1909,6 +1965,7 @@ amr_alloccmd(struct amr_softc *sc)
ac->ac_bio = NULL;
ac->ac_data = NULL;
ac->ac_complete = NULL;
ac->ac_retries = 0;
ac->ac_tag = NULL;
ac->ac_datamap = NULL;
return(ac);
@ -1935,12 +1992,21 @@ amr_alloccmd_cluster(struct amr_softc *sc)
struct amr_command *ac;
int i, nextslot;
/*
* If we haven't found the real limit yet, let us have a couple of
* commands in order to be able to probe.
*/
if (sc->amr_maxio == 0)
sc->amr_maxio = 2;
if (sc->amr_nextslot > sc->amr_maxio)
return;
acc = malloc(AMR_CMD_CLUSTERSIZE, M_AMR, M_NOWAIT | M_ZERO);
if (acc != NULL) {
nextslot = sc->amr_nextslot;
mtx_lock(&sc->amr_list_lock);
TAILQ_INSERT_TAIL(&sc->amr_cmd_clusters, acc, acc_link);
mtx_unlock(&sc->amr_list_lock);
for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++) {
ac = &acc->acc_command[i];
ac->ac_sc = sc;
@ -1989,6 +2055,8 @@ amr_freecmd_cluster(struct amr_command_cluster *acc)
int i;
for (i = 0; i < AMR_CMD_CLUSTERCOUNT; i++) {
if (acc->acc_command[i].ac_sc == NULL)
break;
bus_dmamap_destroy(sc->amr_buffer_dmat, acc->acc_command[i].ac_dmamap);
if (AMR_IS_SG64(sc))
bus_dmamap_destroy(sc->amr_buffer64_dmat, acc->acc_command[i].ac_dma64map);
@ -2009,6 +2077,8 @@ static int
amr_quartz_submit_command(struct amr_command *ac)
{
struct amr_softc *sc = ac->ac_sc;
static struct timeval lastfail;
static int curfail;
int i = 0;
mtx_lock(&sc->amr_hw_lock);
@ -2016,6 +2086,12 @@ amr_quartz_submit_command(struct amr_command *ac)
DELAY(1);
if (sc->amr_mailbox->mb_busy) {
mtx_unlock(&sc->amr_hw_lock);
if (ac->ac_retries++ > 1000) {
if (ppsratecheck(&lastfail, &curfail, 1))
device_printf(sc->amr_dev, "Too many retries on command %p. "
"Controller is likely dead\n", ac);
ac->ac_retries = 0;
}
return (EBUSY);
}
@ -2040,10 +2116,18 @@ static int
amr_std_submit_command(struct amr_command *ac)
{
struct amr_softc *sc = ac->ac_sc;
static struct timeval lastfail;
static int curfail;
mtx_lock(&sc->amr_hw_lock);
if (AMR_SGET_MBSTAT(sc) & AMR_SMBOX_BUSYFLAG) {
mtx_unlock(&sc->amr_hw_lock);
if (ac->ac_retries++ > 1000) {
if (ppsratecheck(&lastfail, &curfail, 1))
device_printf(sc->amr_dev, "Too many retries on command %p. "
"Controller is likely dead\n", ac);
ac->ac_retries = 0;
}
return (EBUSY);
}

View File

@ -126,6 +126,7 @@
#define AMR_CONFIG_READ_NVRAM_CONFIG 0x04
#define AMR_CONFIG_WRITE_NVRAM_CONFIG 0x0d
#define AMR_CONFIG_ENQ3_SOLICITED_NOTIFY 0x01
#define AMR_CONFIG_PRODUCT_INFO 0x0e
#define AMR_CONFIG_ENQ3 0x0f
#define AMR_CONFIG_ENQ3_SOLICITED_NOTIFY 0x01
@ -140,6 +141,14 @@
#define OP_GET_LDID_MAP 0x18
#define OP_DEL_LOGDRV 0x1C
/*
* Command for random deletion of logical drives
*/
#define FC_DEL_LOGDRV 0xA4
#define OP_SUP_DEL_LOGDRV 0x2A
#define OP_GET_LDID_MAP 0x18
#define OP_DEL_LOGDRV 0x1C
/*
* Command results
*/

View File

@ -100,6 +100,9 @@ struct amr_logdrive
#define AMR_CMD_CLUSTERSIZE (16 * 1024)
typedef STAILQ_HEAD(, amr_command) ac_qhead_t;
typedef STAILQ_ENTRY(amr_command) ac_link_t;
union amr_ccb {
struct amr_passthrough ccb_pthru;
struct amr_ext_passthrough ccb_epthru;
@ -111,7 +114,7 @@ union amr_ccb {
*/
struct amr_command
{
TAILQ_ENTRY(amr_command) ac_link;
ac_link_t ac_link;
struct amr_softc *ac_sc;
u_int8_t ac_slot;
@ -134,6 +137,7 @@ struct amr_command
#define AMR_CMD_BUSY (1<<7)
#define AMR_CMD_SG64 (1<<8)
#define AC_IS_SG64(ac) ((ac)->ac_flags & AMR_CMD_SG64)
u_int ac_retries;
struct bio *ac_bio;
void (* ac_complete)(struct amr_command *ac);
@ -219,11 +223,10 @@ struct amr_softc
/* per-controller queues */
struct bio_queue_head amr_bioq; /* pending I/O with no commands */
TAILQ_HEAD(,amr_command) amr_ready; /* commands ready to be submitted */
ac_qhead_t amr_ready; /* commands ready to be submitted */
struct amr_command *amr_busycmd[AMR_MAXCMD];
int amr_busyslots;
TAILQ_HEAD(,amr_command) amr_completed;
TAILQ_HEAD(,amr_command) amr_freecmds;
ac_qhead_t amr_freecmds;
TAILQ_HEAD(,amr_command_cluster) amr_cmd_clusters;
/* CAM attachments for passthrough */
@ -319,18 +322,25 @@ amr_dequeue_bio(struct amr_softc *sc)
return(bio);
}
static __inline void
amr_init_qhead(ac_qhead_t *head)
{
STAILQ_INIT(head);
}
static __inline void
amr_enqueue_ready(struct amr_command *ac)
{
TAILQ_INSERT_TAIL(&ac->ac_sc->amr_ready, ac, ac_link);
STAILQ_INSERT_TAIL(&ac->ac_sc->amr_ready, ac, ac_link);
}
static __inline void
amr_requeue_ready(struct amr_command *ac)
{
TAILQ_INSERT_HEAD(&ac->ac_sc->amr_ready, ac, ac_link);
STAILQ_INSERT_HEAD(&ac->ac_sc->amr_ready, ac, ac_link);
}
static __inline struct amr_command *
@ -338,25 +348,25 @@ amr_dequeue_ready(struct amr_softc *sc)
{
struct amr_command *ac;
if ((ac = TAILQ_FIRST(&sc->amr_ready)) != NULL)
TAILQ_REMOVE(&sc->amr_ready, ac, ac_link);
if ((ac = STAILQ_FIRST(&sc->amr_ready)) != NULL)
STAILQ_REMOVE_HEAD(&sc->amr_ready, ac_link);
return(ac);
}
static __inline void
amr_enqueue_completed(struct amr_command *ac)
amr_enqueue_completed(struct amr_command *ac, ac_qhead_t *head)
{
TAILQ_INSERT_TAIL(&ac->ac_sc->amr_completed, ac, ac_link);
STAILQ_INSERT_TAIL(head, ac, ac_link);
}
static __inline struct amr_command *
amr_dequeue_completed(struct amr_softc *sc)
amr_dequeue_completed(struct amr_softc *sc, ac_qhead_t *head)
{
struct amr_command *ac;
if ((ac = TAILQ_FIRST(&sc->amr_completed)) != NULL)
TAILQ_REMOVE(&sc->amr_completed, ac, ac_link);
if ((ac = STAILQ_FIRST(head)) != NULL)
STAILQ_REMOVE_HEAD(head, ac_link);
return(ac);
}
@ -364,7 +374,7 @@ static __inline void
amr_enqueue_free(struct amr_command *ac)
{
TAILQ_INSERT_TAIL(&ac->ac_sc->amr_freecmds, ac, ac_link);
STAILQ_INSERT_HEAD(&ac->ac_sc->amr_freecmds, ac, ac_link);
}
static __inline struct amr_command *
@ -372,7 +382,7 @@ amr_dequeue_free(struct amr_softc *sc)
{
struct amr_command *ac;
if ((ac = TAILQ_FIRST(&sc->amr_freecmds)) != NULL)
TAILQ_REMOVE(&sc->amr_freecmds, ac, ac_link);
if ((ac = STAILQ_FIRST(&sc->amr_freecmds)) != NULL)
STAILQ_REMOVE_HEAD(&sc->amr_freecmds, ac_link);
return(ac);
}