Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB-C Monitor Support #149

Open
RayanJobs opened this issue Dec 4, 2024 · 2 comments
Open

USB-C Monitor Support #149

RayanJobs opened this issue Dec 4, 2024 · 2 comments

Comments

@RayanJobs
Copy link

Are there updates on this? It was expected in 2024 but the year is practically over. How can I help? I am limited to HDMI atm, but need to use both hdmi and a usbc port for a dual monitor setup
Much appreciated!

@BradStell
Copy link

Also interested in this feature! I really appreciate all the hard work on this, and know how much a side project (especially of this magnitude) can really take a toll on you. The burn out is real. It would be really cool if we could maybe have a progress report or get some sort of insight into this feature and when it might be expected. It would definitely be really nice to be able to use my external monitor while in Linux.

Again thank you so much for all your hard work!

@Foul-Tarnished
Copy link

Foul-Tarnished commented Jan 20, 2025

See this from Marcan: https://social.treehouse.systems/@marcan/113821266231103150

Let's talk about one reason why Asahi Linux doesn't have DisplayPort Alt Mode support yet.

I just spent the past 2 days trying to get the Apple Type-C PHY to work properly in the various alternate modes, including DisplayPort and USB3 host and device.

It's a painful mess. If you touch one part of the code, another part of the code breaks.

Here's the thing: Linux likes to abstract out hardware interfaces. Sometimes this works well, with stuff like I²C and SPI controllers.

Sometimes this doesn't work well, like the Linux mailbox subsystem, which is a poor excuse for abstracting out the concept of a hardware mailbox. Hardware mailboxes are not standards like I²C and SPI, it's a vague concept of similarly-designed hardware. There is no "cross-compatibility" between mailbox "controllers" and consumers. The subsystem exists only because someone decided to abstract out some common code (like queuing and polling/waiting for completion), but got the approach all wrong. When you want to abstract out common code, you want to do it as pluggable functions. The DRM subsystem does this well with its multiple components. What mailbox did is try to define a common interface around the hardware, abstracting out all possible variations through one common interface. That doesn't work.

We tried using mailbox for Asahi Linux, and after a bunch of time wasted in attempts to hack on the code and mailing list reviews, we gave up. I made the executive decision to drop mailbox. We now have an internal interface for Apple mailboxes, which only have Apple-specific consumer drivers anyway. It works much better, is overall less code than trying to work around the impedance mismatches of the mailbox subsystem, is more maintainable, and won't break when someone else touches the common code.

The story now repeats, but it's much, much worse. The players are the Linux Type-C subsystem, the PHY subsystem, DRM, USB, and more. This time there are actually semi-common drivers like dwc3 (the driver for the DesignWare USB3 host/device controller that Apple licensed) and tipd (the driver for the Texas Instruments Type-C port controller that Apple modified and commissioned a variant of), plus our own display/DCP drivers and Apple Type-C PHY drivers.

And the challenge is getting all these drivers to work together and drive the hardware in the right sequence to work, including complex/sudden hotplug cycles, mode switches, data role changes, live changes between USB and USB3+DP mode, the list goes on.

Here's how I spent the past hour:

USB3 host + DP mode works on boot
Hotplug doesn't work because my USB3 hub transiently starts up with the data role backwards (as if it were a host), and device/gadget mode is broken.
Let's fix gadget mode. I need to change the USB3 PIPEHANDLER initialization, but right now it happens in usb3phy->power_on(), which is too early to know if we're device or host mode
Let's move it to usb3phy->set_mode()
After fixing some other stuff related to USB2, now SuperSpeed gadget mode works
Try the hub coldplug again. It's completely broken in host mode.
Turns out this broke because the dwc driver calls usb3phy->set_mode() twice with different but equivalent arguments for some bizarre reason
Okay, add a flag so we only listen to the first call
Now USB3 host mode works, but there's a 5-second delay in enumeration, so the PHY starts up in a bad state
Diff register traces to see what happened
Turns out usb3phy->set_mode() happens too late, after host controller soft-reset, and that's too late to work well.
But usb3phy->power_on() is too early to know the mode, we're screwed
...but usb2phy->set_mode() is early enough for some reason

So now I have PIPEHANDLER setup (a USB3-relevant configuration) happening in the "USB2" PHY callback (which goes to the same shared Apple Type-C PHY driver anyway), just to make it work.

This is just the last debugging mess. It's all like this. Yesterday I found out a previous version of the code was only working due to a race between "mux configuration" and dwc3 initialization in a workqueue, synchronized by an accidentally load-bearing msleep(20) in dwc3 [gist.github.com]. Yes, really. The fix for that is changing the tipd driver to do the mux set before setting up the Type-C data role (which is what triggers dwc3 init). tipd is its own hacky mess, since the hardware is too high-level for the Linux Type-C subsystem's design.

What makes this worse is that this setup is all reverse-engineered, so we have no documentation on what is supposed to work and what isn't. All we have is register traces from how macOS does it. But it's actually completely impractical to replicate them 1:1 in Linux, because the Linux cross-driver sync points are nowhere near enough for that. So we're left guessing and trying different interleaving orders and hoping that if something works, it's reliable, even though it's different to what macOS does.

(continued)

The Linux modular subsystem approach falls flat on its face here. The Type-C, USB, PHY, etc. drivers are too loosely coupled. Even if we get this to work, it's liable to break any time anyone changes common code and subtly changes the order of operations. It is not practically possible to build an overarching, top-down, abstracted environment for tying drivers together like this when the underlying hardware has much more tightly coupled requirements for how it is driven. And don't get me started on adding in PCIe into the mix once we need to get Thunderbolt to work...

The "PHY" concept is the most egregious offender. PHY hardware is as diverse as mailbox hardware, yet Linux thinks it's reasonable to abstract it behind a bunch of rigid operations like "init", "power_on", "set_mode", "set_media", "set_speed", "validate", "calibrate". Then you end up with unions like PHY configuration operations for MIPI DPHY, DP, LVDS, .... And all this starts becoming an intractable mess when you have stuff like a Type-C phy that talks USB2, USB3, DisplayPort, and Thunderbolt on one side, and has interfaces for a USB2+USB3 host/device controller (talking to the USB2 side and either nothing, the USB3 side, or a USB4 tunnel on its SuperSpeed side), a PCIe controller, and a bridge to a DisplayPort mux that then connects to an array of display controllers, and which has fun dependencies like having the USB3 controller hard-reset register as part of the PHY, and init sequences that are tightly coupled to how the other peripherals are driven, and if you get it wrong everything just breaks.

When there is no universal hardware spec for what interface a given type of PHY (never mind all types of PHYs) should have, in terms of operations and state machines, trying to abstract it out under such an interface in software is a path doomed to failure. It might work for simple PHYs for simple protocols of a decade or two ago, but vendors are shipping "do everything USB Type C can do including multiple protocols in parallel and tunneling all in one PHY" now and good luck with that.

What we really need is a bottom-up approach where a vendor-specific driver ties everything together. But Linux hates that approach with a passion, because it's what hacky vendor kernel forks do all the time. And yes, when done poorly, as they do, it sucks. But sometimes it's the only way to get anything sane. Linux does support this concept in at least some places: the ALSA ASoC subsystem supports machine drivers into which different codec/DMA/PCM drivers plug in, and that's how we tie together all the audio hardware for Asahi Linux.

But there's nothing like a "Type C top-level port management driver", nor are the underlying driver interfaces structured to allow this, nor fine-grained enough to compose properly. It would be a major new undertaking.

And then we come to the final problem: We don't have time to fight for a huge refactoring of kernel subsystems. If the kernel community were in general friendlier, discussion and patch submission weren't saddled with a ton of friction, etc., we could entertain the notion. But given past experiences with LKML threads... sorry, I'm not going there.

End result, this is highly annoying and demotivating to work on. That's why the person who started working on it mostly lost interest. Even the USB3 support we've shipped so far partially worked by accident (it has the 5-second delay bug and other issues), we just didn't dare touch it for fear of breaking it. And now we need to get all the other features to work without regressing anything.

Best I can hope right now is I make this barely work with the existing approach and a bunch of hacks, that upstream won't block any of it, and that nobody will touch the code and break it. Wish me luck.

And if you think you can help and are willing to fight the bureaucracy to make this less insane in Linux, please get in touch.

Sent an email to lkml & friends about this, but I'm not holding my breath that it's going to lead to a clear solution...

https://lore.kernel.org/lkml/fda8b831-

Not happy with the pile-of-hacks approach, but it does seem to mostly work for now. I've concluded at this point that I'm going to fork tipd, since if we're putting critical hotplug sequencing behavior in there, I don't want to be coordinating with the non-Apple users of that driver (which use different, related chips anyway, and seem to be in use on vastly simpler platforms with no alt mode support at all). I'll just split it into common helpers and separate top-level drivers for cd321x and tps6598x. Besides, we need an entire new spmi backend for M3+ anyway.

Putting debouncing/event coalescing and asynchronous queuing into tipd, and getting rid of the dwc3 workqueue stuff in turn, seems to have fixed most of the hotplug horribleness.

I also had to make one single subsystem change, adding a data_role member to the TypeC mux config structure. That's arguably useful for "real" muxes anyway since you could hypothetically have to mux between a USB host controller and a USB device controller anyway (instead of them being combined). It's only implemented in tipd and atcphy, all other existing drivers ignore it. Hope upstream doesn't push back on this.

Still really not happy with how interdependent dwc3, tipd, and atcphy have become, but this is the only realistic way to move forward in the short term.

https://social.treehouse.systems/@marc

Pain
Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants