Subject: kern/29892: retain firmware DMA resources for iwi(4), avoiding reload failure
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <mrg@eterna.com.au>
List: netbsd-bugs
Date: 04/05/2005 10:59:00
>Number:         29892
>Category:       kern
>Synopsis:       retain firmware DMA resources for iwi(4), avoiding reload failure
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Apr 05 10:59:00 +0000 2005
>Originator:     matthew green
>Release:        NetBSD 3.99.1
>Organization:
people's front against (bozotic) www (softwar foundation)
>Environment:
	
	
System: NetBSD splode.eterna.com.au 2.99.10 NetBSD 2.99.10 (_splode_) #45: Thu Dec 2 00:47:41 EST 2004 mrg@littlekev.eterna.com.au:/var/obj/sparc/usr/src/sys/arch/sparc/compile/_splode_ sparc64
Architecture: sparc
Machine: sparc64
>Description:

	sometimes iwi(4) needs an 'ifconfig iwi0 down up' to bring it back
	to life.  this calls iwi_init() which reloads the cards firmware.
	this attempts to allocate DMA safe memory at this point, which can
	fail if the system has been running for sometime, which leaves the
	card non-functional.

>How-To-Repeat:

	use iwi(4) hard and have it hang, run 'ifconfig iwi0 down up' to
	bring it back to life.

>Fix:

	the patch below does not free the allocated DMA memory resources
	for the firmware, and tries to use them again if the size is not
	larger.  (it takes care to use the same size map in all dmamem
	ops in the case of a newer smaller image.)

	i've tested this and it works for me.  while i'd like to solve
	the main problem that requires the 'ifconfig iwi0 down up', this
	makes the card more reliable for me.

Index: if_iwi.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/if_iwi.c,v
retrieving revision 1.5
diff -p -r1.5 if_iwi.c
*** if_iwi.c	27 Feb 2005 00:27:33 -0000	1.5
--- if_iwi.c	5 Apr 2005 10:57:46 -0000
*************** iwi_detach(struct device* self, int flag
*** 357,362 ****
--- 357,364 ----
  	}
  
  	bus_space_unmap(sc->sc_st, sc->sc_sh, sc->sc_sz);
+ 	if (sc->sc_fw_allocsz)
+ 		bus_dmamem_free(sc->sc_dmat, &sc->sc_fw_seg, 1);
  
  	return 0;
  }
*************** iwi_load_ucode(struct iwi_softc *sc, voi
*** 1481,1486 ****
--- 1483,1518 ----
  	return 0;
  }
  
+ /*
+  * Try to retain DMA memory for reloads of the firmware, as trying to alloc
+  * 160KB or more later may fail.
+  */
+ static int
+ iwi_alloc_dmamem(struct iwi_softc *sc, bus_dma_tag_t tag, bus_size_t *size,
+ 		 bus_dma_segment_t *seg, int *nsegs)
+ {
+ 	int error;
+ 
+ 	if (sc->sc_fw_allocsz >= *size) {
+ 		*seg = sc->sc_fw_seg;
+ 		*nsegs = sc->sc_fw_nsegs;
+ 		*size = sc->sc_fw_allocsz;
+ 		error = 0;
+ 	} else {
+ 		if (sc->sc_fw_allocsz)
+ 			bus_dmamem_free(tag, &sc->sc_fw_seg, 1);
+ 		error = bus_dmamem_alloc(tag, *size, PAGE_SIZE,
+ 		    0, seg, 1, nsegs, BUS_DMA_NOWAIT);
+ 		if (error != 0) {
+ 			sc->sc_fw_seg = *seg;
+ 			sc->sc_fw_nsegs = *nsegs;
+ 			sc->sc_fw_allocsz = *size;
+ 		}
+ 	}
+ 	return error;
+ }
+ 
+ 
  /* macro to handle unaligned little endian data in firmware image */
  #define GETLE32(p) ((p)[0] | (p)[1] << 8 | (p)[2] << 16 | (p)[3] << 24)
  static int
*************** iwi_load_firmware(struct iwi_softc *sc, 
*** 1492,1520 ****
  	u_char *p, *end;
  	u_int32_t sentinel, ctl, src, dst, sum, len, mlen;
  	int ntries, nsegs, error;
! 
! 	/* Allocate DMA memory for storing firmware image */
! 	error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
! 	    BUS_DMA_NOWAIT, &map);
! 	if (error != 0) {
! 		aprint_error("%s: could not create firmware DMA map\n",
! 		    sc->sc_dev.dv_xname);
! 		goto fail1;
! 	}
  
  	/*
  	 * We cannot map fw directly because of some hardware constraints on
  	 * the mapping address.
  	 */
! 	error = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &seg, 1,
! 	    &nsegs, BUS_DMA_NOWAIT);
  	if (error != 0) {
! 		aprint_error("%s: could allocate firmware DMA memory\n",
  		    sc->sc_dev.dv_xname);
  		goto fail2;
  	}
  
! 	error = bus_dmamem_map(sc->sc_dmat, &seg, nsegs, size, &virtaddr,
  	    BUS_DMA_NOWAIT);
  	if (error != 0) {
  		aprint_error("%s: could not load firmware DMA map\n",
--- 1524,1553 ----
  	u_char *p, *end;
  	u_int32_t sentinel, ctl, src, dst, sum, len, mlen;
  	int ntries, nsegs, error;
! 	bus_size_t nsize;
  
  	/*
  	 * We cannot map fw directly because of some hardware constraints on
  	 * the mapping address.
  	 */
! 	nsize = size;
! 	error = iwi_alloc_dmamem(sc, sc->sc_dmat, &nsize, &seg, &nsegs);
  	if (error != 0) {
! 		aprint_error("%s: could not allocate firmware DMA memory\n",
  		    sc->sc_dev.dv_xname);
  		goto fail2;
  	}
  
! 	/* Allocate DMA memory for storing firmware image */
! 	error = bus_dmamap_create(sc->sc_dmat, nsize, 1, nsize, 0,
! 	    BUS_DMA_NOWAIT, &map);
! 	if (error != 0) {
! 		aprint_error("%s: could not create firmware DMA map\n",
! 		    sc->sc_dev.dv_xname);
! 		goto fail1;
! 	}
! 
! 	error = bus_dmamem_map(sc->sc_dmat, &seg, nsegs, nsize, &virtaddr,
  	    BUS_DMA_NOWAIT);
  	if (error != 0) {
  		aprint_error("%s: could not load firmware DMA map\n",
*************** iwi_load_firmware(struct iwi_softc *sc, 
*** 1522,1528 ****
  		goto fail3;
  	}
  
! 	error = bus_dmamap_load(sc->sc_dmat, map, virtaddr, size, NULL,
  	    BUS_DMA_NOWAIT);
  	if (error != 0) {
  		aprint_error("%s: could not load fw dma map\n",
--- 1555,1561 ----
  		goto fail3;
  	}
  
! 	error = bus_dmamap_load(sc->sc_dmat, map, virtaddr, nsize, NULL,
  	    BUS_DMA_NOWAIT);
  	if (error != 0) {
  		aprint_error("%s: could not load fw dma map\n",
*************** iwi_load_firmware(struct iwi_softc *sc, 
*** 1613,1622 ****
  		goto fail5;
  	}
  
! fail5:	bus_dmamap_sync(sc->sc_dmat, map, 0, size, BUS_DMASYNC_POSTWRITE);
  	bus_dmamap_unload(sc->sc_dmat, map);
! fail4:	bus_dmamem_unmap(sc->sc_dmat, virtaddr, size);
! fail3:	bus_dmamem_free(sc->sc_dmat, &seg, 1);
  fail2:	bus_dmamap_destroy(sc->sc_dmat, map);
  
  fail1:	return error;
--- 1646,1655 ----
  		goto fail5;
  	}
  
! fail5:	bus_dmamap_sync(sc->sc_dmat, map, 0, nsize, BUS_DMASYNC_POSTWRITE);
  	bus_dmamap_unload(sc->sc_dmat, map);
! fail4:	bus_dmamem_unmap(sc->sc_dmat, virtaddr, nsize);
! fail3:	/*bus_dmamem_free(sc->sc_dmat, &seg, 1);*/
  fail2:	bus_dmamap_destroy(sc->sc_dmat, map);
  
  fail1:	return error;
Index: if_iwivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/if_iwivar.h,v
retrieving revision 1.2
diff -p -r1.2 if_iwivar.h
*** if_iwivar.h	11 Jan 2005 18:49:05 -0000	1.2
--- if_iwivar.h	5 Apr 2005 10:57:46 -0000
*************** struct iwi_softc {
*** 80,85 ****
--- 80,89 ----
  
  	bus_dma_tag_t		sc_dmat;
  
+ 	bus_size_t		sc_fw_allocsz;
+ 	bus_dma_segment_t	sc_fw_seg;
+ 	int			sc_fw_nsegs;
+ 
  	struct iwi_tx_desc	*tx_desc;
  	bus_dmamap_t		tx_ring_map;
  	bus_dma_segment_t	tx_ring_seg;