summaryrefslogtreecommitdiff
path: root/platform/zmk/usb_hid.c
blob: 81bc0d4fe754a679ff6394f2bbb629ee74e35c0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * Copyright (c) 2020 The ZMK Contributors
 *
 * SPDX-License-Identifier: MIT
 */

#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include "usbd_core.h"
#include "usbd_hid.h"

#include <zmk/usb.h>
#include <zmk/hid.h>
#include <zmk/keymap.h>

#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
#include <zmk/pointing/resolution_multipliers.h>
#endif

#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
#include <zmk/hid_indicators.h>
#endif

#include <zmk/event_manager.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#define CHERRYUSB_BUS_ID 0
#define HID_INT_EP       0x81

/* Defined in cherryusb/usb.c */
extern struct k_sem zmk_cherryusb_hid_sem;

/* Write buffer must be USB-aligned and in non-cached memory */
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t hid_write_buffer[128];

#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
static uint8_t hid_protocol = 1; /* HID_PROTOCOL_REPORT */

void zmk_usb_hid_set_protocol(uint8_t protocol) { hid_protocol = protocol; }
#endif

static uint8_t *get_keyboard_report(size_t *len) {
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
    if (hid_protocol != 1) { /* boot protocol */
        zmk_hid_boot_report_t *boot_report = zmk_hid_get_boot_report();
        *len = sizeof(*boot_report);
        return (uint8_t *)boot_report;
    }
#endif
    struct zmk_hid_keyboard_report *report = zmk_hid_get_keyboard_report();
    *len = sizeof(*report);
    return (uint8_t *)report;
}

/* ---- CherryUSB HID class callbacks (weak overrides) ---- */

void usbd_hid_get_report(uint8_t busid, uint8_t intf, uint8_t report_id,
                         uint8_t report_type, uint8_t **data, uint32_t *len) {
    (void)busid;
    (void)intf;

    switch (report_type) {
    case 3: /* Feature */
        switch (report_id) {
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
        case ZMK_HID_REPORT_ID_MOUSE: {
            static struct zmk_hid_mouse_resolution_feature_report res_feature_report;
            struct zmk_endpoint_instance endpoint = {
                .transport = ZMK_TRANSPORT_USB,
            };
            *len = sizeof(struct zmk_hid_mouse_resolution_feature_report);
            struct zmk_pointing_resolution_multipliers mult =
                zmk_pointing_resolution_multipliers_get_profile(endpoint);
            res_feature_report.body.wheel_res = mult.wheel;
            res_feature_report.body.hwheel_res = mult.hor_wheel;
            *data = (uint8_t *)&res_feature_report;
            break;
        }
#endif
        default:
            *len = 0;
            break;
        }
        break;

    case 1: /* Input */
        switch (report_id) {
        case ZMK_HID_REPORT_ID_KEYBOARD: {
            size_t size;
            *data = get_keyboard_report(&size);
            *len = (uint32_t)size;
            break;
        }
        case ZMK_HID_REPORT_ID_CONSUMER: {
            struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report();
            *data = (uint8_t *)report;
            *len = sizeof(*report);
            break;
        }
        default:
            LOG_ERR("Invalid report ID %d requested", report_id);
            *len = 0;
            break;
        }
        break;

    default:
        LOG_ERR("Unsupported report type %d requested", report_type);
        *len = 0;
        break;
    }
}

void usbd_hid_set_report(uint8_t busid, uint8_t intf, uint8_t report_id,
                         uint8_t report_type, uint8_t *report, uint32_t report_len) {
    (void)busid;
    (void)intf;

    switch (report_type) {
    case 3: /* Feature */
        switch (report_id) {
#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
        case ZMK_HID_REPORT_ID_MOUSE:
            if (report_len != sizeof(struct zmk_hid_mouse_resolution_feature_report)) {
                return;
            }

            struct zmk_hid_mouse_resolution_feature_report *feat =
                (struct zmk_hid_mouse_resolution_feature_report *)report;
            struct zmk_endpoint_instance endpoint = {
                .transport = ZMK_TRANSPORT_USB,
            };
            zmk_pointing_resolution_multipliers_process_report(&feat->body, endpoint);
            break;
#endif
        default:
            break;
        }
        break;

    case 2: /* Output */
        switch (report_id) {
#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
        case ZMK_HID_REPORT_ID_LEDS:
            if (report_len != sizeof(struct zmk_hid_led_report)) {
                LOG_ERR("LED set report is malformed: length=%d", report_len);
                return;
            }
            struct zmk_hid_led_report *led_report = (struct zmk_hid_led_report *)report;
            struct zmk_endpoint_instance ep = {
                .transport = ZMK_TRANSPORT_USB,
            };
            zmk_hid_indicators_process_report(&led_report->body, ep);
            break;
#endif
        default:
            LOG_ERR("Invalid report ID %d for output", report_id);
            break;
        }
        break;

    default:
        LOG_ERR("Unsupported report type %d", report_type);
        break;
    }
}

void usbd_hid_set_idle(uint8_t busid, uint8_t intf, uint8_t report_id, uint8_t duration) {
    (void)busid;
    (void)intf;
    (void)report_id;
    (void)duration;
}

uint8_t usbd_hid_get_idle(uint8_t busid, uint8_t intf, uint8_t report_id) {
    (void)busid;
    (void)intf;
    (void)report_id;
    return 0;
}

uint8_t usbd_hid_get_protocol(uint8_t busid, uint8_t intf) {
    (void)busid;
    (void)intf;
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
    return hid_protocol;
#else
    return 1; /* Report protocol */
#endif
}

void usbd_hid_set_protocol(uint8_t busid, uint8_t intf, uint8_t protocol) {
    (void)busid;
    (void)intf;
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
    hid_protocol = protocol;
#endif
}

/* ---- Report sending ---- */

static int zmk_usb_hid_send_report(const uint8_t *report, size_t len) {
    if (!zmk_usb_is_hid_ready()) {
        if (zmk_usb_get_conn_state() == ZMK_USB_CONN_HID) {
            /* Suspended - try remote wakeup */
            return usbd_send_remote_wakeup(CHERRYUSB_BUS_ID);
        }
        return -ENODEV;
    }

    if (len > sizeof(hid_write_buffer)) {
        return -EINVAL;
    }

    k_sem_take(&zmk_cherryusb_hid_sem, K_MSEC(30));
    memcpy(hid_write_buffer, report, len);
    int err = usbd_ep_start_write(CHERRYUSB_BUS_ID, HID_INT_EP, hid_write_buffer, len);
    if (err) {
        k_sem_give(&zmk_cherryusb_hid_sem);
    }
    return err;
}

int zmk_usb_hid_send_keyboard_report(void) {
    size_t len;
    uint8_t *report = get_keyboard_report(&len);
    return zmk_usb_hid_send_report(report, len);
}

int zmk_usb_hid_send_consumer_report(void) {
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
    if (hid_protocol != 1) {
        return -ENOTSUP;
    }
#endif
    struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report();
    return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report));
}

#if IS_ENABLED(CONFIG_ZMK_POINTING)
int zmk_usb_hid_send_mouse_report(void) {
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
    if (hid_protocol != 1) {
        return -ENOTSUP;
    }
#endif
    struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report();
    return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report));
}
#endif /* CONFIG_ZMK_POINTING */