Skip to content

Commit

Permalink
Low level access to device file. Minimizes frame loss.
Browse files Browse the repository at this point in the history
Use ppoll to wait until data is available and then read.
It eliminates situations where read() was stuck and required forcing thread cancel.
So the main cause of lost frames is eliminated.

Some frames still get lost but much less frequently.
  • Loading branch information
kmicki committed Jun 22, 2022
1 parent 9843a53 commit 9a651c5
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 46 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
"ios": "cpp",
"locale": "cpp",
"queue": "cpp",
"cstdbool": "cpp"
"cstdbool": "cpp",
"hash_map": "cpp",
"csignal": "cpp",
"list": "cpp"
}
}
30 changes: 30 additions & 0 deletions inc/hiddev/hiddevfile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef _KMICKI_HIDDEV_HIDDEVFILE_
#define _KMICKI_HIDDEV_HIDDEVFILE_

#include <string>
#include <vector>
#include <sys/select.h>
#include "poll.h"

namespace kmicki::hiddev
{
class HidDevFile
{
public:
HidDevFile() = delete;
HidDevFile(std::string const& _filePath, int readTimeoutUs, bool const& open = true);

bool Open();
int Read(std::vector<char> & data);
bool Close();
bool IsOpen();

private:
pollfd fileDescriptors[1];
int & file;
std::string filePath;
timespec timeout;
};
}

#endif
20 changes: 10 additions & 10 deletions inc/hiddev/hiddevreader.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
#define _KMICKI_HIDDEV_HIDDEVREADER_H_

#include <vector>
#include <fstream>
#include <chrono>
#include <mutex>
#include <shared_mutex>

#include "pipeline/thread.h"
#include "pipeline/signalout.h"
#include "pipeline/pipeout.h"
#include "pipeline/serve.h"

#include "hiddevfile.h"

using namespace kmicki::pipeline;

namespace kmicki::hiddev
Expand All @@ -38,7 +37,7 @@ namespace kmicki::hiddev
// If it will be much higher then the generated frames will be out of sync
// (a block of consecutive frames and then skip)
// maxScanTime: maximum scan time
HidDevReader(int hidNo, int _frameLen);
HidDevReader(int const& hidNo, int const& _frameLen, int const& scanTimeUs);

// Destructor.
// Stops pipeline.
Expand Down Expand Up @@ -74,7 +73,7 @@ namespace kmicki::hiddev
{
public:
ReadData() = delete;
ReadData(std::string const& _inputFilePath, int const& _frameLen);//, SignalOut & _tick);
ReadData(std::string const& _inputFilePath, int const& _frameLen, int const& _scanTimeUs);
~ReadData();

void ReconnectInput();
Expand All @@ -91,17 +90,16 @@ namespace kmicki::hiddev
void FlushPipes() override;

private:
bool CheckData(std::unique_ptr<std::vector<char>> const& data);
std::ifstream inputStream;
std::string inputFilePath;
bool CheckData(std::unique_ptr<std::vector<char>> const& data, ssize_t readCnt);
HidDevFile inputFile;
std::vector<char> startMarker;
};

class ProcessData : public Thread
{
public:
ProcessData() = delete;
ProcessData(int const& _frameLen, ReadData & _data);
ProcessData(int const& _frameLen, ReadData & _data, int const& scanTimeUs);
~ProcessData();

PipeOut<frame_t> Frame;
Expand All @@ -115,6 +113,8 @@ namespace kmicki::hiddev
private:
ReadData & readData;
PipeOut<std::vector<char>> & data;

std::chrono::microseconds timeout;
};

class ServeFrame : public Thread
Expand Down Expand Up @@ -152,7 +152,7 @@ namespace kmicki::hiddev

std::vector<std::unique_ptr<Thread>> pipeline;
ServeFrame * serve;
ReadData* read;
ReadData* readData;

// Mutex
std::mutex startStopMutex;
Expand Down
68 changes: 68 additions & 0 deletions src/hiddev/hiddevfile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// for ppoll()
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "hiddev/hiddevfile.h"
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

namespace kmicki::hiddev
{
static const int cUsToTimeout = 1000;

HidDevFile::HidDevFile(std::string const& _filePath, int readTimeoutUs, bool const& open)
: filePath(_filePath), fileDescriptors{{-1,POLLIN,0}},
timeout{0,readTimeoutUs*cUsToTimeout}, file(fileDescriptors[0].fd)
{
if(open)
Open();
}

bool HidDevFile::Open()
{
file = open(filePath.c_str(),O_RDONLY);
return file >= 0;
}

bool HidDevFile::Close()
{
auto result = close(file);
file = -1;
return result == 0;
}

bool HidDevFile::IsOpen()
{
return file >= 0 && (fcntl(file, F_GETFD) != -1 || errno != EBADF);
}

int HidDevFile::Read(std::vector<char> & data)
{
if(file < 0)
return false;

auto retval = ppoll(fileDescriptors,1,&timeout,nullptr);

if(retval == 0)
return 0;

if(retval < 0)
return retval;

int readCnt = 0;

do {
auto readCntLoc = read(file, data.data()+readCnt,data.size()-readCnt);
if(readCntLoc < 0)
return readCntLoc;
if(readCntLoc == 0)
return (readCnt==0)?-1:readCnt;
readCnt += readCntLoc;
}
while(readCnt < data.size());

return readCnt;
}
}
14 changes: 7 additions & 7 deletions src/hiddev/hiddevreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace kmicki::hiddev
pipeline.emplace_back(operation);
}

HidDevReader::HidDevReader(int hidNo, int _frameLen)
HidDevReader::HidDevReader(int const& hidNo, int const& _frameLen, int const& scanTimeUs)
: frameLen(_frameLen), startStopMutex()
{
if(hidNo < 0) throw std::invalid_argument("hidNo");
Expand All @@ -62,16 +62,16 @@ namespace kmicki::hiddev
inputFilePathFormatter << "/dev/usb/hiddev" << hidNo;
inputFilePath = inputFilePathFormatter.str();

auto* readData = new ReadData(inputFilePath, _frameLen);
auto* processData = new ProcessData(_frameLen, *readData);
auto* readDataOp = new ReadData(inputFilePath, _frameLen, scanTimeUs);
auto* processData = new ProcessData(_frameLen, *readDataOp, scanTimeUs);
auto* serveFrame = new ServeFrame(processData->Frame);

AddOperation(readData);
AddOperation(readDataOp);
AddOperation(processData);
AddOperation(serveFrame);

serve = serveFrame;
read = readData;
readData = readDataOp;

Log("HidDevReader: Pipeline initialized. Waiting for start...",LogLevelDebug);
}
Expand All @@ -93,9 +93,9 @@ namespace kmicki::hiddev

void HidDevReader::SetStartMarker(std::vector<char> const& marker)
{
if(read == nullptr)
if(readData == nullptr)
return;
read->SetStartMarker(marker);
readData->SetStartMarker(marker);
}

void HidDevReader::Start()
Expand Down
11 changes: 4 additions & 7 deletions src/hiddev/hiddevreader/processdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ using namespace kmicki::log;

namespace kmicki::hiddev
{
// Definition - ProcessData
static const int cScanTimeToTimeout = 3;

HidDevReader::ProcessData::ProcessData(int const& _frameLen, ReadData & _data)
: readData(_data), data(_data.Data), ReadStuck(),
HidDevReader::ProcessData::ProcessData(int const& _frameLen, ReadData & _data, int const& scanTimeUs)
: readData(_data), data(_data.Data), ReadStuck(), timeout(cScanTimeToTimeout*scanTimeUs),
Frame(new frame_t(_frameLen),new frame_t(_frameLen),new frame_t(_frameLen))
{ }

Expand All @@ -23,9 +23,6 @@ namespace kmicki::hiddev
static const int cReportMissedTicksPeriod = 250;
int missedTicks = 0;
int nonMissedTicks = 0;

// ReadData sometimes hangs on reading from hiddev file forever. This has to be detected and thread needs to be reset forcefully.
static const std::chrono::milliseconds readTaskTimeout(6); // Timeout of ReadData - means that reading from hiddev file hangs

auto const& frame = Frame.GetPointerToFill();
auto const& hidData = data.GetPointer();
Expand All @@ -37,7 +34,7 @@ namespace kmicki::hiddev

while(ShouldContinue())
{
if(!data.WaitForData(readTaskTimeout))
if(!data.WaitForData(timeout))
{
Log("HidDevReader::ProcessData: Reading from hiddev file stuck. Force-restarting reading task.",LogLevelDebug);
ReadStuck.SendSignal();
Expand Down
38 changes: 24 additions & 14 deletions src/hiddev/hiddevreader/readdata.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#include "hiddev/hiddevreader.h"
#include "log/log.h"
#include <fcntl.h>
#include <sys/select.h>

using namespace kmicki::log;

namespace kmicki::hiddev
{
static const int cScanTimeToTimeout = 2;

// Definition - ReadData
HidDevReader::ReadData::ReadData(std::string const& _inputFilePath, int const& _frameLen)
: inputFilePath(_inputFilePath), inputStream(), startMarker(0),
HidDevReader::ReadData::ReadData(std::string const& _inputFilePath, int const& _frameLen, int const& _scanTimeUs)
: inputFile(_inputFilePath,cScanTimeToTimeout*_scanTimeUs,false), startMarker(0),
Data(new std::vector<char>(HidDevReader::cInputRecordLen*_frameLen),
new std::vector<char>(HidDevReader::cInputRecordLen*_frameLen),
new std::vector<char>(HidDevReader::cInputRecordLen*_frameLen)),
Expand All @@ -23,16 +27,15 @@ namespace kmicki::hiddev
{
DisconnectInput();
Log("HidDevReader::ReadData: Opening hiddev file.",LogLevelDebug);
inputStream.open(inputFilePath,std::ios_base::binary);
inputFile.Open();
}

void HidDevReader::ReadData::DisconnectInput()
{
if(inputStream.is_open())
if(inputFile.IsOpen())
{
Log("HidDevReader::ReadData: Closing hiddev file.",LogLevelDebug);
inputStream.close();
inputStream.clear();
inputFile.Close();
}
}

Expand All @@ -48,8 +51,11 @@ namespace kmicki::hiddev
int nonMissedTicks = 0;

ReconnectInput();
if(inputStream.fail())
if(!inputFile.IsOpen())
{
{ LogF() << "Error: " << errno; }
throw std::runtime_error("HidDevReader::ReadData: Problem opening hiddev file. Are priviliges granted?");
}
auto const& data = Data.GetPointerToFill();

Log("HidDevReader::ReadData: Started.",LogLevelDebug);
Expand All @@ -61,9 +67,15 @@ namespace kmicki::hiddev
if(!ShouldContinue())
break;

inputStream.read(data->data(),data->size());
auto readCnt = inputFile.Read(*data);

if(readCnt == 0)
{
Log("HidDevReader::ReadData: Waiting for data timed out.",LogLevelTrace);
continue;
}

if(!CheckData(data))
if(!CheckData(data,readCnt))
continue;

HandleMissedTicks("HidDevReader::ReadData","HID frames",Data.WasReceived(),missedTicks,cReportMissedTicksPeriod,nonMissedTicks);
Expand All @@ -76,16 +88,14 @@ namespace kmicki::hiddev
}

void HidDevReader::ReadData::FlushPipes()
{
//tick.SendSignal();
}
{ }

bool HidDevReader::ReadData::CheckData(std::unique_ptr<std::vector<char>> const& data)
bool HidDevReader::ReadData::CheckData(std::unique_ptr<std::vector<char>> const& data, ssize_t readCnt)
{
static const uint32_t cFirst4Bytes = 0xFFFF0002;
static const uint32_t cFirst4BytesAlternative = 0xFFFF0001;

bool inputFail = inputStream.fail();
bool inputFail = readCnt < data->size();
bool startMarkerFail = false;

if(!inputFail)
Expand Down
16 changes: 9 additions & 7 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ using namespace kmicki::cemuhook;
const LogLevel cLogLevel = LogLevelDebug; // change to Default when configuration is possible
const bool cRunPresenter = false;

const int cFrameLen = 64;
const uint16_t cVID = 0x28de;
const uint16_t cPID = 0x1205;
const std::string cVersion = "1.13-NEXT-DEV";
const int cFrameLen = 64; // Steam Deck Controls' custom HID report length in bytes
const int cScanTimeUs = 4000; // Steam Deck Controls' period between received report data in microseconds
const uint16_t cVID = 0x28de; // Steam Deck Controls' USB Vendor-ID
const uint16_t cPID = 0x1205; // Steam Deck Controls' USB Product-ID

const std::string cVersion = "1.13-dev"; // Release version

bool stop = false;
std::mutex stopMutex = std::mutex();
Expand Down Expand Up @@ -91,7 +93,7 @@ int main()
SetLogLevel(cLogLevel);

{ LogF() << "SteamDeckGyroDSU Version: " << cVersion; }
// Steam Deck controls: usb device VID: 28de, PID: 1205

int hidno = FindHidDevNo(cVID,cPID);
if(hidno < 0)
{
Expand All @@ -101,9 +103,9 @@ int main()

{ LogF() << "Found Steam Deck Controls' HID device at /dev/usb/hiddev" << hidno; }

HidDevReader reader(hidno,cFrameLen);
HidDevReader reader(hidno,cFrameLen,cScanTimeUs);

reader.SetStartMarker({ 0x01, 0x00, 0x09, 0x40 });
reader.SetStartMarker({ 0x01, 0x00, 0x09, 0x40 }); // Beginning of every Steam Decks' HID frame

CemuhookAdapter adapter(reader);
Server server(adapter);
Expand Down

0 comments on commit 9a651c5

Please sign in to comment.