[HN Gopher] Bare metal printf - C standard library without OS
       ___________________________________________________________________
        
       Bare metal printf - C standard library without OS
        
       Author : todsacerdoti
       Score  : 215 points
       Date   : 2025-04-26 21:32 UTC (1 days ago)
        
 (HTM) web link (popovicu.com)
 (TXT) w3m dump (popovicu.com)
        
       | eqvinox wrote:
       | I was very confused by the title, expected someone writing their
       | own printf -- i.e. the part that parses the format string, grabs
       | varargs, converts numbers, lines up strings, etc.
       | 
       | I'd have called it "Bare metal puts()" or "Bare metal write()" or
       | something along those lines instead.
       | 
       | (FWIW, FreeBSD's printf() is quite easy to pluck out of its
       | surrounding libc infrastructure and adapt/customize.)
        
         | anyfoo wrote:
         | FreeBSD's printf is my goto, too! It's indeed enormously simple
         | to pluck out, instantly gives you full-featured printf, and has
         | added features such as dumping memory as hex.
        
           | eqvinox wrote:
           | Funnily enough we're not even referring to the same one, the
           | hexdump thing is in FreeBSD's kernel printf, I was looking at
           | the userspace one :). Haven't looked at the kernel one myself
           | but nice to hear it's also well-engineered.
           | 
           | (The problem with '%D' hexdumps is that it breaks compiler
           | format checking... and also 'D' is a length modifier for
           | _Decimal64 starting in ISO C23... that's why our hexdump is
           | hooked in as '%.*pHX' instead [which still gives a warning
           | because %p is not supposed to have a precision, but at least
           | it's not entirely broken.])
        
         | einpoklum wrote:
         | Is it? Could you elaborate/provide links to examples of this?
         | 
         | What customization would it support? Say, compared to these
         | options:
         | 
         | https://github.com/eyalroz/printf?tab=readme-ov-file#cmake-o...
        
           | eqvinox wrote:
           | > Is it? Could you elaborate/provide links to examples of
           | this?
           | 
           | https://github.com/FRRouting/frr/tree/master/lib/printf
           | 
           | Disclaimer: my work.
           | 
           | Customised to support %pHX, %pI4, %pFX, etc. - docs at
           | https://docs.frrouting.org/projects/dev-
           | guide/en/latest/logg... for what these do.
           | 
           | > What customization would it support?
           | 
           | I don't understand your question. It's reasonably readable
           | and understandable source code. You edit the source code.
           | That's the customisation?
           | 
           | > Say, compared to these options:
           | https://github.com/eyalroz/printf?tab=readme-ov-
           | file#cmake-o...
           | 
           | First, it is customary etiquette to indicate when linking
           | your own code/work.
           | 
           | Second, that is not a POSIX compatible printf, it lacks
           | support for '%n$' (which is used primarily for localisation).
           | Arguably can make sense to omit for tiny embedded platforms -
           | but then why is there FP support?
           | 
           | Third, cmake and build options really seem to be overkill for
           | something like this. Copy the code into the target project,
           | edit it. If you use your own printf, you probably need a
           | bunch of other custom stuff anyway.
           | 
           | Fourth, the output callback is a reasonable idea, but
           | somewhat self-contradictory. You're bringing in your own
           | printf. Just adapt it to your own I/O backend, like libc has
           | FILE*.
        
             | einpoklum wrote:
             | > You edit the source code. That's the customisation?
             | 
             | I meant, customization where you don't have to write the
             | customized code yourself, just choose some build options,
             | or at most set preprocessor variables.
             | 
             | > First, it is customary etiquette to indicate when linking
             | your own code/work.
             | 
             | You're right, although I was only linking to the table of
             | CMake options. And it's only partially my code, since I'm
             | the maintainer rather than the original author
             | 
             | > You're bringing in your own printf. Just adapt it to your
             | own I/O backend, like libc has FILE _.
             | 
             | One can always do that, but - with the output callback -
             | you can bring in an already-compiled object, which is
             | sometimes convenient.
             | 
             | > If you use your own printf, you probably need a bunch of
             | other custom stuff anyway.
             | 
             | My personal use case (and the reason I adopted the library)
             | was printf deficiencies in CUDA GPU kernels. And - I really
             | needed nothing other than printf functions. Other people
             | just use sprintf to format output of their mostly, or
             | wholly, self-contained functions which write output to
             | buffers and such. Different strokes for different folks
             | etc.
             | 
             | But - I will definitely check out the link.
             | 
             | > _Second, that is not a POSIX compatible printf, it lacks
             | support for '%n$' (which is used primarily for
             | localisation).*
             | 
             | That is true. But C99 printf and C++ printf do not support
             | that either. ATM, the aim is completing C99 printf support
             | (when I actually work on the library, which is not that
             | often). So, my priority would be FP denormals and binary FP
             | (with "%a"), before other things.
             | 
             | > * Arguably can make sense to omit for tiny embedded
             | platforms - but then why is there FP support?*
             | 
             | It's there because people wanted it / needed it; and so
             | far, there's not been any demand for numbered position
             | specification.
        
               | eqvinox wrote:
               | > I meant, customization where you don't have to write
               | the customized code yourself, just choose some build
               | options, or at most set preprocessor variables.
               | 
               | Honestly, if you're shying away from customising an
               | 1-2kloc piece of code, you probably shouldn't be using a
               | custom printf().
               | 
               | Case in point: function pointers are either costly or
               | even plain unsupported on GPU architectures. I would
               | speculate that you aren't using the callbacks there?
        
               | einpoklum wrote:
               | > _Honestly, if you 're shying away from customising an
               | 1-2kloc piece of code, you probably shouldn't be using a
               | custom printf()._
               | 
               | Well, it was good enough for the arduino SDK to adopt:
               | https://github.com/embeddedartistry/arduino-printf
               | 
               | > * function pointers are either costly or even plain
               | unsupported on GPU architectures.*
               | 
               | When you printf() from a GPU kernel, your performance is
               | shot anyway, so performance is not a consideration. And -
               | function pointers work, as long as they all get resolved
               | before runtime, and you don't try to cross CPU <-> GPU
               | boundaries.
        
               | eqvinox wrote:
               | > > Honestly, if you're shying away from customising an
               | 1-2kloc piece of code, you probably shouldn't be using a
               | custom printf().
               | 
               | > Well, it was good enough for the arduino SDK to adopt:
               | https://github.com/embeddedartistry/arduino-printf
               | 
               | Well, they didn't shy away from customizing it quite a
               | bit ;)
               | 
               | To be clear I was trying to say it doesn't make too much
               | sense to try to package this as an independent "easy to
               | use" "library" with a handful of build options. Not that
               | it's somehow not "good enough".
               | 
               | Put another way: a situation where you need/want a custom
               | printf is probably a situation where a _package_ like
               | this doesn 't exactly help you anyway and you'll need to
               | muck with it regardless. But the _code_ can be used.
               | Which is exactly what the repo you linked did.
        
       | sylware wrote:
       | I am coding RISC-V assembly (which I run on x86_64 with a mini-
       | interpreter) but I am careful to avoid the usage of the pseudo-
       | instructions and the registers aliases (no compressed instruction
       | ofc). I have a little tool to generate constant loading code,
       | one-liner (semi-colon separated instructions).
       | 
       | And as a pre-processor I use a simple C preprocessor (I don't
       | want to tie the code to the pre-processor of a specific
       | assembler): I did that for x86_64 assembly, and I could assemble
       | with gas, nasm and fasmng(fasm2) transparently.
        
         | 0x000xca0xfe wrote:
         | What's wrong with compressed instructions?
        
           | sylware wrote:
           | I don't feel comfy using duplicate instructions for a
           | 'R'educed instruction set.
           | 
           | That said, I know in some cases it could increase performance
           | since the code would use less memory (and certainly more
           | things which I don't know because I am not into modern
           | advanced hardware CPU micro-architecture design).
        
             | 0x000xca0xfe wrote:
             | It's just an alternative encoding for exactly the same
             | instructions, it does not make the ISA more complex.
             | 
             | If you are writing assembly you probably are using
             | compressed instructions already since your assembler can do
             | the substitions transparently, e.g.                 addi
             | a0,a0,10 -> c.addi a0,10
             | 
             | Example: https://godbolt.org/z/MG3v3jx7P (the disassembly
             | shows addi but the instruction is only two bytes).
             | 
             | They offer a nice reduction in code size with basically no
             | downsides :)
        
               | sylware wrote:
               | I explicitely do disable register ABI alias names,
               | pseudo-instructions and transparent "optimizations",
               | because I run the RISC-V binary in my own x86_64 assembly
               | written little RISC-V machine code interpreter which does
               | support only the core instructions (and a linux syscall
               | translation layer). I may start to add the compressed
               | instructions someday though.
        
       | ChuckMcM wrote:
       | I was feeling a bit like the Petunia and thought "Oh no, not
       | again." :-) One of the annoyances of embedded programming can be
       | having the wheel re-invented a zillion times. I was pleased to
       | see that the author was just describing good software
       | architecture that creates portable code on top of an environment
       | specific library.
       | 
       | For doing 'bare metal' embedded work in C you need the crt0 which
       | is the weirdly named C startup code that satisfies the assumption
       | the C compiler made when it compiled your code. And a set of
       | primitives to do what the i/o drivers of an operating system
       | would have been doing for you. And voila, your C program runs on
       | 'bare metal.'
       | 
       | Another good topic associated with this is setting up hooks to
       | make STDIN and STDOUT work for your particular setup, so that
       | when you type printf() it just automagically works.
       | 
       | This will also then introduce you to the concept of a basic
       | input/output system or BIOS which exports those primitives. Then
       | you can take that code in flash/eprom and load a binary
       | compilation into memory and start it and now you've got a monitor
       | or a primitive one application at a time OS like CP/M or DOS.
       | 
       | Its a fun road for students who really want to understand
       | computer _systems_ to go down.
        
         | marssaxman wrote:
         | This was my attempt at a minimal bare-metal C environment:
         | 
         | https://github.com/marssaxman/startc
        
           | ChuckMcM wrote:
           | That's awesome. Back in the day this was the strong point of
           | eCOS which was a bare metal "platform" for running
           | essentially one application on x86 hardware. The x86
           | ecosystem has gotten so complicated that being able to do
           | this can get you better performance for an "embedded" app
           | than running on top of Linux or another embedded OS. That
           | translates into your appliance type device using lower cost
           | chips which is a win. When I was playing around with eCos a
           | lot of the digital signage market was using it.
        
             | guestbest wrote:
             | Does anyone still do it that way?
        
               | ChuckMcM wrote:
               | With AMD64 style chips? Probably not. Multi-core systems
               | really need a scheduler to get the most out of them so
               | perhaps there are some very specific applications where
               | that would be a win but I cannot think of anything that
               | isn't super specific. For ARM64 chips with a small number
               | of cores, sure that is still a very viable too for
               | appliance type (application specific) applications.
        
         | OnACoffeeBreak wrote:
         | No BIOS necessary when we're talking about bare metal systems.
         | printf() will just resolve to a low-level UART-based routine
         | that writes to a FIFO to be played out to the UART when it's
         | not busy. Hell, I've seen systems that forego the FIFO and just
         | write to the UART blocking while writing.
        
           | ChuckMcM wrote:
           | I hope nobody was confused into thinking I thought a BIOS was
           | required, I was pointing out the evolution from this to a
           | monitor. I've written some code[1] that runs on the STM32
           | series that uses the newlib printf(). I created the UART code
           | [2] that is interrupt driven[3] which gives you the fun
           | feature that you can hit ^C and have it reset the program.
           | (useful when your code goes into an expected place :-)).
           | 
           | [1] https://github.com/ChuckM/
           | 
           | [2] https://github.com/ChuckM/nucleo/blob/master/f446re/uart/
           | uar...
           | 
           | [3] https://github.com/ChuckM/nucleo/blob/master/f446re/commo
           | n/u...
        
           | nonrandomstring wrote:
           | Yup, I recall Atari ST (68000) and BBC Micro (6502) having
           | unbuffered and interrupt access to 6402 UART - which I used
           | to C/ASM to fire MIDI bytes to and from.
        
         | LelouBil wrote:
         | At my school, we did the following project :
         | https://github.com/lse/k
         | 
         | It is a small kernel, from only a bootloader to running elf
         | files.
         | 
         | It has like 10 syscalls if I remember correctly.
         | 
         | It is very fun, and really makes you understand the ton of
         | legacy support still in modern x86_64 CPUs and what the os
         | underneath is doing with privilege levels and task switching.
         | 
         | I even implemented a small rom for it that has an interactive
         | ocarina from Ocarina of Time.
        
           | pyuser583 wrote:
           | What is your school? I thought it was the London School of
           | Economics, but it's another LSE.
        
             | LelouBil wrote:
             | It's EPITA, in France.
             | 
             | LSE is the System's laboratory of EPITA
             | (https://www.lse.epita.fr/)
        
           | ChuckMcM wrote:
           | This is really neat. So many engineers come out of school
           | without ever having had this sort of 'start to finish' level
           | of hands on experience. If you ever want to do systems or
           | systems analysis this kind of thing will really, really help.
        
         | dusanh wrote:
         | This sounds fascinating and absolutely alien to me, a Python
         | dev. Any good books or other sources to learn more you can
         | recommend?
        
           | genewitch wrote:
           | There's always the Minix book!
        
           | pjmlp wrote:
           | You can start here, https://wiki.osdev.org/Expanded_Main_Page
           | 
           | Also regardless of what others say, you can have a go trying
           | to feel how it was to use BASIC in 8 bit computers to do
           | everything their hardware exposed, or even 16 bit systems
           | like MS-DOS, but with Python.
           | 
           | Get a ESP32 board, and have a go at it with MicroPython or
           | CircuitPython,
           | 
           | https://docs.micropython.org/en/latest/esp32/quickref.html
           | 
           | https://learn.adafruit.com/circuitpython-with-esp32-quick-
           | st...
        
       | smackeyacky wrote:
       | Has anybody played with newlib, but grown the complexity as the
       | system came together?
       | 
       | It seems like one thing to get a bare-bones printf() working to
       | get you started on a bit of hardware, but as the complexity of
       | the system grows you might want to move on from (say) pushing
       | characters out of a serial interface onto pushing them onto a
       | bitmapped display.
       | 
       | Does newlib allow you to put different hooks in there as the
       | complexity of the system increases?
        
         | Gibbon1 wrote:
         | You can always write a printf replacement that takes a minimal
         | control block that provides put, get, control, and a context.
         | 
         | That way you can print to a serial port, an LCD Display, or a
         | log.
         | 
         | Meaning seriously the standard printf is late 1970's hot
         | garbage and no one should use it.
        
         | adrian_b wrote:
         | Newlib provides both a standard printf, which is necessarily
         | big, and a printf that does not support any of the floating-
         | point format specifiers.
         | 
         | The latter is small enough so that I have used it in the past
         | with various small microcontrollers, from ancient types based
         | on PowerPC or ARM7TDMI to more recent MCUs with Cortex-M0+.
         | 
         | You just need to make the right configuration choice.
        
       | MuffinFlavored wrote:
       | I always felt with these kinds of things you strip out `stdio.h`
       | and your new API/ABI/blackbox becomes `syscall` for `write()`,
       | etc.
        
       | Neywiny wrote:
       | In school we were taught that the OS does the printf. I think the
       | professors were just trying to generalize to not go on tangents.
       | But, once I learned that no embedded libc variants had printf
       | just no output path, it got a lot easier to figure out how to get
       | it working. I wish I knew about SWO and the magic of semihosting
       | back then. I don't think those would be hard to explain and
       | interestingly it's one of the few things students asked about
       | that in the field I'm also asked how to do by coworkers (the
       | setting up _write).
        
         | wrasee wrote:
         | > But, once I learned that no embedded libc variants had printf
         | just no output path
         | 
         | Did you mean "once I learned that no, embedded libc variants
         | have printf"?
         | 
         | To clarify as I had to check, embedded libc variants do indeed
         | have some (possibly stripped-down) implementation of printf and
         | as you say they just lack the output path (hence custom output
         | backends like UART, etc).
        
       | saagarjha wrote:
       | char buffer[100];       printf("Type something: ");
       | scanf("%s", buffer);
       | 
       | Come on, it's 2025, there's no need to write trivial buffer
       | overflows anymore.
        
         | dbuder wrote:
         | It's 1990, maybe 1999, in embedded land.
        
         | anyfoo wrote:
         | It's a feature to rewrite your OS kernel on the fly.
        
       | Rochus wrote:
       | Newlib is huge and complex (even including old K&R syntax) and
       | adapting the build process to a new system is not trivial. I
       | spent a lot of time with it when I re-targeted chibicc and
       | cparser to EiGen, and finally switched to PDCLib for libc and a
       | part of uClibc for libm; see https://github.com/rochus-
       | keller/EiGen/tree/master/ecc/lib. The result is platform
       | independent besides esentially one file.
        
         | adrian_b wrote:
         | For a static library it does not matter whether it is huge and
         | complex, because you will typically link into your embedded
         | application only a small number of functions from it.
         | 
         | I have used a part of newlib with many different kinds of
         | microcontrollers and its build process has always been
         | essentially the same as a quarter of century ago, so that the
         | script that I have written the first time, before 2000, has
         | always worked without problems, regardless of the target CPU.
         | 
         | The only tricky part that I had to figure the first time was
         | how to split the compilation of the gcc cross-compiler into a
         | part that is built before newlib and a part that is built after
         | newlib.
         | 
         | However that is not specific to newlib, but is the method that
         | must be used when compiling a cross-gcc with any standard C
         | library and it has been simplified over the years, so that now
         | there is little more to it than choosing the appropriate make
         | targets when executing the make commands.
         | 
         | I have never needed to change the build process of newlib for a
         | new system, I had just needed to replace a few functions, for
         | things like I/O peripherals or memory allocation. However, I
         | have never used much of newlib, mostly only stdio and
         | memory/string functions.
        
           | Rochus wrote:
           | > _it does not matter whether it is huge and complex_
           | 
           | I was talking about the migration effort and usage
           | complexity, not what the compiler or linker actually sees. It
           | may well be that Newlib can be configured for every
           | conceivable application, but it was more important to me not
           | to have a such a behemoth and bag full of surprises in the
           | project with preprocessor rules and dependencies that a
           | single developer can hardly understand or keep track of. My
           | solution is lean, complete, and works with standard-
           | conforming compilers on each platform I need it.
        
             | adrian_b wrote:
             | The standard C library does not belong into any project,
             | but it normally is shared together with cross-compilers,
             | linkers and other tools by all projects that target a
             | certain kind of hardware architecture.
             | 
             | So whatever preprocessor rules and dependencies may be
             | needed to build the tool chain, they do not have any
             | influence on the building processes for the software
             | projects used to develop applications.
             | 
             | The building of the tool chain is done again only when new
             | tool versions become available, not during the development
             | of applications.
             | 
             | I assume that you have encountered problems because you
             | have desired to build newlib with something else than gcc +
             | binutils, with which it can be built immediately, as
             | delivered.
             | 
             | Even if for some weird reason the use of gcc is avoided for
             | the intended application, that should have not required the
             | use of a newlib compiled with something else than gcc, as
             | it should be linked without problems with any other ELF
             | object files.
        
               | Rochus wrote:
               | > _The standard C library does not belong into any
               | project_
               | 
               | Why not? Have a look at https://github.com/rochus-
               | keller/Eigen.
               | 
               | > _because you have desired to build newlib with
               | something else than gcc + binutils_
               | 
               | Well, the whole point was to make it compatible with my
               | own C compilers.
        
               | adrian_b wrote:
               | That project is interesting, but it just proves my point,
               | because that is not a software project for some concrete
               | application intended to be run on some embedded computer,
               | but it is a tool chain, i.e. an alternative for commonly
               | used tool chains such as gcc + binutils + newlib.
               | 
               | For its intended purpose, i.e. as what must be added to
               | gcc and binutils for obtaining a complete tool chain
               | usable for the cross-compilation and linking of
               | executable applications for any embedded computer, newlib
               | works fine, with minimal headaches.
               | 
               | If instead of using it as intended, you want to integrate
               | it as a component in a new and different tool chain, then
               | I completely agree with what you have found out, that it
               | is not a good choice.
               | 
               | I have reacted to your first comment because that seemed
               | to imply that newlib is not fit for its purpose of being
               | used in embedded programming applications, which is
               | definitely false.
               | 
               | You have tried to use if for something very different,
               | and in that context you are right, but you should have
               | explained more of that in order to avoid confusions.
               | 
               | Your project seems interesting, but like in most such
               | projects you should add on the initial page some
               | rationale for the existence of the project, i.e. which
               | are the features where it attempts to be different from
               | the better known alternatives based on gcc or clang.
               | 
               | Following the links, one eventually reaches this succinct
               | explanation:
               | 
               | "The Eigen Compiler Suite is a completely self-contained
               | collection of software development tools. It exists to be
               | recognized and adopted as a free development toolchain
               | which is hopefully as useful and easy to use as its
               | source code is intended to be approachable and
               | comprehensible for developers and students wanting to
               | learn, maintain, and customize a complete toolchain."
               | 
               | This does not mention any attempts of being better than
               | alternatives in any direction, except for being much
               | easier to modify if someone desires to implement some
               | kind of compiler/linker customization.
               | 
               | This recommends it mostly for experimental projects, not
               | for production projects. The former are important too,
               | but it is good to know for what it is suitable.
        
               | Rochus wrote:
               | > _it just proves my point, because that is not a
               | software project for some concrete application intended
               | to be run on some embedded computer_
               | 
               | It's a compiler kit, and I also added two C compilers,
               | and of course I needed a standard library for those. It
               | wouldn't make sense to have a separate project just for
               | the standard library. Anyway, Newlib was not a good match
               | for this for the said reasons. That was my own
               | proposition so far. My compilers are expected to also
               | work on embedded systems, even on bare metal.
               | 
               | > _like in most such projects you should add on the
               | initial page some rationale for the existence of the
               | project_
               | 
               | Have a look at the readmes; there is one in the root and
               | most subdirectories.
               | 
               | EDIT: or just ask Perplexity:
               | https://www.perplexity.ai/search/can-you-explain-what-
               | this-p...
        
       | rurban wrote:
       | 220k just to include studio? That's insane. I have 12k and still
       | do IO. Just without the overblown stdio and sbrk, uart_puts is
       | enough. And only in DEBUG mode.
        
         | tails4e wrote:
         | I thought this was going to talk about how printf is
         | implemented. I worked with a tiny embedded processor that had
         | 8k imem, and printf is about 100k alone. Crazy. Switched to a
         | more basic implementation that was around 2k, and ran much,much
         | faster. It seems printf is pretty bloated, though I guess
         | typical people don't care.
        
           | adrian_b wrote:
           | In most C standard libraries intended for embedded
           | applications, including newlib, there is some configuration
           | option to provide a printf that does not support any of the
           | floating-point format specifiers.
           | 
           | That is normally enough to reduce the footprint of printf by
           | more than an order of magnitude, making it compatible with
           | small microcontrollers.
        
           | rurban wrote:
           | I implemented a secure printf_s and its API is the problem.
           | You cannot dead-code eliminate all the unused methods. And
           | it's type unsafe. There are much better API's to implement a
           | safe printer with all the formatting options still. format is
           | not one of them
        
             | bobmcnamara wrote:
             | The only hack I could think of is having the compiler front
             | end generate calls to different functions based on the
             | content of the format string, similar to how some compilers
             | replace memset with a 32-bit memset based on the type of
             | the destination pointer
             | 
             | And it all falls apart as soon as a format string cannot be
             | known at compile time.
        
               | Someone wrote:
               | > The only hack I could think of is having the compiler
               | front end generate calls to different functions based on
               | the content of the format string
               | 
               | Compilers do that, at least for the simple case of
               | constant strings; gcc can compile a _printf_ call as
               | _puts_. See
               | https://stackoverflow.com/questions/60080021/compiler-
               | change...
        
       | gitroom wrote:
       | honestly love reading about this stuff - always makes me realize
       | how much gets glossed over in school. you think modern cpus and
       | all the abstraction layers help or just make things messier for
       | folks trying to learn the real basics?
        
       | einpoklum wrote:
       | While "newlib" is an interesting idea - the approach taken here
       | is, in many cases, the wrong one.
       | 
       | You see, actually, the printf() family of functions don't
       | actually require _any_ metal, bare or otherwise, beyond the
       | ability to print individual characters.
       | 
       | For this reason, a popular approach for the case of not having a
       | full-fledged standard library is to have a fully cross-platform
       | implementation of the family which "exposes" a symbol dependency
       | on a character printing function, e.g.:                 void
       | putchar_(char c);
       | 
       | and variants of the printf functions which take the character-
       | printing function as a runtime parameter:                 int
       | fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg,
       | const char* format, ...);       int vfctprintf(void (*out)(char
       | c, void* extra_arg), void* extra_arg, const char* format, va_list
       | arg);
       | 
       | this is the approach taken in the standalone printf
       | implementation I maintain, originally by Marco Paland:
       | 
       | https://github.com/eyalroz/printf
        
         | eqvinox wrote:
         | As replied on your other comment, when you introduce a custom
         | printf for an embedded platform it makes more sense to just
         | edit in support for your local I/O backend rather than having
         | the complexity of a putch() callback function pointer.
         | 
         | cf. https://news.ycombinator.com/item?id=43811191 for other
         | notes.
        
       | dailykoder wrote:
       | // QEMU UART registers - these addresses are for QEMU's 16550A
       | UART       #define UART_BASE 0x10000000       #define UART_THR
       | (*(volatile char *)(UART_BASE + 0x00)) // Transmit Holding
       | Register       #define UART_RBR  (*(volatile char *)(UART_BASE +
       | 0x00)) // Receive Buffer Register       #define UART_LSR
       | (*(volatile char *)(UART_BASE + 0x05)) // Line Status Register
       | 
       | This looks odd. Why are receive and transmit buffer the same and
       | why would you use such a weird offset? Iirc RISC-V allows that,
       | but my gut says I'd still align this to the word size.
        
         | eqvinox wrote:
         | My sweet summer child... this is backwards compatibility to the
         | I/O register set of NatSemi/Intel's 8250 UART chip...
         | 
         | ...from 1978.
         | 
         | https://en.m.wikipedia.org/wiki/8250_UART
         | 
         | The definitions are correct, look up an 16550 datasheet if you
         | want to lose some sanity :)
        
           | dailykoder wrote:
           | Oh damn, thanks!
        
         | bobmcnamara wrote:
         | > Why are receive and transmit buffer the same?
         | 
         | Backwards compatibility aside, why bother implementing
         | additional register address decoding? Since the host already
         | doesn't need to read THR or write RBR they can be safely
         | combined. Some UARTs call this a DATA register instead.
        
       | p0w3n3d wrote:
       | Bare metal printf is usually faster but (surprise surprise)
       | platform dependent.
       | 
       | I remember I was trying to program Atari 8-bit using C compiler,
       | and writing directly characters to Antic memory range WITH
       | charcode translation was 100x faster than using printf.
       | 
       | However I'm not sharing this code because it won't work on
       | UART... _laughs nervously_
        
       ___________________________________________________________________
       (page generated 2025-04-27 23:01 UTC)