find device when no args are given, accept RM850i
[corsairmi.git] / corsairmi.c
1 /* 
2  * minimal program to read out data from Corsair RMi series of PSUs
3  * tested on RM750i
4  *
5  * Copyright (c) notaz, 2016
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above copyright
12  *       notice, this list of conditions and the following disclaimer in the
13  *       documentation and/or other materials provided with the distribution.
14  *     * Neither the name of the organization nor the
15  *       names of its contributors may be used to endorse or promote products
16  *       derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 /*
30  * register list from SIV by Ray Hinchliffe
31  *
32  * left unimplemented:
33  * 3a fan mode
34  * 3b fan pwm
35  * 81 fan status
36  * f0 fan1 mode
37  *
38  * left unknown:
39  * 40: e6 d3 00 ... (15.6; const?)
40  * 44: 1a d2 00 ... ( 8.4; const?)
41  * 46: 2c f1 00 ... (75.0; const?)
42  * 4f: 46 00 ...
43  * 7a: 00 ...
44  * 7b: 00 ...
45  * 7d: 00 ...
46  * 7e: c0 00 ...
47  * c4: 01 00 ...
48  * d4: b9 bd eb fe 00 ... (32bit const?)
49  * d8: 02 00 ...
50  * d9: 00 ...
51  */
52
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <stdarg.h>
57 #include <stdint.h>
58 #include <math.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <fcntl.h>
62 #include <sys/ioctl.h>
63 #include <unistd.h>
64 #include <linux/hidraw.h>
65
66 static const uint16_t products[] = {
67         0x1c0b, /* RM750i */
68         0x1c0c, /* RM850i */
69 };
70
71 static void dump(const uint8_t *buf, size_t size)
72 {
73         size_t i, j;
74
75         for (i = 0; i < size; i += 16) {
76                 for (j = 0; j < 16; j++) {
77                         if (i + j < size)
78                                 printf(" %02x", buf[i + j]);
79                         else
80                                 fputs("   ", stdout);
81                 }
82                 fputs("  ", stdout);
83                 for (j = 0; j < 16; j++) {
84                         if (i + j < size) {
85                                 uint8_t c = buf[i + j];
86                                 printf("%c", 0x20 <= c && c <= 0x7f ? c : '.');
87                         }
88                         else
89                                 break;
90                 }
91                 puts("");
92         }
93 }
94
95 static void send_recv_cmd(int fd, uint8_t b0, uint8_t b1, uint8_t b2,
96         void *buf, size_t buf_size)
97 {
98         uint8_t buf_w[65], buf_r[64];
99         ssize_t ret;
100
101         memset(buf_w, 0, sizeof(buf_w));
102         buf_w[1] = b0;
103         buf_w[2] = b1;
104         buf_w[3] = b2;
105         ret = write(fd, buf_w, sizeof(buf_w));
106         if (ret != sizeof(buf_w)) {
107                 fprintf(stderr, "write %zd/%zd: ", ret, sizeof(buf_w));
108                 perror(NULL);
109                 exit(1);
110         }
111
112         ret = read(fd, buf_r, sizeof(buf_r));
113         if (ret != sizeof(buf_r)) {
114                 fprintf(stderr, "read %zd/%zd: ", ret, sizeof(buf_r));
115                 perror(NULL);
116                 if (ret > 0)
117                         dump(buf_r, ret);
118                 exit(1);
119         }
120
121         if (buf_r[0] != b0 || buf_r[1] != b1) {
122                 fprintf(stderr, "unexpected response %02x %02x "
123                                 "to cmd %02x %02x %02x\n",
124                                 buf_r[0], buf_r[1], b0, b1, b2);
125                 dump(buf_r, sizeof(buf_r));
126                 exit(1);
127         }
128
129         if (buf != NULL && buf_size > 0) {
130                 if (buf_size > sizeof(buf_r) - 2)
131                         buf_size = sizeof(buf_r) - 2;
132                 memcpy(buf, buf_r + 2, buf_size);
133         }
134 }
135
136 static void read_reg(int fd, uint8_t reg, void *buf, size_t buf_size)
137 {
138         send_recv_cmd(fd, 0x03, reg, 0x00, buf, buf_size);
139 }
140
141 static void read_reg16(int fd, uint8_t reg, uint16_t *v)
142 {
143         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
144         // FIXME: big endian host
145 }
146
147 static void read_reg32(int fd, uint8_t reg, uint32_t *v)
148 {
149         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
150         // FIXME: big endian host
151 }
152
153 static double mkv(uint16_t v16)
154 {
155         int p = (int)(int16_t)v16 >> 11;
156         int v = ((int)v16 << 21) >> 21;
157
158         return (double)v * pow(2.0, p);
159 }
160
161 static void print_std_reg(int fd, uint8_t reg, const char *fmt, ...)
162 {
163         size_t len = 0;
164         uint16_t val;
165         va_list ap;
166
167         read_reg16(fd, reg, &val);
168
169         va_start(ap, fmt);
170         len += vprintf(fmt, ap);
171         len += printf(": ");
172         va_end(ap);
173         for (; len < 16; len++)
174                 fputs(" ", stdout);
175
176         printf("%5.1f\n", mkv(val));
177 }
178
179 static int try_open_device(const char *name, int report_errors)
180 {
181         struct hidraw_devinfo info;
182         int found = 0;
183         int i, ret, fd;
184
185         fd = open(name, O_RDWR);
186         if (fd == -1) {
187                 if (report_errors) {
188                         fprintf(stderr, "open %s: ", name);
189                         perror(NULL);
190                 }
191                 return -1;
192         }
193
194         memset(&info, 0, sizeof(info));
195         ret = ioctl(fd, HIDIOCGRAWINFO, &info);
196         if (ret != 0) {
197                 perror("HIDIOCGRAWINFO");
198                 goto out;
199         }
200
201         if (info.vendor != 0x1b1c)
202                 goto out;
203
204         for (i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
205                 if (info.product == products[i]) {
206                         found = 1;
207                         break;
208                 }
209         }
210
211 out:
212         if (!found) {
213                 if (report_errors)
214                         fprintf(stderr, "unexpected device: %04hx:%04hx\n",
215                                 info.vendor, info.product);
216                 close(fd);
217                 fd = -1;
218         }
219         return fd;
220 }
221
222 int main(int argc, char *argv[])
223 {
224         char name[63];
225         uint32_t v32;
226         uint8_t osel;
227         int i, fd;
228
229         if (argc > 1) {
230                 if (argv[1][0] == '-' || argc != 2) {
231                         fprintf(stderr, "usage:\n");
232                         fprintf(stderr, "%s [/dev/hidrawN]\n", argv[0]);
233                         return 1;
234                 }
235                 fd = try_open_device(argv[1], 1);
236         }
237         else {
238                 for (i = 0; i < 16; i++) {
239                         snprintf(name, sizeof(name), "/dev/hidraw%d", i);
240                         fd = try_open_device(name, 0);
241                         if (fd != -1)
242                                 break;
243                 }
244         }
245
246         if (fd == -1)
247                 return 1;
248
249         name[sizeof(name) - 1] = 0;
250         send_recv_cmd(fd, 0xfe, 0x03, 0x00, name, sizeof(name) - 1);
251         printf("name:           '%s'\n", name);
252         read_reg(fd, 0x99, name, sizeof(name) - 1);
253         printf("vendor:         '%s'\n", name);
254         read_reg(fd, 0x9a, name, sizeof(name) - 1);
255         printf("product:        '%s'\n", name);
256
257         read_reg32(fd, 0xd1, &v32);
258         printf("powered:        %u (%dd. %dh)\n",
259                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
260         read_reg32(fd, 0xd2, &v32);
261         printf("uptime:         %u (%dd. %dh)\n",
262                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
263
264         print_std_reg(fd, 0x8d, "temp1");
265         print_std_reg(fd, 0x8e, "temp2");
266         print_std_reg(fd, 0x90, "fan rpm");
267         print_std_reg(fd, 0x88, "supply volts");
268         print_std_reg(fd, 0xee, "total watts");
269
270         for (osel = 0; osel < 3; osel++) {
271                 // reg0 write (output select)
272                 send_recv_cmd(fd, 0x02, 0x00, osel, NULL, 0);
273                 print_std_reg(fd, 0x8b, "output%u volts", osel);
274                 print_std_reg(fd, 0x8c, "output%u amps", osel);
275                 print_std_reg(fd, 0x96, "output%u watts", osel);
276         }
277
278         send_recv_cmd(fd, 0x02, 0x00, 0x00, NULL, 0);
279
280         close(fd);
281         return 0;
282 }