Re: [RFC PATCH v3 2/2] USB: add HCD_NO_COHERENT_MEM host controller driver flag

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Alan,

Alan Stern wrote:
>> The HCD_NO_COHERENT_MEM USB host controller driver flag can be enabled
>> to instruct the USB stack to avoid allocating coherent memory for USB
>> buffers.
>
>This patch appears to be based on your earlier work, not on the current 
>development kernel.
>

This patch is the second part of a 2 patch set based on top of v2.6.33.

The patchset was sent as:

[RFC PATCH v3 1/2] USB: refactor unmap_urb_for_dma/map_urb_for_dma
http://marc.info/?l=linux-usb&m=126756887220876

[RFC PATCH v3 2/2] USB: add HCD_NO_COHERENT_MEM host controller driver flag
http://marc.info/?l=linux-usb&m=126756887220879

The first patch, which basically refactors code, tries to cover these concerns from v2:
- expand urb_needs_*_dma_map() into multiple test and return statements
- record the mappings in the URB flags and use that info to do the right unmappings

The second patch, implements the HCD_NO_COHERENT_MEM support, and tries to cover these concerns:
- use urb->num_sgs > 0 to check for s-g requests
- don't check for s-g requests on control requests
- use the following revised logic for mapping transfer buffer (similar for setup_packet):
	If transfer_buffer_length is 0 then do nothing.
	Otherwise if num_sgs > 0 then do nothing.
	Otherwise if URB_NO_TRANSFER_DMA_MAP and transfer_dma != ~0
		(this avoids your HCD_NO_COHERENT_MEM case) then do nothing.

	Otherwise if hcd->self.uses_dma is set then call dma_map_single
	Otherwise if HCD_LOCAL_MEM is set then call hcd_alloc_coherent
	Otherwise do nothing (PIO case).

>> -	if (!bus->controller->dma_mask &&
>> -	    !(hcd->driver->flags & HCD_LOCAL_MEM)) {
>> +	if ((!bus->controller->dma_mask &&
>> +	    !(hcd->driver->flags & HCD_LOCAL_MEM)) ||
>> +	    (hcd->driver->flags & HCD_NO_COHERENT_MEM)) {
>>  		kfree(addr);
>>  		return;
>>  	}
>
>These three tests should be encapsulated in a single subroutine.
>

Ok, I'll fix that in a following v4 patch.

>> --- a/drivers/usb/core/hcd.c
>> +++ b/drivers/usb/core/hcd.c
>> @@ -1267,6 +1267,16 @@ static void unmap_urb_setup_packet(struct usb_hcd *hcd, struct urb *urb)
>>  		dma_unmap_single(hcd->self.controller, urb->setup_dma,
>>  				 sizeof(struct usb_ctrlrequest),
>>  				 DMA_TO_DEVICE);
>> +		/*
>> +		 * For the HCD_NO_COHERENT_MEM case we need to reset
>> +		 * setup_dma to its original value if the buffer was
>> +		 * allocated via usb_buffer_alloc().
>> +		 * This is necessary to force a new streaming DMA mapping
>> +		 * when the same buffer is used again as the setup packet.
>
>This is a little fragile.  I think it would be better to move the test
>for HCD_NO_COHERENT_MEM into the submission path, by combining it with
>the URB_NO_SETUP_DMA_MAP test.  Evidently this case has to be covered
>either during submission or during giveback, and doing it during
>submission would be cleaner.
>
>The same goes for URB_NO_TRANSFER_DMA_SETUP, of course.
>

Why exactly is this a little fragile?
Doing the reset of {transfer,setup}_dma on unmap time has benefits from doing it on map time.

I'll try to illustrate with an example.
A USB driver that submits several times the same "coherent" USB buffer allocated via usb_buffer_alloc() assumes that the DMA address returned by usb_buffer_alloc() is valid during the life of the buffer, and can be used as is by the USB driver.

When we do the reset of transfer_dma on unmap, which is my proposal, we have the following (pseudo-code) scenario:

	/*
	 * HCD_NO_COHERENT_MEM set.
	 * Strategy A.
	 * transfer_dma reset on unmap
	 */

	urb = usb_alloc_urb(0, GFP_KERNEL);
	buf = usb_buffer_alloc(dev, bufsz, GFP_KERNEL, &urb->transfer_dma);
	/* transfer_dma == ~0 */

	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 1st transfer, HCD_NO_COHERENT_MEM && transfer_dma == ~0 -> needs map true */
	usb_submit_urb(urb, GFP_KERNEL);
	/* transfer_dma reset on unmap */

	/* transfer_dma == ~0 */

	/* 2nd transfer, HCD_NO_COHERENT_MEM && transfer_dma == ~0 -> needs map true */
	usb_submit_urb(urb, GFP_KERNEL);

	/* transfer_dma == ~0 */

	/* ... etc */

In this case the value of urb->transfer_dma is always preserved from the point of view of the USB driver, and its value changes only _within_ usb_submit_urb() time.

On the contrary, when we do the reset of transfer_dma on map, we have the following (pseudo-code) scenario with the current logic:

	/*
	 * HCD_NO_COHERENT_MEM set.
	 * Strategy B.
	 * transfer_dma reset on map
	 */

	urb = usb_alloc_urb(0, GFP_KERNEL);
	buf = usb_buffer_alloc(dev, bufsz, GFP_KERNEL, &urb->transfer_dma);
	/* transfer_dma == ~0 */

	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 1st transfer, HCD_NO_COHERENT_MEM + transfer_dma == ~0 -> needs map true */
	usb_submit_urb(urb, GFP_KERNEL);
	/* this works, the kernel memory was properly mapped for the transfer */
	/* transfer_dma _not_ reset on unmap, has now a different value */

	/* transfer_dma != 0 && transfer_dma != ~0 */

	/*
	 * No way to distinguish now if transfer_dma really needs mapping or not,
	 * unless you _assume_ that it must be unconditionally reset to ~0 if
	 * HCD_NO_COHERENT_MEM is set.
	 * This is more fragile IMHO because if, for some reason, a DMA handle for
	 * a coherent buffer or a valid existing mapping is passed in
	 * transfer_dma then we will be doing the wrong thing.
	 *
	 * On the other hand, when we do the reset of transfer_dma at unmap time
	 * we are absolutely sure that transfer_dma came in with value ~0 when we are
	 * doing the unmap of a URB_NO_TRANSFER_DMA_MAP transfer in the HCD_NO_COHERENT_MEM case.
	 */

	/* 2nd transfer, HCD_NO_COHERENT_MEM && transfer_dma != ~0 -> needs map false! */
	usb_submit_urb(urb, GFP_KERNEL);
	/* this fails! the kernel memory was not properly mapped for the transfer! */
	/* transfer_dma != ~0 */


In this case we will have a different transfer_dma value visible for the USB driver depending on if we are doing the first transfer of the URB after its allocation or a subsequent transfer.
Also it is hard (and may fail to do it right) for the USB stack to determine for the subsequent transfers if the transfer_dma value is then a real mapping or not.

This problem is not present in the "reset-on-unmap" strategy.

I hope this clarifies a little bit the logic behind that decision.

Thanks,
Albert
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux