Subject: Re: [PATCH] bcm43xx: Fix problem with >1 GB RAM

From: Michael Buesch <[email-masked]>
Date: Mon, 22 Jan 2007 19:20:28 +0100

On Saturday 20 January 2007 17:18, Larry Finger wrote:
> Some versions of the bcm43xx chips only support 30-bit DMA, which means
> that the descriptors and buffers must be in the first 1 GB of RAM. On
> the i386 and x86_64 architectures with more than 1 GB RAM, an incorrect
> assignment may occur. This patch ensures that the various DMA addresses
> are within the capability of the chip. Testing has been limited to x86_64
> as no one has an i386 system with more than 1 GB RAM.
> 
> Signed-off-by: Larry Finger <[email-masked]>
> ---
> 
> John,
> 
> This patch should be applied to wireless-2.6 and pushed up to 2.6.20.
> 
> Thanks,
> 
> Larry
> 
> Index: linux-2.6/drivers/net/wireless/bcm43xx/bcm43xx_dma.c
> ===================================================================
> --- linux-2.6.orig/drivers/net/wireless/bcm43xx/bcm43xx_dma.c
> +++ linux-2.6/drivers/net/wireless/bcm43xx/bcm43xx_dma.c
> @@ -145,16 +145,14 @@ dma_addr_t map_descbuffer(struct bcm43xx
>  			  int tx)
>  {
>  	dma_addr_t dmaaddr;
> +	int direction = PCI_DMA_FROMDEVICE;
>  
> -	if (tx) {
> -		dmaaddr = dma_map_single(&ring->bcm->pci_dev->dev,
> -					 buf, len,
> -					 DMA_TO_DEVICE);
> -	} else {
> -		dmaaddr = dma_map_single(&ring->bcm->pci_dev->dev,
> +	if (tx)
> +		direction = PCI_DMA_TODEVICE;
> +
> +	dmaaddr = pci_map_single(ring->bcm->pci_dev,
>  					 buf, len,
> -					 DMA_FROM_DEVICE);
> -	}
> +					 direction);
>  
>  	return dmaaddr;
>  }
> @@ -166,13 +164,13 @@ void unmap_descbuffer(struct bcm43xx_dma
>  		      int tx)
>  {
>  	if (tx) {
> -		dma_unmap_single(&ring->bcm->pci_dev->dev,
> +		pci_unmap_single(ring->bcm->pci_dev,
>  				 addr, len,
> -				 DMA_TO_DEVICE);
> +				 PCI_DMA_TODEVICE);
>  	} else {
> -		dma_unmap_single(&ring->bcm->pci_dev->dev,
> +		pci_unmap_single(ring->bcm->pci_dev,
>  				 addr, len,
> -				 DMA_FROM_DEVICE);
> +				 PCI_DMA_FROMDEVICE);
>  	}
>  }
>  
> @@ -183,8 +181,8 @@ void sync_descbuffer_for_cpu(struct bcm4
>  {
>  	assert(!ring->tx);
>  
> -	dma_sync_single_for_cpu(&ring->bcm->pci_dev->dev,
> -				addr, len, DMA_FROM_DEVICE);
> +	pci_dma_sync_single_for_cpu(ring->bcm->pci_dev,
> +				    addr, len, PCI_DMA_FROMDEVICE);
>  }

Any special reason why you convert the DMA operations to the PCI
stuff? I ask, because if this makes a difference, it affects the
new SSB subsystem as well.

>  static inline
> @@ -194,8 +192,8 @@ void sync_descbuffer_for_device(struct b
>  {
>  	assert(!ring->tx);
>  
> -	dma_sync_single_for_device(&ring->bcm->pci_dev->dev,
> -				   addr, len, DMA_FROM_DEVICE);
> +	pci_dma_sync_single_for_cpu(ring->bcm->pci_dev,
> +				    addr, len, PCI_DMA_TODEVICE);
>  }
>  
>  /* Unmap and free a descriptor buffer. */
> @@ -214,17 +212,53 @@ void free_descriptor_buffer(struct bcm43
>  
>  static int alloc_ringmemory(struct bcm43xx_dmaring *ring)
>  {
> -	struct device *dev = &(ring->bcm->pci_dev->dev);
> -
> -	ring->descbase = dma_alloc_coherent(dev, BCM43xx_DMA_RINGMEMSIZE,
> -					    &(ring->dmabase), GFP_KERNEL);
> +	ring->descbase = pci_alloc_consistent(ring->bcm->pci_dev, BCM43xx_DMA_RINGMEMSIZE,
> +					    &(ring->dmabase));
>  	if (!ring->descbase) {
> -		printk(KERN_ERR PFX "DMA ringmemory allocation failed\n");
> -		return -ENOMEM;
> +		/* Allocation may have failed due to pci_alloc_consistent
> +		   insisting on use of GFP_DMA, which is more restrictive
> +		   than necessary...  */
> +		struct dma_desc *rx_ring;
> +		dma_addr_t rx_ring_dma;
> +
> +		rx_ring = kzalloc(BCM43xx_DMA_RINGMEMSIZE, GFP_KERNEL);
> +		if (!rx_ring)
> +			goto out_err;
> +
> +		rx_ring_dma = pci_map_single(ring->bcm->pci_dev, rx_ring,
> +					     BCM43xx_DMA_RINGMEMSIZE,
> +					     PCI_DMA_BIDIRECTIONAL);
> +
> +		if (pci_dma_mapping_error(rx_ring_dma) ||
> +		    rx_ring_dma + BCM43xx_DMA_RINGMEMSIZE > ring->bcm->dma_mask) {
> +			/* Sigh... */
> +			if (!pci_dma_mapping_error(rx_ring_dma))
> +				pci_unmap_single(ring->bcm->pci_dev,
> +						 rx_ring_dma, BCM43xx_DMA_RINGMEMSIZE,
> +						 PCI_DMA_BIDIRECTIONAL);
> +			rx_ring_dma = pci_map_single(ring->bcm->pci_dev,
> +						 rx_ring, BCM43xx_DMA_RINGMEMSIZE,
> +						 PCI_DMA_BIDIRECTIONAL);
> +			if (pci_dma_mapping_error(rx_ring_dma) ||
> +			    rx_ring_dma + BCM43xx_DMA_RINGMEMSIZE > ring->bcm->dma_mask) {
> +				assert(0);
> +				if (!pci_dma_mapping_error(rx_ring_dma))
> +					pci_unmap_single(ring->bcm->pci_dev,
> +							 rx_ring_dma, BCM43xx_DMA_RINGMEMSIZE,
> +							 PCI_DMA_BIDIRECTIONAL);
> +                        	goto out_err;
> +			}
> +                }
> +
> +                ring->descbase = rx_ring;
> +                ring->dmabase = rx_ring_dma;
>  	}
>  	memset(ring->descbase, 0, BCM43xx_DMA_RINGMEMSIZE);
>  
>  	return 0;
> +out_err:
> +	printk(KERN_ERR PFX "DMA ringmemory allocation failed\n");
> +	return -ENOMEM;
>  }
>  
>  static void free_ringmemory(struct bcm43xx_dmaring *ring)
> @@ -407,6 +441,29 @@ static int setup_rx_descbuffer(struct bc
>  	if (unlikely(!skb))
>  		return -ENOMEM;
>  	dmaaddr = map_descbuffer(ring, skb->data, ring->rx_buffersize, 0);
> +	/* This hardware bug work-around adapted from the b44 driver.
> +	   The chip may be unable to do PCI DMA to/from anything above 1GB */
> +	if (pci_dma_mapping_error(dmaaddr) ||
> +	    dmaaddr + ring->rx_buffersize > ring->bcm->dma_mask) {
> +		/* This one has 30-bit addressing... */
> +		if (!pci_dma_mapping_error(dmaaddr))
> +			pci_unmap_single(ring->bcm->pci_dev,
> +					 dmaaddr, ring->rx_buffersize,
> +					 PCI_DMA_FROMDEVICE);
> +		dev_kfree_skb_any(skb);
> +		skb = __dev_alloc_skb(ring->rx_buffersize,GFP_DMA);
> +		if (skb == NULL)
> +			return -ENOMEM;
> +		dmaaddr = pci_map_single(ring->bcm->pci_dev,
> +					 skb->data, ring->rx_buffersize,
> +					 PCI_DMA_FROMDEVICE);
> +		if (pci_dma_mapping_error(dmaaddr) ||
> +		    dmaaddr + ring->rx_buffersize > ring->bcm->dma_mask) {
> +			assert(0);
> +			dev_kfree_skb_any(skb);
> +			return -ENOMEM;
> +		}
> +	}
>  	meta->skb = skb;
>  	meta->dmaaddr = dmaaddr;
>  	skb->dev = ring->bcm->net_dev;
> @@ -636,8 +693,10 @@ struct bcm43xx_dmaring * bcm43xx_setup_d
>  	err = dmacontroller_setup(ring);
>  	if (err)
>  		goto err_free_ringmemory;
> +	return ring;
>  
>  out:
> +	printk(KERN_ERR PFX "Error in bcm43xx_setup_dmaring\n");
>  	return ring;
>  
>  err_free_ringmemory:
> @@ -705,30 +764,16 @@ int bcm43xx_dma_init(struct bcm43xx_priv
>  	struct bcm43xx_dmaring *ring;
>  	int err = -ENOMEM;
>  	int dma64 = 0;
> -	u64 mask = bcm43xx_get_supported_dma_mask(bcm);
> -	int nobits;
>  
> -	if (mask == DMA_64BIT_MASK) {
> +	bcm->dma_mask = bcm43xx_get_supported_dma_mask(bcm);
> +	if (bcm->dma_mask == DMA_64BIT_MASK)
>  		dma64 = 1;
> -		nobits = 64;
> -	} else if (mask == DMA_32BIT_MASK)
> -		nobits = 32;
> -	else
> -		nobits = 30;
> -	err = pci_set_dma_mask(bcm->pci_dev, mask);
> -	err |= pci_set_consistent_dma_mask(bcm->pci_dev, mask);
> -	if (err) {
> -#ifdef CONFIG_BCM43XX_PIO
> -		printk(KERN_WARNING PFX "DMA not supported on this device."
> -					" Falling back to PIO.\n");
> -		bcm->__using_pio = 1;
> -		return -ENOSYS;
> -#else
> -		printk(KERN_ERR PFX "FATAL: DMA not supported and PIO not configured. "
> -				    "Please recompile the driver with PIO support.\n");
> -		return -ENODEV;
> -#endif /* CONFIG_BCM43XX_PIO */
> -	}
> +	err = pci_set_dma_mask(bcm->pci_dev, bcm->dma_mask);
> +	if (err)
> +		goto no_dma;
> +	err = pci_set_consistent_dma_mask(bcm->pci_dev, bcm->dma_mask);
> +	if (err)
> +		goto no_dma;
>  
>  	/* setup TX DMA channels. */
>  	ring = bcm43xx_setup_dmaring(bcm, 0, 1, dma64);
> @@ -774,9 +819,12 @@ int bcm43xx_dma_init(struct bcm43xx_priv
>  		dma->rx_ring3 = ring;
>  	}
>  
> -	dprintk(KERN_INFO PFX "%d-bit DMA initialized\n", nobits);
> +	dprintk(KERN_INFO PFX "%d-bit DMA initialized\n",
> +		(bcm->dma_mask == DMA_64BIT_MASK) ? 64 :
> +		(bcm->dma_mask == DMA_32BIT_MASK) ? 32 : 30);
>  	err = 0;
>  out:
> +	if(err)BUG();
>  	return err;
>  
>  err_destroy_rx0:
> @@ -800,7 +848,18 @@ err_destroy_tx1:
>  err_destroy_tx0:
>  	bcm43xx_destroy_dmaring(dma->tx_ring0);
>  	dma->tx_ring0 = NULL;
> -	goto out;
> +no_dma:
> +#ifdef CONFIG_BCM43XX_PIO
> +	printk(KERN_WARNING PFX "DMA not supported on this device."
> +				" Falling back to PIO.\n");
> +	bcm->__using_pio = 1;
> +	BUG();

That isn't a BUG. Just remove this call, please.

> +	return -ENOSYS;
> +#else
> +	printk(KERN_ERR PFX "FATAL: DMA not supported and PIO not configured. "
> +			    "Please recompile the driver with PIO support.\n");
> +	return -ENODEV;
> +#endif /* CONFIG_BCM43XX_PIO */
>  }
>  
>  /* Generate a cookie for the TX header. */
> @@ -905,6 +964,7 @@ static void dma_tx_fragment(struct bcm43
>  	struct bcm43xx_dmadesc_generic *desc;
>  	struct bcm43xx_dmadesc_meta *meta;
>  	dma_addr_t dmaaddr;
> +	struct sk_buff *bounce_skb;
>  
>  	assert(skb_shinfo(skb)->nr_frags == 0);
>  
> @@ -924,9 +984,28 @@ static void dma_tx_fragment(struct bcm43
>  			       skb->len - sizeof(struct bcm43xx_txhdr),
>  			       (cur_frag == 0),
>  			       generate_cookie(ring, slot));
> +	dmaaddr = map_descbuffer(ring, skb->data, skb->len, 1);
> +	if (dma_mapping_error(dmaaddr) || dmaaddr + skb->len > ring->bcm->dma_mask) {
> +		/* chip cannot handle DMA to/from > 1GB, use bounce buffer (copied from b44 driver) */
> +		if (!dma_mapping_error(dmaaddr))
> +			unmap_descbuffer(ring, dmaaddr, skb->len, 1);
> +		bounce_skb = __dev_alloc_skb(skb->len, GFP_ATOMIC|GFP_DMA);
> +		if (!bounce_skb)
> +			return;
> +		dmaaddr = map_descbuffer(ring, bounce_skb->data, bounce_skb->len, 1);
> +		if (dma_mapping_error(dmaaddr) || dmaaddr + skb->len > ring->bcm->dma_mask) {
> +			if (!dma_mapping_error(dmaaddr))
> +				unmap_descbuffer(ring, dmaaddr, skb->len, 1);
> +			dev_kfree_skb_any(bounce_skb);
> +			assert(0);
> +			return;
> +		}
> +		memcpy(skb_put(bounce_skb, skb->len), skb->data, skb->len);
> +		dev_kfree_skb_any(skb);
> +		skb = bounce_skb;
> +	}
>  
>  	meta->skb = skb;
> -	dmaaddr = map_descbuffer(ring, skb->data, skb->len, 1);
>  	meta->dmaaddr = dmaaddr;
>  
>  	fill_descriptor(ring, desc, dmaaddr,
> Index: linux-2.6/drivers/net/wireless/bcm43xx/bcm43xx.h
> ===================================================================
> --- linux-2.6.orig/drivers/net/wireless/bcm43xx/bcm43xx.h
> +++ linux-2.6/drivers/net/wireless/bcm43xx/bcm43xx.h
> @@ -771,6 +771,7 @@ struct bcm43xx_private {
>  	 * This is currently always BCM43xx_BUSTYPE_PCI
>  	 */
>  	u8 bustype;
> +	u64 dma_mask;
>  
>  	u16 board_vendor;
>  	u16 board_type;

The rest is OK, I think.
Thanks for the nice work.

-- 
Greetings Michael.