mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-23 15:11:47 +00:00
302 lines
9 KiB
C
302 lines
9 KiB
C
|
/*
|
||
|
* Copyright 2020 Jan Schmidt
|
||
|
* SPDX-License-Identifier: BSL-1.0
|
||
|
*
|
||
|
* OpenHMD - Free and Open Source API and drivers for immersive technology.
|
||
|
*/
|
||
|
/*!
|
||
|
* @file
|
||
|
* @brief Oculus Rift S HMD Radio management
|
||
|
*
|
||
|
* Functions for serialising requests to communicate with
|
||
|
* Touch controllers over the HMDs wireless radio link,
|
||
|
* collecting responses and delivering them back via callbacks.
|
||
|
*
|
||
|
* Ported from OpenHMD
|
||
|
*
|
||
|
* @author Jan Schmidt <jan@centricular.com>
|
||
|
*/
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
#include "util/u_misc.h"
|
||
|
#include "rift_s.h"
|
||
|
#include "rift_s_radio.h"
|
||
|
#include "rift_s_protocol.h"
|
||
|
|
||
|
/* Struct that forms a double linked queue of pending commands,
|
||
|
* with the head being the currently active command */
|
||
|
struct rift_s_radio_command
|
||
|
{
|
||
|
rift_s_radio_command *prev;
|
||
|
rift_s_radio_command *next;
|
||
|
|
||
|
/* Request packet data */
|
||
|
rift_s_hmd_radio_command_t read_command;
|
||
|
|
||
|
/* Completion callback */
|
||
|
rift_s_radio_completion_fn cb;
|
||
|
void *cb_data;
|
||
|
};
|
||
|
|
||
|
static int
|
||
|
get_radio_response_report(struct os_hid_device *hid, rift_s_hmd_radio_response_t *radio_response)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
radio_response->cmd = 0xb;
|
||
|
ret = os_hid_get_feature(hid, radio_response->cmd, (uint8_t *)(radio_response),
|
||
|
sizeof(rift_s_hmd_radio_response_t));
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
rift_s_radio_update(rift_s_radio_state *state, struct os_hid_device *hid)
|
||
|
{
|
||
|
bool read_another = false;
|
||
|
|
||
|
do {
|
||
|
/* Send a radio command if there is none active and some pending */
|
||
|
if (state->command_result_pending == false && state->pending_commands) {
|
||
|
rift_s_radio_command *cmd = state->pending_commands;
|
||
|
rift_s_hmd_radio_command_t *pkt = &cmd->read_command;
|
||
|
|
||
|
pkt->cmd = 0x12;
|
||
|
os_hid_set_feature(hid, (uint8_t *)pkt, sizeof(*pkt));
|
||
|
// rift_s_hexdump_buffer ("ControllerFWSend", (unsigned char *)(pkt), sizeof(*pkt));
|
||
|
state->command_result_pending = true;
|
||
|
}
|
||
|
|
||
|
if (!state->command_result_pending)
|
||
|
break; /* Nothing to do right now */
|
||
|
|
||
|
/* There's a command result pending, poll the radio response until it's complete */
|
||
|
rift_s_hmd_radio_response_t radio_response;
|
||
|
|
||
|
/* The radio response is ready when the busy flag has cleared, and the seqnum
|
||
|
* has incremented */
|
||
|
int ret = get_radio_response_report(hid, &radio_response);
|
||
|
|
||
|
if (ret < 2) {
|
||
|
break; /* HID read failed - bail */
|
||
|
}
|
||
|
|
||
|
if (radio_response.busy_flag != 0x00 || radio_response.seqnum == state->last_radio_seqnum) {
|
||
|
if (radio_response.busy_flag)
|
||
|
state->last_radio_seqnum = radio_response.seqnum;
|
||
|
return;
|
||
|
}
|
||
|
state->last_radio_seqnum = radio_response.seqnum;
|
||
|
|
||
|
/* We have the controller response! */
|
||
|
assert(ret > 3 && ret <= (int)sizeof(radio_response));
|
||
|
|
||
|
state->command_result_pending = false;
|
||
|
// rift_s_hexdump_buffer ("ControllerFWReply", (unsigned char *)(&radio_response), ret);
|
||
|
|
||
|
assert(state->pending_commands != NULL);
|
||
|
|
||
|
rift_s_radio_command *cmd = state->pending_commands;
|
||
|
|
||
|
/* Pop the head off the cmds queue, because it's complete now */
|
||
|
state->pending_commands = cmd->next;
|
||
|
if (state->pending_commands == NULL)
|
||
|
state->pending_commands_tail = NULL;
|
||
|
else
|
||
|
state->pending_commands->prev = NULL;
|
||
|
|
||
|
/* Call the completion callback */
|
||
|
if (cmd->cb)
|
||
|
cmd->cb(true, radio_response.response_bytes, ret - 3, cmd->cb_data);
|
||
|
free(cmd);
|
||
|
read_another = true;
|
||
|
|
||
|
} while (read_another);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
rift_s_radio_state_init(rift_s_radio_state *state)
|
||
|
{
|
||
|
state->command_result_pending = false;
|
||
|
state->pending_commands = NULL;
|
||
|
state->pending_commands_tail = NULL;
|
||
|
state->last_radio_seqnum = -1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
rift_s_radio_state_clear(rift_s_radio_state *state)
|
||
|
{
|
||
|
/* Clear and free any pending commands. */
|
||
|
rift_s_radio_command *head = state->pending_commands;
|
||
|
while (head != NULL) {
|
||
|
rift_s_radio_command *prev = head;
|
||
|
head = prev->next;
|
||
|
|
||
|
if (prev->cb)
|
||
|
prev->cb(false, NULL, 0, prev->cb_data);
|
||
|
free(prev);
|
||
|
}
|
||
|
|
||
|
state->pending_commands = state->pending_commands_tail = NULL;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
rift_s_radio_queue_command(rift_s_radio_state *state,
|
||
|
const uint64_t device_id,
|
||
|
const uint8_t *cmd_bytes,
|
||
|
const int cmd_bytes_len,
|
||
|
rift_s_radio_completion_fn cb,
|
||
|
void *cb_data)
|
||
|
{
|
||
|
rift_s_radio_command *cmd = U_TYPED_CALLOC(rift_s_radio_command);
|
||
|
|
||
|
assert(cmd_bytes_len <= (int)sizeof(cmd->read_command.cmd_bytes));
|
||
|
|
||
|
cmd->read_command.device_id = device_id;
|
||
|
memcpy(cmd->read_command.cmd_bytes, cmd_bytes, cmd_bytes_len);
|
||
|
cmd->cb = cb;
|
||
|
cmd->cb_data = cb_data;
|
||
|
|
||
|
/* Append to the pending commands queue. The command itself will be sent by the update() function
|
||
|
* when possible */
|
||
|
if (state->pending_commands_tail == NULL) {
|
||
|
assert(state->pending_commands == NULL);
|
||
|
state->pending_commands = state->pending_commands_tail = cmd;
|
||
|
} else {
|
||
|
state->pending_commands_tail->next = cmd;
|
||
|
cmd->prev = state->pending_commands_tail;
|
||
|
state->pending_commands_tail = cmd;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The current blocks are ~2-3KB, so this should be enough to read
|
||
|
* the JSON config: */
|
||
|
#define MAX_JSON_LEN 4096
|
||
|
|
||
|
typedef struct rift_s_radio_json_read_state
|
||
|
{
|
||
|
rift_s_radio_state *state;
|
||
|
uint64_t device_id;
|
||
|
rift_s_radio_completion_fn cb;
|
||
|
void *cb_data;
|
||
|
|
||
|
uint32_t cur_offset;
|
||
|
uint16_t block_len; /* Expected length, from the header */
|
||
|
|
||
|
uint8_t data[MAX_JSON_LEN + 1];
|
||
|
uint16_t data_len;
|
||
|
|
||
|
} rift_s_radio_json_read_state;
|
||
|
|
||
|
static void
|
||
|
read_json_cb(bool success, uint8_t *response_bytes, int response_bytes_len, rift_s_radio_json_read_state *json_read)
|
||
|
{
|
||
|
if (!success) {
|
||
|
/* Failed, report to the caller */
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (response_bytes_len < 5) {
|
||
|
RIFT_S_WARN("Not enough bytes in radio response - needed 5, got %d\n", response_bytes_len);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
uint8_t reply_len = response_bytes[4];
|
||
|
response_bytes += 5;
|
||
|
|
||
|
if (json_read->cur_offset == 0) {
|
||
|
/* Read the header 0u32 0x20 01 00 62 09 7b 22 67 79 | .{..... ..b.{"gy
|
||
|
72 6f 5f 6d 22 3a 5b 2d 30 2e 30 31 34 33 35 36 | ro_m":[-0.014356
|
||
|
38 38 36 36 2c 2d 30 2e | 8866,-0.
|
||
|
= len 0x20, 0001 (file type, or sequence number?), 0x0962 = file length */
|
||
|
if (reply_len < 4) {
|
||
|
RIFT_S_WARN("Not enough bytes in remote configuration header - needed 4, got %d\n", reply_len);
|
||
|
goto fail; /* Not enough bytes in header */
|
||
|
}
|
||
|
|
||
|
uint16_t file_type = (response_bytes[1] << 8) | response_bytes[0];
|
||
|
uint16_t block_len = (response_bytes[3] << 8) | response_bytes[2];
|
||
|
|
||
|
if (file_type != 1) {
|
||
|
RIFT_S_WARN("Unknown file type in remote configuration header - expected 1, got %d\n",
|
||
|
file_type);
|
||
|
goto fail;
|
||
|
}
|
||
|
/* Assert if the MAX_JSON_LEN ever needs expanding */
|
||
|
assert(block_len <= MAX_JSON_LEN);
|
||
|
if (block_len > MAX_JSON_LEN) {
|
||
|
RIFT_S_WARN(
|
||
|
"Remote configuration block too long. Please expand the read buffer (needed %u bytes)\n",
|
||
|
block_len);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
json_read->block_len = block_len;
|
||
|
json_read->cur_offset = 0x4;
|
||
|
} else {
|
||
|
uint16_t data_remain = json_read->block_len - json_read->data_len;
|
||
|
|
||
|
if (reply_len > data_remain)
|
||
|
reply_len = data_remain; /* Truncate any over-read */
|
||
|
|
||
|
/* Append these bytes to the buffer */
|
||
|
memcpy(json_read->data + json_read->data_len, response_bytes, reply_len);
|
||
|
json_read->data_len += reply_len;
|
||
|
}
|
||
|
|
||
|
/* If there is no more to read, then report success to the caller and return */
|
||
|
if (json_read->data_len >= json_read->block_len) {
|
||
|
json_read->data[json_read->data_len] = 0;
|
||
|
|
||
|
if (json_read->cb)
|
||
|
json_read->cb(true, json_read->data, json_read->data_len, json_read->cb_data);
|
||
|
free(json_read);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Otherwise request more data */
|
||
|
uint8_t read_len = MIN(0x20, json_read->block_len - json_read->data_len);
|
||
|
uint8_t read_cmd[] = {0x2b, 0x20, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
|
||
|
|
||
|
read_cmd[4] = json_read->cur_offset;
|
||
|
read_cmd[5] = json_read->cur_offset >> 8;
|
||
|
read_cmd[6] = json_read->cur_offset >> 16;
|
||
|
read_cmd[7] = json_read->cur_offset >> 24;
|
||
|
read_cmd[8] = read_len;
|
||
|
|
||
|
rift_s_radio_queue_command(json_read->state, json_read->device_id, read_cmd, sizeof(read_cmd),
|
||
|
(rift_s_radio_completion_fn)read_json_cb, json_read);
|
||
|
|
||
|
json_read->cur_offset += read_len;
|
||
|
return;
|
||
|
|
||
|
fail:
|
||
|
if (json_read->cb)
|
||
|
json_read->cb(success, json_read->data, json_read->data_len, json_read->cb_data);
|
||
|
free(json_read);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
rift_s_radio_get_json_block(rift_s_radio_state *state,
|
||
|
const uint64_t device_id,
|
||
|
rift_s_radio_completion_fn cb,
|
||
|
void *cb_data)
|
||
|
{
|
||
|
/* Configuration JSON block reading */
|
||
|
rift_s_radio_json_read_state *json_read = U_TYPED_CALLOC(rift_s_radio_json_read_state);
|
||
|
/* cmd = 0x2b reply_buffer_len = 0x20 timeout(?) = 0x3e8 (=1000) offset = 0u32 len = 0x20 */
|
||
|
const uint8_t read_cmd[] = {0x2b, 0x20, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
|
||
|
|
||
|
json_read->state = state;
|
||
|
json_read->device_id = device_id;
|
||
|
json_read->cb = cb;
|
||
|
json_read->cb_data = cb_data;
|
||
|
|
||
|
rift_s_radio_queue_command(state, device_id, read_cmd, sizeof(read_cmd),
|
||
|
(rift_s_radio_completion_fn)read_json_cb, json_read);
|
||
|
}
|