Wednesday, March 23, 2016

Plugging Gaps


So the previous set of compiler directives singlehandedly fixed a wide-ranging set of errors when I tried to build the Linux code. But there were plenty more in there -- here are some others I found interesting to track down.

The porting code I had brought over included a couple already, such as likely and unlikely, which are hints to the compiler about the path it would do better to optimize for:
#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

There were a couple more obvious ones, such as __init and __exit used to designate the startup and shutdown functions for a Linux module. Since this code is all going to be one "module" (e.g. kext) on OS X and it comes with its own API for startup and shutdown, I just disabled those:
#define __init
#define __exit

Another simple copy from the Linux source was BUILD_BUG_ON, which aims to cause a compiler error:
#define BUILD_BUG_ON(condition) \
       ((void)sizeof(char[1 - 2*!!(condition ? 1 : 0)]))
While a little obscure, this relies on the fact that the calculated value will either be compiled out of existence or cause an error:
(void)sizeof(char[0])   // Compiled away
(void)sizeof(char[-1])  // Compiler error

The trickier ones were more embedded into the way things work in Linux, such as debugging. WARN_ON prints a message when the provided expression evaluates to true, and returns the value for use in an if statement. So the usage looks like this:
if(WARN_ON(foo == 3)) {
    return -EINVAL;
}
Well, WARN_ON causes an Oops, similar to a panic on Linux except it doesn't stop the machine dead. OS X doesn't have an apparent analog. Further, the Linux implementation quickly descends into large functions in printk.c, which also doesn't have an OS X equivalent. Instead, I went with the workaround of defining a global function to save a bug to the log. And because it's not expected to happen very often, I went with a fairly straightforward one and then saved the message to the log with IOLog. The only tricky bit is that is uses a printf-style string, so I had to handle variable arguments:
static void porting_print_warning(char *fmt, ...) {
    char buffer[200] = "AppleIntelWiFiMVM FATAL ";
    char *remainder = &buffer[24];
    va_list args;
    va_start(args, fmt);
    vsnprintf(remainder, 176, fmt, args);
    va_end(args);
    IOLog(buffer);
}

That made the WARN_ON implementation an easy copy from the Linux source, just replacing the function to call to log the actual warning:
#define WARN_ON(condition) ({                                 \
    int __ret_warn_on = !!(condition);                        \
    if (unlikely(__ret_warn_on))                              \
        porting_print_warning("at %s:%d/%s()!\n",             \
                              __FILE__, __LINE__, __func__);  \
    unlikely(__ret_warn_on);                                  \
})

The similar BUG and BUG_ON are a little trickier, because they're supposed to print a message and then panic the kernel. Well, the panic part is OK, but I think the message part is the problem -- I believe IOLog saves to an in-memory buffer, and something else flushes the buffer to the system log file. And I expect when the line after IOLog is panic(); probably the buffer never gets flushed to the log file. I've read that OS X saves the panic data to nvram in order to display it on the next boot, exactly because nothing can be reliably saved to disk in the event of a panic. So here's my implementation, which probably doesn't really work as desired (a note for future investigation):
#define BUG() do {                             \
    IOLog("BUG: failure at %s:%d/%s()!\n",     \
                __FILE__, __LINE__, __func__); \
    panic("BUG!");                             \
} while (0)

#define BUG_ON(condition) do {      \
    if (unlikely(condition)) BUG(); \
} while (0)

Now, you may be curious about the specific syntax used in the last couple examples (I know I was):
({...; foo;})

do {...} while (0)

The first, it seems, is the way you write some code that does something and then return foo from a macro that will be substituted into the condition of an if statement (shown here after substitution):
if(({...; foo;})) {...}
But also works (albeit suboptimally) as the result of the if statement instead of the condition:
if(foo) ({...; foo;});

The other is how you write a macro that returns nothing and may be used as the result of an if block with or without curly braces:
if(...)
    do {...} while (0);
or
if(...) {
    do {...} while (0);
}
Without the do/while, you might end up with something like this after substitution:
if(...)
    IOLog("BUG: failure at %s:%d/%s()!\n", ...);
panic("BUG!");

Ah, code that sometimes logs but always panics. That would deserve its own Oops.

No comments:

Post a Comment