dealing with I/O pins and I2C is very hard on operating systems like Linux
I tend to disagree with that.
I/O: (GPIO/LED)
If you want to blink a LED on ARM you can assume the drivers are available and it’s just an entry in the flattened device tree and you get an entry in /sys/class/leds where you can change triggers in runtime.
I/O: (GPIO/Input)
If you want a GPIO as an input you can assume the drivers are available and it’s just an entry in the flattened device tree. You can use the input event subsystem.
I2C:
You could create /dev/i2cX device nodes and access them as such, but this lacks the abstraction I would like to see (and has other issues as well)
Think about the use case of a GPIO expander. It could be connected via I2C, SPI or it could be some custom stuff with some FPGA. In the case of a LED the user should not care how this led is connected. It could be via a GPIO, via a GPIO expander (I2C/SPI), or via the custom FPGA stuff. With a device driver model, we can have classes and busses and connect stuff. So you can reuse the same top-level code independent of the underlying hardware. Or you can connect your LED in a different way without rewriting the top-level code which accesses it.