Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | /* Copyright 2014-2015 ARM Limited |
| 2 | * |
| 3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | * you may not use this file except in compliance with the License. |
| 5 | * You may obtain a copy of the License at |
| 6 | * |
| 7 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | * |
| 9 | * Unless required by applicable law or agreed to in writing, software |
| 10 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | * See the License for the specific language governing permissions and |
| 13 | * limitations under the License. |
| 14 | */ |
| 15 | |
| 16 | |
| 17 | /* |
| 18 | * readenergy.c |
| 19 | * |
| 20 | * Reads APB energy registers in Juno and outputs the measurements (converted to appropriate units). |
| 21 | * |
| 22 | */ |
| 23 | #include <errno.h> |
| 24 | #include <fcntl.h> |
| 25 | #include <stdint.h> |
| 26 | #include <stdio.h> |
| 27 | #include <stdlib.h> |
| 28 | #include <string.h> |
| 29 | #include <signal.h> |
| 30 | #include <sys/mman.h> |
| 31 | #include <sys/stat.h> |
| 32 | #include <sys/types.h> |
| 33 | #include <time.h> |
| 34 | #include <unistd.h> |
| 35 | |
| 36 | // The following values obtained from Juno TRM 2014/03/04 section 4.5 |
| 37 | |
| 38 | // Location of APB registers in memory |
| 39 | #define APB_BASE_MEMORY 0x1C010000 |
| 40 | // APB energy counters start at offset 0xD0 from the base APB address. |
| 41 | #define BASE_INDEX 0xD0 / 4 |
| 42 | // the one-past last APB counter |
| 43 | #define APB_SIZE 0x120 |
| 44 | |
| 45 | // Masks specifying the bits that contain the actual counter values |
| 46 | #define CMASK 0xFFF |
| 47 | #define VMASK 0xFFF |
| 48 | #define PMASK 0xFFFFFF |
| 49 | |
| 50 | // Sclaing factor (divisor) or getting measured values from counters |
| 51 | #define SYS_ADC_CH0_PM1_SYS_SCALE 761 |
| 52 | #define SYS_ADC_CH1_PM2_A57_SCALE 381 |
| 53 | #define SYS_ADC_CH2_PM3_A53_SCALE 761 |
| 54 | #define SYS_ADC_CH3_PM4_GPU_SCALE 381 |
| 55 | #define SYS_ADC_CH4_VSYS_SCALE 1622 |
| 56 | #define SYS_ADC_CH5_VA57_SCALE 1622 |
| 57 | #define SYS_ADC_CH6_VA53_SCALE 1622 |
| 58 | #define SYS_ADC_CH7_VGPU_SCALE 1622 |
| 59 | #define SYS_POW_CH04_SYS_SCALE (SYS_ADC_CH0_PM1_SYS_SCALE * SYS_ADC_CH4_VSYS_SCALE) |
| 60 | #define SYS_POW_CH15_A57_SCALE (SYS_ADC_CH1_PM2_A57_SCALE * SYS_ADC_CH5_VA57_SCALE) |
| 61 | #define SYS_POW_CH26_A53_SCALE (SYS_ADC_CH2_PM3_A53_SCALE * SYS_ADC_CH6_VA53_SCALE) |
| 62 | #define SYS_POW_CH37_GPU_SCALE (SYS_ADC_CH3_PM4_GPU_SCALE * SYS_ADC_CH7_VGPU_SCALE) |
| 63 | #define SYS_ENM_CH0_SYS_SCALE 12348030000 |
| 64 | #define SYS_ENM_CH1_A57_SCALE 6174020000 |
| 65 | #define SYS_ENM_CH0_A53_SCALE 12348030000 |
| 66 | #define SYS_ENM_CH0_GPU_SCALE 6174020000 |
| 67 | |
| 68 | // Original values prior to re-callibrations. |
| 69 | /*#define SYS_ADC_CH0_PM1_SYS_SCALE 819.2*/ |
| 70 | /*#define SYS_ADC_CH1_PM2_A57_SCALE 409.6*/ |
| 71 | /*#define SYS_ADC_CH2_PM3_A53_SCALE 819.2*/ |
| 72 | /*#define SYS_ADC_CH3_PM4_GPU_SCALE 409.6*/ |
| 73 | /*#define SYS_ADC_CH4_VSYS_SCALE 1638.4*/ |
| 74 | /*#define SYS_ADC_CH5_VA57_SCALE 1638.4*/ |
| 75 | /*#define SYS_ADC_CH6_VA53_SCALE 1638.4*/ |
| 76 | /*#define SYS_ADC_CH7_VGPU_SCALE 1638.4*/ |
| 77 | /*#define SYS_POW_CH04_SYS_SCALE (SYS_ADC_CH0_PM1_SYS_SCALE * SYS_ADC_CH4_VSYS_SCALE)*/ |
| 78 | /*#define SYS_POW_CH15_A57_SCALE (SYS_ADC_CH1_PM2_A57_SCALE * SYS_ADC_CH5_VA57_SCALE)*/ |
| 79 | /*#define SYS_POW_CH26_A53_SCALE (SYS_ADC_CH2_PM3_A53_SCALE * SYS_ADC_CH6_VA53_SCALE)*/ |
| 80 | /*#define SYS_POW_CH37_GPU_SCALE (SYS_ADC_CH3_PM4_GPU_SCALE * SYS_ADC_CH7_VGPU_SCALE)*/ |
| 81 | /*#define SYS_ENM_CH0_SYS_SCALE 13421772800.0*/ |
| 82 | /*#define SYS_ENM_CH1_A57_SCALE 6710886400.0*/ |
| 83 | /*#define SYS_ENM_CH0_A53_SCALE 13421772800.0*/ |
| 84 | /*#define SYS_ENM_CH0_GPU_SCALE 6710886400.0*/ |
| 85 | |
| 86 | // Ignore individual errors but if see too many, abort. |
| 87 | #define ERROR_THRESHOLD 10 |
| 88 | |
| 89 | // Default counter poll period (in milliseconds). |
| 90 | #define DEFAULT_PERIOD 100 |
| 91 | |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 92 | // Default duration for the instrument execution (in seconds); 0 means 'forever' |
| 93 | #define DEFAULT_DURATION 0 |
| 94 | |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 95 | // A single reading from the energy meter. The values are the proper readings converted |
| 96 | // to appropriate units (e.g. Watts for power); they are *not* raw counter values. |
| 97 | struct reading |
| 98 | { |
| 99 | double sys_adc_ch0_pm1_sys; |
| 100 | double sys_adc_ch1_pm2_a57; |
| 101 | double sys_adc_ch2_pm3_a53; |
| 102 | double sys_adc_ch3_pm4_gpu; |
| 103 | double sys_adc_ch4_vsys; |
| 104 | double sys_adc_ch5_va57; |
| 105 | double sys_adc_ch6_va53; |
| 106 | double sys_adc_ch7_vgpu; |
| 107 | double sys_pow_ch04_sys; |
| 108 | double sys_pow_ch15_a57; |
| 109 | double sys_pow_ch26_a53; |
| 110 | double sys_pow_ch37_gpu; |
| 111 | double sys_enm_ch0_sys; |
| 112 | double sys_enm_ch1_a57; |
| 113 | double sys_enm_ch0_a53; |
| 114 | double sys_enm_ch0_gpu; |
| 115 | }; |
| 116 | |
Basil Eljuse | 70d755d | 2017-07-28 12:23:06 +0100 | [diff] [blame] | 117 | static inline uint64_t join_64bit_register(uint32_t *buffer, int index) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 118 | { |
| 119 | uint64_t result = 0; |
| 120 | result |= buffer[index]; |
| 121 | result |= (uint64_t)(buffer[index+1]) << 32; |
| 122 | return result; |
| 123 | } |
| 124 | |
| 125 | int nsleep(const struct timespec *req, struct timespec *rem) |
| 126 | { |
| 127 | struct timespec temp_rem; |
| 128 | if (nanosleep(req, rem) == -1) |
| 129 | { |
| 130 | if (errno == EINTR) |
| 131 | { |
| 132 | nsleep(rem, &temp_rem); |
| 133 | } |
| 134 | else |
| 135 | { |
| 136 | return errno; |
| 137 | } |
| 138 | } |
| 139 | else |
| 140 | { |
| 141 | return 0; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | void print_help() |
| 146 | { |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 147 | fprintf(stderr, "Usage: readenergy [-t PERIOD] [-o OUTFILE]\n\n" |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 148 | "Read Juno energy counters every PERIOD milliseconds, writing them\n" |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 149 | "to OUTFILE in CSV format either until SIGTERM is received OR\n" |
| 150 | "till the specified duration elapsed.\n" |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 151 | "If OUTFILE is not specified, stdout will be used.\n\n" |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 152 | "Parameters:\n" |
| 153 | " PERIOD is the counter poll period in milliseconds.\n" |
| 154 | " (Defaults to 100 milliseconds.)\n" |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 155 | " DURATION is the duration before execution terminates.\n" |
| 156 | " (Defaults to 0 seconds, meaning run till user\n" |
| 157 | " terminates execution.\n" |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 158 | " OUTFILE is the output file path\n"); |
| 159 | } |
| 160 | |
| 161 | // debugging only... |
| 162 | inline void dprint(char *msg) |
| 163 | { |
| 164 | fprintf(stderr, "%s\n", msg); |
| 165 | sync(); |
| 166 | } |
| 167 | |
| 168 | // -------------------------------------- config ---------------------------------------------------- |
| 169 | |
| 170 | struct config |
| 171 | { |
| 172 | struct timespec period; |
| 173 | char *output_file; |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 174 | long duration_in_sec; |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 175 | }; |
| 176 | |
| 177 | void config_init_period_from_millis(struct config *this, long millis) |
| 178 | { |
| 179 | this->period.tv_sec = (time_t)(millis / 1000); |
| 180 | this->period.tv_nsec = (millis % 1000) * 1000000; |
| 181 | } |
| 182 | |
| 183 | void config_init(struct config *this, int argc, char *argv[]) |
| 184 | { |
| 185 | this->output_file = NULL; |
| 186 | config_init_period_from_millis(this, DEFAULT_PERIOD); |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 187 | this->duration_in_sec = DEFAULT_DURATION; |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 188 | |
| 189 | int opt; |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 190 | while ((opt = getopt(argc, argv, "ht:o:d:")) != -1) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 191 | { |
| 192 | switch(opt) |
| 193 | { |
| 194 | case 't': |
| 195 | config_init_period_from_millis(this, atol(optarg)); |
| 196 | break; |
| 197 | case 'o': |
| 198 | this->output_file = optarg; |
| 199 | break; |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 200 | case 'd': |
| 201 | this->duration_in_sec = atol(optarg); |
| 202 | break; |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 203 | case 'h': |
| 204 | print_help(); |
| 205 | exit(EXIT_SUCCESS); |
| 206 | break; |
| 207 | default: |
| 208 | fprintf(stderr, "ERROR: Unexpected option %s\n\n", opt); |
| 209 | print_help(); |
| 210 | exit(EXIT_FAILURE); |
| 211 | } |
| 212 | } |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | // -------------------------------------- /config --------------------------------------------------- |
| 216 | |
| 217 | // -------------------------------------- emeter ---------------------------------------------------- |
| 218 | |
| 219 | struct emeter |
| 220 | { |
| 221 | int fd; |
| 222 | FILE *out; |
| 223 | void *mmap_base; |
| 224 | }; |
| 225 | |
| 226 | void emeter_init(struct emeter *this, char *outfile) |
| 227 | { |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 228 | if(outfile) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 229 | { |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 230 | this->out = fopen(outfile, "w"); |
| 231 | if (this->out == NULL) |
| 232 | { |
| 233 | fprintf(stderr, "ERROR: Could not open output file %s; got %s\n", outfile, strerror(errno)); |
| 234 | exit(EXIT_FAILURE); |
| 235 | } |
| 236 | } else { |
| 237 | this->out = stdout; |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 238 | } |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 239 | this->fd = open("/dev/mem", O_RDONLY); |
| 240 | if(this->fd < 0) |
| 241 | { |
| 242 | fprintf(stderr, "ERROR: Can't open /dev/mem; got %s\n", strerror(errno)); |
| 243 | fclose(this->out); |
| 244 | exit(EXIT_FAILURE); |
| 245 | } |
| 246 | |
| 247 | this->mmap_base = mmap(NULL, APB_SIZE, PROT_READ, MAP_SHARED, this->fd, APB_BASE_MEMORY); |
| 248 | if (this->mmap_base == MAP_FAILED) |
| 249 | { |
| 250 | fprintf(stderr, "ERROR: mmap failed; got %s\n", strerror(errno)); |
| 251 | close(this->fd); |
| 252 | fclose(this->out); |
| 253 | exit(EXIT_FAILURE); |
| 254 | } |
| 255 | |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 256 | if(this->out) { |
| 257 | fprintf(this->out, "sys_curr,a57_curr,a53_curr,gpu_curr," |
| 258 | "sys_volt,a57_volt,a53_volt,gpu_volt," |
| 259 | "sys_pow,a57_pow,a53_pow,gpu_pow," |
| 260 | "sys_cenr,a57_cenr,a53_cenr,gpu_cenr\n"); |
| 261 | } |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 262 | } |
| 263 | |
| 264 | void emeter_read_measurements(struct emeter *this, struct reading *reading) |
| 265 | { |
| 266 | uint32_t *buffer = (uint32_t *)this->mmap_base; |
| 267 | reading->sys_adc_ch0_pm1_sys = (double)(CMASK & buffer[BASE_INDEX+0]) / SYS_ADC_CH0_PM1_SYS_SCALE; |
| 268 | reading->sys_adc_ch1_pm2_a57 = (double)(CMASK & buffer[BASE_INDEX+1]) / SYS_ADC_CH1_PM2_A57_SCALE; |
| 269 | reading->sys_adc_ch2_pm3_a53 = (double)(CMASK & buffer[BASE_INDEX+2]) / SYS_ADC_CH2_PM3_A53_SCALE; |
| 270 | reading->sys_adc_ch3_pm4_gpu = (double)(CMASK & buffer[BASE_INDEX+3]) / SYS_ADC_CH3_PM4_GPU_SCALE; |
| 271 | reading->sys_adc_ch4_vsys = (double)(VMASK & buffer[BASE_INDEX+4]) / SYS_ADC_CH4_VSYS_SCALE; |
| 272 | reading->sys_adc_ch5_va57 = (double)(VMASK & buffer[BASE_INDEX+5]) / SYS_ADC_CH5_VA57_SCALE; |
| 273 | reading->sys_adc_ch6_va53 = (double)(VMASK & buffer[BASE_INDEX+6]) / SYS_ADC_CH6_VA53_SCALE; |
| 274 | reading->sys_adc_ch7_vgpu = (double)(VMASK & buffer[BASE_INDEX+7]) / SYS_ADC_CH7_VGPU_SCALE; |
| 275 | reading->sys_pow_ch04_sys = (double)(PMASK & buffer[BASE_INDEX+8]) / SYS_POW_CH04_SYS_SCALE; |
| 276 | reading->sys_pow_ch15_a57 = (double)(PMASK & buffer[BASE_INDEX+9]) / SYS_POW_CH15_A57_SCALE; |
| 277 | reading->sys_pow_ch26_a53 = (double)(PMASK & buffer[BASE_INDEX+10]) / SYS_POW_CH26_A53_SCALE; |
| 278 | reading->sys_pow_ch37_gpu = (double)(PMASK & buffer[BASE_INDEX+11]) / SYS_POW_CH37_GPU_SCALE; |
| 279 | reading->sys_enm_ch0_sys = (double)join_64bit_register(buffer, BASE_INDEX+12) / SYS_ENM_CH0_SYS_SCALE; |
| 280 | reading->sys_enm_ch1_a57 = (double)join_64bit_register(buffer, BASE_INDEX+14) / SYS_ENM_CH1_A57_SCALE; |
| 281 | reading->sys_enm_ch0_a53 = (double)join_64bit_register(buffer, BASE_INDEX+16) / SYS_ENM_CH0_A53_SCALE; |
| 282 | reading->sys_enm_ch0_gpu = (double)join_64bit_register(buffer, BASE_INDEX+18) / SYS_ENM_CH0_GPU_SCALE; |
| 283 | } |
| 284 | |
| 285 | void emeter_take_reading(struct emeter *this) |
| 286 | { |
| 287 | static struct reading reading; |
| 288 | int error_count = 0; |
| 289 | emeter_read_measurements(this, &reading); |
| 290 | int ret = fprintf(this->out, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n", |
| 291 | reading.sys_adc_ch0_pm1_sys, |
| 292 | reading.sys_adc_ch1_pm2_a57, |
| 293 | reading.sys_adc_ch2_pm3_a53, |
| 294 | reading.sys_adc_ch3_pm4_gpu, |
| 295 | reading.sys_adc_ch4_vsys, |
| 296 | reading.sys_adc_ch5_va57, |
| 297 | reading.sys_adc_ch6_va53, |
| 298 | reading.sys_adc_ch7_vgpu, |
| 299 | reading.sys_pow_ch04_sys, |
| 300 | reading.sys_pow_ch15_a57, |
| 301 | reading.sys_pow_ch26_a53, |
| 302 | reading.sys_pow_ch37_gpu, |
| 303 | reading.sys_enm_ch0_sys, |
| 304 | reading.sys_enm_ch1_a57, |
| 305 | reading.sys_enm_ch0_a53, |
| 306 | reading.sys_enm_ch0_gpu); |
| 307 | if (ret < 0) |
| 308 | { |
| 309 | fprintf(stderr, "ERROR: while writing a meter reading: %s\n", strerror(errno)); |
| 310 | if (++error_count > ERROR_THRESHOLD) |
| 311 | exit(EXIT_FAILURE); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | void emeter_finalize(struct emeter *this) |
| 316 | { |
| 317 | if (munmap(this->mmap_base, APB_SIZE) == -1) |
| 318 | { |
| 319 | // Report the error but don't bother doing anything else, as we're not gonna do |
| 320 | // anything with emeter after this point anyway. |
| 321 | fprintf(stderr, "ERROR: munmap failed; got %s\n", strerror(errno)); |
| 322 | } |
| 323 | close(this->fd); |
| 324 | fclose(this->out); |
| 325 | } |
| 326 | |
| 327 | // -------------------------------------- /emeter ---------------------------------------------------- |
| 328 | |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 329 | volatile int done = 0; |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 330 | |
| 331 | void term_handler(int signum) |
| 332 | { |
| 333 | done = 1; |
| 334 | } |
| 335 | |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 336 | void sigalrm_handler(int signum) |
| 337 | { |
| 338 | done = 1; |
| 339 | } |
| 340 | |
| 341 | |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 342 | int main(int argc, char *argv[]) |
| 343 | { |
| 344 | struct sigaction action; |
| 345 | memset(&action, 0, sizeof(struct sigaction)); |
| 346 | action.sa_handler = term_handler; |
| 347 | sigaction(SIGTERM, &action, NULL); |
| 348 | |
| 349 | struct config config; |
| 350 | struct emeter emeter; |
| 351 | config_init(&config, argc, argv); |
| 352 | emeter_init(&emeter, config.output_file); |
| 353 | |
Basil Eljuse | dfc63a1 | 2017-03-15 18:46:56 +0000 | [diff] [blame] | 354 | if (0 != config.duration_in_sec) |
| 355 | { |
| 356 | /*Set the alarm with the duration from use only if a non-zero value is specified |
| 357 | else it will run forever until SIGTERM signal received from user*/ |
| 358 | /*Set the signal handler first*/ |
| 359 | signal(SIGALRM, sigalrm_handler); |
| 360 | /*Now set the alarm for the duration specified by the user*/ |
| 361 | alarm(config.duration_in_sec); |
| 362 | |
| 363 | } |
| 364 | |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 365 | if(config.output_file) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 366 | { |
Chris Redpath | 0991510 | 2015-12-12 21:46:59 +0000 | [diff] [blame] | 367 | struct timespec remaining; |
| 368 | while (!done) |
| 369 | { |
| 370 | emeter_take_reading(&emeter); |
| 371 | nsleep(&config.period, &remaining); |
| 372 | } |
| 373 | } else { |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 374 | emeter_take_reading(&emeter); |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 375 | } |
| 376 | |
| 377 | emeter_finalize(&emeter); |
| 378 | return EXIT_SUCCESS; |
| 379 | } |