| /* |
| * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc. |
| * |
| * Licensed under the terms of the GNU GPL License version 2. |
| * |
| * PCI initialization based on example code from: |
| * Andreas Herrmann <andreas.herrmann3@amd.com> |
| */ |
| |
| #if defined(__i386__) || defined(__x86_64__) |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <time.h> |
| #include <string.h> |
| |
| #include <pci/pci.h> |
| |
| #include "idle_monitor/cpupower-monitor.h" |
| #include "helpers/helpers.h" |
| |
| /******** PCI parts could go into own file and get shared ***************/ |
| |
| #define PCI_NON_PC0_OFFSET 0xb0 |
| #define PCI_PC1_OFFSET 0xb4 |
| #define PCI_PC6_OFFSET 0xb8 |
| |
| #define PCI_MONITOR_ENABLE_REG 0xe0 |
| |
| #define PCI_NON_PC0_ENABLE_BIT 0 |
| #define PCI_PC1_ENABLE_BIT 1 |
| #define PCI_PC6_ENABLE_BIT 2 |
| |
| #define PCI_NBP1_STAT_OFFSET 0x98 |
| #define PCI_NBP1_ACTIVE_BIT 2 |
| #define PCI_NBP1_ENTERED_BIT 1 |
| |
| #define PCI_NBP1_CAP_OFFSET 0x90 |
| #define PCI_NBP1_CAPABLE_BIT 31 |
| |
| #define OVERFLOW_MS 343597 /* 32 bit register filled at 12500 HZ |
| (1 tick per 80ns) */ |
| |
| enum amd_fam14h_states {NON_PC0 = 0, PC1, PC6, NBP1, |
| AMD_FAM14H_STATE_NUM}; |
| |
| static int fam14h_get_count_percent(unsigned int self_id, double *percent, |
| unsigned int cpu); |
| static int fam14h_nbp1_count(unsigned int id, unsigned long long *count, |
| unsigned int cpu); |
| |
| static cstate_t amd_fam14h_cstates[AMD_FAM14H_STATE_NUM] = { |
| { |
| .name = "!PC0", |
| .desc = N_("Package in sleep state (PC1 or deeper)"), |
| .id = NON_PC0, |
| .range = RANGE_PACKAGE, |
| .get_count_percent = fam14h_get_count_percent, |
| }, |
| { |
| .name = "PC1", |
| .desc = N_("Processor Package C1"), |
| .id = PC1, |
| .range = RANGE_PACKAGE, |
| .get_count_percent = fam14h_get_count_percent, |
| }, |
| { |
| .name = "PC6", |
| .desc = N_("Processor Package C6"), |
| .id = PC6, |
| .range = RANGE_PACKAGE, |
| .get_count_percent = fam14h_get_count_percent, |
| }, |
| { |
| .name = "NBP1", |
| .desc = N_("North Bridge P1 boolean counter (returns 0 or 1)"), |
| .id = NBP1, |
| .range = RANGE_PACKAGE, |
| .get_count = fam14h_nbp1_count, |
| }, |
| }; |
| |
| static struct pci_access *pci_acc; |
| static int pci_vendor_id = 0x1022; |
| static int pci_dev_ids[2] = {0x1716, 0}; |
| static struct pci_dev *amd_fam14h_pci_dev; |
| |
| static int nbp1_entered; |
| |
| struct timespec start_time; |
| static unsigned long long timediff; |
| |
| #ifdef DEBUG |
| struct timespec dbg_time; |
| long dbg_timediff; |
| #endif |
| |
| static unsigned long long *previous_count[AMD_FAM14H_STATE_NUM]; |
| static unsigned long long *current_count[AMD_FAM14H_STATE_NUM]; |
| |
| static int amd_fam14h_get_pci_info(struct cstate *state, |
| unsigned int *pci_offset, |
| unsigned int *enable_bit, |
| unsigned int cpu) |
| { |
| switch (state->id) { |
| case NON_PC0: |
| *enable_bit = PCI_NON_PC0_ENABLE_BIT; |
| *pci_offset = PCI_NON_PC0_OFFSET; |
| break; |
| case PC1: |
| *enable_bit = PCI_PC1_ENABLE_BIT; |
| *pci_offset = PCI_PC1_OFFSET; |
| break; |
| case PC6: |
| *enable_bit = PCI_PC6_ENABLE_BIT; |
| *pci_offset = PCI_PC6_OFFSET; |
| break; |
| case NBP1: |
| *enable_bit = PCI_NBP1_ENTERED_BIT; |
| *pci_offset = PCI_NBP1_STAT_OFFSET; |
| break; |
| default: |
| return -1; |
| }; |
| return 0; |
| } |
| |
| static int amd_fam14h_init(cstate_t *state, unsigned int cpu) |
| { |
| int enable_bit, pci_offset, ret; |
| uint32_t val; |
| |
| ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu); |
| if (ret) |
| return ret; |
| |
| /* NBP1 needs extra treating -> write 1 to D18F6x98 bit 1 for init */ |
| if (state->id == NBP1) { |
| val = pci_read_long(amd_fam14h_pci_dev, pci_offset); |
| val |= 1 << enable_bit; |
| val = pci_write_long(amd_fam14h_pci_dev, pci_offset, val); |
| return ret; |
| } |
| |
| /* Enable monitor */ |
| val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG); |
| dprint("Init %s: read at offset: 0x%x val: %u\n", state->name, |
| PCI_MONITOR_ENABLE_REG, (unsigned int) val); |
| val |= 1 << enable_bit; |
| pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val); |
| |
| dprint("Init %s: offset: 0x%x enable_bit: %d - val: %u (%u)\n", |
| state->name, PCI_MONITOR_ENABLE_REG, enable_bit, |
| (unsigned int) val, cpu); |
| |
| /* Set counter to zero */ |
| pci_write_long(amd_fam14h_pci_dev, pci_offset, 0); |
| previous_count[state->id][cpu] = 0; |
| |
| return 0; |
| } |
| |
| static int amd_fam14h_disable(cstate_t *state, unsigned int cpu) |
| { |
| int enable_bit, pci_offset, ret; |
| uint32_t val; |
| |
| ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu); |
| if (ret) |
| return ret; |
| |
| val = pci_read_long(amd_fam14h_pci_dev, pci_offset); |
| dprint("%s: offset: 0x%x %u\n", state->name, pci_offset, val); |
| if (state->id == NBP1) { |
| /* was the bit whether NBP1 got entered set? */ |
| nbp1_entered = (val & (1 << PCI_NBP1_ACTIVE_BIT)) | |
| (val & (1 << PCI_NBP1_ENTERED_BIT)); |
| |
| dprint("NBP1 was %sentered - 0x%x - enable_bit: " |
| "%d - pci_offset: 0x%x\n", |
| nbp1_entered ? "" : "not ", |
| val, enable_bit, pci_offset); |
| return ret; |
| } |
| current_count[state->id][cpu] = val; |
| |
| dprint("%s: Current - %llu (%u)\n", state->name, |
| current_count[state->id][cpu], cpu); |
| dprint("%s: Previous - %llu (%u)\n", state->name, |
| previous_count[state->id][cpu], cpu); |
| |
| val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG); |
| val &= ~(1 << enable_bit); |
| pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val); |
| |
| return 0; |
| } |
| |
| static int fam14h_nbp1_count(unsigned int id, unsigned long long *count, |
| unsigned int cpu) |
| { |
| if (id == NBP1) { |
| if (nbp1_entered) |
| *count = 1; |
| else |
| *count = 0; |
| return 0; |
| } |
| return -1; |
| } |
| static int fam14h_get_count_percent(unsigned int id, double *percent, |
| unsigned int cpu) |
| { |
| unsigned long diff; |
| |
| if (id >= AMD_FAM14H_STATE_NUM) |
| return -1; |
| /* residency count in 80ns -> divide through 12.5 to get us residency */ |
| diff = current_count[id][cpu] - previous_count[id][cpu]; |
| |
| if (timediff == 0) |
| *percent = 0.0; |
| else |
| *percent = 100.0 * diff / timediff / 12.5; |
| |
| dprint("Timediff: %llu - res~: %lu us - percent: %.2f %%\n", |
| timediff, diff * 10 / 125, *percent); |
| |
| return 0; |
| } |
| |
| static int amd_fam14h_start(void) |
| { |
| int num, cpu; |
| clock_gettime(CLOCK_REALTIME, &start_time); |
| for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) { |
| for (cpu = 0; cpu < cpu_count; cpu++) |
| amd_fam14h_init(&amd_fam14h_cstates[num], cpu); |
| } |
| #ifdef DEBUG |
| clock_gettime(CLOCK_REALTIME, &dbg_time); |
| dbg_timediff = timespec_diff_us(start_time, dbg_time); |
| dprint("Enabling counters took: %lu us\n", |
| dbg_timediff); |
| #endif |
| return 0; |
| } |
| |
| static int amd_fam14h_stop(void) |
| { |
| int num, cpu; |
| struct timespec end_time; |
| |
| clock_gettime(CLOCK_REALTIME, &end_time); |
| |
| for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) { |
| for (cpu = 0; cpu < cpu_count; cpu++) |
| amd_fam14h_disable(&amd_fam14h_cstates[num], cpu); |
| } |
| #ifdef DEBUG |
| clock_gettime(CLOCK_REALTIME, &dbg_time); |
| dbg_timediff = timespec_diff_us(end_time, dbg_time); |
| dprint("Disabling counters took: %lu ns\n", dbg_timediff); |
| #endif |
| timediff = timespec_diff_us(start_time, end_time); |
| if (timediff / 1000 > OVERFLOW_MS) |
| print_overflow_err((unsigned int)timediff / 1000000, |
| OVERFLOW_MS / 1000); |
| |
| return 0; |
| } |
| |
| static int is_nbp1_capable(void) |
| { |
| uint32_t val; |
| val = pci_read_long(amd_fam14h_pci_dev, PCI_NBP1_CAP_OFFSET); |
| return val & (1 << 31); |
| } |
| |
| struct cpuidle_monitor *amd_fam14h_register(void) |
| { |
| int num; |
| |
| if (cpupower_cpu_info.vendor != X86_VENDOR_AMD) |
| return NULL; |
| |
| if (cpupower_cpu_info.family == 0x14) { |
| if (cpu_count <= 0 || cpu_count > 2) { |
| fprintf(stderr, "AMD fam14h: Invalid cpu count: %d\n", |
| cpu_count); |
| return NULL; |
| } |
| } else |
| return NULL; |
| |
| /* We do not alloc for nbp1 machine wide counter */ |
| for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) { |
| previous_count[num] = calloc(cpu_count, |
| sizeof(unsigned long long)); |
| current_count[num] = calloc(cpu_count, |
| sizeof(unsigned long long)); |
| } |
| |
| amd_fam14h_pci_dev = pci_acc_init(&pci_acc, pci_vendor_id, pci_dev_ids); |
| if (amd_fam14h_pci_dev == NULL || pci_acc == NULL) |
| return NULL; |
| |
| if (!is_nbp1_capable()) |
| amd_fam14h_monitor.hw_states_num = AMD_FAM14H_STATE_NUM - 1; |
| |
| amd_fam14h_monitor.name_len = strlen(amd_fam14h_monitor.name); |
| return &amd_fam14h_monitor; |
| } |
| |
| static void amd_fam14h_unregister(void) |
| { |
| int num; |
| for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) { |
| free(previous_count[num]); |
| free(current_count[num]); |
| } |
| pci_cleanup(pci_acc); |
| } |
| |
| struct cpuidle_monitor amd_fam14h_monitor = { |
| .name = "Ontario", |
| .hw_states = amd_fam14h_cstates, |
| .hw_states_num = AMD_FAM14H_STATE_NUM, |
| .start = amd_fam14h_start, |
| .stop = amd_fam14h_stop, |
| .do_register = amd_fam14h_register, |
| .unregister = amd_fam14h_unregister, |
| .needs_root = 1, |
| .overflow_s = OVERFLOW_MS / 1000, |
| }; |
| #endif /* #if defined(__i386__) || defined(__x86_64__) */ |