initial commit
[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 void dump(const uint8_t *buf, size_t size)
67 {
68         size_t i, j;
69
70         for (i = 0; i < size; i += 16) {
71                 for (j = 0; j < 16; j++) {
72                         if (i + j < size)
73                                 printf(" %02x", buf[i + j]);
74                         else
75                                 fputs("   ", stdout);
76                 }
77                 fputs("  ", stdout);
78                 for (j = 0; j < 16; j++) {
79                         if (i + j < size) {
80                                 uint8_t c = buf[i + j];
81                                 printf("%c", 0x20 <= c && c <= 0x7f ? c : '.');
82                         }
83                         else
84                                 break;
85                 }
86                 puts("");
87         }
88 }
89
90 static void send_recv_cmd(int fd, uint8_t b0, uint8_t b1, uint8_t b2,
91         void *buf, size_t buf_size)
92 {
93         uint8_t buf_w[65], buf_r[64];
94         ssize_t ret;
95
96         memset(buf_w, 0, sizeof(buf_w));
97         buf_w[1] = b0;
98         buf_w[2] = b1;
99         buf_w[3] = b2;
100         ret = write(fd, buf_w, sizeof(buf_w));
101         if (ret != sizeof(buf_w)) {
102                 fprintf(stderr, "write %zd/%zd: ", ret, sizeof(buf_w));
103                 perror(NULL);
104                 exit(1);
105         }
106
107         ret = read(fd, buf_r, sizeof(buf_r));
108         if (ret != sizeof(buf_r)) {
109                 fprintf(stderr, "read %zd/%zd: ", ret, sizeof(buf_r));
110                 perror(NULL);
111                 if (ret > 0)
112                         dump(buf_r, ret);
113                 exit(1);
114         }
115
116         if (buf_r[0] != b0 || buf_r[1] != b1) {
117                 fprintf(stderr, "unexpected response %02x %02x "
118                                 "to cmd %02x %02x %02x\n",
119                                 buf_r[0], buf_r[1], b0, b1, b2);
120                 dump(buf_r, sizeof(buf_r));
121                 exit(1);
122         }
123
124         if (buf != NULL && buf_size > 0) {
125                 if (buf_size > sizeof(buf_r) - 2)
126                         buf_size = sizeof(buf_r) - 2;
127                 memcpy(buf, buf_r + 2, buf_size);
128         }
129 }
130
131 static void read_reg(int fd, uint8_t reg, void *buf, size_t buf_size)
132 {
133         send_recv_cmd(fd, 0x03, reg, 0x00, buf, buf_size);
134 }
135
136 static void read_reg16(int fd, uint8_t reg, uint16_t *v)
137 {
138         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
139         // FIXME: big endian host
140 }
141
142 static void read_reg32(int fd, uint8_t reg, uint32_t *v)
143 {
144         send_recv_cmd(fd, 0x03, reg, 0x00, v, sizeof(*v));
145         // FIXME: big endian host
146 }
147
148 static double mkv(uint16_t v16)
149 {
150         int p = (int)(int16_t)v16 >> 11;
151         int v = ((int)v16 << 21) >> 21;
152
153         return (double)v * pow(2.0, p);
154 }
155
156 static void print_std_reg(int fd, uint8_t reg, const char *fmt, ...)
157 {
158         size_t len = 0;
159         uint16_t val;
160         va_list ap;
161
162         read_reg16(fd, reg, &val);
163
164         va_start(ap, fmt);
165         len += vprintf(fmt, ap);
166         len += printf(": ");
167         va_end(ap);
168         for (; len < 16; len++)
169                 fputs(" ", stdout);
170
171         printf("%5.1f\n", mkv(val));
172 }
173
174 int main(int argc, char *argv[])
175 {
176         const char *device = "/dev/hidraw0";
177         struct hidraw_devinfo info;
178         char name[63];
179         uint32_t v32;
180         uint8_t osel;
181         int ret, fd;
182
183         if (argc > 1) {
184                 if (argv[1][0] == '-') {
185                         fprintf(stderr, "usage:\n");
186                         fprintf(stderr, "%s /dev/hidrawN\n", argv[0]);
187                         return 1;
188                 }
189                 device = argv[1];
190         }
191
192         fd = open(device, O_RDWR);
193         if (fd == -1) {
194                 fprintf(stderr, "open %s: ", device);
195                 perror(NULL);
196                 return 1;
197         }
198
199         memset(&info, 0, sizeof(info));
200         ret = ioctl(fd, HIDIOCGRAWINFO, &info);
201         if (ret != 0) {
202                 perror("HIDIOCGRAWINFO");
203                 return 1;
204         }
205
206         if (info.vendor != 0x1b1c && info.product != 0x1c0b) {
207                 fprintf(stderr, "unexpected device: %04hx:%04hx\n",
208                         info.vendor, info.product);
209                 return 1;
210         }
211
212         name[sizeof(name) - 1] = 0;
213         send_recv_cmd(fd, 0xfe, 0x03, 0x00, name, sizeof(name) - 1);
214         printf("name:           '%s'\n", name);
215         read_reg(fd, 0x99, name, sizeof(name) - 1);
216         printf("vendor:         '%s'\n", name);
217         read_reg(fd, 0x9a, name, sizeof(name) - 1);
218         printf("product:        '%s'\n", name);
219
220         read_reg32(fd, 0xd1, &v32);
221         printf("powered:        %u (%dd. %dh)\n",
222                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
223         read_reg32(fd, 0xd2, &v32);
224         printf("uptime:         %u (%dd. %dh)\n",
225                 v32, v32 / (24*60*60), v32 / (60*60) % 24);
226
227         print_std_reg(fd, 0x8d, "temp1");
228         print_std_reg(fd, 0x8e, "temp2");
229         print_std_reg(fd, 0x90, "fan rpm");
230         print_std_reg(fd, 0x88, "supply volts");
231         print_std_reg(fd, 0xee, "total watts");
232
233         for (osel = 0; osel < 3; osel++) {
234                 // reg0 write (output select)
235                 send_recv_cmd(fd, 0x02, 0x00, osel, NULL, 0);
236                 print_std_reg(fd, 0x8b, "output%u volts", osel);
237                 print_std_reg(fd, 0x8c, "output%u amps", osel);
238                 print_std_reg(fd, 0x96, "output%u watts", osel);
239         }
240
241         send_recv_cmd(fd, 0x02, 0x00, 0x00, NULL, 0);
242
243         close(fd);
244         return 0;
245 }