desktop-tools/ddc-ci.c
Přemysl Eric Janouch 9e91058ed9
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
Add elksmart-comm for transceiving infrared codes
The receive functionality is quite unstable,
however useful enough for something that is officially unsupported.

The gadget is picky about cables,
but it has ridiculous reach when it works.
2024-08-30 02:55:35 +02:00

243 lines
6.5 KiB
C

/*
* ddc-ci.c: DDC-CI utilities, Linux-only
*
* Copyright (c) 2015 - 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// Sources: ddcciv1r1.pdf, i2c-dev.c in Linux, ddccontrol source code,
// http://www.boichat.ch/nicolas/ddcci/specs.html was also helpful
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#ifndef I2C_FUNC_I2C
// Fuck you, openSUSE, for fucking up the previous file, see e.g.
// https://github.com/solettaproject/soletta/commit/427f47f
#include <linux/i2c.h>
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
log_message_custom (void *user_data, const char *quote, const char *fmt,
va_list ap)
{
(void) user_data;
FILE *stream = stdout;
fprintf (stream, PROGRAM_NAME ": ");
fputs (quote, stream);
vfprintf (stream, fmt, ap);
fputs ("\n", stream);
}
static void
wait_ms (long ms)
{
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep (&ts, NULL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#define DDC_LENGTH_XOR 0x80
enum
{
DDC_ADDRESS_HOST = 0x50, ///< Bus master's base address
DDC_ADDRESS_DISPLAY = 0x6E ///< The display's base address
};
enum { I2C_WRITE, I2C_READ };
enum
{
DDC_GET_VCP_FEATURE = 0x01, ///< Request info about a feature
DDC_GET_VCP_FEATURE_REPLY = 0x02, ///< Feature info response
DDC_SET_VCP_FEATURE = 0x03 ///< Set or activate a feature
};
enum
{
VCP_BRIGHTNESS = 0x10, ///< Standard VCP opcode for brightness
VCP_CONTRAST = 0x12, ///< Standard VCP opcode for contrast
VCP_INPUT_SOURCE = 0x60 ///< Standard VCP opcode for input
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
check_edid (int fd, struct error **e)
{
uint8_t edid_req[] = { 0x00 };
uint8_t buf[8] = "";
struct i2c_msg bufs[] =
{
{ .addr = 0x50, .flags = 0,
.len = 1, .buf = edid_req },
{ .addr = 0x50, .flags = I2C_M_RD,
.len = sizeof buf, .buf = buf },
};
struct i2c_rdwr_ioctl_data data;
data.msgs = bufs;
data.nmsgs = 2;
if (ioctl (fd, I2C_RDWR, &data) < 0)
return error_set (e, "%s: %s", "ioctl", strerror (errno));
if (memcmp ("\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", buf, sizeof buf))
return error_set (e, "invalid EDID");
return true;
}
static bool
is_a_display (int fd, struct error **e)
{
struct stat st;
if (fstat (fd, &st) < 0)
return error_set (e, "%s: %s", "fstat", strerror (errno));
unsigned long funcs;
if (!(st.st_mode & S_IFCHR)
|| ioctl (fd, I2C_FUNCS, &funcs) < 0
|| !(funcs & I2C_FUNC_I2C))
return error_set (e, "not an I2C device");
return check_edid (fd, e);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static bool
ddc_send (int fd, unsigned command, void *args, size_t args_len,
struct error **e)
{
struct str buf = str_make ();
str_pack_u8 (&buf, DDC_ADDRESS_HOST | I2C_READ);
str_pack_u8 (&buf, DDC_LENGTH_XOR | (args_len + 1));
str_pack_u8 (&buf, command);
str_append_data (&buf, args, args_len);
unsigned xor = DDC_ADDRESS_DISPLAY;
for (size_t i = 0; i < buf.len; i++)
xor ^= buf.str[i];
str_pack_u8 (&buf, xor);
struct i2c_msg msg =
{
// The driver unshifts it back
.addr = DDC_ADDRESS_DISPLAY >> 1, .flags = 0,
.len = buf.len, .buf = (uint8_t *) buf.str,
};
struct i2c_rdwr_ioctl_data data;
data.msgs = &msg;
data.nmsgs = 1;
bool failed = ioctl (fd, I2C_RDWR, &data) < 0;
str_free (&buf);
if (failed)
return error_set (e, "%s: %s", "ioctl", strerror (errno));
return true;
}
static bool
ddc_read (int fd, unsigned *command, void *out_buf, size_t *n_read,
struct error **e)
{
uint8_t buf[128] = "";
struct i2c_msg msg =
{
// The driver unshifts it back
.addr = DDC_ADDRESS_DISPLAY >> 1, .flags = I2C_M_RD,
.len = sizeof buf, .buf = buf,
};
struct i2c_rdwr_ioctl_data data;
data.msgs = &msg;
data.nmsgs = 1;
if (ioctl (fd, I2C_RDWR, &data) < 0)
return error_set (e, "%s: %s", "ioctl", strerror (errno));
struct msg_unpacker unpacker = msg_unpacker_make (buf, sizeof buf);
uint8_t sender, length, cmd;
(void) msg_unpacker_u8 (&unpacker, &sender);
(void) msg_unpacker_u8 (&unpacker, &length);
(void) msg_unpacker_u8 (&unpacker, &cmd);
if (sender != (DDC_ADDRESS_DISPLAY | I2C_WRITE) || !(length & 0x80))
return error_set (e, "invalid response");
if (!(length ^= 0x80))
return error_set (e, "NULL response");
// TODO: also check the checksum
*command = cmd;
memcpy (out_buf, unpacker.data + unpacker.offset, (*n_read = length - 1));
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct vcp_feature_readout
{
uint8_t type; ///< Type of the value
int16_t max; ///< Maximum value
int16_t cur; ///< Current value
};
static bool
vcp_get_feature (int fd, uint8_t feature, struct vcp_feature_readout *out,
struct error **e)
{
uint8_t get_req[] = { feature };
if (!ddc_send (fd, DDC_GET_VCP_FEATURE, get_req, sizeof get_req, e))
return false;
wait_ms (40);
unsigned command = 0;
uint8_t buf[128] = "";
size_t len = 0;
if (!ddc_read (fd, &command, buf, &len, e))
return false;
if (command != DDC_GET_VCP_FEATURE_REPLY || len != 7)
return error_set (e, "invalid response");
struct msg_unpacker unpacker = msg_unpacker_make (buf, len);
uint8_t result; msg_unpacker_u8 (&unpacker, &result);
uint8_t vcp_opcode; msg_unpacker_u8 (&unpacker, &vcp_opcode);
msg_unpacker_u8 (&unpacker, &out->type);
msg_unpacker_i16 (&unpacker, &out->max);
msg_unpacker_i16 (&unpacker, &out->cur);
if (result == 0x01)
return error_set (e, "error reported by monitor");
if (result != 0x00
|| vcp_opcode != feature)
return error_set (e, "invalid response");
// These are unsigned but usually just one byte long
if (out->max < 0 || out->cur < 0)
return error_set (e, "capability range overflow");
return true;
}