blog.polynom.me/content/2019-07-01-Mainline-Hero.md
Alexander "PapaTutuWawa caef031d48
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Initial commit
2024-01-05 18:10:44 +01:00

321 lines
18 KiB
Markdown

+++
title = "Mainline Hero Part 0 - Modern Linux For My Galaxy S7"
date = "2019-07-01"
template = "post.html"
aliases = [ "/Mainline-Hero.html" ]
[extra]
mathjax = true
+++
Ever heard of [PostmarketOS](https://postmarketos.org/)? If not, then here's a short summary:
PostmarketOS aims to bring *"[a] real Linux distribution for phones and other mobile devices [...]"* to,
well, phones and other mobile devices.
<!-- more -->
Ever since reading about it, I've been intrigued by the idea of running a real Linux distro
with my UI of choice, be it *Plasma* or *Unity*, on my phone. Perhaps even running the device
without any proprietary firmware blobs. So, I tried my best at contributing to PostmarketOS, which
resulted in 3 MRs that have been accepted into master (Sorry for forgetting to bump the pkgver...).
With this series - if I manage to not break my phone - I want to document what I, someone
who has absolutely no idea what he is doing, learned about all this stuff, how I went about it
and what the results are.
## Mainline Hero #0 - Preparations
Before I can even think about trying to make mainline Linux run on my *Galaxy S7*, we should think
about how we can diagnose any issues that the kernel or the bootloader might have. And how do
professionals debug? Exactly! With **a lot** of `printf()` statements. But how can we retrieve those
from the device?
### Getting Output
While preparing myself for this task, I learned that there are a couple of ways.
One is called [*RAM console*](https://wiki.postmarketos.org/wiki/Mainlining_FAQ#Writing_dmesg_to_RAM_and_reading_it_out_after_reboot). What it does is just dump everything that the kernel prints into a
reserved region of memory, which can later be retrieved by reading from `/proc/last_kmsg` with a
downstream kernel.
The other one is via a [serial cable](https://wiki.postmarketos.org/wiki/Serial_debugging). This sounded
pretty difficult at first, the reason being that I have no idea about hardware, besides the occasional
**PC** hardware talk. I imagined a cable coming out of a box, packed to the brim with electronics
doing some black magic.
The reality is - thankfully - much simpler. It is, basically, just a normal USB cable. I mean: *USB* literally
stands for [*Universal Serial Bus*](https://en.wikipedia.org/wiki/USB). But how come my PC does not
read those kernel logs when I plug in my phone?
As it turns out, there is a component built into my phone which decides exactly what data flows from my
phone to the PC. Reading the [XDA post](https://forum.xda-developers.com/galaxy-s7/how-to/guide-samsung-galaxy-s7-uart-t3743895) which the PostmarketOS Wiki linked helped understand that my
device contains a *MUIC*, a chip which multiplexes the data lines of the USB cable towards different
"subsystems". As I later learned, the USB standard for connectors of type Micro Type B requires 5 pins:
power, ground, RX, TX and ID. Power and ground should be self-explanatory if you know anything
about electronics (I don't). RX and TX are the two data lines that USB uses. As USB is just a serial
connection, only **one** line is used for sending and one for receiving data. The ID line is the interesting
one: it tells the MUIC what subsystem it should multiplex the data lines to.
[Pinout diagram](https://web.archive.org/web/20190120234321/https://pinouts.ru/PortableDevices/micro_usb_pinout.shtml) of the Micro Type B connector:
```
_______________
/ \
| 1 2 3 4 5 |
+--|--|--|--|--|--+
| | | | +-o Ground
| | | +----o ID
| | +-------o D+ (Data)
| +----------o D- (Data)
+-------------o VCC (Power)
```
According to the XDA post, the MUIC switches to serial - used for dumping output of the bootloader and the
kernel - if it measures a resistance of 619kOhm attached to the ID pin. So, according to the diagram in the
post, I built a serial cable.
But how did the author of the XDA post know of the exact resistance that would tell the MUIC to switch to
serial? If you `grep` the
[*S7*'s defconfig](https://raw.githubusercontent.com/ivanmeler/android_kernel_samsung_herolte/lineage-15.1/arch/arm64/configs/exynos8890-herolte_defconfig),
for `MUIC`, then one of the results is the KConfig flag `CONFIG_MUIC_UNIVERSAL_MAX77854`.
If we then search the kernel tree for the keyword `max77854`, we find multiple files; one being
`drivers/mfd/max77854.c`. This file's copyright header tells us that we deal with a *Maxim 77854* chip. Judging
from the different files we find, it seems as if this chip is not only responsible for switching between serial
and regular USB, but also for e.g. charging (`drivers/battery_v2/include/charger/max77854_charger.h`).
However, the really interesting file is `drivers/muic/max77854.c`, since there we can find an array of structs
that contain strings. Sounds pretty normal until you look at the strings more closely: One of the strings is
the value `"Jig UART On"`:
```
[...]
#if defined(CONFIG_SEC_FACTORY)
{
.adc1k = 0x00,
.adcerr = 0x00,
.adc = ADC_JIG_UART_ON,
.vbvolt = VB_LOW,
.chgdetrun = CHGDETRUN_FALSE,
.chgtyp = CHGTYP_NO_VOLTAGE,
.control1 = CTRL1_UART,
.vps_name = "Jig UART On",
.attached_dev = ATTACHED_DEV_JIG_UART_ON_MUIC,
},
#endif /* CONFIG_SEC_FACTORY */
[...]
```
The keyword `ADC_JIG_UART_ON` seems especially interesting. Why? Well, the driver has to know what to do
with each measured resistance. It would make sense that we call the constant which contains the resistance
something like that. Additionally, it is the only constant name that does not immediately hint at its
value or function.
So we search the kernel source for this keyword. Most occurences are just
drivers using this constant. But one hit shows its definition: `include/linux/muic/muic.h`. There we
find on [line 106](https://github.com/ivanmeler/android_kernel_samsung_herolte/blob/b51cf88008606ebac535785ff549b9f55e5660b4/include/linux/muic/muic.h#L106)
a comment which states that this constant represents a resistance of 619kOhm.
To actually build the serial cable, we need to have a USB Type B male connector that we can solder our cables to.
My first thought was to buy a simple and cheap USB Type B cable, cut it, remove the isolation and solder my
connectors to it. I, however, failed to notice that the Type A part of the cable - the one you plug into e.g.
your PC - only has 4 pins, while the Type B part has 5. After stumbling upon some random diagram, I learned that
for regular USB connectivity, such as connecting your phone to your PC, the ID pin is not needed, so it is left
disconnected. As this plan failed, I proceeded to buy a USB Type B male connector. Since I bought it on the
Internet and the seller did not provide a diagram of what pad on the connector connects to what pin, I also
ordered a USB Type B female breakout board.
After all parts arrived, I used a digital multimeter to measure the resistance between each pad on the connector
and on the breakout board. Since I have no idea about electronics, let me explain: Resistance is defined as
$R = \frac{U}{I}$, where $R$ is the resistance, $U$ the voltage and $I$ the current. This means that we should
measure - practically speaking - infinite resistance when no current is flowing and some resistance $R \gt 0$
when we have a flowing current, meaning that we can test for continuity by attempting to measure resistance.
After some poking around, I got the following diagram:
```
+---------o VCC
| +-----o D+
| | +-o GND
___|___|___|___
/ ? ? ? \
| ? ? |
+------|---|------+
| +---o ID
+-------o D-
```
![The "Serial Cable"](/img/serial-cable.jpg)
Since the data that the serial port inside the phone is coming in using a certain protocol, which also includes
timing, bit order and error correcting codes, we need something to convert this data into something that is
usable on the host. Since the USB specification for data may differ from what we actually receive, we can't just
connect the phone's D- and D+ lines to the host USB's D- and D+. Hence the need for a device which does this
conversion for us and also deals with the timing of the data: The tiny board to which all cables lead to
basically just contains an *FT232RL* chip from *FTDI*. It is what does all the conversion and timing magic.
Since I don't want to accidentally brick by phone by frying it with 3.3V or 5V - though I think that damaging
the hardware with 5V is pretty difficult - I did not connect the USB's 5V to the *FT232*'s VCC port.
Booting up the device, we start to see data being sent via serial!
```
[...]
CP Mailbox Debug
0x10540180 : 0xdca7b414 0x 804f99f
0x10540184 : 0xdeb36080 0x8112566f
0x10540188 : 0xf4bf0800 0x2534862d
0x1054018C : 0x61ff350e 0x1208fd27
0x10540190 : 0x17e60624 0x18121baf
0x105C0038 : 0x3bd58404 0x5674fb39
CP BL flow
0x10920014 : 0x79dab841 0x9b01b3fd
0x10800028 : 0xffbd34b1 0x9fd118cc
Resume el3 flow
EL3_VAL : 0xdcfee785 0xfbb6b0a2 0xccf99641
muic_register_max77854_apis
muic_is_max77854 chip_id:0x54 muic_id:0xb5 -> matched.
[MUIC] print_init_regs
INT:01 00 00 ST:1d 00 00 IM:00 00 00 CDET:2d 0c CTRL:1b 3b 09 b2 HVCT:00 00 LDO0:47
MUIC rev = MAX77854(181)
init_multi_microusb_ic Active MUIC 0xb5
[...]
```
Nice! We can see what *SBOOT*, the bootloader that *Samsung* uses, tells us. But for some reason, I wasn't
able to get into the *SBOOT* prompt to tell the kernel to dump everything via serial. While the XDA post
used the programm `minicom`, which I could use to get *SBOOT* output, it never seemed to send the carriage
returns while I was pressing the return key like crazy. So what I did was try to use a different tool to
interact with the serial converter: `picocom`. And it worked!
Although I set the kernel parameters to output to the TTY device `ttySAC4`, just like the XDA post said,
I did not receive any data.
### Device Tree
So we can just try and boot mainline on the phone then, yes? With a very high probability: no. The reason being
that the kernel has no idea about the actual hardware inside the phone.
This may seem weird as you don't have to tell your kernel about your shiny new GPU or about your RAM. The reason
is that your PC is designed to be modular: You can swap the CPU, the RAM and even the attached devices, like
your GPU. This means that on X86, the CPU is able to discover its hardware since there is only one bus for
attaching devices (ignoring RAM and the CPU): the PCI bus. How does the CPU know about its RAM?
The RAM-modules are swappable, which means that the CPU cannot anticipate just how much RAM you
have in your system. These information get relayed, perhaps via the MMU, to the CPU.
Can't we just probe the available memory in an ARM SoC? Technically yes, but it would take a lot
of time if we have a modern 64 bit CPU. Moreover, how do you know that a probed memory location
is not a memory mapped device? Wouldn't it make sense to bake this data into the SoC then? Here
again: not really. The reason is that the SoCs are vendor specific. This means that the vendor
basically just buys the rights to put the CPU into their SoC. The rest is up to the vendor. They
can add as much RAM as they want, without the CPU designer having much input. This means that the
data must not be **hardcoded** into the CPU.
On ARM and probably most other microprocessors devices can be memory mapped, which means that they respond to
a certain region of memory being written to or read from. This makes auto-discovering devices quite difficult
as you would have to probe **a lot** of memory regions.
As an example: Imagine we can access 4 different locations in memory, each holding 1 byte of data. These regions
are at the memory addresses `0x1` to `0x4`. This means that we would have to probe 4 memory locations. Easy,
right?
Not exactly. We would have to probe 4 times to discover 4 possible memory mapped areas with a width of 1 byte.
If we allow a width of 2 bytes, then we would have to probe 3 different regions: `0x1`-`0x2`, `0x2`-`0x3` and
`0x3`-`0x4`.
This assumes that memory maps need to be directly next to each other. Otherwise we would need to use the
binomial coefficient.
This results in 10 (4x 1 byte, 3x 2 bytes, 2x 3 bytes and 1x 4 bytes) different probing attempts to discover
possible memory mapped devices. This does not seem much when we only have a 2 bit CPU, but in the case of the
*S7*, we have a 64 bit CPU; so we would have to probe about $\sum_{n=1}^{2^{64}} n$ times. This finite sum
is equal ([German Wikipedia](https://de.wikipedia.org/wiki/Gau%C3%9Fsche_Summenformel)) to
$\frac{1}{2} 2^{64} {(2^{64} + 1)} = 1.7014 \cdot 10^{38}$. Quite a lot! Keep in mind that this
calculation does not factor in any other busses that the SoC might use; they can, probably, use their own
address space.
So, long story short: We need to tell the kernel about all the hardware beforehand. This is where the so-called
Device Tree comes into play. It is a structured way of describing the attached hardware. You can find examples
in the kernel tree under `arch/arm{,64}/boot/dts/`. The problem that arises for my phone is that it
uses the Exynos SoC from Samsung. While Exynos 7 or older would just require an addition to the already existing
Device Tree files, the *S7* uses the Exynos 8890 SoC. This one is not in mainline, which mean that it is
required to port it from the [downstream kernel](https://github.com/ivanmeler/android_kernel_samsung_universal8890/) into mainline.
### Device Support
The challenge that follows, provided I don't brick my phone, is the kernel support for the SoC's hardware.
#### GPU
The GPU of the Exynos 8890 SoC is a Mali-T880 from ARM. While there is no "official" FOSS-driver for it, one
is in development: [Panfrost](https://gitlab.freedesktop.org/panfrost/linux). One of the developers once
mentioned in PostmarketOS' Matrix channel that the driver is not ready for day-to-day use. But hopefully it
will be in the forseeable future.
#### Wifi
While I found no data on the Exynos 8890's Wifi-chip, I managed to allow the downstream kernel to use it, albeit
with its proprietary firmware ([MR](https://gitlab.com/postmarketOS/pmaports/merge_requests/309)).
This patch requires a patch which changes the path of the firmware in the file `drivers/net/wireless/bcmdhd4359/dhd.h`.
The license header of [said file](https://github.com/ivanmeler/android_kernel_samsung_universal8890/blob/lineage-15.0/drivers/net/wireless/bcmdhd4359/dhd.h)
hints at a chip from Broadcom. The model of the chip appears to be 4359. What the *dhd* stand for? I don't know.
Looking at the compatibility of the [kernel modules](https://wireless.wiki.kernel.org/en/users/drivers/brcm80211) for Broadcom wireless chips, we can find
that the *BCM4359* chip is compatible. But is that the same as the module folder's name specifies? Again, I don't know.
Hopefully it is...
#### Other Components
At the time of writing this post, it has been a "long time" since I last flashed PostmarketOS on
my phone to look at what the kernel is saying. All of this device data I gathered by looking at
spec sheets by Samsung or the kernel. So I don't really know what other hardware is inside my
*S7*.
## Next Steps
The next steps are actually testing things out and playing around with values and settings and all kinds of things.
## Other Devices I Have Lying Around
This may be off-topic for the "*Mainline Hero*" series but I recently tried to find out whether another device
I have lying around - a *Samsung Galaxy Note 8.0* - also uses such a MUIC to multiplex its USB port. While
at first I somehow found out, which I now know is wrong, that the *Note 8.0* uses the same *Maxim 77854* as my
*S7*, I discovered that the *Note 8.0* does use a MUIC, just not the *77854*. Since I found no other links
talking about this, I am not sure until I test it, but what I will do is tell you about how I reached this
conclusion!
If you `grep` the [defconfig for the herolte](https://github.com/ivanmeler/android_kernel_samsung_herolte/blob/lineage-15.1/arch/arm64/configs/exynos8890-herolte_defconfig) for
"*77854*", then one of the results is the flag `CONFIG_MUIC_UNIVERSAL_MAX77854`. The prefix `CONFIG_MUIC` makes
sense since this enables kernel support for the *Maxim 77854* **MUIC**. As such, we should be able to find
an enabled MUIC in the *Note 8.0*'s [defconfig](https://github.com/LineageOS/android_kernel_samsung_smdk4412/blob/lineage-16.0/arch/arm/configs/lineageos_n5110_defconfig).
If we grep for `CONFIG_MUIC`, then we indeed get results. While the results do not look like the one for
the *77854*, we get ones like `CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK`. This indicates that the *Note 8.0*
has a *Maxim 77693* MUIC built in. But it's not a very strong indicator. Since the [kernel source](https://github.com/LineageOS/android_kernel_samsung_smdk4412/) is available
on Github, we can just search the repo for the keyword "*MAX77693*". One of the results hints at the file
`drivers/misc/max77693-muic.c`. Looking at the Makefile of the `drivers/misc` directory, we find that this
source file is only compiled with the KConfig flag `CONFIG_MFD_MAX77693`. Grepping the *Note 8.0*'s defconfig
for this flag yields the result that this kernel module is enabled, hence hinting at the existence of a MUIC
in the *Note 8.0*.
If we take a closer look at the source file at `drivers/misc/max77693-muic.c`, we can find an interesting part
at [line 102](https://github.com/LineageOS/android_kernel_samsung_smdk4412/blob/b7ffe7f2aea2391737cdeac2a33217ee0ea4f2ba/drivers/misc/max77693-muic.c#L102):
```
[...]
ADC_JIG_UART_ON = 0x1d, /* 0x11101 619K ohm */
[...]
```
This means that, as the *Maxim 77854* requires a 619kOhm resistor to enable UART, we can debug
the *Note 8.0* with the same serial cable as the *S7*.
Plugging it into the DIY serial cable and booting it up, we also get some output:
```
[...]
BUCK1OUT(vdd_mif) = 0x05
BUCK3DVS1(vdd_int) = 0x20
cardtype: 0x00000007
SB_MMC_HS_52MHZ_1_8V_3V_IO
mmc->card_caps: 0x00000311
mmc->host_caps: 0x00000311
[mmc] capacity = 30777344
```
Theory proven! We **can** also serial debug the *Note 8.0* using the same cable.
## Some Closing Words
I want to emphasize that just very few of the things I mentioned were discovered or implemented by me. I just collected
all these information to tell you about what I learned. The only thing that I can truly say I discovered is the MR for
the Wifi firmware...
Additionally, I want to make it clear that I have no idea about microelectronics, electronics or ARM in general. All the
things I wrote that are about ARM or electronic - especially everything in the *Device Tree* section - is pure speculation
on my side. I never really looked into these things, but all the statements I made make sense to me. You can't just probe
$2^{64}$ different memory addresses just to figure out how much RAM you have, can you?