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