246 lines
7.6 KiB
C
246 lines
7.6 KiB
C
/*
|
|
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
|
|
* Copyright (c) 2024 Hrvoje Cavrak
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "main.h"
|
|
|
|
void task_scheduler(device_t *state, task_t *task) {
|
|
uint64_t current_time = time_us_64();
|
|
|
|
if (current_time < task->next_run)
|
|
return;
|
|
|
|
task->next_run = current_time + task->frequency;
|
|
task->exec(state);
|
|
}
|
|
|
|
/**================================================== *
|
|
* ============== Watchdog Functions ============== *
|
|
* ================================================== */
|
|
|
|
void kick_watchdog_task(device_t *state) {
|
|
/* Read the timer AFTER duplicating the core1 timestamp,
|
|
so it doesn't get updated in the meantime. */
|
|
uint64_t core1_last_loop_pass = state->core1_last_loop_pass;
|
|
uint64_t current_time = time_us_64();
|
|
|
|
/* If a reboot is requested, we'll stop updating watchdog */
|
|
if (state->reboot_requested)
|
|
return;
|
|
|
|
/* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */
|
|
if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
|
|
watchdog_update();
|
|
}
|
|
|
|
/**================================================== *
|
|
* =============== USB Device / Host ============== *
|
|
* ================================================== */
|
|
|
|
void usb_device_task(device_t *state) {
|
|
tud_task();
|
|
}
|
|
|
|
void usb_host_task(device_t *state) {
|
|
if (tuh_inited())
|
|
tuh_task();
|
|
}
|
|
|
|
mouse_report_t *screensaver_pong(device_t *state) {
|
|
static mouse_report_t report = {0};
|
|
static int dx = 20, dy = 25;
|
|
|
|
/* Check if we are bouncing off the walls and reverse direction in that case. */
|
|
if (report.x + dx < MIN_SCREEN_COORD || report.x + dx > MAX_SCREEN_COORD)
|
|
dx = -dx;
|
|
|
|
if (report.y + dy < MIN_SCREEN_COORD || report.y + dy > MAX_SCREEN_COORD)
|
|
dy = -dy;
|
|
|
|
report.x += dx;
|
|
report.y += dy;
|
|
|
|
return &report;
|
|
}
|
|
|
|
mouse_report_t *screensaver_jitter(device_t *state) {
|
|
const int16_t jitter_distance = 2;
|
|
static mouse_report_t report = {
|
|
.x = jitter_distance,
|
|
.y = jitter_distance,
|
|
.mode = RELATIVE,
|
|
};
|
|
|
|
report.x = -report.x;
|
|
report.y = -report.y;
|
|
|
|
return &report;
|
|
}
|
|
|
|
/* Have something fun and entertaining when idle. */
|
|
void screensaver_task(device_t *state) {
|
|
const int mouse_move_delay = 5000;
|
|
static int last_pointer_move = 0;
|
|
screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver;
|
|
uint64_t inactivity_period = time_us_64() - state->last_activity[BOARD_ROLE];
|
|
|
|
/* If we're not enabled, nothing to do here. */
|
|
if (screensaver->mode == DISABLED)
|
|
return;
|
|
|
|
/* System is still not idle for long enough to activate or we've been running for too long */
|
|
if (inactivity_period < screensaver->idle_time_us)
|
|
return;
|
|
|
|
/* We exceeded the maximum permitted screensaver runtime */
|
|
if (screensaver->max_time_us
|
|
&& inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us))
|
|
return;
|
|
|
|
/* If we're the selected output and we can only run on inactive output, nothing to do here. */
|
|
if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT)
|
|
return;
|
|
|
|
/* We're active! Now check if it's time to move the cursor yet. */
|
|
if ((time_us_32()) - last_pointer_move < mouse_move_delay)
|
|
return;
|
|
|
|
mouse_report_t *report;
|
|
switch (screensaver->mode) {
|
|
case PONG:
|
|
report = screensaver_pong(state);
|
|
break;
|
|
|
|
case JITTER:
|
|
report = screensaver_jitter(state);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* Move mouse pointer */
|
|
queue_mouse_report(report, state);
|
|
|
|
/* Update timer of the last pointer move */
|
|
last_pointer_move = time_us_32();
|
|
}
|
|
|
|
/* Periodically emit heartbeat packets */
|
|
void heartbeat_output_task(device_t *state) {
|
|
/* If firmware upgrade is in progress, don't touch flash_cs */
|
|
if (state->fw.upgrade_in_progress)
|
|
return;
|
|
|
|
if (state->config_mode_active) {
|
|
/* Leave config mode if timeout expired and user didn't click exit */
|
|
if (time_us_64() > state->config_mode_timer)
|
|
reboot();
|
|
|
|
/* Keep notifying the user we're still in config mode */
|
|
blink_led(state);
|
|
}
|
|
|
|
#ifdef DH_DEBUG
|
|
/* Holding the button invokes bootsel firmware upgrade */
|
|
if (is_bootsel_pressed())
|
|
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
|
|
#endif
|
|
|
|
uart_packet_t packet = {
|
|
.type = HEARTBEAT_MSG,
|
|
.data16 = {
|
|
[0] = state->_running_fw.version,
|
|
[2] = state->active_output,
|
|
},
|
|
};
|
|
|
|
queue_try_add(&global_state.uart_tx_queue, &packet);
|
|
}
|
|
|
|
|
|
/* Process other outgoing hid report messages. */
|
|
void process_hid_queue_task(device_t *state) {
|
|
hid_generic_pkt_t packet;
|
|
|
|
if (!queue_try_peek(&state->hid_queue_out, &packet))
|
|
return;
|
|
|
|
if (!tud_hid_n_ready(packet.instance))
|
|
return;
|
|
|
|
/* ... try sending it to the host, if it's successful */
|
|
bool succeeded = tud_hid_n_report(packet.instance, packet.report_id, packet.data, packet.len);
|
|
|
|
/* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */
|
|
if (succeeded)
|
|
queue_try_remove(&state->hid_queue_out, &packet);
|
|
}
|
|
|
|
/* Task that handles copying firmware from the other device to ours */
|
|
void firmware_upgrade_task(device_t *state) {
|
|
if (!state->fw.upgrade_in_progress || !state->fw.byte_done)
|
|
return;
|
|
|
|
if (queue_is_full(&state->uart_tx_queue))
|
|
return;
|
|
|
|
/* End condition, when reached the process is completed. */
|
|
if (state->fw.address > STAGING_IMAGE_SIZE) {
|
|
state->fw.upgrade_in_progress = 0;
|
|
state->fw.checksum = ~state->fw.checksum;
|
|
|
|
/* Checksum mismatch, we wipe the stage 2 bootloader and rely on ROM recovery */
|
|
if(calculate_firmware_crc32() != state->fw.checksum) {
|
|
flash_range_erase((uint32_t)ADDR_FW_RUNNING - XIP_BASE, FLASH_SECTOR_SIZE);
|
|
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
|
|
}
|
|
|
|
else {
|
|
state->_running_fw = _firmware_metadata;
|
|
global_state.reboot_requested = true;
|
|
}
|
|
}
|
|
|
|
/* If we're on the last element of the current page, page is done - write it. */
|
|
if (TU_U32_BYTE0(state->fw.address) == 0x00) {
|
|
|
|
uint32_t page_start_addr = (state->fw.address - 1) & 0xFFFFFF00;
|
|
write_flash_page((uint32_t)ADDR_FW_RUNNING + page_start_addr - XIP_BASE, state->page_buffer);
|
|
}
|
|
|
|
request_byte(state, state->fw.address);
|
|
}
|
|
|
|
void packet_receiver_task(device_t *state) {
|
|
uint32_t current_pointer
|
|
= (uint32_t)DMA_RX_BUFFER_SIZE - dma_channel_hw_addr(state->dma_rx_channel)->transfer_count;
|
|
uint32_t delta = get_ptr_delta(current_pointer, state);
|
|
|
|
/* If we don't have enough characters for a packet, skip loop and return immediately */
|
|
while (delta >= RAW_PACKET_LENGTH) {
|
|
if (is_start_of_packet(state)) {
|
|
fetch_packet(state);
|
|
process_packet(&state->in_packet, state);
|
|
return;
|
|
}
|
|
|
|
/* No packet found, advance to next position and decrement delta */
|
|
state->dma_ptr = NEXT_RING_IDX(state->dma_ptr);
|
|
delta--;
|
|
}
|
|
}
|