[SCSI] sym53c8xx_2: slave_alloc/destroy safety (2.6.27.5)

Make the sym53c8xx_2 driver slave_alloc/destroy less unsafe. References
to the destroyed LCB are cleared from the target structure (instead of
leaving a dangling pointer), and when the last LCB for the target is
destroyed the reference to the upper layer target data is cleared. The
host lock is used to prevent a race with the interrupt handler. Also
user commands are prevented for targets with all LCBs destroyed.

Signed-off-by: Aaro Koskinen <Aaro.Koskinen@nokia.com>
Tested-by: Tony Battersby <tonyb@cybernetics.com>
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
This commit is contained in:
Aaro Koskinen 2009-04-14 15:47:00 -05:00 committed by James Bottomley
parent 410604d25f
commit fa8584566c
3 changed files with 93 additions and 15 deletions

View File

@ -737,11 +737,14 @@ static int sym53c8xx_slave_alloc(struct scsi_device *sdev)
struct sym_hcb *np = sym_get_hcb(sdev->host); struct sym_hcb *np = sym_get_hcb(sdev->host);
struct sym_tcb *tp = &np->target[sdev->id]; struct sym_tcb *tp = &np->target[sdev->id];
struct sym_lcb *lp; struct sym_lcb *lp;
unsigned long flags;
int error;
if (sdev->id >= SYM_CONF_MAX_TARGET || sdev->lun >= SYM_CONF_MAX_LUN) if (sdev->id >= SYM_CONF_MAX_TARGET || sdev->lun >= SYM_CONF_MAX_LUN)
return -ENXIO; return -ENXIO;
tp->starget = sdev->sdev_target; spin_lock_irqsave(np->s.host->host_lock, flags);
/* /*
* Fail the device init if the device is flagged NOSCAN at BOOT in * Fail the device init if the device is flagged NOSCAN at BOOT in
* the NVRAM. This may speed up boot and maintain coherency with * the NVRAM. This may speed up boot and maintain coherency with
@ -753,26 +756,37 @@ static int sym53c8xx_slave_alloc(struct scsi_device *sdev)
if (tp->usrflags & SYM_SCAN_BOOT_DISABLED) { if (tp->usrflags & SYM_SCAN_BOOT_DISABLED) {
tp->usrflags &= ~SYM_SCAN_BOOT_DISABLED; tp->usrflags &= ~SYM_SCAN_BOOT_DISABLED;
starget_printk(KERN_INFO, tp->starget, starget_printk(KERN_INFO, sdev->sdev_target,
"Scan at boot disabled in NVRAM\n"); "Scan at boot disabled in NVRAM\n");
return -ENXIO; error = -ENXIO;
goto out;
} }
if (tp->usrflags & SYM_SCAN_LUNS_DISABLED) { if (tp->usrflags & SYM_SCAN_LUNS_DISABLED) {
if (sdev->lun != 0) if (sdev->lun != 0) {
return -ENXIO; error = -ENXIO;
starget_printk(KERN_INFO, tp->starget, goto out;
}
starget_printk(KERN_INFO, sdev->sdev_target,
"Multiple LUNs disabled in NVRAM\n"); "Multiple LUNs disabled in NVRAM\n");
} }
lp = sym_alloc_lcb(np, sdev->id, sdev->lun); lp = sym_alloc_lcb(np, sdev->id, sdev->lun);
if (!lp) if (!lp) {
return -ENOMEM; error = -ENOMEM;
goto out;
}
if (tp->nlcb == 1)
tp->starget = sdev->sdev_target;
spi_min_period(tp->starget) = tp->usr_period; spi_min_period(tp->starget) = tp->usr_period;
spi_max_width(tp->starget) = tp->usr_width; spi_max_width(tp->starget) = tp->usr_width;
return 0; error = 0;
out:
spin_unlock_irqrestore(np->s.host->host_lock, flags);
return error;
} }
/* /*
@ -819,12 +833,34 @@ static int sym53c8xx_slave_configure(struct scsi_device *sdev)
static void sym53c8xx_slave_destroy(struct scsi_device *sdev) static void sym53c8xx_slave_destroy(struct scsi_device *sdev)
{ {
struct sym_hcb *np = sym_get_hcb(sdev->host); struct sym_hcb *np = sym_get_hcb(sdev->host);
struct sym_lcb *lp = sym_lp(&np->target[sdev->id], sdev->lun); struct sym_tcb *tp = &np->target[sdev->id];
struct sym_lcb *lp = sym_lp(tp, sdev->lun);
unsigned long flags;
if (lp->itlq_tbl) spin_lock_irqsave(np->s.host->host_lock, flags);
sym_mfree_dma(lp->itlq_tbl, SYM_CONF_MAX_TASK * 4, "ITLQ_TBL");
kfree(lp->cb_tags); if (lp->busy_itlq || lp->busy_itl) {
sym_mfree_dma(lp, sizeof(*lp), "LCB"); /*
* This really shouldn't happen, but we can't return an error
* so let's try to stop all on-going I/O.
*/
starget_printk(KERN_WARNING, tp->starget,
"Removing busy LCB (%d)\n", sdev->lun);
sym_reset_scsi_bus(np, 1);
}
if (sym_free_lcb(np, sdev->id, sdev->lun) == 0) {
/*
* It was the last unit for this target.
*/
tp->head.sval = 0;
tp->head.wval = np->rv_scntl3;
tp->head.uval = 0;
tp->tgoal.check_nego = 1;
tp->starget = NULL;
}
spin_unlock_irqrestore(np->s.host->host_lock, flags);
} }
/* /*
@ -890,6 +926,8 @@ static void sym_exec_user_command (struct sym_hcb *np, struct sym_usrcmd *uc)
if (!((uc->target >> t) & 1)) if (!((uc->target >> t) & 1))
continue; continue;
tp = &np->target[t]; tp = &np->target[t];
if (!tp->nlcb)
continue;
switch (uc->cmd) { switch (uc->cmd) {

View File

@ -4997,7 +4997,7 @@ struct sym_lcb *sym_alloc_lcb (struct sym_hcb *np, u_char tn, u_char ln)
*/ */
if (ln && !tp->lunmp) { if (ln && !tp->lunmp) {
tp->lunmp = kcalloc(SYM_CONF_MAX_LUN, sizeof(struct sym_lcb *), tp->lunmp = kcalloc(SYM_CONF_MAX_LUN, sizeof(struct sym_lcb *),
GFP_KERNEL); GFP_ATOMIC);
if (!tp->lunmp) if (!tp->lunmp)
goto fail; goto fail;
} }
@ -5017,6 +5017,7 @@ struct sym_lcb *sym_alloc_lcb (struct sym_hcb *np, u_char tn, u_char ln)
tp->lun0p = lp; tp->lun0p = lp;
tp->head.lun0_sa = cpu_to_scr(vtobus(lp)); tp->head.lun0_sa = cpu_to_scr(vtobus(lp));
} }
tp->nlcb++;
/* /*
* Let the itl task point to error handling. * Let the itl task point to error handling.
@ -5093,6 +5094,43 @@ fail:
return; return;
} }
/*
* Lun control block deallocation. Returns the number of valid remaing LCBs
* for the target.
*/
int sym_free_lcb(struct sym_hcb *np, u_char tn, u_char ln)
{
struct sym_tcb *tp = &np->target[tn];
struct sym_lcb *lp = sym_lp(tp, ln);
tp->nlcb--;
if (ln) {
if (!tp->nlcb) {
kfree(tp->lunmp);
sym_mfree_dma(tp->luntbl, 256, "LUNTBL");
tp->lunmp = NULL;
tp->luntbl = NULL;
tp->head.luntbl_sa = cpu_to_scr(vtobus(np->badluntbl));
} else {
tp->luntbl[ln] = cpu_to_scr(vtobus(&np->badlun_sa));
tp->lunmp[ln] = NULL;
}
} else {
tp->lun0p = NULL;
tp->head.lun0_sa = cpu_to_scr(vtobus(&np->badlun_sa));
}
if (lp->itlq_tbl) {
sym_mfree_dma(lp->itlq_tbl, SYM_CONF_MAX_TASK*4, "ITLQ_TBL");
kfree(lp->cb_tags);
}
sym_mfree_dma(lp, sizeof(*lp), "LCB");
return tp->nlcb;
}
/* /*
* Queue a SCSI IO to the controller. * Queue a SCSI IO to the controller.
*/ */

View File

@ -401,6 +401,7 @@ struct sym_tcb {
* An array of bus addresses is used on reselection. * An array of bus addresses is used on reselection.
*/ */
u32 *luntbl; /* LCBs bus address table */ u32 *luntbl; /* LCBs bus address table */
int nlcb; /* Number of valid LCBs (including LUN #0) */
/* /*
* LUN table used by the C code. * LUN table used by the C code.
@ -1065,6 +1066,7 @@ int sym_clear_tasks(struct sym_hcb *np, int cam_status, int target, int lun, int
struct sym_ccb *sym_get_ccb(struct sym_hcb *np, struct scsi_cmnd *cmd, u_char tag_order); struct sym_ccb *sym_get_ccb(struct sym_hcb *np, struct scsi_cmnd *cmd, u_char tag_order);
void sym_free_ccb(struct sym_hcb *np, struct sym_ccb *cp); void sym_free_ccb(struct sym_hcb *np, struct sym_ccb *cp);
struct sym_lcb *sym_alloc_lcb(struct sym_hcb *np, u_char tn, u_char ln); struct sym_lcb *sym_alloc_lcb(struct sym_hcb *np, u_char tn, u_char ln);
int sym_free_lcb(struct sym_hcb *np, u_char tn, u_char ln);
int sym_queue_scsiio(struct sym_hcb *np, struct scsi_cmnd *csio, struct sym_ccb *cp); int sym_queue_scsiio(struct sym_hcb *np, struct scsi_cmnd *csio, struct sym_ccb *cp);
int sym_abort_scsiio(struct sym_hcb *np, struct scsi_cmnd *ccb, int timed_out); int sym_abort_scsiio(struct sym_hcb *np, struct scsi_cmnd *ccb, int timed_out);
int sym_reset_scsi_target(struct sym_hcb *np, int target); int sym_reset_scsi_target(struct sym_hcb *np, int target);