deskhop/src/tasks.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--;
}
}