cyrozap / mct-usb-display-adapter-re

Notes and utilities for reverse engineering the Magic Control Technology (MCT) "Trigger" USB display adapter protocol.
BSD Zero Clause License
12 stars 0 forks source link

Linux driver for Trigger 5 #1

Open rhgndf opened 11 months ago

rhgndf commented 11 months ago

Thanks for your description of the protocol!

With this and analyzing my own wireshark captures I have made this: https://github.com/rhgndf/trigger5

Some questions I have:

  1. Is it possible to detect which connector the device has? HDMI/VGA/etc...
  2. How to do partial updates on screen with uncompressed RGB?
cyrozap commented 11 months ago

Wow, I'm so glad my notes were good enough to help someone write a kernel driver! Also, thank you for figuring out the PLL configuration--it would be nice to be able to support fully custom resolutions, or even simply less-common aspect ratios like the 16:10 1280x800 or 1920x1200.

To answer your questions:

  1. I have no idea if it's possible to detect the connector type. I think maybe we can use the USB PID with a lookup table, but that lookup table would need to be created based on experimentation. For instance, a PID of 0x5800 indicates that the connector is HDMI, while a PID of 0x5804 indicates that it's VGA, but I only know this because I plugged in some Trigger 5 devices and noted their PIDs manually (USB32HDES, JUA350, JUA310)--I don't know the connector types for any of the other Trigger 5 PIDs because I don't own any devices that use those PIDs.
  2. Partial updates are straightforward. While the example code updates the whole screen, it can be modified to update only part of the screen by changing 0, 0, width, height to the horizontal offset, vertical offset, width, and height of the rectangular area you want to update. See the "Horizontal pixel offset info" and "Vertical pixel offset info" fields in the bulk endpoint packet format.
rhgndf commented 11 months ago

Thanks!

You can look at the PLL code in pllcheck.py

The mode set needs a mode number as the value, not sure when setting a custom resolution how to deal with this.

  1. This looks straightforward to implement
  2. I tried to do that but all I got is a blank screen. Only when the fields are 0,0,width,height the screen starts displaying.

Is there a way to power on or off the whole dongle or the display? That will be useful in implementing some functions in the driver.

rhgndf commented 11 months ago

Seems like the the mode number doesn't matter. It is possible to output other resolutions.

cyrozap commented 11 months ago

This looks straightforward to implement

I reviewed some of my not-yet-published notes, and found some dumps of the result of the "Get firmware info?" request for the three Trigger 5 devices I own:

The first three bytes are the firmware version (I think), the last three are the firmware date (I think), and the eight bytes in-between are unknown. Some of those bits may indicate the connector type, but then again maybe none of them indicate the connector type and they're used for something else entirely. Unfortunately, I simply don't have a large enough sample size to be able to definitively isolate and identify device attributes in these bytes, assuming they have anything to do with them in the first place.

I tried to do that but all I got is a blank screen. Only when the fields are 0,0,width,height the screen starts displaying.

It's strange to me that partial updates aren't working for you. Are you clearing the image FIFO on device initialization and sending keepalive packets as needed? Are you updating the frame counter and setting all the necessary bits in the bulk data header?

Maybe you could try patching test_t5.py using the diff below, and then compare a packet capture of it to what your kernel driver is doing? Over the past couple of days I've added support to the Wireshark plugin to dissect the Trigger 5 protocol, so if there's any big differences in how your driver is operating compared to my test code it should be easy to see them in the capture.

diff --git a/test_t5.py b/test_t5.py
index 111ba5c..e5ffd04 100755
--- a/test_t5.py
+++ b/test_t5.py
@@ -18,6 +18,7 @@

 import argparse
 import itertools
+import random
 import struct
 import time

@@ -215,8 +216,16 @@ def main():
     while True:
         dev.ctrl_transfer(CONTROL_OUT, 0xc8, c_x, c_y)

-        payload = bytes(struct.pack('<I', (red << 16) | (green << 8) | blue)[:3] * width * height)
-        header = struct.pack('<BBHHHHHIBBB', 0xfb, 0x14, (0 << 13) | (0 << 12) | counter, 0, 0, width, height, len(payload), 0x01, 0, 0)
+        rwidth = random.randrange(1, (width//4)+1, 1)
+        rheight = random.randrange(1, (height//4)+1, 1)
+        rx = random.randrange(0, width-rwidth+1, 1)
+        ry = random.randrange(0, height-rheight+1, 1)
+        #rwidth = 1920
+        #rheight = 1080
+        #rx = 0
+        #ry = 0
+        payload = bytes(struct.pack('<I', (red << 16) | (green << 8) | blue)[:3] * rwidth * rheight)
+        header = struct.pack('<BBHHHHHIBBB', 0xfb, 0x14, (0 << 13) | (0 << 12) | counter, rx, ry, rwidth, rheight, len(payload), 0x01, 0, 0)
         header += bytes([checksum(header)])
         bulk_data = header + payload

Is there a way to power on or off the whole dongle or the display? That will be useful in implementing some functions in the driver.

The only way I know how to power off the display is to stop outputting a video signal by stopping sending keepalive packets and then waiting for about three seconds. Or I guess you could send a "Firmware reset" command, but I haven't yet determined what the wValue parameter controls.

Seems like the the mode number doesn't matter. It is possible to output other resolutions.

I'm glad to hear that!

rhgndf commented 11 months ago

I detected whether it is HDMI or VGA by checking the number of interfaces on the device. HDMI supports audio so there are additional audio interfaces.

Thanks for the advice for the partial updates, it might be that I didn't clear the image fifo.

I was hoping to support powering on or off to implement power management since the device gets quite hot after plugged in for a while.

rhgndf commented 11 months ago

For the PLL, it is verified that the first value 0x1 is a pre-divider. The last value can only be a power of 2.

rhgndf commented 11 months ago

Seems like partial update works now. Would you like to test out the driver? Wayland might work better for some reason.

What exactly is the keepalive packet? My code does not ever send the keepalive packet but the screen is still turned on after a while?

cyrozap commented 10 months ago

Seems like partial update works now. Would you like to test out the driver? Wayland might work better for some reason.

Sure, I can test it.

What exactly is the keepalive packet? My code does not ever send the keepalive packet but the screen is still turned on after a while?

The keepalive packet is a control request (bmRequestType: 0xc0, bRequest: 0x91, wValue: 0x0002) that needs to be sent every two seconds to keep the screen active. IIRC, if this request isn't sent, the display output will deactivate several seconds after the last display update.

rhgndf commented 10 months ago

Not sure why, but the screen turning off didn't happen for my device.