/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains preset event names from the Performance Application * Programming Interface v3.5 which included the following notice: * * Copyright (c) 2005,6 * Innovative Computing Labs * Computer Science Department, * University of Tennessee, * Knoxville, TN. * All Rights Reserved. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the University of Tennessee nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * * This open source software license conforms to the BSD License template. */ /* * Niagara Performance Counter Backend */ #include #include #include #include #include #include #include #include #include static int ni_pcbe_init(void); static uint_t ni_pcbe_ncounters(void); static const char *ni_pcbe_impl_name(void); static const char *ni_pcbe_cpuref(void); static char *ni_pcbe_list_events(uint_t picnum); static char *ni_pcbe_list_attrs(void); static uint64_t ni_pcbe_event_coverage(char *event); static uint64_t ni_pcbe_overflow_bitmap(void); static int ni_pcbe_configure(uint_t picnum, char *event, uint64_t preset, uint32_t flags, uint_t nattrs, kcpc_attr_t *attrs, void **data, void *token); static void ni_pcbe_program(void *token); static void ni_pcbe_allstop(void); static void ni_pcbe_sample(void *token); static void ni_pcbe_free(void *config); extern void ultra_setpcr(uint64_t); extern uint64_t ultra_getpcr(void); extern void ultra_setpic(uint64_t); extern uint64_t ultra_getpic(void); extern uint64_t ultra_gettick(void); pcbe_ops_t ni_pcbe_ops = { PCBE_VER_1, CPC_CAP_OVERFLOW_INTERRUPT | CPC_CAP_OVERFLOW_PRECISE, ni_pcbe_ncounters, ni_pcbe_impl_name, ni_pcbe_cpuref, ni_pcbe_list_events, ni_pcbe_list_attrs, ni_pcbe_event_coverage, ni_pcbe_overflow_bitmap, ni_pcbe_configure, ni_pcbe_program, ni_pcbe_allstop, ni_pcbe_sample, ni_pcbe_free }; typedef struct _ni_pcbe_config { uint8_t pcbe_picno; /* 0 for pic0 or 1 for pic1 */ uint32_t pcbe_bits; /* %pcr event code unshifted */ uint32_t pcbe_flags; /* user/system/priv */ uint32_t pcbe_pic; /* unshifted raw %pic value */ } ni_pcbe_config_t; struct nametable { const uint8_t bits; const char *name; }; typedef struct _ni_generic_events { char *name; char *event; } ni_generic_event_t; #define ULTRA_PCR_PRIVPIC (UINT64_C(1) << CPC_PCR_PRIVPIC) #define NT_END 0xFF #define GEN_EVT_END { NULL, NULL } static const uint64_t allstopped = ULTRA_PCR_PRIVPIC; static const struct nametable Niagara_names1[] = { {0x00, "Instr_cnt"}, {NT_END, ""} }; static const struct nametable Niagara_names0[] = { {0x0, "SB_full"}, {0x1, "FP_instr_cnt"}, {0x2, "IC_miss"}, {0x3, "DC_miss"}, {0x4, "ITLB_miss"}, {0x5, "DTLB_miss"}, {0x6, "L2_imiss"}, {0x7, "L2_dmiss_ld"}, {NT_END, ""} }; static const struct nametable *Niagara_names[2] = { Niagara_names0, Niagara_names1 }; static const ni_generic_event_t Niagara_generic_names1[] = { { "PAPI_tot_ins", "Instr_cnt" }, { NULL, NULL } }; static const ni_generic_event_t Niagara_generic_names0[] = { { "PAPI_l2_icm", "L2_imiss" }, { "PAPI_l2_ldm", "L2_dmiss_ld" }, { "PAPI_fp_ops", "FP_instr_cnt" }, { "PAPI_fp_ins", "FP_instr_cnt" }, { "PAPI_l1_icm", "IC_miss" }, { "PAPI_l1_dcm", "DC_miss" }, { "PAPI_tlb_im", "ITLB_miss" }, { "PAPI_tlb_dm", "DTLB_miss" }, { NULL, NULL } }; static const ni_generic_event_t *Niagara_generic_names[2] = { Niagara_generic_names0, Niagara_generic_names1 }; static const struct nametable **events; static const ni_generic_event_t **generic_events; static const char *ni_impl_name = "UltraSPARC T1"; static char *pic_events[2]; static uint16_t pcr_pic0_mask; static uint16_t pcr_pic1_mask; #define CPU_REF_URL " Documentation for Sun processors can be found at: " \ "http://www.sun.com/processors/manuals" static const char *niagara_cpuref = "See the \"UltraSPARC T1 User's Manual\" " "for descriptions of these events." CPU_REF_URL; static int ni_pcbe_init(void) { const struct nametable *n; const ni_generic_event_t *gevp; int i; size_t size; events = Niagara_names; generic_events = Niagara_generic_names; pcr_pic0_mask = CPC_PCR_PIC0_MASK; pcr_pic1_mask = CPC_PCR_PIC1_MASK; /* * Initialize the list of events for each PIC. * Do two passes: one to compute the size necessary and another * to copy the strings. Need room for event, comma, and NULL terminator. */ for (i = 0; i < 2; i++) { size = 0; for (n = events[i]; n->bits != NT_END; n++) size += strlen(n->name) + 1; for (gevp = generic_events[i]; gevp->name != NULL; gevp++) size += strlen(gevp->name) + 1; pic_events[i] = kmem_alloc(size + 1, KM_SLEEP); *pic_events[i] = '\0'; for (n = events[i]; n->bits != NT_END; n++) { (void) strcat(pic_events[i], n->name); (void) strcat(pic_events[i], ","); } for (gevp = generic_events[i]; gevp->name != NULL; gevp++) { (void) strcat(pic_events[i], gevp->name); (void) strcat(pic_events[i], ","); } /* * Remove trailing comma. */ pic_events[i][size - 1] = '\0'; } return (0); } static uint_t ni_pcbe_ncounters(void) { return (2); } static const char * ni_pcbe_impl_name(void) { return (ni_impl_name); } static const char * ni_pcbe_cpuref(void) { return (niagara_cpuref); } static char * ni_pcbe_list_events(uint_t picnum) { ASSERT(picnum >= 0 && picnum < cpc_ncounters); return (pic_events[picnum]); } static char * ni_pcbe_list_attrs(void) { return (""); } static const ni_generic_event_t * find_generic_event(int regno, char *name) { const ni_generic_event_t *gevp; for (gevp = generic_events[regno]; gevp->name != NULL; gevp++) { if (strcmp(gevp->name, name) == 0) return (gevp); } return (NULL); } static const struct nametable * find_event(int regno, char *name) { const struct nametable *n; n = events[regno]; for (; n->bits != NT_END; n++) if (strcmp(name, n->name) == 0) return (n); return (NULL); } static uint64_t ni_pcbe_event_coverage(char *event) { uint64_t bitmap = 0; if ((find_event(0, event) != NULL) || (find_generic_event(0, event) != NULL)) bitmap = 0x1; if ((find_event(1, event) != NULL) || (find_generic_event(1, event) != NULL)) bitmap |= 0x2; return (bitmap); } /* * These processors cannot tell which counter overflowed. The PCBE interface * requires such processors to act as if _all_ counters had overflowed. */ static uint64_t ni_pcbe_overflow_bitmap(void) { uint64_t pcr, overflow; pcr = ultra_getpcr(); DTRACE_PROBE1(niagara__getpcr, uint64_t, pcr); overflow = (pcr & CPC_PCR_OVF_MASK) >> CPC_PCR_OVF_SHIFT; #if 0 /* * Not needed if the CPC framework is responsible to stop counters * and that action ends up clearing overflow flags. */ if (overflow) ultra_setpcr(pcr & ~CPC_PCR_OVF_MASK); #endif return (overflow); } /*ARGSUSED*/ static int ni_pcbe_configure(uint_t picnum, char *event, uint64_t preset, uint32_t flags, uint_t nattrs, kcpc_attr_t *attrs, void **data, void *token) { ni_pcbe_config_t *conf; const struct nametable *n; const ni_generic_event_t *gevp; ni_pcbe_config_t *other_config; /* * If we've been handed an existing configuration, we need only preset * the counter value. */ if (*data != NULL) { conf = *data; conf->pcbe_pic = (uint32_t)preset; return (0); } if (picnum < 0 || picnum > 1) return (CPC_INVALID_PICNUM); if (nattrs != 0) return (CPC_INVALID_ATTRIBUTE); /* * Find other requests that will be programmed with this one, and ensure * the flags don't conflict. */ if (((other_config = kcpc_next_config(token, NULL, NULL)) != NULL) && (other_config->pcbe_flags != flags)) return (CPC_CONFLICTING_REQS); if ((n = find_event(picnum, event)) == NULL) { if ((gevp = find_generic_event(picnum, event)) != NULL) { n = find_event(picnum, gevp->event); ASSERT(n != NULL); } else { return (CPC_INVALID_EVENT); } } conf = kmem_alloc(sizeof (ni_pcbe_config_t), KM_SLEEP); conf->pcbe_picno = picnum; conf->pcbe_bits = (uint32_t)n->bits; conf->pcbe_flags = flags; conf->pcbe_pic = (uint32_t)preset; *data = conf; return (0); } static void ni_pcbe_program(void *token) { ni_pcbe_config_t *pic0; ni_pcbe_config_t *pic1; ni_pcbe_config_t *tmp; ni_pcbe_config_t empty = { 1, 0x1c, 0, 0 }; /* SW_count_1 */ uint64_t pcr; uint64_t curpic; if ((pic0 = (ni_pcbe_config_t *)kcpc_next_config(token, NULL, NULL)) == NULL) panic("ni_pcbe: token %p has no configs", token); if ((pic1 = kcpc_next_config(token, pic0, NULL)) == NULL) { pic1 = ∅ empty.pcbe_flags = pic0->pcbe_flags; } if (pic0->pcbe_picno != 0) { /* * pic0 is counter 1, so if we need the empty config it should * be counter 0. */ empty.pcbe_picno = 0; #if 0 /* no selection for counter 0 */ empty.pcbe_bits = 0x14; /* SW_count_0 - won't overflow */ #endif tmp = pic0; pic0 = pic1; pic1 = tmp; } if (pic0->pcbe_picno != 0 || pic1->pcbe_picno != 1) panic("ni_pcbe: bad config on token %p\n", token); /* * UltraSPARC does not allow pic0 to be configured differently * from pic1. If the flags on these two configurations are * different, they are incompatible. This condition should be * caught at configure time. */ ASSERT(pic0->pcbe_flags == pic1->pcbe_flags); ultra_setpcr(allstopped); ultra_setpic(((uint64_t)pic1->pcbe_pic << PIC1_SHIFT) | (uint64_t)pic0->pcbe_pic); pcr = (pic0->pcbe_bits & pcr_pic0_mask) << CPC_PCR_PIC0_SHIFT; pcr |= (pic1->pcbe_bits & pcr_pic1_mask) << CPC_PCR_PIC1_SHIFT; if (pic0->pcbe_flags & CPC_COUNT_USER) pcr |= (1ull << CPC_PCR_USR); if (pic0->pcbe_flags & CPC_COUNT_SYSTEM) pcr |= (1ull << CPC_PCR_SYS); DTRACE_PROBE1(niagara__setpcr, uint64_t, pcr); ultra_setpcr(pcr); /* * On UltraSPARC, only read-to-read counts are accurate. We cannot * expect the value we wrote into the PIC, above, to be there after * starting the counter. We must sample the counter value now and use * that as the baseline for future samples. */ curpic = ultra_getpic(); pic0->pcbe_pic = (uint32_t)(curpic & PIC0_MASK); pic1->pcbe_pic = (uint32_t)(curpic >> PIC1_SHIFT); DTRACE_PROBE1(niagara__newpic, uint64_t, curpic); } static void ni_pcbe_allstop(void) { ultra_setpcr(allstopped); } static void ni_pcbe_sample(void *token) { uint64_t curpic; int64_t diff; uint64_t *pic0_data; uint64_t *pic1_data; uint64_t *dtmp; uint64_t tmp; ni_pcbe_config_t *pic0; ni_pcbe_config_t *pic1; ni_pcbe_config_t empty = { 1, 0, 0, 0 }; ni_pcbe_config_t *ctmp; curpic = ultra_getpic(); DTRACE_PROBE1(niagara__getpic, uint64_t, curpic); if ((pic0 = kcpc_next_config(token, NULL, &pic0_data)) == NULL) panic("%s: token %p has no configs", ni_impl_name, token); if ((pic1 = kcpc_next_config(token, pic0, &pic1_data)) == NULL) { pic1 = ∅ pic1_data = &tmp; } if (pic0->pcbe_picno != 0) { empty.pcbe_picno = 0; ctmp = pic0; pic0 = pic1; pic1 = ctmp; dtmp = pic0_data; pic0_data = pic1_data; pic1_data = dtmp; } if (pic0->pcbe_picno != 0 || pic1->pcbe_picno != 1) panic("%s: bad config on token %p\n", ni_impl_name, token); diff = (curpic & PIC0_MASK) - (uint64_t)pic0->pcbe_pic; if (diff < 0) diff += (1ll << 32); *pic0_data += diff; diff = (curpic >> 32) - (uint64_t)pic1->pcbe_pic; if (diff < 0) diff += (1ll << 32); *pic1_data += diff; pic0->pcbe_pic = (uint32_t)(curpic & PIC0_MASK); pic1->pcbe_pic = (uint32_t)(curpic >> PIC1_SHIFT); } static void ni_pcbe_free(void *config) { kmem_free(config, sizeof (ni_pcbe_config_t)); } static struct modlpcbe modlpcbe = { &mod_pcbeops, "UltraSPARC T1 Performance Counters", &ni_pcbe_ops }; static struct modlinkage modl = { MODREV_1, &modlpcbe, }; int _init(void) { if (ni_pcbe_init() != 0) return (ENOTSUP); return (mod_install(&modl)); } int _fini(void) { return (mod_remove(&modl)); } int _info(struct modinfo *mi) { return (mod_info(&modl, mi)); }