add some maybe helpful error messages
[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 <errno.h>
65 #include <linux/hidraw.h>
66
67 static const uint16_t products[] = {
68         0x1c0b, /* RM750i */
69         0x1c0c, /* RM850i */
70 };
71
72 static void dump(const uint8_t *buf, size_t size)
73 {
74         size_t i, j;
75
76         for (i = 0; i < size; i += 16) {
77                 for (j = 0; j < 16; j++) {
78                         if (i + j < size)
79                                 printf(" %02x", buf[i + j]);
80                         else
81                                 fputs("   ", stdout);
82                 }
83                 fputs("  ", stdout);
84                 for (j = 0; j < 16; j++) {
85                         if (i + j < size) {
86                                 uint8_t c = buf[i + j];
87                                 printf("%c", 0x20 <= c && c <= 0x7f ? c : '.');
88                         }
89                         else
90                                 break;
91                 }
92                 puts("");
93         }
94 }
95
96 static void send_recv_cmd(int fd, uint8_t b0, uint8_t b1, uint8_t b2,
97         void *buf, size_t buf_size)
98 {
99         uint8_t buf_w[65], buf_r[64];
100         ssize_t ret;
101
102         memset(buf_w, 0, sizeof(buf_w));
103         buf_w[1] = b0;
104         buf_w[2] = b1;
105         buf_w[3] = b2;
106         ret = write(fd, buf_w, sizeof(buf_w));
107         if (ret != sizeof(buf_w)) {
108                 fprintf(stderr, "write %zd/%zd: ", ret, sizeof(buf_w));
109                 perror(NULL);
110                 exit(1);
111         }
112
113         ret = read(fd, buf_r, sizeof(buf_r));
114         if (ret != sizeof(buf_r)) {
115                 fprintf(stderr, "read %zd/%zd: ", ret, sizeof(buf_r));
116                 perror(NULL);
117                 if (ret > 0)
118                         dump(buf_r, ret);
119                 exit(1);
120         }
121
122         if (buf_r[0] != b0 || buf_r[1] != b1) {
123                 fprintf(stderr, "unexpected response %02x %02x "
124                                 "to cmd %02x %02x %02x\n",
125                                 buf_r[0], buf_r[1], b0, b1, b2);
126                 dump(buf_r, sizeof(buf_r));
127                 exit(1);
128         }
129
130         if (buf != NULL && buf_size > 0) {
131                 if (buf_size > sizeof(buf_r) - 2)
132                         buf_size = sizeof(buf_r) - 2;
133                 memcpy(buf, buf_r + 2, buf_size);
134         }
135 }
136
137 static void read_reg(int fd, uint8_t reg, void *buf, size_t buf_size)
138 {
139         send_recv_cmd(fd, 0x03, reg, 0x00, buf, buf_size);
140 }
141
142 static void read_reg16(int fd, uint8_t reg, uint16_t *v)
143 {
144         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
145         // FIXME: big endian host
146 }
147
148 static void read_reg32(int fd, uint8_t reg, uint32_t *v)
149 {
150         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
151         // FIXME: big endian host
152 }
153
154 static double mkv(uint16_t v16)
155 {
156         int p = (int)(int16_t)v16 >> 11;
157         int v = ((int)v16 << 21) >> 21;
158
159         return (double)v * pow(2.0, p);
160 }
161
162 static void print_std_reg(int fd, uint8_t reg, const char *fmt, ...)
163 {
164         size_t len = 0;
165         uint16_t val;
166         va_list ap;
167
168         read_reg16(fd, reg, &val);
169
170         va_start(ap, fmt);
171         len += vprintf(fmt, ap);
172         len += printf(": ");
173         va_end(ap);
174         for (; len < 16; len++)
175                 fputs(" ", stdout);
176
177         printf("%5.1f\n", mkv(val));
178 }
179
180 static int try_open_device(const char *name, int report_errors)
181 {
182         struct hidraw_devinfo info;
183         int found = 0;
184         int i, ret, fd;
185
186         fd = open(name, O_RDWR);
187         if (fd == -1) {
188                 if (report_errors) {
189                         fprintf(stderr, "open %s: ", name);
190                         perror(NULL);
191                 }
192                 return -1;
193         }
194
195         memset(&info, 0, sizeof(info));
196         ret = ioctl(fd, HIDIOCGRAWINFO, &info);
197         if (ret != 0) {
198                 perror("HIDIOCGRAWINFO");
199                 goto out;
200         }
201
202         if (info.vendor != 0x1b1c)
203                 goto out;
204
205         for (i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
206                 if (info.product == products[i]) {
207                         found = 1;
208                         break;
209                 }
210         }
211
212 out:
213         if (!found) {
214                 if (report_errors)
215                         fprintf(stderr, "unexpected device: %04hx:%04hx\n",
216                                 info.vendor, info.product);
217                 close(fd);
218                 fd = -1;
219         }
220         return fd;
221 }
222
223 int main(int argc, char *argv[])
224 {
225         int had_eacces = 0;
226         char name[63];
227         uint32_t v32;
228         uint8_t osel;
229         int i, fd;
230
231         if (argc > 1) {
232                 if (argv[1][0] == '-' || argc != 2) {
233                         fprintf(stderr, "usage:\n");
234                         fprintf(stderr, "%s [/dev/hidrawN]\n", argv[0]);
235                         return 1;
236                 }
237                 fd = try_open_device(argv[1], 1);
238         }
239         else {
240                 for (i = 0; i < 16; i++) {
241                         snprintf(name, sizeof(name), "/dev/hidraw%d", i);
242                         fd = try_open_device(name, 0);
243                         if (fd != -1)
244                                 break;
245                         if (errno == EACCES)
246                                 had_eacces = 1;
247                 }
248                 if (fd == -1) {
249                         fprintf(stderr, "No compatible devices found.\n");
250                         if (had_eacces)
251                                 fprintf(stderr, "At least one device "
252                                         "could not be checked because "
253                                         "of lack of permissions for "
254                                         "/dev/hidraw*.\n");
255                 }
256         }
257
258         if (fd == -1)
259                 return 1;
260
261         name[sizeof(name) - 1] = 0;
262         send_recv_cmd(fd, 0xfe, 0x03, 0x00, name, sizeof(name) - 1);
263         printf("name:           '%s'\n", name);
264         read_reg(fd, 0x99, name, sizeof(name) - 1);
265         printf("vendor:         '%s'\n", name);
266         read_reg(fd, 0x9a, name, sizeof(name) - 1);
267         printf("product:        '%s'\n", name);
268
269         read_reg32(fd, 0xd1, &v32);
270         printf("powered:        %u (%dd. %dh)\n",
271                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
272         read_reg32(fd, 0xd2, &v32);
273         printf("uptime:         %u (%dd. %dh)\n",
274                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
275
276         print_std_reg(fd, 0x8d, "temp1");
277         print_std_reg(fd, 0x8e, "temp2");
278         print_std_reg(fd, 0x90, "fan rpm");
279         print_std_reg(fd, 0x88, "supply volts");
280         print_std_reg(fd, 0xee, "total watts");
281
282         for (osel = 0; osel < 3; osel++) {
283                 // reg0 write (output select)
284                 send_recv_cmd(fd, 0x02, 0x00, osel, NULL, 0);
285                 print_std_reg(fd, 0x8b, "output%u volts", osel);
286                 print_std_reg(fd, 0x8c, "output%u amps", osel);
287                 print_std_reg(fd, 0x96, "output%u watts", osel);
288         }
289
290         send_recv_cmd(fd, 0x02, 0x00, 0x00, NULL, 0);
291
292         close(fd);
293         return 0;
294 }