cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

corsair-cpro.c (12834B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * corsair-cpro.c - Linux driver for Corsair Commander Pro
      4 * Copyright (C) 2020 Marius Zachmann <mail@mariuszachmann.de>
      5 *
      6 * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers
      7 * still being used. The device does not use report ids. When using hidraw and this driver
      8 * simultaniously, reports could be switched.
      9 */
     10
     11#include <linux/bitops.h>
     12#include <linux/completion.h>
     13#include <linux/hid.h>
     14#include <linux/hwmon.h>
     15#include <linux/kernel.h>
     16#include <linux/module.h>
     17#include <linux/mutex.h>
     18#include <linux/slab.h>
     19#include <linux/types.h>
     20
     21#define USB_VENDOR_ID_CORSAIR			0x1b1c
     22#define USB_PRODUCT_ID_CORSAIR_COMMANDERPRO	0x0c10
     23#define USB_PRODUCT_ID_CORSAIR_1000D		0x1d00
     24
     25#define OUT_BUFFER_SIZE		63
     26#define IN_BUFFER_SIZE		16
     27#define LABEL_LENGTH		11
     28#define REQ_TIMEOUT		300
     29
     30#define CTL_GET_TMP_CNCT	0x10	/*
     31					 * returns in bytes 1-4 for each temp sensor:
     32					 * 0 not connected
     33					 * 1 connected
     34					 */
     35#define CTL_GET_TMP		0x11	/*
     36					 * send: byte 1 is channel, rest zero
     37					 * rcv:  returns temp for channel in centi-degree celsius
     38					 * in bytes 1 and 2
     39					 * returns 0x11 in byte 0 if no sensor is connected
     40					 */
     41#define CTL_GET_VOLT		0x12	/*
     42					 * send: byte 1 is rail number: 0 = 12v, 1 = 5v, 2 = 3.3v
     43					 * rcv:  returns millivolt in bytes 1,2
     44					 * returns error 0x10 if request is invalid
     45					 */
     46#define CTL_GET_FAN_CNCT	0x20	/*
     47					 * returns in bytes 1-6 for each fan:
     48					 * 0 not connected
     49					 * 1 3pin
     50					 * 2 4pin
     51					 */
     52#define CTL_GET_FAN_RPM		0x21	/*
     53					 * send: byte 1 is channel, rest zero
     54					 * rcv:  returns rpm in bytes 1,2
     55					 */
     56#define CTL_GET_FAN_PWM		0x22	/*
     57					 * send: byte 1 is channel, rest zero
     58					 * rcv:  returns pwm in byte 1 if it was set
     59					 *	 returns error 0x12 if fan is controlled via
     60					 *	 fan_target or fan curve
     61					 */
     62#define CTL_SET_FAN_FPWM	0x23	/*
     63					 * set fixed pwm
     64					 * send: byte 1 is fan number
     65					 * send: byte 2 is percentage from 0 - 100
     66					 */
     67#define CTL_SET_FAN_TARGET	0x24	/*
     68					 * set target rpm
     69					 * send: byte 1 is fan number
     70					 * send: byte 2-3 is target
     71					 * device accepts all values from 0x00 - 0xFFFF
     72					 */
     73
     74#define NUM_FANS		6
     75#define NUM_TEMP_SENSORS	4
     76
     77struct ccp_device {
     78	struct hid_device *hdev;
     79	struct device *hwmon_dev;
     80	struct completion wait_input_report;
     81	struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */
     82	u8 *buffer;
     83	int target[6];
     84	DECLARE_BITMAP(temp_cnct, NUM_TEMP_SENSORS);
     85	DECLARE_BITMAP(fan_cnct, NUM_FANS);
     86	char fan_label[6][LABEL_LENGTH];
     87};
     88
     89/* converts response error in buffer to errno */
     90static int ccp_get_errno(struct ccp_device *ccp)
     91{
     92	switch (ccp->buffer[0]) {
     93	case 0x00: /* success */
     94		return 0;
     95	case 0x01: /* called invalid command */
     96		return -EOPNOTSUPP;
     97	case 0x10: /* called GET_VOLT / GET_TMP with invalid arguments */
     98		return -EINVAL;
     99	case 0x11: /* requested temps of disconnected sensors */
    100	case 0x12: /* requested pwm of not pwm controlled channels */
    101		return -ENODATA;
    102	default:
    103		hid_dbg(ccp->hdev, "unknown device response error: %d", ccp->buffer[0]);
    104		return -EIO;
    105	}
    106}
    107
    108/* send command, check for error in response, response in ccp->buffer */
    109static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2, u8 byte3)
    110{
    111	unsigned long t;
    112	int ret;
    113
    114	memset(ccp->buffer, 0x00, OUT_BUFFER_SIZE);
    115	ccp->buffer[0] = command;
    116	ccp->buffer[1] = byte1;
    117	ccp->buffer[2] = byte2;
    118	ccp->buffer[3] = byte3;
    119
    120	reinit_completion(&ccp->wait_input_report);
    121
    122	ret = hid_hw_output_report(ccp->hdev, ccp->buffer, OUT_BUFFER_SIZE);
    123	if (ret < 0)
    124		return ret;
    125
    126	t = wait_for_completion_timeout(&ccp->wait_input_report, msecs_to_jiffies(REQ_TIMEOUT));
    127	if (!t)
    128		return -ETIMEDOUT;
    129
    130	return ccp_get_errno(ccp);
    131}
    132
    133static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
    134{
    135	struct ccp_device *ccp = hid_get_drvdata(hdev);
    136
    137	/* only copy buffer when requested */
    138	if (completion_done(&ccp->wait_input_report))
    139		return 0;
    140
    141	memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
    142	complete(&ccp->wait_input_report);
    143
    144	return 0;
    145}
    146
    147/* requests and returns single data values depending on channel */
    148static int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data)
    149{
    150	int ret;
    151
    152	mutex_lock(&ccp->mutex);
    153
    154	ret = send_usb_cmd(ccp, command, channel, 0, 0);
    155	if (ret)
    156		goto out_unlock;
    157
    158	ret = ccp->buffer[1];
    159	if (two_byte_data)
    160		ret = (ret << 8) + ccp->buffer[2];
    161
    162out_unlock:
    163	mutex_unlock(&ccp->mutex);
    164	return ret;
    165}
    166
    167static int set_pwm(struct ccp_device *ccp, int channel, long val)
    168{
    169	int ret;
    170
    171	if (val < 0 || val > 255)
    172		return -EINVAL;
    173
    174	/* The Corsair Commander Pro uses values from 0-100 */
    175	val = DIV_ROUND_CLOSEST(val * 100, 255);
    176
    177	mutex_lock(&ccp->mutex);
    178
    179	ret = send_usb_cmd(ccp, CTL_SET_FAN_FPWM, channel, val, 0);
    180	if (!ret)
    181		ccp->target[channel] = -ENODATA;
    182
    183	mutex_unlock(&ccp->mutex);
    184	return ret;
    185}
    186
    187static int set_target(struct ccp_device *ccp, int channel, long val)
    188{
    189	int ret;
    190
    191	val = clamp_val(val, 0, 0xFFFF);
    192	ccp->target[channel] = val;
    193
    194	mutex_lock(&ccp->mutex);
    195	ret = send_usb_cmd(ccp, CTL_SET_FAN_TARGET, channel, val >> 8, val);
    196
    197	mutex_unlock(&ccp->mutex);
    198	return ret;
    199}
    200
    201static int ccp_read_string(struct device *dev, enum hwmon_sensor_types type,
    202			   u32 attr, int channel, const char **str)
    203{
    204	struct ccp_device *ccp = dev_get_drvdata(dev);
    205
    206	switch (type) {
    207	case hwmon_fan:
    208		switch (attr) {
    209		case hwmon_fan_label:
    210			*str = ccp->fan_label[channel];
    211			return 0;
    212		default:
    213			break;
    214		}
    215		break;
    216	default:
    217		break;
    218	}
    219
    220	return -EOPNOTSUPP;
    221}
    222
    223static int ccp_read(struct device *dev, enum hwmon_sensor_types type,
    224		    u32 attr, int channel, long *val)
    225{
    226	struct ccp_device *ccp = dev_get_drvdata(dev);
    227	int ret;
    228
    229	switch (type) {
    230	case hwmon_temp:
    231		switch (attr) {
    232		case hwmon_temp_input:
    233			ret = get_data(ccp, CTL_GET_TMP, channel, true);
    234			if (ret < 0)
    235				return ret;
    236			*val = ret * 10;
    237			return 0;
    238		default:
    239			break;
    240		}
    241		break;
    242	case hwmon_fan:
    243		switch (attr) {
    244		case hwmon_fan_input:
    245			ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true);
    246			if (ret < 0)
    247				return ret;
    248			*val = ret;
    249			return 0;
    250		case hwmon_fan_target:
    251			/* how to read target values from the device is unknown */
    252			/* driver returns last set value or 0			*/
    253			if (ccp->target[channel] < 0)
    254				return -ENODATA;
    255			*val = ccp->target[channel];
    256			return 0;
    257		default:
    258			break;
    259		}
    260		break;
    261	case hwmon_pwm:
    262		switch (attr) {
    263		case hwmon_pwm_input:
    264			ret = get_data(ccp, CTL_GET_FAN_PWM, channel, false);
    265			if (ret < 0)
    266				return ret;
    267			*val = DIV_ROUND_CLOSEST(ret * 255, 100);
    268			return 0;
    269		default:
    270			break;
    271		}
    272		break;
    273	case hwmon_in:
    274		switch (attr) {
    275		case hwmon_in_input:
    276			ret = get_data(ccp, CTL_GET_VOLT, channel, true);
    277			if (ret < 0)
    278				return ret;
    279			*val = ret;
    280			return 0;
    281		default:
    282			break;
    283		}
    284		break;
    285	default:
    286		break;
    287	}
    288
    289	return -EOPNOTSUPP;
    290};
    291
    292static int ccp_write(struct device *dev, enum hwmon_sensor_types type,
    293		     u32 attr, int channel, long val)
    294{
    295	struct ccp_device *ccp = dev_get_drvdata(dev);
    296
    297	switch (type) {
    298	case hwmon_pwm:
    299		switch (attr) {
    300		case hwmon_pwm_input:
    301			return set_pwm(ccp, channel, val);
    302		default:
    303			break;
    304		}
    305		break;
    306	case hwmon_fan:
    307		switch (attr) {
    308		case hwmon_fan_target:
    309			return set_target(ccp, channel, val);
    310		default:
    311			break;
    312		}
    313		break;
    314	default:
    315		break;
    316	}
    317
    318	return -EOPNOTSUPP;
    319};
    320
    321static umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type,
    322			      u32 attr, int channel)
    323{
    324	const struct ccp_device *ccp = data;
    325
    326	switch (type) {
    327	case hwmon_temp:
    328		if (!test_bit(channel, ccp->temp_cnct))
    329			break;
    330
    331		switch (attr) {
    332		case hwmon_temp_input:
    333			return 0444;
    334		case hwmon_temp_label:
    335			return 0444;
    336		default:
    337			break;
    338		}
    339		break;
    340	case hwmon_fan:
    341		if (!test_bit(channel, ccp->fan_cnct))
    342			break;
    343
    344		switch (attr) {
    345		case hwmon_fan_input:
    346			return 0444;
    347		case hwmon_fan_label:
    348			return 0444;
    349		case hwmon_fan_target:
    350			return 0644;
    351		default:
    352			break;
    353		}
    354		break;
    355	case hwmon_pwm:
    356		if (!test_bit(channel, ccp->fan_cnct))
    357			break;
    358
    359		switch (attr) {
    360		case hwmon_pwm_input:
    361			return 0644;
    362		default:
    363			break;
    364		}
    365		break;
    366	case hwmon_in:
    367		switch (attr) {
    368		case hwmon_in_input:
    369			return 0444;
    370		default:
    371			break;
    372		}
    373		break;
    374	default:
    375		break;
    376	}
    377
    378	return 0;
    379};
    380
    381static const struct hwmon_ops ccp_hwmon_ops = {
    382	.is_visible = ccp_is_visible,
    383	.read = ccp_read,
    384	.read_string = ccp_read_string,
    385	.write = ccp_write,
    386};
    387
    388static const struct hwmon_channel_info *ccp_info[] = {
    389	HWMON_CHANNEL_INFO(chip,
    390			   HWMON_C_REGISTER_TZ),
    391	HWMON_CHANNEL_INFO(temp,
    392			   HWMON_T_INPUT,
    393			   HWMON_T_INPUT,
    394			   HWMON_T_INPUT,
    395			   HWMON_T_INPUT
    396			   ),
    397	HWMON_CHANNEL_INFO(fan,
    398			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
    399			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
    400			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
    401			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
    402			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
    403			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET
    404			   ),
    405	HWMON_CHANNEL_INFO(pwm,
    406			   HWMON_PWM_INPUT,
    407			   HWMON_PWM_INPUT,
    408			   HWMON_PWM_INPUT,
    409			   HWMON_PWM_INPUT,
    410			   HWMON_PWM_INPUT,
    411			   HWMON_PWM_INPUT
    412			   ),
    413	HWMON_CHANNEL_INFO(in,
    414			   HWMON_I_INPUT,
    415			   HWMON_I_INPUT,
    416			   HWMON_I_INPUT
    417			   ),
    418	NULL
    419};
    420
    421static const struct hwmon_chip_info ccp_chip_info = {
    422	.ops = &ccp_hwmon_ops,
    423	.info = ccp_info,
    424};
    425
    426/* read fan connection status and set labels */
    427static int get_fan_cnct(struct ccp_device *ccp)
    428{
    429	int channel;
    430	int mode;
    431	int ret;
    432
    433	ret = send_usb_cmd(ccp, CTL_GET_FAN_CNCT, 0, 0, 0);
    434	if (ret)
    435		return ret;
    436
    437	for (channel = 0; channel < NUM_FANS; channel++) {
    438		mode = ccp->buffer[channel + 1];
    439		if (mode == 0)
    440			continue;
    441
    442		set_bit(channel, ccp->fan_cnct);
    443		ccp->target[channel] = -ENODATA;
    444
    445		switch (mode) {
    446		case 1:
    447			scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
    448				  "fan%d 3pin", channel + 1);
    449			break;
    450		case 2:
    451			scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
    452				  "fan%d 4pin", channel + 1);
    453			break;
    454		default:
    455			scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
    456				  "fan%d other", channel + 1);
    457			break;
    458		}
    459	}
    460
    461	return 0;
    462}
    463
    464/* read temp sensor connection status */
    465static int get_temp_cnct(struct ccp_device *ccp)
    466{
    467	int channel;
    468	int mode;
    469	int ret;
    470
    471	ret = send_usb_cmd(ccp, CTL_GET_TMP_CNCT, 0, 0, 0);
    472	if (ret)
    473		return ret;
    474
    475	for (channel = 0; channel < NUM_TEMP_SENSORS; channel++) {
    476		mode = ccp->buffer[channel + 1];
    477		if (mode == 0)
    478			continue;
    479
    480		set_bit(channel, ccp->temp_cnct);
    481	}
    482
    483	return 0;
    484}
    485
    486static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
    487{
    488	struct ccp_device *ccp;
    489	int ret;
    490
    491	ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL);
    492	if (!ccp)
    493		return -ENOMEM;
    494
    495	ccp->buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);
    496	if (!ccp->buffer)
    497		return -ENOMEM;
    498
    499	ret = hid_parse(hdev);
    500	if (ret)
    501		return ret;
    502
    503	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
    504	if (ret)
    505		return ret;
    506
    507	ret = hid_hw_open(hdev);
    508	if (ret)
    509		goto out_hw_stop;
    510
    511	ccp->hdev = hdev;
    512	hid_set_drvdata(hdev, ccp);
    513	mutex_init(&ccp->mutex);
    514	init_completion(&ccp->wait_input_report);
    515
    516	hid_device_io_start(hdev);
    517
    518	/* temp and fan connection status only updates when device is powered on */
    519	ret = get_temp_cnct(ccp);
    520	if (ret)
    521		goto out_hw_close;
    522
    523	ret = get_fan_cnct(ccp);
    524	if (ret)
    525		goto out_hw_close;
    526	ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
    527							 ccp, &ccp_chip_info, 0);
    528	if (IS_ERR(ccp->hwmon_dev)) {
    529		ret = PTR_ERR(ccp->hwmon_dev);
    530		goto out_hw_close;
    531	}
    532
    533	return 0;
    534
    535out_hw_close:
    536	hid_hw_close(hdev);
    537out_hw_stop:
    538	hid_hw_stop(hdev);
    539	return ret;
    540}
    541
    542static void ccp_remove(struct hid_device *hdev)
    543{
    544	struct ccp_device *ccp = hid_get_drvdata(hdev);
    545
    546	hwmon_device_unregister(ccp->hwmon_dev);
    547	hid_hw_close(hdev);
    548	hid_hw_stop(hdev);
    549}
    550
    551static const struct hid_device_id ccp_devices[] = {
    552	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_COMMANDERPRO) },
    553	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_1000D) },
    554	{ }
    555};
    556
    557static struct hid_driver ccp_driver = {
    558	.name = "corsair-cpro",
    559	.id_table = ccp_devices,
    560	.probe = ccp_probe,
    561	.remove = ccp_remove,
    562	.raw_event = ccp_raw_event,
    563};
    564
    565MODULE_DEVICE_TABLE(hid, ccp_devices);
    566MODULE_LICENSE("GPL");
    567
    568static int __init ccp_init(void)
    569{
    570	return hid_register_driver(&ccp_driver);
    571}
    572
    573static void __exit ccp_exit(void)
    574{
    575	hid_unregister_driver(&ccp_driver);
    576}
    577
    578/*
    579 * When compiling this driver as built-in, hwmon initcalls will get called before the
    580 * hid driver and this driver would fail to register. late_initcall solves this.
    581 */
    582late_initcall(ccp_init);
    583module_exit(ccp_exit);