Skip to content
Advertisement

Specific reasons to use |= instead of =

I am currently looking over some code in the Linux kernel (thermal management). In some places there is a return value used to signal errors, which is set to 0 at the beginning of the function. Then when calling a function which may fail it is set to the new value using |= instead of =. Here is an example:

int ti_bandgap_read_temperature(struct ti_bandgap *bgp, int id,
                int *temperature)
{
    u32 temp;
    int ret;

    ret = ti_bandgap_validate(bgp, id);
    if (ret)
        return ret;

    spin_lock(&bgp->lock);
    temp = ti_bandgap_read_temp(bgp, id);
    spin_unlock(&bgp->lock);

    ret |= ti_bandgap_adc_to_mcelsius(bgp, temp, &temp);
    if (ret)
        return -EIO;

    *temperature = temp;

    return 0;
}

The definition of ti_bandgap_validate is:

/**
 * ti_bandgap_validate() - helper to check the sanity of a struct ti_bandgap
 * @bgp: struct ti_bandgap pointer
 * @id: bandgap sensor id
 *
 * Checks if the bandgap pointer is valid and if the sensor id is also
 * applicable.
 *
 * Return: 0 if no errors, -EINVAL for invalid @bgp pointer or -ERANGE if
 * @id cannot index @bgp sensors.
 */
static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id)

So, if my reasoning is correct, at the call to ti_bandgap_adc_to_mcelsius(), the value of ret must be 0 (otherwise the function would have exited already). So what is the reason for using |= here instead of =? Doing an “or” with a pattern of all zeros will just give back the normal pattern. Is this some kind of optimization for the usual case, i.e. that the function is returning no failure (i.e. return value 0)? Or is there some other difference I am missing? This code is running on an ARM-architecture, so it might have to do something with specific optimizations for that platform.

Advertisement

Answer

In this case, there is no reason to use |= however, if you are tracking a number of functions that might error, and return if they error as the return code, the pattern is

boolean error = false

error |= doFirstStep(...);
error |= doSecondStep(...);
error |= doThirdStep(...);

if (error) {
  printf("error occurred: %sn", strerror(errno));
}

It is a lesser used pattern in C, occasionally used in languages with some affinity with C. In the C language, a great number of C library functions return an “error code” which is typically 0 on successful operation.

When using this pattern, the user relies on the return of zero as a success condition. This means that the above-mentioned log_and_recover() might pull the error message out of the error.h a static variable, as is common to the C #include <error.h> routines.

—- To continue in why this is often used on int fields —-

You also see this pattern with an int holding the error.

int error = 0; // or a variable that's defined to zero

error |= doFirstStep(...);
error |= doSecondStep(...);
error |= doThirdStep(...);

if (error != 0) {
  ... some error handling ...
}

When you see this, it is the same idea as above, but the developer combined two patterns. The bit field pattern which is often used for packing configuration parameters is being leveraged to pack multiple kinds of errors. Typically when this happens you can find a list of errors similar to

#define ERROR_NO_DISK  (1<<1);
#define ERROR_NO_NETWORK (1<<2);
#define ERROR_NO_SANITY (1<<3);

It isn’t terribly sensible for most items to return multiple errors and handle them as one; but sometimes it is done when error suppression is important. For example, if you fail transmit a message from a client to a host, odds are you could suppress the various “failure to open a socket”, “failure to write to socket”, “failure to copy to buffer”, etc. failures into a generic “failure to send X”. At one level, the entire top level workflow failed, and if needed the details of why are still somewhat available.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement