Even before harware discovery and plug-and-play you could do this by simply specifying what hardware you had or by 'probing' the hardware for presence of certain characteristics (this wasn't always fool proof). The hardest parts were when interrupts were still selected with jumpers rather than automatically enumerated.
8086 has 256 IO addresses, and hardware of that era had fairly simple initialization, so completely naive way to find peripheral X was to 'probe' each and every possible IO address with something like:
for i in range(256):
poke(i,magic1)
if peek(i) == magic2:
found!
256 probes in all is not that bad, and real-world probing would only try a handful of commonly used addresses, making it even faster.
Phone SoCs on the other hand have many peripherals memory-mapped, (meaning there are millions/billions addresses to 'probe'), plus there are things like power sequencing, GPIO enable lines that need to be asserted, and clock-sources configured before peripheral would even respond at all. Oh, and that GPIO, or power controller, or clock source themselves might be accessible via an i2c chip speaking its own protocol, so you need to initialize those first, etc, etc.
All of this complexity could be described via linux "devicetree" subsystem, and devicetrees are in a usable state for some hardware (although DT itself is often a labyrinth to navigate). Thing is - factory software for most phones have been extremely slow to adopt DT, and even some that do use DT, don't do it in a particularly portable way.