Date: Thu, 23 Jul 2009 15:32:30 +0200 From: Roman Divacky <rdivacky@freebsd.org> To: current@freebsd.org Subject: [PATCH]: MPSAFE isa_dma* functions for i386/amd64 Message-ID: <20090723133230.GA69577@freebsd.org>
next in thread | raw e-mail | index | archive | help
--gatW/ieO32f1wygP Content-Type: multipart/mixed; boundary="LZvS9be/3tNcYl/X" Content-Disposition: inline --LZvS9be/3tNcYl/X Content-Type: text/plain; charset=us-ascii Content-Disposition: inline hi I have a patch that makes isa_dma* routines MPSAFE on i386/amd64. These routines are also present in ia64/sparc64. Sparc64 provides just empty stubs and ia64 should be removed (according to Marcel Moolenaar). there are some old drivers that use this - most prominently ppc(4) and fdc(4) if you have this hardware please test the attached patch or you can download it here: http://www.vlakno.cz/~rdivacky/isa_dma-locking.patch the patch was reviewed by jhb@ but never tested on a real hw (as I have none). most of the users of the isa_dma* routines are still covered by Giant because they are used in drivers attach routines (which are Giant locked) but ppc/fdc use those in the normal code as well thank you very much for testing roman --LZvS9be/3tNcYl/X Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="isa_dma-locking.patch" Content-Transfer-Encoding: quoted-printable Index: amd64/isa/isa_dma.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- amd64/isa/isa_dma.c (revision 195829) +++ amd64/isa/isa_dma.c (working copy) @@ -71,6 +71,8 @@ static u_int8_t dma_busy =3D 0; /* Used in isa_dmastart() */ static u_int8_t dma_inuse =3D 0; /* User for acquire/release */ static u_int8_t dma_auto_mode =3D 0; +static struct mtx isa_dma_lock; +MTX_SYSINIT(isa_dma_lock, &isa_dma_lock, "isa DMA lock", MTX_DEF); =20 #define VALID_DMA_MASK (7) =20 @@ -84,39 +86,56 @@ isa_dma_init(int chan, u_int bouncebufsize, int flag) { void *buf; + int contig; =20 - /* - * If a DMA channel is shared, both drivers have to call isa_dma_init - * since they don't know that the other driver will do it. - * Just return if we're already set up good. - * XXX: this only works if they agree on the bouncebuf size. This - * XXX: is typically the case since they are multiple instances of - * XXX: the same driver. - */ - if (dma_bouncebuf[chan] !=3D NULL) - return (0); - #ifdef DIAGNOSTIC if (chan & ~VALID_DMA_MASK) panic("isa_dma_init: channel out of range"); #endif =20 - dma_bouncebufsize[chan] =3D bouncebufsize; =20 /* Try malloc() first. It works better if it works. */ buf =3D malloc(bouncebufsize, M_DEVBUF, flag); if (buf !=3D NULL) { - if (isa_dmarangecheck(buf, bouncebufsize, chan) =3D=3D 0) { - dma_bouncebuf[chan] =3D buf; - return (0); + if (isa_dmarangecheck(buf, bouncebufsize, chan) !=3D 0) { + free(buf, M_DEVBUF); + buf =3D NULL; } - free(buf, M_DEVBUF); + contig =3D 0; } - buf =3D contigmalloc(bouncebufsize, M_DEVBUF, flag, 0ul, 0xfffffful, + + if (buf =3D=3D NULL) { + buf =3D contigmalloc(bouncebufsize, M_DEVBUF, flag, 0ul, 0xfffffful, 1ul, chan & 4 ? 0x20000ul : 0x10000ul); + contig =3D 1; + } + if (buf =3D=3D NULL) return (ENOMEM); + + mtx_lock(&isa_dma_lock); + /* + * If a DMA channel is shared, both drivers have to call isa_dma_init + * since they don't know that the other driver will do it. + * Just return if we're already set up good. + * XXX: this only works if they agree on the bouncebuf size. This + * XXX: is typically the case since they are multiple instances of + * XXX: the same driver. + */ + if (dma_bouncebuf[chan] !=3D NULL) { + if (contig) + contigfree(buf, bouncebufsize, M_DEVBUF); + else + free(buf, M_DEVBUF); + mtx_unlock(&isa_dma_lock); + return (0); + } + + dma_bouncebufsize[chan] =3D bouncebufsize; dma_bouncebuf[chan] =3D buf; + + mtx_unlock(&isa_dma_lock); + return (0); } =20 @@ -133,12 +152,15 @@ panic("isa_dma_acquire: channel out of range"); #endif =20 + mtx_lock(&isa_dma_lock); if (dma_inuse & (1 << chan)) { printf("isa_dma_acquire: channel %d already in use\n", chan); + mtx_unlock(&isa_dma_lock); return (EBUSY); } dma_inuse |=3D (1 << chan); dma_auto_mode &=3D ~(1 << chan); + mtx_unlock(&isa_dma_lock); =20 return (0); } @@ -155,8 +177,11 @@ if (chan & ~VALID_DMA_MASK) panic("isa_dma_release: channel out of range"); =20 + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dma_release: channel %d not in use\n", chan); +#else + mtx_lock(&isa_dma_lock); #endif =20 if (dma_busy & (1 << chan)) { @@ -171,6 +196,8 @@ =20 dma_inuse &=3D ~(1 << chan); dma_auto_mode &=3D ~(1 << chan); + + mtx_unlock(&isa_dma_lock); } =20 /* @@ -186,6 +213,7 @@ panic("isa_dmacascade: channel out of range"); #endif =20 + mtx_lock(&isa_dma_lock); /* set dma channel mode, and set dma channel mode */ if ((chan & 4) =3D=3D 0) { outb(DMA1_MODE, DMA37MD_CASCADE | chan); @@ -194,6 +222,7 @@ outb(DMA2_MODE, DMA37MD_CASCADE | (chan & 3)); outb(DMA2_SMSK, chan & 3); } + mtx_unlock(&isa_dma_lock); } =20 /* @@ -206,8 +235,11 @@ vm_paddr_t phys; int waport; caddr_t newaddr; + int dma_range_checked; =20 - GIANT_REQUIRED; + /* translate to physical */ + phys =3D pmap_extract(kernel_pmap, (vm_offset_t)addr); + dma_range_checked =3D isa_dmarangecheck(addr, nbytes, chan); =20 #ifdef DIAGNOSTIC if (chan & ~VALID_DMA_MASK) @@ -217,8 +249,11 @@ || (chan >=3D 4 && (nbytes > (1<<17) || (uintptr_t)addr & 1))) panic("isa_dmastart: impossible request"); =20 + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dmastart: channel %d not acquired\n", chan); +#else + mtx_lock(&isa_dma_lock); #endif =20 #if 0 @@ -233,7 +268,7 @@ =20 dma_busy |=3D (1 << chan); =20 - if (isa_dmarangecheck(addr, nbytes, chan)) { + if (dma_range_checked) { if (dma_bouncebuf[chan] =3D=3D NULL || dma_bouncebufsize[chan] < nbytes) panic("isa_dmastart: bad bounce buffer");=20 @@ -246,9 +281,6 @@ addr =3D newaddr; } =20 - /* translate to physical */ - phys =3D pmap_extract(kernel_pmap, (vm_offset_t)addr); - if (flags & ISADMA_RAW) { dma_auto_mode |=3D (1 << chan); } else {=20 @@ -323,6 +355,7 @@ /* unmask channel */ outb(DMA2_SMSK, chan & 3); } + mtx_unlock(&isa_dma_lock); } =20 void @@ -336,6 +369,7 @@ printf("isa_dmadone: channel %d not acquired\n", chan); #endif =20 + mtx_lock(&isa_dma_lock); if (((dma_busy & (1 << chan)) =3D=3D 0) &&=20 (dma_auto_mode & (1 << chan)) =3D=3D 0 ) printf("isa_dmadone: channel %d not busy\n", chan); @@ -351,6 +385,7 @@ dma_bounced &=3D ~(1 << chan); } dma_busy &=3D ~(1 << chan); + mtx_unlock(&isa_dma_lock); } =20 /* @@ -367,8 +402,6 @@ vm_offset_t endva; u_int dma_pgmsk =3D (chan & 4) ? ~(128*1024-1) : ~(64*1024-1); =20 - GIANT_REQUIRED; - endva =3D (vm_offset_t)round_page((vm_offset_t)va + length); for (; va < (caddr_t) endva ; va +=3D PAGE_SIZE) { phys =3D trunc_page(pmap_extract(kernel_pmap, (vm_offset_t)va)); @@ -420,13 +453,15 @@ * or -1 if the channel requested is not active. * */ -int -isa_dmastatus(int chan) +static int +isa_dmastatus_locked(int chan) { u_long cnt =3D 0; int ffport, waport; u_long low1, high1, low2, high2; =20 + mtx_assert(&isa_dma_lock, MA_OWNED); + /* channel active? */ if ((dma_inuse & (1 << chan)) =3D=3D 0) { printf("isa_dmastatus: channel %d not active\n", chan); @@ -472,6 +507,18 @@ return(cnt); } =20 +int +isa_dmastatus(int chan) +{ + int status; + + mtx_lock(&isa_dma_lock); + status =3D isa_dmastatus_locked(chan); + mtx_unlock(&isa_dma_lock); + + return (status); +} + /* * Reached terminal count yet ? */ @@ -491,12 +538,16 @@ int isa_dmastop(int chan)=20 { + int status; + + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dmastop: channel %d not acquired\n", chan); =20 =20 if (((dma_busy & (1 << chan)) =3D=3D 0) && ((dma_auto_mode & (1 << chan)) =3D=3D 0)) { printf("chan %d not busy\n", chan); + mtx_unlock(&isa_dma_lock); return -2 ; } =20 @@ -505,7 +556,12 @@ } else { outb(DMA2_SMSK, (chan & 3) | 4 /* disable mask */); } - return(isa_dmastatus(chan)); + + status =3D isa_dmastatus_locked(chan); + + mtx_unlock(&isa_dma_lock); + + return (status); } =20 /* Index: i386/isa/isa_dma.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- i386/isa/isa_dma.c (revision 195829) +++ i386/isa/isa_dma.c (working copy) @@ -69,6 +69,8 @@ static u_int8_t dma_busy =3D 0; /* Used in isa_dmastart() */ static u_int8_t dma_inuse =3D 0; /* User for acquire/release */ static u_int8_t dma_auto_mode =3D 0; +static struct mtx isa_dma_lock; +MTX_SYSINIT(isa_dma_lock, &isa_dma_lock, "isa DMA lock", MTX_DEF); =20 #define VALID_DMA_MASK (7) =20 @@ -82,39 +84,56 @@ isa_dma_init(int chan, u_int bouncebufsize, int flag) { void *buf; + int contig; =20 - /* - * If a DMA channel is shared, both drivers have to call isa_dma_init - * since they don't know that the other driver will do it. - * Just return if we're already set up good. - * XXX: this only works if they agree on the bouncebuf size. This - * XXX: is typically the case since they are multiple instances of - * XXX: the same driver. - */ - if (dma_bouncebuf[chan] !=3D NULL) - return (0); - #ifdef DIAGNOSTIC if (chan & ~VALID_DMA_MASK) panic("isa_dma_init: channel out of range"); #endif =20 - dma_bouncebufsize[chan] =3D bouncebufsize; =20 /* Try malloc() first. It works better if it works. */ buf =3D malloc(bouncebufsize, M_DEVBUF, flag); if (buf !=3D NULL) { - if (isa_dmarangecheck(buf, bouncebufsize, chan) =3D=3D 0) { - dma_bouncebuf[chan] =3D buf; - return (0); + if (isa_dmarangecheck(buf, bouncebufsize, chan) !=3D 0) { + free(buf, M_DEVBUF); + buf =3D NULL; } - free(buf, M_DEVBUF); + contig =3D 0; } - buf =3D contigmalloc(bouncebufsize, M_DEVBUF, flag, 0ul, 0xfffffful, + + if (buf =3D=3D NULL) { + buf =3D contigmalloc(bouncebufsize, M_DEVBUF, flag, 0ul, 0xfffffful, 1ul, chan & 4 ? 0x20000ul : 0x10000ul); + contig =3D 1; + } + if (buf =3D=3D NULL) return (ENOMEM); + + mtx_lock(&isa_dma_lock); + /* + * If a DMA channel is shared, both drivers have to call isa_dma_init + * since they don't know that the other driver will do it. + * Just return if we're already set up good. + * XXX: this only works if they agree on the bouncebuf size. This + * XXX: is typically the case since they are multiple instances of + * XXX: the same driver. + */ + if (dma_bouncebuf[chan] !=3D NULL) { + if (contig) + contigfree(buf, bouncebufsize, M_DEVBUF); + else + free(buf, M_DEVBUF); + mtx_unlock(&isa_dma_lock); + return (0); + } + + dma_bouncebufsize[chan] =3D bouncebufsize; dma_bouncebuf[chan] =3D buf; + + mtx_unlock(&isa_dma_lock); + return (0); } =20 @@ -131,12 +150,15 @@ panic("isa_dma_acquire: channel out of range"); #endif =20 + mtx_lock(&isa_dma_lock); if (dma_inuse & (1 << chan)) { printf("isa_dma_acquire: channel %d already in use\n", chan); + mtx_unlock(&isa_dma_lock); return (EBUSY); } dma_inuse |=3D (1 << chan); dma_auto_mode &=3D ~(1 << chan); + mtx_unlock(&isa_dma_lock); =20 return (0); } @@ -153,8 +175,11 @@ if (chan & ~VALID_DMA_MASK) panic("isa_dma_release: channel out of range"); =20 + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dma_release: channel %d not in use\n", chan); +#else + mtx_lock(&isa_dma_lock); #endif =20 if (dma_busy & (1 << chan)) { @@ -169,6 +194,8 @@ =20 dma_inuse &=3D ~(1 << chan); dma_auto_mode &=3D ~(1 << chan); + + mtx_unlock(&isa_dma_lock); } =20 /* @@ -184,6 +211,7 @@ panic("isa_dmacascade: channel out of range"); #endif =20 + mtx_lock(&isa_dma_lock); /* set dma channel mode, and set dma channel mode */ if ((chan & 4) =3D=3D 0) { outb(DMA1_MODE, DMA37MD_CASCADE | chan); @@ -192,6 +220,7 @@ outb(DMA2_MODE, DMA37MD_CASCADE | (chan & 3)); outb(DMA2_SMSK, chan & 3); } + mtx_unlock(&isa_dma_lock); } =20 /* @@ -204,8 +233,11 @@ vm_paddr_t phys; int waport; caddr_t newaddr; + int dma_range_checked; =20 - GIANT_REQUIRED; + /* translate to physical */ + phys =3D pmap_extract(kernel_pmap, (vm_offset_t)addr); + dma_range_checked =3D isa_dmarangecheck(addr, nbytes, chan); =20 #ifdef DIAGNOSTIC if (chan & ~VALID_DMA_MASK) @@ -215,8 +247,11 @@ || (chan >=3D 4 && (nbytes > (1<<17) || (u_int)addr & 1))) panic("isa_dmastart: impossible request"); =20 + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dmastart: channel %d not acquired\n", chan); +#else + mtx_lock(&isa_dma_lock); #endif =20 #if 0 @@ -231,7 +266,7 @@ =20 dma_busy |=3D (1 << chan); =20 - if (isa_dmarangecheck(addr, nbytes, chan)) { + if (dma_range_checked) { if (dma_bouncebuf[chan] =3D=3D NULL || dma_bouncebufsize[chan] < nbytes) panic("isa_dmastart: bad bounce buffer");=20 @@ -244,9 +279,6 @@ addr =3D newaddr; } =20 - /* translate to physical */ - phys =3D pmap_extract(kernel_pmap, (vm_offset_t)addr); - if (flags & ISADMA_RAW) { dma_auto_mode |=3D (1 << chan); } else {=20 @@ -321,6 +353,7 @@ /* unmask channel */ outb(DMA2_SMSK, chan & 3); } + mtx_unlock(&isa_dma_lock); } =20 void @@ -334,6 +367,7 @@ printf("isa_dmadone: channel %d not acquired\n", chan); #endif =20 + mtx_lock(&isa_dma_lock); if (((dma_busy & (1 << chan)) =3D=3D 0) &&=20 (dma_auto_mode & (1 << chan)) =3D=3D 0 ) printf("isa_dmadone: channel %d not busy\n", chan); @@ -349,6 +383,7 @@ dma_bounced &=3D ~(1 << chan); } dma_busy &=3D ~(1 << chan); + mtx_unlock(&isa_dma_lock); } =20 /* @@ -365,8 +400,6 @@ vm_offset_t endva; u_int dma_pgmsk =3D (chan & 4) ? ~(128*1024-1) : ~(64*1024-1); =20 - GIANT_REQUIRED; - endva =3D (vm_offset_t)round_page((vm_offset_t)va + length); for (; va < (caddr_t) endva ; va +=3D PAGE_SIZE) { phys =3D trunc_page(pmap_extract(kernel_pmap, (vm_offset_t)va)); @@ -419,13 +452,15 @@ * or -1 if the channel requested is not active. * */ -int -isa_dmastatus(int chan) +static int +isa_dmastatus_locked(int chan) { u_long cnt =3D 0; int ffport, waport; u_long low1, high1, low2, high2; =20 + mtx_assert(&isa_dma_lock, MA_OWNED); + /* channel active? */ if ((dma_inuse & (1 << chan)) =3D=3D 0) { printf("isa_dmastatus: channel %d not active\n", chan); @@ -471,6 +506,18 @@ return(cnt); } =20 +int +isa_dmastatus(int chan) +{ + int status; + + mtx_lock(&isa_dma_lock); + status =3D isa_dmastatus_locked(chan); + mtx_unlock(&isa_dma_lock); + + return (status); +} + /* * Reached terminal count yet ? */ @@ -490,12 +537,16 @@ int isa_dmastop(int chan)=20 { + int status; + + mtx_lock(&isa_dma_lock); if ((dma_inuse & (1 << chan)) =3D=3D 0) printf("isa_dmastop: channel %d not acquired\n", chan); =20 =20 if (((dma_busy & (1 << chan)) =3D=3D 0) && ((dma_auto_mode & (1 << chan)) =3D=3D 0)) { printf("chan %d not busy\n", chan); + mtx_unlock(&isa_dma_lock); return -2 ; } =20 @@ -504,7 +555,12 @@ } else { outb(DMA2_SMSK, (chan & 3) | 4 /* disable mask */); } - return(isa_dmastatus(chan)); + + status =3D isa_dmastatus_locked(chan); + + mtx_unlock(&isa_dma_lock); + + return (status); } =20 /* Index: dev/ieee488/ibfoo.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- dev/ieee488/ibfoo.c (revision 195829) +++ dev/ieee488/ibfoo.c (working copy) @@ -397,18 +397,14 @@ KASSERT(u->dmachan >=3D 0, ("Bogus dmachan %d", u->dmachan)); ib =3D u->ibfoo; ib->mode =3D DMA_IDATA; - mtx_lock(&Giant); isa_dmastart(ISADMA_READ, data, len, u->dmachan); - mtx_unlock(&Giant); mtx_lock(&u->mutex); upd7210_wr(u, IMR1, IXR1_ENDRX); upd7210_wr(u, IMR2, IMR2_DMAI); gpib_ib_wait_xfer(u, ib); mtx_unlock(&u->mutex); - mtx_lock(&Giant); j =3D isa_dmastatus(u->dmachan); isa_dmadone(ISADMA_READ, data, len, u->dmachan); - mtx_unlock(&Giant); return (len - j); } =20 @@ -790,14 +786,12 @@ mtx_unlock(&u->mutex); =20 if (u->dmachan >=3D 0) { - mtx_lock(&Giant); error =3D isa_dma_acquire(u->dmachan); if (!error) { error =3D isa_dma_init(u->dmachan, PAGE_SIZE, M_WAITOK); if (error) isa_dma_release(u->dmachan); } - mtx_unlock(&Giant); } =20 if (error) { @@ -855,9 +849,7 @@ free(ib, M_IBFOO); =20 if (u->dmachan >=3D 0) { - mtx_lock(&Giant); isa_dma_release(u->dmachan); - mtx_unlock(&Giant); } mtx_lock(&u->mutex); u->busy =3D 0; Index: dev/fdc/fdc.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- dev/fdc/fdc.c (revision 195829) +++ dev/fdc/fdc.c (working copy) @@ -781,11 +781,9 @@ =20 /* Disable ISADMA if we bailed while it was active */ if (fd !=3D NULL && (fd->flags & FD_ISADMA)) { - mtx_lock(&Giant); isa_dmadone( bp->bio_cmd & BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); - mtx_unlock(&Giant); mtx_lock(&fdc->fdc_mtx); fd->flags &=3D ~FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); @@ -958,11 +956,9 @@ /* Setup ISADMA if we need it and have it */ if ((bp->bio_cmd & (BIO_READ|BIO_WRITE|BIO_FMT)) && !(fdc->flags & FDC_NODMA)) { - mtx_lock(&Giant); isa_dmastart( bp->bio_cmd & BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); - mtx_unlock(&Giant); mtx_lock(&fdc->fdc_mtx); fd->flags |=3D FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); @@ -1040,11 +1036,9 @@ =20 /* Finish DMA */ if (fd->flags & FD_ISADMA) { - mtx_lock(&Giant); isa_dmadone( bp->bio_cmd & BIO_READ ? ISADMA_READ : ISADMA_WRITE, fd->fd_ioptr, fd->fd_iosize, fdc->dmachan); - mtx_unlock(&Giant); mtx_lock(&fdc->fdc_mtx); fd->flags &=3D ~FD_ISADMA; mtx_unlock(&fdc->fdc_mtx); --LZvS9be/3tNcYl/X-- --gatW/ieO32f1wygP Content-Type: application/pgp-signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.12 (FreeBSD) iEYEARECAAYFAkpoZm4ACgkQLVEj6D3CBExhmgCffL74DHfSYRKmwzY254c1+wSY hmcAni9yjfOzPfu7fc+eNhMecrBnVTcL =SI4E -----END PGP SIGNATURE----- --gatW/ieO32f1wygP--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20090723133230.GA69577>