Access to this is of course considered unsafe, because it's a raw pointer and you need to ensure there are no data races, but technically that's identical to a global pointer that you'd define in C.
You could wrap that in a struct (AKA newtype) and implement safe methods on that struct.
It's fine to have it behind a mutex that will ensure no mutable aliasing at run time, but it can't be a bare global variable that doesn't ensure that (but the type implies it is guaranteed).
You can set global variables without lazy_static! if the initializer is const. Rust is currently expanding number of things that are const-compatible, so check the latest version or nightly.
If you feature-gate the following code based on some feature / target that guarantees that reads and writes to 0xB8000 (and to the 80 * 25 - 1 following u16s) are valid, I like doing the following:
Having a singleton type (i.e., accessible only from a single static or with a constructor that enforces it with a runtime check).
This singleton type is a Mutex that owns/wraps a dummy ().
The RAII guard from .lock() is also wrapped so that it Deref[Mut]s to the desired buffer, thus acting as if the Mutex was actually wrapping the buffer.
This provides a sound interface thanks to the singleton invariant, as long as the address is valid etc. and not accessed elsewhere.