Subject: [PATCH 11/31] dma: add channel request API that supports deferred probe

From: [email-masked] (Stephen Warren)
Date: Thu, 21 Nov 2013 11:22:13 -0700

On 11/20/2013 08:22 PM, Dan Williams wrote:
> On Wed, Nov 20, 2013 at 1:24 PM, Stephen Warren <[email-masked]> wrote:
>> On 11/20/2013 01:23 PM, Williams, Dan J wrote:
>> ...
>>> Why do the drivers that call dma_request_channel need to convert it to
>>> an ERR value?  i.e. what's problematic about the below (not compile
>>> tested)?
>> ...
>>> diff --git a/arch/arm/plat-samsung/dma-ops.c b/arch/arm/plat-samsung/dma-ops.c
>> ...
>>> @@ -22,16 +22,20 @@ static unsigned samsung_dmadev_request(enum dma_ch dma_ch,
>>>                               struct samsung_dma_req *param,
>>>                               struct device *dev, char *ch_name)
>> ...
>>> +     if (dev->of_node) {
>>> +             chan = dma_request_slave_channel(dev, ch_name);
>>> +             return IS_ERR(chan) ? (unsigned) NULL : (unsigned) chan;
>>> +     } else {
>>>               return (unsigned)dma_request_channel(mask, pl330_filter,
>>>                                                       (void *)dma_ch);
>>> +     }
>>
>> The argument is that if a function returns errors encoded as an ERR
>> pointer, then callers must assume that any non-IS_ERR value that the
>> function returns is valid. NULL is one of those values. As such, callers
>> can no longer check the value against NULL, but must use IS_ERR().
>> Converting any IS_ERR() returns to NULL theoretically is the act of
>> converting one valid return value to some other completely random return
>> value.
> 
> You describe how IS_ERR() works, but you didn't address the patch.
> There's nothing random about the changes to samsung_dmadev_request().
> It still returns NULL or a valid channel just as before.

I was addressing the patch. I guess I should have explained as follows.

First, the following code is technically buggy:

+             chan = dma_request_slave_channel(dev, ch_name);
+             return IS_ERR(chan) ? (unsigned) NULL : (unsigned) chan;

... since it assumes that dma_request_slave_channel() never returns NULL
as a valid non-error value. This is specifically prohibited by the fact
that dma_request_slave_channel() returns either an ERR value or a valid
value; in that case, NULL is not an ERR value, and hence must be
considered valid.

Also, observe that the following are semantically equivalent:

1)

	/*
	 * This is a demonstration of using IS_ERR_OR_NULL for error
	 * checking. We want to remove use of IS_ERR_OR_NULL though.
	 */
	chan = dma_request_slave_channel(dev, ch_name);
	if (IS_ERR_OR_NULL(chan))
		return -ESOMETHING;

2)

	/* These first 3 lines are part of your patch */
	chan = dma_request_slave_channel(dev, ch_name);
	if (IS_ERR(chan)
		chan = NULL;
	if (!chan) // This test and the above are IS_ERR_OR_NULL
		attempt allocation some other way;
	/*
	 * This is code elsewhere in a driver where DMA is optional;
	 * that code must act differently based on whether a DMA
	 * channel was acquired or not. So, it tests chan against
	 * NULL.
	 */
	if (!chan) // This test and the above IS_ERR are IS_ERR_OR_NULL
		return -ESOMETHING;

In case (2) above, if the driver /only/ calls a modified
dma_request_slave_channel(), all the checks could just be if
(IS_ERR(chan)) instead - then problem solved. However, if the driver
mixes the new dma_request_slave_channel() and the unconverted
dma_request_channel(), it has to either (a) convert an ERR return from
dma_request_slave_channel() to match dma_request_channel()'s NULL error
return, or (b) convert a NULL return from dma_request_channel() to match
dma_request_slave_channel()'s ERR return. Without the conversion, all
tests would have to use the deprecated IS_ERR_OR_NULL. Either of those
conversion options converts an error value from 1 API into a
theoretically valid return value from the other API, which is a bug.

Perhaps one can argue that an API that returns either a valid value or
NULL can never return a value that matches an ERR value? If so, perhaps
the following would work in practice:

if (dev->of_node) {
	chan = dma_request_slave_channel(dev, ch_name);
} else {
	chan = dma_request_channel(mask, pl330_filter,
					(void *)dma_ch);
	if (!chan)
		chan = ERR_PTR(-ENODEV);
}
...
if (IS_ERR(chan))
...

... but I'm not sure whether that assumption is acceptable.