Currently dkcsumattach() walks the alldevs list whilst performing operations that will sleep. This is rather bad if the list happens to be modified (i.e. a device detachs) whilst it is sleeping.
The following diff resolves this issue. The process is very similar to that used for softraid - we maintain a list of disks that we have checked and each time we do something that may have slept we restart the scan from the top of the list. A couple of other things come along for the ride: 1. We can walk disklist rather than walking alldevs. 2. We can benefit from the more recent changes to disk_attach() and use the devno that is stored in the disk struct. 3. The DKF_NOREADLABEL flag is used to skip disks to dkcsum, rather than using dev_rawpart() - the list should be similar and we already exclude cd(4) and fd(4) devices. If we do this dev_rawpart() can probably be removed. I have lightly tested this, however further testing (on i386 and amd64) would be appreciated. Comments or oks? Index: arch/i386/i386/dkcsum.c =================================================================== RCS file: /cvs/src/sys/arch/i386/i386/dkcsum.c,v retrieving revision 1.29 diff -u -p -r1.29 dkcsum.c --- arch/i386/i386/dkcsum.c 16 Apr 2011 03:21:15 -0000 1.29 +++ arch/i386/i386/dkcsum.c 2 Jul 2011 16:01:57 -0000 @@ -33,8 +33,10 @@ #include <sys/buf.h> #include <sys/conf.h> #include <sys/device.h> +#include <sys/disk.h> #include <sys/disklabel.h> #include <sys/fcntl.h> +#include <sys/malloc.h> #include <sys/proc.h> #include <sys/reboot.h> #include <sys/stat.h> @@ -44,16 +46,23 @@ #include <lib/libz/zlib.h> -dev_t dev_rawpart(struct device *); /* XXX */ - extern u_int32_t bios_cksumlen; extern bios_diskinfo_t *bios_diskinfo; extern dev_t bootdev; +struct dkcsum_disk { + dev_t dkc_devno; + SLIST_ENTRY(dkcsum_disk) dkc_link; +}; +SLIST_HEAD(dkcsum_disklist_head, dkcsum_disk); + void dkcsumattach(void) { + struct dkcsum_disklist_head dkc_disklist; + struct dkcsum_disk *dkc; struct device *dv; + struct disk *dk; struct buf *bp; struct bdevsw *bdsw; dev_t dev, pribootdev, altbootdev; @@ -88,12 +97,39 @@ dkcsumattach(void) */ bp = geteblk(bios_cksumlen * DEV_BSIZE); /* XXX error check? */ - TAILQ_FOREACH(dv, &alldevs, dv_list) { - if (dv->dv_class != DV_DISK) + SLIST_INIT(&dkc_disklist); + dk = TAILQ_FIRST(&disklist); + while (dk != TAILQ_END(&disklist)) { + + dv = dk->dk_device; + if (dv == NULL || dk->dk_devno == NODEV || + (dk->dk_flags & DKF_NOLABELREAD)) { + dk = TAILQ_NEXT(dk, dk_link); continue; - bp->b_dev = dev = dev_rawpart(dv); - if (dev == NODEV) + } + + SLIST_FOREACH(dkc, &dkc_disklist, dkc_link) + if (dkc->dkc_devno == dk->dk_devno) + break; + + if (dkc != NULL) { + dk = TAILQ_NEXT(dk, dk_link); continue; + } + +#ifdef DEBUG + printf("dkcsum: adding %s to the list of checked disks\n", + dv->dv_xname); +#endif + dkc = malloc(sizeof(struct dkcsum_disk), M_DEVBUF, + M_NOWAIT | M_CANFAIL | M_ZERO); + if (dkc == NULL) + panic("dkcsum: failed to allocate memory"); + dkc->dkc_devno = dk->dk_devno; + SLIST_INSERT_HEAD(&dkc_disklist, dkc, dkc_link); + + bp->b_dev = dev = MAKEDISKDEV(major(dk->dk_devno), + DISKUNIT(dk->dk_devno), RAW_PART); bdsw = &bdevsw[major(dev)]; /* @@ -107,6 +143,7 @@ dkcsumattach(void) printf("dkcsum: %s open failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -129,6 +166,7 @@ dkcsumattach(void) printf("dkcsum: %s close failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } error = (*bdsw->d_close)(dev, FREAD, S_IFCHR, curproc); @@ -138,6 +176,7 @@ dkcsumattach(void) printf("dkcsum: %s closed failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -173,6 +212,7 @@ dkcsumattach(void) printf("dkcsum: %s has no matching BIOS drive\n", dv->dv_xname); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -223,9 +263,18 @@ dkcsumattach(void) hit->bsd_dev = MAKEBOOTDEV(major(bp->b_dev), 0, 0, DISKUNIT(bp->b_dev), RAW_PART); hit->flags |= BDI_PICKED; + + /* Restart scan since we may have slept. */ + dk = TAILQ_FIRST(&disklist); } bootdev = pribootdev ? pribootdev : altbootdev ? altbootdev : bootdev; bp->b_flags |= B_INVAL; brelse(bp); + + while (!SLIST_EMPTY(&dkc_disklist)) { + dkc = SLIST_FIRST(&dkc_disklist); + SLIST_REMOVE_HEAD(&dkc_disklist, dkc_link); + free(dkc, M_DEVBUF); + } } Index: arch/amd64/amd64/dkcsum.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/amd64/dkcsum.c,v retrieving revision 1.18 diff -u -p -r1.18 dkcsum.c --- arch/amd64/amd64/dkcsum.c 16 Apr 2011 03:21:15 -0000 1.18 +++ arch/amd64/amd64/dkcsum.c 2 Jul 2011 16:01:57 -0000 @@ -33,8 +33,10 @@ #include <sys/buf.h> #include <sys/conf.h> #include <sys/device.h> +#include <sys/disk.h> #include <sys/disklabel.h> #include <sys/fcntl.h> +#include <sys/malloc.h> #include <sys/proc.h> #include <sys/reboot.h> #include <sys/stat.h> @@ -44,16 +46,23 @@ #include <lib/libz/zlib.h> -dev_t dev_rawpart(struct device *); /* XXX */ - extern u_int32_t bios_cksumlen; extern bios_diskinfo_t *bios_diskinfo; extern dev_t bootdev; +struct dkcsum_disk { + dev_t dkc_devno; + SLIST_ENTRY(dkcsum_disk) dkc_link; +}; +SLIST_HEAD(dkcsum_disklist_head, dkcsum_disk); + void dkcsumattach(void) { + struct dkcsum_disklist_head dkc_disklist; + struct dkcsum_disk *dkc; struct device *dv; + struct disk *dk; struct buf *bp; struct bdevsw *bdsw; dev_t dev, pribootdev, altbootdev; @@ -88,12 +97,39 @@ dkcsumattach(void) */ bp = geteblk(bios_cksumlen * DEV_BSIZE); /* XXX error check? */ - TAILQ_FOREACH(dv, &alldevs, dv_list) { - if (dv->dv_class != DV_DISK) + SLIST_INIT(&dkc_disklist); + dk = TAILQ_FIRST(&disklist); + while (dk != TAILQ_END(&disklist)) { + + dv = dk->dk_device; + if (dv == NULL || dk->dk_devno == NODEV || + (dk->dk_flags & DKF_NOLABELREAD)) { + dk = TAILQ_NEXT(dk, dk_link); continue; - bp->b_dev = dev = dev_rawpart(dv); - if (dev == NODEV) + } + + SLIST_FOREACH(dkc, &dkc_disklist, dkc_link) + if (dkc->dkc_devno == dk->dk_devno) + break; + + if (dkc != NULL) { + dk = TAILQ_NEXT(dk, dk_link); continue; + } + +#ifdef DEBUG + printf("dkcsum: adding %s to the list of checked disks\n", + dv->dv_xname); +#endif + dkc = malloc(sizeof(struct dkcsum_disk), M_DEVBUF, + M_NOWAIT | M_CANFAIL | M_ZERO); + if (dkc == NULL) + panic("dkcsum: failed to allocate memory"); + dkc->dkc_devno = dk->dk_devno; + SLIST_INSERT_HEAD(&dkc_disklist, dkc, dkc_link); + + bp->b_dev = dev = MAKEDISKDEV(major(dk->dk_devno), + DISKUNIT(dk->dk_devno), RAW_PART); bdsw = &bdevsw[major(dev)]; /* @@ -107,6 +143,7 @@ dkcsumattach(void) printf("dkcsum: %s open failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -129,6 +166,7 @@ dkcsumattach(void) printf("dkcsum: %s close failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } error = (*bdsw->d_close)(dev, FREAD, S_IFCHR, curproc); @@ -138,6 +176,7 @@ dkcsumattach(void) printf("dkcsum: %s closed failed (%d)\n", dv->dv_xname, error); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -173,6 +212,7 @@ dkcsumattach(void) printf("dkcsum: %s has no matching BIOS drive\n", dv->dv_xname); #endif + dk = TAILQ_FIRST(&disklist); continue; } @@ -223,9 +263,18 @@ dkcsumattach(void) hit->bsd_dev = MAKEBOOTDEV(major(bp->b_dev), 0, 0, DISKUNIT(bp->b_dev), RAW_PART); hit->flags |= BDI_PICKED; + + /* Restart scan since we may have slept. */ + dk = TAILQ_FIRST(&disklist); } bootdev = pribootdev ? pribootdev : altbootdev ? altbootdev : bootdev; bp->b_flags |= B_INVAL; brelse(bp); + + while (!SLIST_EMPTY(&dkc_disklist)) { + dkc = SLIST_FIRST(&dkc_disklist); + SLIST_REMOVE_HEAD(&dkc_disklist, dkc_link); + free(dkc, M_DEVBUF); + } } -- "Reason is not automatic. Those who deny it cannot be conquered by it. Do not count on them. Leave them alone." -- Ayn Rand