/* * Corsair protocol for PSUs * * message size = 64 bytes (request and response, little endian) * request: * [length][command][param0][param1][paramX]... * reply: * [echo of length][echo of command][data0][data1][dataX]... * * - commands are byte sized opcodes * - length is the sum of all bytes of the commands/params * - the micro-controller of most of these PSUs support concatenation in the request and reply, * but it is better to not rely on this (it is also hard to parse) * - the driver uses raw events to be accessible from userspace (though this is not really * supported, it is just there for convenience, may be removed in the future) * - a reply always starts with the length and command in the same order the request used it * - length of the reply data is specific to the command used * - some of the commands work on a rail and can be switched to a specific rail (0 = 12v, * 1 = 5v, 2 = 3.3v) * - the format of the init command 0xFE is swapped length/command bytes * - parameter bytes amount and values are specific to the command (rail setting is the only * one for now that uses non-zero values) * - the driver supports debugfs for values not fitting into the hwmon class * - not every device class (HXi or RMi) supports all commands * - if configured wrong the PSU resets or shuts down, often before actually hitting the * reported critical temperature * - new models like HX1500i Series 2023 have changes in the reported vendor and product * strings, both are slightly longer now, report vendor and product in one string and are * the same now
*/
#define DRIVER_NAME "corsair-psu"
#define REPLY_SIZE 24 /* max length of a reply to a single command */ #define CMD_BUFFER_SIZE 64 #define CMD_TIMEOUT_MS 250 #define SECONDS_PER_HOUR (60 * 60) #define SECONDS_PER_DAY (SECONDS_PER_HOUR * 24) #define RAIL_COUNT 3 /* 3v3 + 5v + 12v */ #define TEMP_COUNT 2 #define OCP_MULTI_RAIL 0x02
ret = hid_hw_output_report(priv->hdev, priv->cmd_buffer, CMD_BUFFER_SIZE); if (ret < 0) return ret;
time = wait_for_completion_timeout(&priv->wait_completion,
msecs_to_jiffies(CMD_TIMEOUT_MS)); if (!time) return -ETIMEDOUT;
/* * at the start of the reply is an echo of the send command/length in the same order it * was send, not every command is supported on every device class, if a command is not * supported, the length value in the reply is okay, but the command value is set to 0
*/ if (p0 != priv->cmd_buffer[0] || p1 != priv->cmd_buffer[1]) return -EOPNOTSUPP;
if (data)
memcpy(data, priv->cmd_buffer + 2, REPLY_SIZE);
return 0;
}
staticint corsairpsu_init(struct corsairpsu_data *priv)
{ /* * PSU_CMD_INIT uses swapped length/command and expects 2 parameter bytes, this command * actually generates a reply, but we don't need it
*/ return corsairpsu_usb_cmd(priv, PSU_CMD_INIT, 3, 0, NULL);
}
staticint corsairpsu_fwinfo(struct corsairpsu_data *priv)
{ int ret;
ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_VEND_STR, 0, priv->vendor); if (ret < 0) return ret;
ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_PROD_STR, 0, priv->product); if (ret < 0) return ret;
mutex_lock(&priv->lock); switch (cmd) { case PSU_CMD_RAIL_VOLTS_HCRIT: case PSU_CMD_RAIL_VOLTS_LCRIT: case PSU_CMD_RAIL_AMPS_HCRIT: case PSU_CMD_RAIL_VOLTS: case PSU_CMD_RAIL_AMPS: case PSU_CMD_RAIL_WATTS:
ret = corsairpsu_usb_cmd(priv, 2, PSU_CMD_SELECT_RAIL, rail, NULL); if (ret < 0) goto cmd_fail; break; default: break;
}
staticint corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, long *val)
{
u8 data[REPLY_SIZE]; long tmp; int ret;
ret = corsairpsu_request(priv, cmd, rail, data); if (ret < 0) return ret;
/* * the biggest value here comes from the uptime command and to exceed MAXINT total uptime * needs to be about 68 years, the rest are u16 values and the biggest value coming out of * the LINEAR11 conversion are the watts values which are about 1500 for the strongest psu * supported (HX1500i)
*/
tmp = ((long)data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; switch (cmd) { case PSU_CMD_RAIL_VOLTS_HCRIT: case PSU_CMD_RAIL_VOLTS_LCRIT: case PSU_CMD_RAIL_AMPS_HCRIT: case PSU_CMD_TEMP_HCRIT: case PSU_CMD_IN_VOLTS: case PSU_CMD_IN_AMPS: case PSU_CMD_RAIL_VOLTS: case PSU_CMD_RAIL_AMPS: case PSU_CMD_TEMP0: case PSU_CMD_TEMP1:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000); break; case PSU_CMD_FAN:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1); break; case PSU_CMD_FAN_PWM_ENABLE:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1); /* * 0 = automatic mode, means the micro-controller controls the fan using a plan * which can be modified, but changing this plan is not supported by this * driver, the matching PWM mode is automatic fan speed control = PWM 2 * 1 = fixed mode, fan runs at a fixed speed represented by a percentage * value 0-100, this matches the PWM manual fan speed control = PWM 1 * technically there is no PWM no fan speed control mode, it would be a combination * of 1 at 100%
*/ if (*val == 0)
*val = 2; break; case PSU_CMD_FAN_PWM:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1);
*val = corsairpsu_dutycycle_to_pwm(*val); break; case PSU_CMD_RAIL_WATTS: case PSU_CMD_TOTAL_WATTS:
*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000000); break; case PSU_CMD_TOTAL_UPTIME: case PSU_CMD_UPTIME: case PSU_CMD_OCPMODE:
*val = tmp; break; default:
ret = -EOPNOTSUPP; break;
}
return ret;
}
staticvoid corsairpsu_get_criticals(struct corsairpsu_data *priv)
{ long tmp; int rail;
staticint ocpmode_show(struct seq_file *seqf, void *unused)
{ struct corsairpsu_data *priv = seqf->private; long val; int ret;
/* * The rail mode is switchable on the fly. The RAW interface can be used for this. But it * will not be included here, because I consider it somewhat dangerous for the health of the * PSU. The returned value can be a bogus one, if the PSU is in the process of switching and * getting of the value itself can also fail during this. Because of this every other value * than OCP_MULTI_RAIL can be considered as "single rail".
*/
ret = corsairpsu_get_value(priv, PSU_CMD_OCPMODE, 0, &val); if (ret < 0)
seq_puts(seqf, "N/A\n"); else
seq_printf(seqf, "%s\n", (val == OCP_MULTI_RAIL) ? "multi rail" : "single rail");
/* * With module_init() the driver would load before the HID bus when * built-in, so use late_initcall() instead.
*/
late_initcall(corsair_init);
module_exit(corsair_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wilken Gottwalt ");
MODULE_DESCRIPTION("Linux driver for Corsair power supplies with HID sensors interface");
Messung V0.5
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.