USB-C Display Support #9

Open
opened 2026-05-05 10:13:05 +02:00 by deusch · 4 comments
Owner

Displayport only works if UART is disabled

fastboot oem uart disable

Right now, display hotplug is broken. Also, manually forcing the display leads to Mir destroying the internal display.

Displayport only works if UART is disabled ``` fastboot oem uart disable ``` Right now, display hotplug is broken. Also, manually forcing the display leads to Mir destroying the internal display.
Author
Owner

Ok, USB-C display out works in Ubuntu Touch with the following modifications:

  • ubports/libhybris@fbeb930a18 -> only treat display id 0 as primary, otherwise the external display causes the internal one to get disconnected
  • a custom gbinder program that registers with the vendor USB HAL, otherwise it does not trigger DP

The Pixel defaults to 1920x1080 for external displays (hardcoded in the exynosdisplay vendor library). It can be overwritten via

setprop vendor.display.external.preferred_mode 2560x1440@59.950550

(the comparison is exact, so writing 2560x1440@60 does not work if the mode is 59.95 Hz).

Ok, USB-C display out works in Ubuntu Touch with the following modifications: * https://git.deusch.me/ubports/libhybris/commit/fbeb930a182777cf7d0dac98242d63d5c88096e6 -> only treat display id 0 as primary, otherwise the external display causes the internal one to get disconnected * a custom gbinder program that registers with the vendor USB HAL, otherwise it does not trigger DP The Pixel defaults to 1920x1080 for external displays (hardcoded in the exynosdisplay vendor library). It can be overwritten via ``` setprop vendor.display.external.preferred_mode 2560x1440@59.950550 ``` (the comparison is exact, so writing 2560x1440@60 does not work if the mode is 59.95 Hz).
Author
Owner
// Minimal libgbinder client for android.hardware.usb.IUsb/default.
// It registers an IUsbCallback object and optionally sends one queryPortStatus.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int gboolean;
typedef unsigned int guint;
typedef unsigned int guint32;
typedef unsigned long gulong;
typedef uint64_t guint64;

typedef struct _GMainLoop GMainLoop;
typedef struct gbinder_client GBinderClient;
typedef struct gbinder_local_object GBinderLocalObject;
typedef struct gbinder_local_reply GBinderLocalReply;
typedef struct gbinder_local_request GBinderLocalRequest;
typedef struct gbinder_remote_object GBinderRemoteObject;
typedef struct gbinder_remote_request GBinderRemoteRequest;
typedef struct gbinder_servicemanager GBinderServiceManager;

typedef GBinderLocalReply* (*GBinderLocalTransactFunc)(
    GBinderLocalObject* obj,
    GBinderRemoteRequest* req,
    guint code,
    guint flags,
    int* status,
    void* user_data);

extern GMainLoop* g_main_loop_new(void* context, gboolean is_running);
extern void g_main_loop_run(GMainLoop* loop);
extern void g_main_loop_unref(GMainLoop* loop);

extern GBinderServiceManager* gbinder_servicemanager_new(const char* dev);
extern GBinderRemoteObject* gbinder_servicemanager_get_service_sync(
    GBinderServiceManager* sm, const char* name, int* status);
extern GBinderLocalObject* gbinder_servicemanager_new_local_object(
    GBinderServiceManager* sm, const char* iface,
    GBinderLocalTransactFunc handler, void* user_data);
extern void gbinder_servicemanager_unref(GBinderServiceManager* sm);

extern GBinderClient* gbinder_client_new(GBinderRemoteObject* object, const char* iface);
extern GBinderLocalRequest* gbinder_client_new_request(GBinderClient* client);
extern int gbinder_client_transact_sync_oneway(
    GBinderClient* client, guint32 code, GBinderLocalRequest* req);
extern void gbinder_client_unref(GBinderClient* client);

extern GBinderLocalRequest* gbinder_local_request_append_local_object(
    GBinderLocalRequest* request, GBinderLocalObject* obj);
extern GBinderLocalRequest* gbinder_local_request_append_int64(
    GBinderLocalRequest* request, guint64 value);
extern void gbinder_local_request_unref(GBinderLocalRequest* request);

extern void gbinder_local_object_set_stability(GBinderLocalObject* obj, int stability);
extern GBinderLocalReply* gbinder_local_object_new_reply(GBinderLocalObject* obj);
extern void gbinder_local_object_unref(GBinderLocalObject* obj);

extern GBinderLocalReply* gbinder_local_reply_append_int32(
    GBinderLocalReply* reply, guint32 value);
extern GBinderLocalReply* gbinder_local_reply_append_string16(
    GBinderLocalReply* reply, const char* utf8);

extern const char* gbinder_remote_request_interface(GBinderRemoteRequest* req);

#define GBINDER_STATUS_OK 0
#define GBINDER_STATUS_FAILED 1
#define GBINDER_TX_FLAG_ONEWAY 0x01
#define GBINDER_STABILITY_VINTF 0x3f

#define IFACE_USB "android.hardware.usb.IUsb"
#define IFACE_USB_CALLBACK "android.hardware.usb.IUsbCallback"
#define SERVICE_USB "android.hardware.usb.IUsb/default"
#define DEVICE_BINDER "/dev/binder"

#define IUSB_QUERY_PORT_STATUS 4
#define IUSB_SET_CALLBACK 5

#define IUSBCB_NOTIFY_PORT_STATUS_CHANGE 1
#define IUSBCB_NOTIFY_ROLE_SWITCH_STATUS 2
#define IUSBCB_NOTIFY_ENABLE_USB_DATA_STATUS 3
#define IUSBCB_NOTIFY_ENABLE_USB_DATA_WHILE_DOCKED_STATUS 4
#define IUSBCB_NOTIFY_CONTAMINANT_ENABLED_STATUS 5
#define IUSBCB_NOTIFY_QUERY_PORT_STATUS 6
#define IUSBCB_NOTIFY_LIMIT_POWER_TRANSFER_STATUS 7
#define IUSBCB_NOTIFY_RESET_USB_PORT_STATUS 8

// AIDL reserved transactions used by generated stubs.
#define AIDL_GET_INTERFACE_HASH 16777214
#define AIDL_GET_INTERFACE_VERSION 16777215

#define IUSBCB_VERSION 3
#define IUSBCB_HASH "7fe46e9531884739d925b8caeee9dba5c411e228"

static const char* callback_code_name(guint code)
{
    switch (code) {
    case IUSBCB_NOTIFY_PORT_STATUS_CHANGE:
        return "notifyPortStatusChange";
    case IUSBCB_NOTIFY_ROLE_SWITCH_STATUS:
        return "notifyRoleSwitchStatus";
    case IUSBCB_NOTIFY_ENABLE_USB_DATA_STATUS:
        return "notifyEnableUsbDataStatus";
    case IUSBCB_NOTIFY_ENABLE_USB_DATA_WHILE_DOCKED_STATUS:
        return "notifyEnableUsbDataWhileDockedStatus";
    case IUSBCB_NOTIFY_CONTAMINANT_ENABLED_STATUS:
        return "notifyContaminantEnabledStatus";
    case IUSBCB_NOTIFY_QUERY_PORT_STATUS:
        return "notifyQueryPortStatus";
    case IUSBCB_NOTIFY_LIMIT_POWER_TRANSFER_STATUS:
        return "notifyLimitPowerTransferStatus";
    case IUSBCB_NOTIFY_RESET_USB_PORT_STATUS:
        return "notifyResetUsbPortStatus";
    case AIDL_GET_INTERFACE_HASH:
        return "getInterfaceHash";
    case AIDL_GET_INTERFACE_VERSION:
        return "getInterfaceVersion";
    default:
        return "unknown";
    }
}

static GBinderLocalReply* usb_callback_transact(
    GBinderLocalObject* obj,
    GBinderRemoteRequest* req,
    guint code,
    guint flags,
    int* status,
    void* user_data)
{
    const char* iface = gbinder_remote_request_interface(req);
    (void)user_data;

    fprintf(stderr, "callback iface=%s code=%u (%s) flags=0x%x\n",
        iface ? iface : "(null)", code, callback_code_name(code), flags);

    if (!iface || strcmp(iface, IFACE_USB_CALLBACK)) {
        *status = GBINDER_STATUS_FAILED;
        return NULL;
    }

    *status = GBINDER_STATUS_OK;

    if (code == AIDL_GET_INTERFACE_VERSION) {
        GBinderLocalReply* reply = gbinder_local_object_new_reply(obj);
        return gbinder_local_reply_append_int32(reply, IUSBCB_VERSION);
    }

    if (code == AIDL_GET_INTERFACE_HASH) {
        GBinderLocalReply* reply = gbinder_local_object_new_reply(obj);
        return gbinder_local_reply_append_string16(reply, IUSBCB_HASH);
    }

    // All declared IUsbCallback methods are oneway.
    if (flags & GBINDER_TX_FLAG_ONEWAY) {
        return NULL;
    }

    return gbinder_local_object_new_reply(obj);
}

int main(int argc, char** argv)
{
    const int do_initial_query = (argc < 2 || strcmp(argv[1], "--no-query"));
    int status = 0;

    GBinderServiceManager* sm = gbinder_servicemanager_new(DEVICE_BINDER);
    if (!sm) {
        fprintf(stderr, "failed to create service manager for %s\n", DEVICE_BINDER);
        return 1;
    }

    GBinderRemoteObject* remote =
        gbinder_servicemanager_get_service_sync(sm, SERVICE_USB, &status);
    if (!remote) {
        fprintf(stderr, "failed to get %s status=%d\n", SERVICE_USB, status);
        gbinder_servicemanager_unref(sm);
        return 1;
    }

    GBinderClient* client = gbinder_client_new(remote, IFACE_USB);
    if (!client) {
        fprintf(stderr, "failed to create IUsb client\n");
        gbinder_servicemanager_unref(sm);
        return 1;
    }

    GBinderLocalObject* callback =
        gbinder_servicemanager_new_local_object(
            sm, IFACE_USB_CALLBACK, usb_callback_transact, NULL);
    if (!callback) {
        fprintf(stderr, "failed to create IUsbCallback local object\n");
        gbinder_client_unref(client);
        gbinder_servicemanager_unref(sm);
        return 1;
    }
    gbinder_local_object_set_stability(callback, GBINDER_STABILITY_VINTF);

    GBinderLocalRequest* req = gbinder_client_new_request(client);
    gbinder_local_request_append_local_object(req, callback);
    status = gbinder_client_transact_sync_oneway(client, IUSB_SET_CALLBACK, req);
    gbinder_local_request_unref(req);
    fprintf(stderr, "setCallback status=%d\n", status);
    if (status != GBINDER_STATUS_OK) {
        gbinder_local_object_unref(callback);
        gbinder_client_unref(client);
        gbinder_servicemanager_unref(sm);
        return 1;
    }

    if (do_initial_query) {
        req = gbinder_client_new_request(client);
        gbinder_local_request_append_int64(req, 1);
        status = gbinder_client_transact_sync_oneway(client, IUSB_QUERY_PORT_STATUS, req);
        gbinder_local_request_unref(req);
        fprintf(stderr, "queryPortStatus status=%d\n", status);
    }

    fprintf(stderr, "callback registered; waiting for USB events\n");
    GMainLoop* loop = g_main_loop_new(NULL, 0);
    g_main_loop_run(loop);
    g_main_loop_unref(loop);

    gbinder_local_object_unref(callback);
    gbinder_client_unref(client);
    gbinder_servicemanager_unref(sm);
    return 0;
}

Usually, Android's system_server would register with IUsb, which causes the Pixel's vendor USB HAL to start its uevent worker listening for USB type-C events. Since we don't run system_server in Halium, we need to manually register with IUsb. This could be done by a new service included in the Halium container, but I decided to do it on the host instead to prevent Pixel specific code in the Halium build.

I still need to set up an automated build for this and set up a systemd service to autorun it when the container starts.

```c // Minimal libgbinder client for android.hardware.usb.IUsb/default. // It registers an IUsbCallback object and optionally sends one queryPortStatus. #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef int gboolean; typedef unsigned int guint; typedef unsigned int guint32; typedef unsigned long gulong; typedef uint64_t guint64; typedef struct _GMainLoop GMainLoop; typedef struct gbinder_client GBinderClient; typedef struct gbinder_local_object GBinderLocalObject; typedef struct gbinder_local_reply GBinderLocalReply; typedef struct gbinder_local_request GBinderLocalRequest; typedef struct gbinder_remote_object GBinderRemoteObject; typedef struct gbinder_remote_request GBinderRemoteRequest; typedef struct gbinder_servicemanager GBinderServiceManager; typedef GBinderLocalReply* (*GBinderLocalTransactFunc)( GBinderLocalObject* obj, GBinderRemoteRequest* req, guint code, guint flags, int* status, void* user_data); extern GMainLoop* g_main_loop_new(void* context, gboolean is_running); extern void g_main_loop_run(GMainLoop* loop); extern void g_main_loop_unref(GMainLoop* loop); extern GBinderServiceManager* gbinder_servicemanager_new(const char* dev); extern GBinderRemoteObject* gbinder_servicemanager_get_service_sync( GBinderServiceManager* sm, const char* name, int* status); extern GBinderLocalObject* gbinder_servicemanager_new_local_object( GBinderServiceManager* sm, const char* iface, GBinderLocalTransactFunc handler, void* user_data); extern void gbinder_servicemanager_unref(GBinderServiceManager* sm); extern GBinderClient* gbinder_client_new(GBinderRemoteObject* object, const char* iface); extern GBinderLocalRequest* gbinder_client_new_request(GBinderClient* client); extern int gbinder_client_transact_sync_oneway( GBinderClient* client, guint32 code, GBinderLocalRequest* req); extern void gbinder_client_unref(GBinderClient* client); extern GBinderLocalRequest* gbinder_local_request_append_local_object( GBinderLocalRequest* request, GBinderLocalObject* obj); extern GBinderLocalRequest* gbinder_local_request_append_int64( GBinderLocalRequest* request, guint64 value); extern void gbinder_local_request_unref(GBinderLocalRequest* request); extern void gbinder_local_object_set_stability(GBinderLocalObject* obj, int stability); extern GBinderLocalReply* gbinder_local_object_new_reply(GBinderLocalObject* obj); extern void gbinder_local_object_unref(GBinderLocalObject* obj); extern GBinderLocalReply* gbinder_local_reply_append_int32( GBinderLocalReply* reply, guint32 value); extern GBinderLocalReply* gbinder_local_reply_append_string16( GBinderLocalReply* reply, const char* utf8); extern const char* gbinder_remote_request_interface(GBinderRemoteRequest* req); #define GBINDER_STATUS_OK 0 #define GBINDER_STATUS_FAILED 1 #define GBINDER_TX_FLAG_ONEWAY 0x01 #define GBINDER_STABILITY_VINTF 0x3f #define IFACE_USB "android.hardware.usb.IUsb" #define IFACE_USB_CALLBACK "android.hardware.usb.IUsbCallback" #define SERVICE_USB "android.hardware.usb.IUsb/default" #define DEVICE_BINDER "/dev/binder" #define IUSB_QUERY_PORT_STATUS 4 #define IUSB_SET_CALLBACK 5 #define IUSBCB_NOTIFY_PORT_STATUS_CHANGE 1 #define IUSBCB_NOTIFY_ROLE_SWITCH_STATUS 2 #define IUSBCB_NOTIFY_ENABLE_USB_DATA_STATUS 3 #define IUSBCB_NOTIFY_ENABLE_USB_DATA_WHILE_DOCKED_STATUS 4 #define IUSBCB_NOTIFY_CONTAMINANT_ENABLED_STATUS 5 #define IUSBCB_NOTIFY_QUERY_PORT_STATUS 6 #define IUSBCB_NOTIFY_LIMIT_POWER_TRANSFER_STATUS 7 #define IUSBCB_NOTIFY_RESET_USB_PORT_STATUS 8 // AIDL reserved transactions used by generated stubs. #define AIDL_GET_INTERFACE_HASH 16777214 #define AIDL_GET_INTERFACE_VERSION 16777215 #define IUSBCB_VERSION 3 #define IUSBCB_HASH "7fe46e9531884739d925b8caeee9dba5c411e228" static const char* callback_code_name(guint code) { switch (code) { case IUSBCB_NOTIFY_PORT_STATUS_CHANGE: return "notifyPortStatusChange"; case IUSBCB_NOTIFY_ROLE_SWITCH_STATUS: return "notifyRoleSwitchStatus"; case IUSBCB_NOTIFY_ENABLE_USB_DATA_STATUS: return "notifyEnableUsbDataStatus"; case IUSBCB_NOTIFY_ENABLE_USB_DATA_WHILE_DOCKED_STATUS: return "notifyEnableUsbDataWhileDockedStatus"; case IUSBCB_NOTIFY_CONTAMINANT_ENABLED_STATUS: return "notifyContaminantEnabledStatus"; case IUSBCB_NOTIFY_QUERY_PORT_STATUS: return "notifyQueryPortStatus"; case IUSBCB_NOTIFY_LIMIT_POWER_TRANSFER_STATUS: return "notifyLimitPowerTransferStatus"; case IUSBCB_NOTIFY_RESET_USB_PORT_STATUS: return "notifyResetUsbPortStatus"; case AIDL_GET_INTERFACE_HASH: return "getInterfaceHash"; case AIDL_GET_INTERFACE_VERSION: return "getInterfaceVersion"; default: return "unknown"; } } static GBinderLocalReply* usb_callback_transact( GBinderLocalObject* obj, GBinderRemoteRequest* req, guint code, guint flags, int* status, void* user_data) { const char* iface = gbinder_remote_request_interface(req); (void)user_data; fprintf(stderr, "callback iface=%s code=%u (%s) flags=0x%x\n", iface ? iface : "(null)", code, callback_code_name(code), flags); if (!iface || strcmp(iface, IFACE_USB_CALLBACK)) { *status = GBINDER_STATUS_FAILED; return NULL; } *status = GBINDER_STATUS_OK; if (code == AIDL_GET_INTERFACE_VERSION) { GBinderLocalReply* reply = gbinder_local_object_new_reply(obj); return gbinder_local_reply_append_int32(reply, IUSBCB_VERSION); } if (code == AIDL_GET_INTERFACE_HASH) { GBinderLocalReply* reply = gbinder_local_object_new_reply(obj); return gbinder_local_reply_append_string16(reply, IUSBCB_HASH); } // All declared IUsbCallback methods are oneway. if (flags & GBINDER_TX_FLAG_ONEWAY) { return NULL; } return gbinder_local_object_new_reply(obj); } int main(int argc, char** argv) { const int do_initial_query = (argc < 2 || strcmp(argv[1], "--no-query")); int status = 0; GBinderServiceManager* sm = gbinder_servicemanager_new(DEVICE_BINDER); if (!sm) { fprintf(stderr, "failed to create service manager for %s\n", DEVICE_BINDER); return 1; } GBinderRemoteObject* remote = gbinder_servicemanager_get_service_sync(sm, SERVICE_USB, &status); if (!remote) { fprintf(stderr, "failed to get %s status=%d\n", SERVICE_USB, status); gbinder_servicemanager_unref(sm); return 1; } GBinderClient* client = gbinder_client_new(remote, IFACE_USB); if (!client) { fprintf(stderr, "failed to create IUsb client\n"); gbinder_servicemanager_unref(sm); return 1; } GBinderLocalObject* callback = gbinder_servicemanager_new_local_object( sm, IFACE_USB_CALLBACK, usb_callback_transact, NULL); if (!callback) { fprintf(stderr, "failed to create IUsbCallback local object\n"); gbinder_client_unref(client); gbinder_servicemanager_unref(sm); return 1; } gbinder_local_object_set_stability(callback, GBINDER_STABILITY_VINTF); GBinderLocalRequest* req = gbinder_client_new_request(client); gbinder_local_request_append_local_object(req, callback); status = gbinder_client_transact_sync_oneway(client, IUSB_SET_CALLBACK, req); gbinder_local_request_unref(req); fprintf(stderr, "setCallback status=%d\n", status); if (status != GBINDER_STATUS_OK) { gbinder_local_object_unref(callback); gbinder_client_unref(client); gbinder_servicemanager_unref(sm); return 1; } if (do_initial_query) { req = gbinder_client_new_request(client); gbinder_local_request_append_int64(req, 1); status = gbinder_client_transact_sync_oneway(client, IUSB_QUERY_PORT_STATUS, req); gbinder_local_request_unref(req); fprintf(stderr, "queryPortStatus status=%d\n", status); } fprintf(stderr, "callback registered; waiting for USB events\n"); GMainLoop* loop = g_main_loop_new(NULL, 0); g_main_loop_run(loop); g_main_loop_unref(loop); gbinder_local_object_unref(callback); gbinder_client_unref(client); gbinder_servicemanager_unref(sm); return 0; } ``` Usually, Android's system_server would register with IUsb, which causes the Pixel's vendor USB HAL to start its uevent worker listening for USB type-C events. Since we don't run system_server in Halium, we need to manually register with IUsb. This could be done by a new service included in the Halium container, but I decided to do it on the host instead to prevent Pixel specific code in the Halium build. I still need to set up an automated build for this and set up a systemd service to autorun it when the container starts.
Author
Owner

(the comparison is exact, so writing 2560x1440@60 does not work if the mode is 59.95 Hz).

It only compares the integer part, so 2560x1440@59 works for 59.95Hz.

> (the comparison is exact, so writing 2560x1440@60 does not work if the mode is 59.95 Hz). It only compares the integer part, so `2560x1440@59` works for 59.95Hz.
Author
Owner

For 1440p, one also needs to set

sudo sh -c 'echo 2 > /sys/module/exynos_drm/parameters/dp_rate'

Otherwise it defaults to a displayport link rate that only supports 1080p and the exynos module drops higher resolution modes.

For 1440p, one also needs to set ``` sudo sh -c 'echo 2 > /sys/module/exynos_drm/parameters/dp_rate' ``` Otherwise it defaults to a displayport link rate that only supports 1080p and the exynos module drops higher resolution modes.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ubports/device-google-tegu#9
No description provided.