markbolhuis / hideizo

Linux HID device driver for EIZO EV FlexScan monitors
GNU General Public License v2.0
26 stars 3 forks source link

Using with EIZO FelxScan 3895, some improvements for Ubuntu based system #3

Open laasa opened 2 years ago

laasa commented 2 years ago

First I have to say thanks for creating these driver. That's very helpfully because I am switching between 2 PCs (Windows and Linux) every time.

Because I don't know if it is helpfully for anybody I have created an issue for that. The issue is to decide to taken over any code below.

The openeizo driver works unmodified fine here with Xubuntu 20.04 and EIZO FlexScan 3895.

A bit tricky was to find out the function requested numbers related to the usage value in HID command. I have used Wireshark with PCapUSB on Windows site. With that I got the EIZOUSAGE.. values (eizo.h) behind the shortcuts. With a small C code based tool (based on the exiting example code, using hid_get_feature_report instead of hid_send_feature_report, running val[0] from 0 to 255) I got the relationchip between id and the usage values. Therefor it is helpfully to start the openeizo driver via sudo modprobe -s -v eizo and checking the outputs in /var/log/syslog. The rest was not so complicate by using the existing source code and header file eizo.h. Please note that USB-C input is DP2 in the header.

I have adapted the example code for my needs (switching the inputs to screens via shortcurts CTRL+1..CTRL+b). Please look above.

Next task is to create a script for installing openeizo driver under Xubuntu: openeizo.sh. You need to call these script once and each time after installing a new kernel via update and reboot. After setting the executeable flag of the script via sudo chmod +x openeizo.sh you have to call it via ./openeizo.sh.

# openeizo installer: openeizo.sh
# remove eizo driver
sudo rmmod eizo

#remove existing openeizo directory
sudo rm -r openeizo

# install the openeizo driver
git clone https://github.com/openeizo/openeizo.git
cd openeizo
make -C /lib/modules/`uname -r`/build  M=$('pwd') modules
sudo cp eizo.ko /lib/modules/`uname -r`/kernel/drivers/hid/.
sudo cp udev/eizo.rules /etc/udev/rules.d/99-eizo.rules
sudo depmod
sudo modprobe -s -v eizo

# create the input switcher
cd ..
sudo apt-get install libhidapi*
gcc eizo3895.c -o eizo3895 -lhidapi-hidraw
sudo cp eizo3895 /usr/bin/.

Last is to insert shortcuts. In Xubuntu these works via "Einstellungen" "Einstellungsbearbeitung" "xcfe4-keyboard-shortcuts". Sorry for German words. I would expect to find "Property" "Edit Properties" in English version. I have inserted the following shortcuts:

<CTRL>1: eizo3895  --screen=1 --input=usb-c
<CTRL>2: eizo3895  --screen=1 --input=db
<CTRL>3: eizo3895  --screen=1 --input=hdmi1
<CTRL>4: eizo3895  --screen=1 --input=hdmi2
<CTRL>5: eizo3895  --screen=2 --input=usb-c
<CTRL>6: eizo3895  --screen=2 --input=db
<CTRL>7: eizo3895  --screen=2 --input=hdmi1
<CTRL>8: eizo3895  --screen=2 --input=hdmi2
<CTRL>9: eizo3895  --screen=3 --input=usb-c
<CTRL>0: eizo3895  --screen=3 --input=db
<CTRL>a: eizo3895  --screen=3 --input=hdmi1
<CTRL>b: eizo3895  --screen=3 --input=hdmi2

Exact the same shortcuts I am using at windows site via "Screen Instyle". And yes: These days I have a note on my EIZO with that "matrix". I am using "3 PbyP" by default.

Sourcecode for EIZO input switcher eizo3895.c:

#include <stdio.h>
#include <memory.h>
#include <linux/hid.h>
#include <hidapi/hidapi.h>

void usage(void)
{
    printf("\n");
    printf("eizo3895 usage\n");
    printf(" --screen=[1/2/3/4]\n");
    printf(" --input=[usb-c/dp/hdmi1/hdmi2]\n");
    printf(" --deviceID=[deviceID]\n");
}

int main(int argc, char *argv[])
{
    char    dev_name[255] = {0, };
    int     screenVal = -1;
    int     inputVal = 0;
    int     deviceID = 0x056d;

    if ( argc < 3 )
    {
        usage();
        exit (-1);
    }

    for(int i = 1 ; i < argc; i++ )
    {
        if ( !strncmp(argv[i], "--screen=", 9) )
        {
            if ( !strcmp(&argv[i][9], "1") )
                screenVal = 0x0000;
            else
            if ( !strcmp(&argv[i][9], "2") )
                screenVal = 0x0001;
            else
            if ( !strcmp(&argv[i][9], "3") )
                screenVal = 0x0002;
            else
            if ( !strcmp(&argv[i][9], "4") )
                screenVal = 0x0003;
            else
            {
                usage();
                exit (-1);
            }
        }
        else
        if ( !strncmp(argv[i], "--input=", 8) )
        {
            if ( !strcmp(&argv[i][8], "dp") )
                inputVal = 0x0300;
            else
            if ( !strcmp(&argv[i][8], "usb-c") )
                inputVal = 0x0301;
            else
            if ( !strcmp(&argv[i][8], "hdmi1") )
                inputVal = 0x0400;
            else
            if ( !strcmp(&argv[i][8], "hdmi2") )
                inputVal = 0x0401;
            else
            {
                usage();
                exit (-1);
            }               
        }
        else
        if ( !strncmp(argv[i], "--deviceID=", 11) )
        {
            deviceID = atoi(&argv[i][11]);
        }
        else
        {
            usage();
            exit (-1);
        }
    }

    if ( (screenVal < 0) || !inputVal )
    {
        usage();
        exit (-1);
    }

    hid_init();

    struct hid_device_info *devs, *cur_dev;
    devs = hid_enumerate(0x0, 0x0);
    cur_dev = devs; 
    while (cur_dev) 
    {
        if ((cur_dev->vendor_id == deviceID) && strlen(cur_dev->serial_number))
        {
            printf("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
            printf("\n");
            printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
            printf("  Product:      %ls\n", cur_dev->product_string);
            printf("  Release:      %hx\n", cur_dev->release_number);
            printf("  Interface:    %d\n",  cur_dev->interface_number);
            printf("\n");

            strcpy(dev_name, cur_dev->path);
        }
        cur_dev = cur_dev->next;
    }
    hid_free_enumeration(devs);    

    if ( !strlen(dev_name) )
    {
        printf("Can't found specified EIZO device [vendor=0x056d, deviceID=0x%4.4lx]\n", deviceID);
        exit (-1);
    }

    // Open the HID raw device
    // Set this to the path of the device the driver creates.
    struct hid_device_ *dev = hid_open_path(dev_name);
    if(dev) 
    {
        // Define a buffer big enough for the report
        // The maximum size for any report is 513
        unsigned char val[3];

        // Setup screen
        printf("Setup screen to %d\n", screenVal);
        memset(val, 0, sizeof(val));
        val[0] = 53;
        *((unsigned short *) &val[1]) = screenVal;
        // Send the report and either return an error or the actual number of bytes written.
        int len = hid_send_feature_report(dev, val, sizeof(val));
        if (len >= 0) {
            printf("%d bytes written", len);
        } else {
            const wchar_t *err = hid_error(dev);
            fwprintf(stderr, L"%ls\n", err);
        }

        // Setup input
        switch(inputVal)
        {
            case 0x300: printf("Setup input to dp (0x%2.2hx)\n", inputVal); break;
            case 0x301: printf("Setup input to usb-c (0x%2.2hx)\n", inputVal); break;
            case 0x400: printf("Setup input to hdmi1 (0x%2.2hx)\n", inputVal); break;
            case 0x401: printf("Setup input to hdmi2 (0x%2.2hx)\n", inputVal); break;
        }
        memset(val, 0, sizeof(val));
        val[0] = 39;
        *((unsigned short *) &val[1]) = inputVal;
        // Send the report and either return an error or the actual number of bytes written.
        len = hid_send_feature_report(dev, val, sizeof(val));
        if (len >= 0) {
            printf("%d bytes written", len);
        } else {
            const wchar_t *err = hid_error(dev);
            fwprintf(stderr, L"%ls\n", err);
        }

        hid_close(dev);
    }
    else
        printf("Can't open device %s!\n", dev_name);

    hid_exit();
    return 0;
}
markbolhuis commented 2 years ago

Glad you found it useful :) I'm quite relieved that the code works with no changes for other models.

You can discover what reports your monitor supports with the following:

hexdump -e '16/1 "%02x " "\n"' report_descriptor

for both the parent and child hid report descriptor

cd /sys/bus/hid/devices/

and the use an hid report descriptor decoder to list it in human readable form.

If you can reply with either the raw hexdump or the decoded descriptor (see desc/ev2760.txt) I can then commit a new file: desc/ev3895.txt, or you can submit a PR.

laasa commented 2 years ago

Here are the dumps for parent and child for EIZO FelxScan 3895

parent: /sys/bus/hid/devices/0003:056D:4065.0013$ hexdump -e '16/1 "%02x " "\n"' report_descriptor 05 80 09 01 a1 01 06 30 ff 85 01 75 08 96 04 02 15 00 26 ff 00 09 01 b1 02 85 02 75 08 95 26 15 00 26 ff 00 09 02 b1 02 85 03 75 08 95 26 15 00 26 ff 00 09 03 81 02 09 03 b1 02 85 04 75 08 96 06 02 15 00 26 ff 00 09 04 b1 02 85 05 75 08 96 06 02 15 00 26 ff 00 09 05 b1 02 85 06 75 10 95 01 15 01 26 ff 7f 09 06 b1 02 85 07 75 08 95 07 15 00 26 ff 00 09 07 b1 02 85 08 75 08 95 18 15 00 26 ff 00 09 08 b1 02 85 09 75 08 95 54 15 00 26 ff 00 09 09 b1 02 85 0a 75 08 95 04 15 00 26 ff 00 09 10 b1 02 85 0b 75 08 96 06 01 15 00 26 ff 00 09 11 b1 02 c0

child: /sys/bus/hid/devices/0003:056D:4065.0014$ hexdump -e '16/1 "%02x " "\n"' report_descriptor 05 80 09 01 a1 01 06 00 ff 85 01 75 10 95 01 15 00 25 0f 09 07 81 02 09 07 b1 02 85 02 75 10 95 01 15 00 27 ff ff 00 00 09 0f 81 02 09 0f b1 02 85 03 75 10 95 01 15 02 25 22 09 15 81 02 09 15 b1 02 85 04 75 10 95 02 15 00 27 ff ff 00 00 09 2f 81 02 09 2f b1 02 85 05 75 10 95 01 15 00 27 ff bf 00 00 09 30 b1 02 85 06 75 10 95 01 15 00 26 ff 00 09 31 b1 02 85 07 75 20 95 01 15 00 27 ff ff ff 7f 09 32 b1 02 85 08 75 08 95 01 15 00 26 ff 00 09 33 b1 02 85 09 75 08 95 80 15 00 26 ff 00 09 34 b2 02 01 85 0a 75 08 95 03 15 00 26 ff 00 09 37 b1 02 85 0b 75 10 95 01 15 02 25 05 09 66 81 02 09 66 b1 02 85 0c 75 10 95 01 15 00 25 01 09 7c b1 02 85 0d 75 10 95 01 15 00 26 ff 03 09 89 b1 02 85 0e 75 10 95 01 15 00 26 ff 03 09 8a b1 02 85 0f 75 10 95 01 15 00 26 ff 03 09 8b b1 02 85 10 75 10 95 01 15 00 27 ff ff 00 00 09 8c b1 02 85 11 75 10 95 01 15 00 25 0d 09 a5 81 02 09 a5 b1 02 85 12 75 10 95 02 15 00 26 10 27 09 ad 81 02 09 ad b1 02 85 13 75 10 95 02 15 00 26 10 27 09 ae 81 02 09 ae b1 02 85 14 75 10 95 02 15 00 26 10 27 09 af 81 02 09 af b1 02 85 15 75 10 95 02 15 00 26 10 27 09 b0 81 02 09 b0 b1 02 85 16 75 08 95 01 15 00 25 64 09 b3 81 02 09 b3 b1 02 85 17 75 08 95 01 15 00 25 64 09 b4 81 02 09 b4 b1 02 85 18 75 08 95 01 15 00 25 01 09 b8 81 02 09 b8 b1 02 85 19 75 10 95 01 15 00 25 01 09 b9 81 02 09 b9 b1 02 85 1a 75 08 95 08 15 00 26 ff 00 09 c3 b2 02 01 85 1b 75 10 95 04 15 00 27 ff ff 00 00 09 c5 b1 02 85 1c 75 08 95 01 15 01 25 02 09 c9 81 02 09 c9 b1 02 85 1d 75 10 95 01 15 00 27 ff ff 00 00 09 ca 81 02 09 ca b1 02 85 1e 75 10 95 01 15 00 27 ff ff 00 00 09 cb 81 02 09 cb b1 02 85 1f 75 08 95 12 15 00 26 ff 00 09 d8 b1 02 85 20 75 10 95 01 15 0a 25 0a 09 e1 81 02 09 e1 b1 02 06 01 ff 85 21 75 10 95 01 15 00 25 01 09 07 b1 02 85 22 75 10 95 01 15 00 27 ff ff 00 00 09 0c b1 02 85 23 75 10 95 01 15 00 27 ff ff 00 00 09 3d 81 02 09 3d b1 02 85 24 75 08 95 01 15 00 25 01 09 40 81 02 09 40 b1 02 85 25 75 08 95 01 15 00 25 03 09 44 81 02 09 44 b1 02 85 26 75 08 95 01 15 00 25 01 09 45 81 02 09 45 b1 02 85 27 75 08 95 02 15 00 27 ff ff 00 00 09 48 82 02 01 09 48 b2 02 01 85 28 75 08 95 01 15 00 25 01 09 4a 81 02 09 4a b1 02 85 29 75 08 95 01 15 00 25 01 09 53 b1 02 85 2a 75 08 95 01 15 00 25 01 09 54 81 02 09 54 b1 02 85 2b 75 08 95 04 15 00 26 ff 00 09 58 b1 02 85 2c 75 08 95 40 15 00 26 ff 00 09 59 b1 02 85 2d 75 08 95 06 15 00 26 ff 00 09 5a b1 02 85 2e 75 08 95 01 15 00 25 02 09 5b b1 02 09 5b 81 02 85 2f 75 08 95 08 15 00 26 ff 00 09 78 81 02 09 78 b1 02 85 30 75 10 95 01 15 00 25 15 09 a0 b1 02 85 31 75 10 95 01 15 00 25 07 09 a1 b1 02 85 32 75 08 95 02 15 00 25 01 09 eb 81 02 09 eb b1 02 85 33 75 08 95 02 15 00 26 ff 00 09 f1 b1 02 85 34 75 08 95 04 15 00 26 ff 00 09 f2 b1 02 85 35 75 08 95 01 15 00 25 02 09 f9 b1 02 09 f9 81 02 85 36 75 08 95 01 15 00 25 05 09 fa b1 02 09 fa 81 02 85 37 75 08 95 01 15 00 25 01 09 fe b1 02 85 39 75 20 95 01 15 00 27 ff ff 3f 00 0a 0d 01 b1 02 85 3a 75 08 96 00 02 15 00 26 ff 00 0a 0e 01 b1 02 85 3b 75 20 95 01 15 00 27 ff ff ff 7f 0a 11 01 81 02 0a 11 01 b1 02 06 02 ff 85 3c 75 08 95 01 15 00 25 01 09 06 b1 02 85 3d 75 08 95 01 15 00 26 ff 00 09 0d 81 02 09 0d b1 02 85 3e 75 20 95 01 17 00 00 00 80 27 ff ff ff 7f 09 1a b1 02 85 3f 75 08 95 01 15 00 25 01 09 29 b1 02 85 40 75 08 95 01 15 00 25 13 09 33 b1 02 85 41 75 08 96 00 01 15 00 26 ff 00 09 34 b2 02 01 85 42 75 08 95 02 15 00 26 ff 00 09 35 b1 02 85 43 75 08 95 08 15 00 26 ff 00 09 36 b2 02 01 85 44 75 08 95 01 15 00 26 ff 00 09 37 b1 02 85 45 75 08 95 01 15 00 26 c8 00 09 39 b1 02 85 46 75 08 95 01 15 00 25 0d 09 3a b1 02 85 47 75 08 95 01 15 00 25 03 09 44 b1 02 85 48 75 08 95 10 15 00 26 ff 00 09 50 b2 02 01 85 49 75 10 95 04 15 00 27 ff ff 00 00 09 55 b2 02 01 85 4a 75 08 95 01 15 00 25 0b 09 58 b1 02 85 4b 75 08 96 00 02 15 00 26 ff 00 09 59 b1 02 85 4c 75 08 96 00 01 15 00 26 ff 00 09 5b b2 02 01 85 4d 75 20 95 01 17 00 00 00 80 27 ff ff ff 7f 09 5c b1 02 85 4f 75 08 95 01 15 00 25 02 09 65 b1 02 85 50 75 08 96 00 02 15 00 26 ff 00 09 66 b2 02 01 85 51 75 08 95 01 15 50 25 fa 09 a3 b1 02 85 52 75 08 95 2d 15 00 26 ff 00 09 dd b1 02 85 54 75 20 95 01 17 00 00 00 80 27 ff ff ff 7f 09 e3 b1 02 85 55 75 10 95 01 15 00 26 4f 02 09 e6 b1 02 85 56 75 08 96 00 02 15 00 26 ff 00 09 e7 b1 02 85 57 75 08 95 20 15 00 26 ff 00 0a 00 01 b2 02 01 85 58 75 08 95 10 15 00 26 ff 00 0a 02 01 b2 02 01 85 59 75 08 95 04 15 00 26 ff 00 0a 05 01 b1 02 85 5a 75 08 95 01 15 00 26 ff 00 0a 08 01 b1 02 85 5b 75 08 95 01 15 00 25 01 0a 09 01 b1 02 85 5c 75 08 95 01 15 00 25 03 0a 0b 01 b1 02 85 5d 75 08 95 01 15 00 25 02 0a 0c 01 b1 02 05 0c 85 5e 75 10 95 01 15 00 25 1e 09 e0 81 02 09 e0 b1 02 05 82 85 5f 75 10 95 01 15 00 26 c8 00 09 10 81 02 09 10 b1 02 85 60 75 10 95 01 15 00 26 8c 00 09 12 81 02 09 12 b1 02 85 61 75 10 95 01 15 00 26 ff 00 09 16 81 02 09 16 b1 02 85 62 75 10 95 01 15 00 26 ff 00 09 18 81 02 09 18 b1 02 85 63 75 10 95 01 15 00 26 ff 00 09 1a 81 02 09 1a b1 02 85 64 75 20 95 01 15 00 27 40 0d 03 00 09 ac 81 02 09 ac b1 02 85 65 75 10 95 01 15 00 26 20 4e 09 ae 81 02 09 ae b1 02 85 66 75 02 95 01 15 01 25 02 09 b0 a1 02 05 81 09 01 09 02 b1 00 c0 75 06 b1 03 c0

laasa commented 2 years ago

Sorry for asking. What is the information of the file desc/ev3895.txt? Let's say I want to switch the input via the hidraw device: Which line is relevant/helpfully? Via trial&error I found out that I must use id 39 to switch the input.

For id 39 I found that: 0x85, 0x27, // Report ID (39) 0x75, 0x08, // Report Size (8) 0x95, 0x02, // Report Count (2) 0x15, 0x00, // Logical Minimum (0) 0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534) 0x09, 0x48, // Usage (0x48) 0x82, 0x02, 0x01, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) 0x09, 0x48, // Usage (0x48) 0xB2, 0x02, 0x01, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes) What does it mean?

markbolhuis commented 2 years ago

The descriptor contains a list of reports that the monitor supports (brightness, contrast, etc...) So in your example to change the input you must send an hid raw byte array of three bytes

b[0] = 39
b[1..] = value

Since this driver only translates requests from the child hid device (the one it creates) to the parent (the actual monitor), it has to translate the byte array into the following:

b[0] = EIZO_REPORT_SET (see src/eizo.h)
b[1..3] = usage page 
b[3..5] = usage id
b[5..7] = counter
b[7..] = value

The usage id here is 0x0048 for input selection: https://github.com/openeizo/openeizo/blob/1d8c14ca7c1582f6ef6448b81f9424c7188dbc1a/desc/ev3895.txt#L465

The usage page will be whatever the global usage page was set to in the descriptor, in this case 0xff01: https://github.com/openeizo/openeizo/blob/1d8c14ca7c1582f6ef6448b81f9424c7188dbc1a/desc/ev3895.txt#L400

src/eizo.h contains a list of already understood usages: https://github.com/openeizo/openeizo/blob/1d8c14ca7c1582f6ef6448b81f9424c7188dbc1a/src/eizo.h#L109


I recommend you read the HID specification. It should help you understand the report descriptors.

laasa commented 2 years ago

Thanks.

When I want to use a special usage from eizo.h I can go into the file ev3895.txt and get the related Report ID. That's very nice and much faster then to use the trial&error method.

With that knowledge I have checked against the files ev2760.txt and ev2785.txt. For the 2760 the report ID to switch the input is report ID 35 (instead of 39 for 3895). For the 2785 there is no report ID to switch the input. This means that my code above is only working for the 3895 model.

The second function used in my code to set the screen (report ID 53) is usage 0xF9 for the 3895. For 2760 the related report ID is 50 and for 2785 there is no report ID.

And yes, to read the specification is a good idea at all (but sometimes a bit painfully). But I think that to you have read it to get these knowledge. Thank you for doing it.

Thinking about making it a bit better. One is to add some sentences in the readme section about that theme (I have missed that). Second is to have an tool to show the mapping between usage in eizo.h and report ID very easy. At the end there could be a table with the eizo models in the colums and the usages in the raws. When other people

franbu commented 2 years ago

Thank you so much for making this work public! I'm a complete noob, please forgive me. Just to summarize: doing what you posted, you are now able to use Eizo FlexScan 3895 with Ubuntu? Because I'm not. :( I've noticed that the behavior is erratic: it depends on what connection type I use between the display and the laptop. Are you using HDMI? Or UsbC perhaps? Any help would be VERY MUCH appreciated!

Using Ubuntu 20.04 with kernel 5.13 (but also Linux Mint 20.3 with kernel 5.14).