Friday, January 5, 2024

Fixing Blue Homer

In the sample code we are copying, when configuring the framebuffer device, one of the options refers to a choice of byte order in the color setting: it can be "RGB" or "BGR". RGB was requested, but the sample accepts that the answer may come back "BGR". I ignored this, in part because the emulator accepts RGB.

My best guess is that the real hardware doesn't - and so Homer is inverted. Let's test this theory.

The response is (now) processed in lfb_init. So let's intercept that and send the RGB/BGR bit down the UART.
    let pixorder = unsafe { read_volatile(volbuf.add(24)) };
    write("pixel order is ");
    write_8_chars(hex32(pixorder));
    write("\r\n");
And we see:
pixel order is 00000000
That's zero, which our comments tell us is BGR, not RGB. So we need to store that in our struct and use it when rendering. This adds a certain amount of complexity, but here are the highlights:

We declare an enum to make it clearer what the two cases are. For some reason, this needs an annotation declaring that it derives PartialEq, although presumably we could not do that and explicitly define it. I assume this just means that we can test that two values with BGR are the same, as are two RGBs. We then add this into the struct of information about the Framebuffer:
#[derive(PartialEq)]
enum PixelOrder {
    BGR,
    RGB
}

struct FrameBufferInfo {
    width : u32,
    height : u32,
    pitch: u32,
    base_addr: u32,
    pixorder: PixelOrder
}
And then we need to set this to the correct enum value based on the 0 or 1 return value from the mailbox call:
    fb.pixorder = if pixorder == 1 { PixelOrder::RGB } else { PixelOrder::BGR };
Note that, if (like me) you did not already know this, Rust eschews the ternary operator in favour of having functional-style if statements that can return values (as long as you don't put semicolons after the final expression in a block).

And, of course, when we come to draw Homer we need to take this into account:
            if (fb.pixorder == PixelOrder::RGB) {
                unsafe { *((ptr + x*4 + 0) as *mut u8) = homer[homer_index + 0]; }
                unsafe { *((ptr + x*4 + 1) as *mut u8) = homer[homer_index + 1]; }
                unsafe { *((ptr + x*4 + 2) as *mut u8) = homer[homer_index + 2]; }
                unsafe { *((ptr + x*4 + 3) as *mut u8) = homer[homer_index + 3]; }
            } else {
                unsafe { *((ptr + x*4 + 2) as *mut u8) = homer[homer_index + 0]; }
                unsafe { *((ptr + x*4 + 1) as *mut u8) = homer[homer_index + 1]; }
                unsafe { *((ptr + x*4 + 0) as *mut u8) = homer[homer_index + 2]; }
                unsafe { *((ptr + x*4 + 3) as *mut u8) = homer[homer_index + 3]; }
            }
(It's a very small change, but the offsets in the indices on the left hand side in the second block go 2,1,0,3).

OK, let's try that. Yup, Homer is now the right colour. Let's check this in before it stops working! It's tagged as RUST_BARE_METAL_HOMER_PI.

And now that I have everything fairly stable, I'm going to finish the task I'd set myself and clean up all the dead code, removing unnecessary blocks of tracing and associated functions. The final version is tagged RUST_BARE_METAL_CLEANED_HOMER.

Conclusion

It's been painful (for me, at least) but we are now at the point where we can do a number of things on the bare metal of a Pi working almost entirely in Rust. But it's one big mess.

I think we've gone as far as Homer can really take us, but I do want to read about - and experiment with - some other concepts in this space, and improving Homer seems like the most sensible way forward.

As a list as much to myself as anything, I want to:
  • figure out how to get most of the standard library in, without pulling in dependencies on Linux;
  • after that, figure out how to get memory management to work so I can allocate blocks of memory;
  • using that, be able to allocate memory blocks of arbitrary alignment;
  • figure out how to get unit tests to run in this environment
  • look into making the code more modular.
While doing all this, I hope to gain a better understanding of what "idiomatic" Rust looks like and then, within that, to find my own "voice" At the moment, I suspect I am essentially trying to write Java code in Rust, which is slightly odd because I feel Rust itself is closer to my natural intuition of how to code than Java is.

When I have done all that, I think I want to move on and try and build a proper console on the monitor which can display the messages that would otherwise go to the UART (although it should be configurable to do both).

No comments:

Post a Comment