Implement fair_mutex in i2c_smbus so that i2c controllers are given access to the bus in a more fair manner, which should help with high frame rates on slow buses

This commit is contained in:
Adam Honse committed 2026-09-02 19:12:26 -05:00
1 parent 999cbd2578
commit 3e290907d5
3 files changed
+120 -1

No files matched your search

+1
View File
@@ -166,6 +166,7 @@ HEADERS +=
Colors.h \
dependencies/ColorWheel/ColorWheel.h \
dependencies/json/nlohmann/json.hpp \
fair_mutex.h \
JsonUtils.h \
LogManager.h \
NetworkClient.h \
+116
View File
@@ -0,0 +1,116 @@
/*---------------------------------------------------------*\
| fair_mutex.h |
| |
| Fair (FIFO) mutex for serialising I2C/SMBus transfers |
| Services callers in the order they lock the mutex |
| |
| Adam Honse (CalcProgrammer1) 02 Sep 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <mutex>
#include <condition_variable>
/*---------------------------------------------------------*\
| fair_mutex |
| |
| A fair (FIFO) mutex that services lock requests in the |
| order they are received, preventing any single caller |
| from monopolising the mutex at the expense of callers |
| that have been waiting longer. |
| |
| This is implemented as a ticket lock: every caller draws |
| a sequentially-increasing ticket number and then waits |
| until that ticket is at the head of the queue. An |
| internal std::mutex guards the ticket counters; a |
| std::condition_variable wakes exactly one waiter when its |
| ticket reaches the front of the queue. |
| |
| The class satisfies the BasicLockable requirements |
| (lock / unlock) so it can be used with std::lock_guard |
| and std::unique_lock, or with direct lock() / unlock() |
| calls. |
\*---------------------------------------------------------*/
class fair_mutex
{
public:
fair_mutex() = default;
~fair_mutex() = default;
fair_mutex(const fair_mutex&) = delete;
fair_mutex& operator=(const fair_mutex&) = delete;
/*-----------------------------------------------------*\
| Acquire the mutex. |
| |
| Draws the next ticket and blocks until that ticket |
| becomes the one being served, guaranteeing FIFO |
| ordering among all waiting threads. |
\*-----------------------------------------------------*/
void lock()
{
std::unique_lock<std::mutex> lock(internal_mutex);
const unsigned int ticket = next_ticket++;
while(ticket != serving)
{
cv.wait(lock);
}
}
/*-----------------------------------------------------*\
| Release the mutex. |
| |
| Increments the "serving" counter so that the next |
| ticket in the queue becomes eligible to proceed, then |
| wakes exactly one waiting thread. |
\*-----------------------------------------------------*/
void unlock()
{
std::lock_guard<std::mutex> lock(internal_mutex);
serving++;
cv.notify_one();
}
private:
/*-----------------------------------------------------*\
| Guards the ticket counters. This is only held for |
| very brief critical sections (incrementing the |
| counters) so it does not interfere with the fairness |
| of the outer mutex. |
\*-----------------------------------------------------*/
std::mutex internal_mutex;
/*-----------------------------------------------------*\
| Sleeps waiters until their ticket reaches the front |
| of the queue. notify_one() is used because only the |
| single thread at the head of the queue should be |
| woken. |
\*-----------------------------------------------------*/
std::condition_variable cv;
/*-----------------------------------------------------*\
| Sequentially-assigned ticket number. Incremented |
| atomically (under internal_mutex) each time a thread |
| calls lock(). |
| |
| Wrapped unsigned arithmetic is safe here: the FIFO |
| property is preserved even on wrap-around, and the |
| probability of approaching 2^32 acquisitions is |
| negligible for this use case. |
\*-----------------------------------------------------*/
unsigned int next_ticket = 0;
/*-----------------------------------------------------*\
| The ticket number currently being served. A waiter |
| whose ticket equals this value is permitted to |
| proceed. |
\*-----------------------------------------------------*/
unsigned int serving = 0;
};
+3 -1
View File
@@ -18,6 +18,8 @@
#include <condition_variable>
#include <mutex>
#include "fair_mutex.h"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
@@ -118,7 +120,7 @@ public:
virtual s32 i2c_xfer(u8 addr, char read_write, int* size, u8* data) = 0;
private:
std::mutex i2c_smbus_xfer_mutex;
fair_mutex i2c_smbus_xfer_mutex;
};