# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 (c) 1997-1999 by Sun Microsystems, Inc. # All rights reserved. # #ident "%Z%%M% %I% %E% SMI" SUBDIRS = modules include ../Makefile.subdirs # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # Hammerhead: flat paths - no /usr/platform/amd64/ or /kernel/kmdb/amd64/ ROOTMOD = $(ROOT)/usr/platform/lib/mdb/$(MDBTGT) ROOTKMOD = $(ROOT)/kernel/kmdb $(ROOT)/usr/platform/lib/mdb: $(INS.dir) $(ROOT)/usr/platform/lib/mdb/$(MDBTGT): $(ROOT)/usr/platform/lib/mdb $(INS.dir) $(ROOT)/kernel/kmdb: $(INS.dir) # # 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. # SUBDIRS = \ apix \ pcplusmp \ uppc \ unix include ../../Makefile.subdirs # # 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # include $(SRC)/Makefile.master include ../../../Makefile.subdirs # # 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2016, Joyent, Inc. # MODULE = apix.so MDBTGT = kvm MODSRCS = apix.c apic_common.c intr_common.c include ../../../../../Makefile.cmd include ../../../../../Makefile.cmd.64 include ../../../../intel/Makefile.amd64 include ../../../Makefile.i86pc include ../../../../Makefile.module MODSRCS_DIR = ../../common CPPFLAGS += -DMP -D_MACHDEP CPPFLAGS += -I../../../../common CPPFLAGS += -I../../common CPPFLAGS += -I$(SRC)/uts/intel CPPFLAGS += -I$(SRC)/uts/i86pc CERRWARN += -Wno-unused-function /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Joyent, Inc. */ #include "intr_common.h" /* * Globals */ static apic_irq_t *irq_tbl[APIC_MAX_VECTOR+1]; static char level_tbl[APIC_MAX_VECTOR+1]; static apix_impl_t *d_apixs[NCPU]; static int d_ncpus = NCPU; /* * Dump interrupt information for apix PSM. */ /* ARGSUSED */ int interrupt_dump_apix(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i, j; apix_impl_t apix; apix_vector_t apix_vector; struct autovec av; apic_irq_t apic_irq; option_flags = 0; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags, 'i', MDB_OPT_SETBITS, INTR_DISPLAY_INTRSTAT, &option_flags, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&d_apixs, "apixs") == -1) { mdb_warn("failed to read apixs"); return (DCMD_ERR); } if (mdb_readvar(&d_ncpus, "apic_nproc") == -1) { mdb_warn("failed to read apic_nproc"); d_ncpus = NCPU; } if (d_ncpus == 0 || d_ncpus > NCPU) d_ncpus = NCPU; if (mdb_readvar(&irq_tbl, "apic_irq_table") == -1) { mdb_warn("failed to read apic_irq_table"); return (DCMD_ERR); } if (mdb_readvar(&level_tbl, "apic_level_intr") == -1) { mdb_warn("failed to read apic_level_intr"); return (DCMD_ERR); } /* Print the header first */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%CPU "); else mdb_printf("%CPU/Vect IRQ IPL Bus Trg Type " "Share APIC/INT# "); mdb_printf("%s %\n", option_flags & INTR_DISPLAY_DRVR_INST ? "Driver Name(s)" : "ISR"); /* Walk all the entries */ for (i = 0; i < d_ncpus; i++) { /* Read the per CPU apix entry */ if (mdb_vread(&apix, sizeof (apix_impl_t), (uintptr_t)d_apixs[i]) == -1) continue; for (j = 0; j < APIX_NVECTOR; j++) { /* Read the vector entry */ if (mdb_vread(&apix_vector, sizeof (apix_vector_t), (uintptr_t)apix.x_vectbl[j]) == -1) continue; /* If invalid vector state; continue */ if (apix_vector.v_state == APIX_STATE_FREED || apix_vector.v_state == APIX_STATE_OBSOLETED) continue; if (apix_vector.v_type == APIX_TYPE_IPI) continue; if (mdb_vread(&av, sizeof (struct autovec), (uintptr_t)(apix_vector.v_autovect)) == -1) continue; if ((apix_vector.v_type == APIX_TYPE_FIXED) && (mdb_vread(&apic_irq, sizeof (apic_irq_t), (uintptr_t)irq_tbl[apix_vector.v_inum]) == -1)) continue; apix_interrupt_dump(&apix_vector, &apic_irq, &av, NULL, level_tbl[apix_vector.v_inum]); } } /* print IPIs */ if (mdb_vread(&apix, sizeof (apix_impl_t), (uintptr_t)d_apixs[0]) != -1) { for (j = 0; j < APIX_NVECTOR; j++) { /* Read the vector entry */ if (mdb_vread(&apix_vector, sizeof (apix_vector_t), (uintptr_t)apix.x_vectbl[j]) == -1) continue; /* If invalid vector state; continue */ if (apix_vector.v_state == APIX_STATE_FREED || apix_vector.v_state == APIX_STATE_OBSOLETED) continue; if (apix_vector.v_type != APIX_TYPE_IPI) continue; if (mdb_vread(&av, sizeof (struct autovec), (uintptr_t)(apix_vector.v_autovect)) == -1) { /* v_share for poke_cpu is 0 */ if (apix_vector.v_share != 0) continue; } apix_interrupt_ipi_dump(&apix_vector, &av, NULL); } } return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { { "interrupts", "?[-di]", "print interrupts", interrupt_dump_apix, interrupt_help}, { "softint", "?[-d]", "print soft interrupts", soft_interrupt_dump, soft_interrupt_help}, #ifdef _KMDB { "apic", NULL, "print apic register contents", apic }, { "ioapic", NULL, "print ioapic register contents", ioapic }, #endif /* _KMDB */ { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_name("gld_intr", &sym) != -1) if (GELF_ST_TYPE(sym.st_info) == STT_FUNC) gld_intr_addr = (uintptr_t)sym.st_value; if (mdb_readvar(&apic_pir_vect, "apic_pir_vect") == -1) { apic_pir_vect = -1; } return (&modinfo); } /* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include "intr_common.h" #ifdef _KMDB /* Macros for reading/writing the IOAPIC RDT entries */ #define APIC_READ_IOAPIC_RDT_ENTRY_LOW_DWORD(ioapic_ix, ipin) \ apic_ioapic_read(ioapic_ix, APIC_RDT_CMD + (2 * (ipin))) #define APIC_READ_IOAPIC_RDT_ENTRY_HIGH_DWORD(ioapic_ix, ipin) \ apic_ioapic_read(ioapic_ix, APIC_RDT_CMD2 + (2 * (ipin))) static uint32_t *ioapic_adr[MAX_IO_APIC]; static uint32_t apic_ioapic_read(int ioapic_ix, uint32_t reg) { volatile uint32_t *ioapic; ioapic = ioapic_adr[ioapic_ix]; ioapic[APIC_IO_REG] = reg; return (ioapic[APIC_IO_DATA]); } /* * ioapic dcmd - Print out the ioapic registers, nicely formatted. */ /*ARGSUSED*/ int ioapic(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint32_t apic_io_max; int reg; int reg_max; int i; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_readvar(&ioapic_adr, "apicioadr") == -1) { /* * If the mdb_warn string does not end in a \n, mdb will * automatically append the reason for the failure. */ mdb_warn("failed to read ioapicadr"); return (DCMD_ERR); } if (mdb_readvar(&apic_io_max, "apic_io_max") == -1) { /* * If the mdb_warn string does not end in a \n, mdb will * automatically append the reason for the failure. */ mdb_warn("failed to read apic_io_max"); return (DCMD_ERR); } mdb_printf("ioapicadr\t%p\n", ioapic_adr); for (i = 0; i < apic_io_max; i++) { /* Bits 23-16 define the maximum redirection entries */ reg_max = apic_ioapic_read(i, APIC_VERS_CMD); reg_max = (reg_max >> 16) & 0xff; mdb_printf("%4s %8s %8s\n", "reg", "high", " low"); for (reg = 0; reg <= reg_max; reg++) { uint32_t high, low; high = APIC_READ_IOAPIC_RDT_ENTRY_HIGH_DWORD(i, reg); low = APIC_READ_IOAPIC_RDT_ENTRY_LOW_DWORD(i, reg); mdb_printf("%2d %8x %8x\n", reg, high, low); } mdb_printf("\n"); } return (DCMD_OK); } /* * apic dcmd - Print out the apic registers, nicely formatted. */ /*ARGSUSED*/ int apic(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint32_t *papic; if ((flags & DCMD_ADDRSPEC) || argc != 0) return (DCMD_USAGE); if (mdb_readvar(&papic, "apicadr") == -1) { /* * If the mdb_warn string does not end in a \n, mdb will * automatically append the reason for the failure. */ mdb_warn("failed to read apicadr"); return (DCMD_ERR); } mdb_printf("apicadr\t%p\n", papic); mdb_printf("as_task_reg\t%x\n", papic[APIC_TASK_REG]); mdb_printf("as_dest_reg\t%x\n", papic[APIC_DEST_REG]); mdb_printf("as_format_reg\t%x\n", papic[APIC_FORMAT_REG]); mdb_printf("as_local_timer\t%x\n", papic[APIC_LOCAL_TIMER]); mdb_printf("as_pcint_vect\t%x\n", papic[APIC_PCINT_VECT]); mdb_printf("as_int_vect0\t%x\n", papic[APIC_INT_VECT0]); mdb_printf("as_int_vect1\t%x\n", papic[APIC_INT_VECT1]); mdb_printf("as_err_vect\t%x\n", papic[APIC_ERR_VECT]); mdb_printf("as_init_count\t%x\n", papic[APIC_INIT_COUNT]); mdb_printf("as_divide_reg\t%x\n", papic[APIC_DIVIDE_REG]); mdb_printf("as_spur_int_reg\t%x\n", papic[APIC_SPUR_INT_REG]); return (DCMD_OK); } #endif /* _KMDB */ /* * 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Joyent, Inc. */ #include "intr_common.h" #include #include int option_flags; uintptr_t gld_intr_addr; int apic_pir_vect; static struct av_head softvec_tbl[LOCK_LEVEL + 1]; static char *businfo_array[] = { " ", "CBUS", "CBUSII", "EISA", "FUTURE", "INTERN", "ISA", "MBI", "MBII", "PCIe", "MPI", "MPSA", "NUBUS", "PCI", "PCMCIA", "TC", "VL", "VME", "XPRESS", " " }; void interrupt_help(void) { mdb_printf("Prints the interrupt usage on the system.\n" "By default, only interrupt service routine names are printed.\n\n" "Switches:\n" " -d instead of ISR, print \n" " -i show like intrstat, cpu# ISR/\n"); } void soft_interrupt_help(void) { mdb_printf("Prints the soft interrupt usage on the system.\n" "By default, only interrupt service routine names are printed.\n\n" "Switch:\n" " -d instead of ISR, print \n"); } /* * This is copied from avintr.c * NOTE: Ensure that this definition stays in sync */ typedef struct av_softinfo { cpuset_t av_pending; /* pending bitmasks */ } av_softinfo_t; /* ARGSUSED */ int soft_interrupt_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; av_softinfo_t avsoftinfo; struct autovec avhp; ddi_softint_hdl_impl_t hdlp; option_flags = 0; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&softvec_tbl, "softvect") == -1) { mdb_warn("failed to read autovect"); return (DCMD_ERR); } /* Print the header first */ mdb_printf("%ADDR PEND PIL ARG1 " "ARG2 ISR(s)%\n"); /* Walk all the entries */ for (i = 0; i < LOCK_LEVEL + 1; i++) { /* Read the entry, if invalid continue */ if (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)softvec_tbl[i].avh_link) == -1) continue; do { if (!avhp.av_vector || (mdb_vread(&hdlp, sizeof (ddi_softint_hdl_impl_t), (uintptr_t)avhp.av_intr_id) == -1) || (mdb_vread(&avsoftinfo, sizeof (av_softinfo_t), (uintptr_t)hdlp.ih_pending) == -1)) continue; /* Print each soft interrupt entry */ mdb_printf("%-16p %-2d %-2d %-16p %-16p", avhp.av_intr_id, mdb_cpuset_find( (uintptr_t)&avsoftinfo.av_pending) != -1 ? 1 : 0, avhp.av_prilevel, avhp.av_intarg1, avhp.av_intarg2); interrupt_print_isr((uintptr_t)avhp.av_vector, (uintptr_t)avhp.av_intarg1, (uintptr_t)hdlp.ih_dip); mdb_printf("\n"); } while (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avhp.av_link) != -1); } return (DCMD_OK); } void interrupt_print_isr(uintptr_t vector, uintptr_t arg1, uintptr_t dip) { uintptr_t isr_addr = vector; struct dev_info dev_info; /* * figure out the real ISR function name from gld_intr() */ if (isr_addr == gld_intr_addr) { gld_mac_info_t macinfo; if (mdb_vread(&macinfo, sizeof (gld_mac_info_t), arg1) != -1) { /* verify gld data structure and get the real ISR */ if (macinfo.gldm_GLD_version == GLD_VERSION) isr_addr = (uintptr_t)macinfo.gldm_intr; } } if ((option_flags & INTR_DISPLAY_DRVR_INST) && dip) { char drvr_name[MODMAXNAMELEN]; if (dip && mdb_devinfo2driver(dip, drvr_name, sizeof (drvr_name)) == 0) { (void) mdb_vread(&dev_info, sizeof (dev_info), dip); mdb_printf("%s#%d", drvr_name, dev_info.devi_instance); } else { mdb_printf("%a", isr_addr); } } else { mdb_printf("%a", isr_addr); } } /* * get_interrupt_type: * * Get some interrupt related useful information * * NOTE: a0 is clock, c0/d0/e0 are x-calls, e1 is apic_error_intr * d1/d3 are cbe_fire interrupts */ static char * get_interrupt_type(short index) { if (index == RESERVE_INDEX) return ("IPI"); else if (index == ACPI_INDEX) return ("Fixed"); else if (index == MSI_INDEX) return ("MSI"); else if (index == MSIX_INDEX) return ("MSI-X"); else return ("Fixed"); } static char * get_apix_interrupt_type(short type) { if (type == APIX_TYPE_IPI) return ("IPI"); else if (type == APIX_TYPE_FIXED) return ("Fixed"); else if (type == APIX_TYPE_MSI) return ("MSI"); else if (type == APIX_TYPE_MSIX) return ("MSI-X"); else return ("Fixed"); } void apic_interrupt_dump(apic_irq_t *irqp, struct av_head *avp, int i, ushort_t *evtchnp, char level) { int bus_type; int j; char *intr_type; char ioapic_iline[10]; char ipl[3]; char cpu_assigned[4]; char evtchn[8]; uint32_t assigned_cpu; struct autovec avhp; /* If invalid index; continue */ if (!irqp->airq_mps_intr_index || irqp->airq_mps_intr_index == FREE_INDEX) return; /* Figure out interrupt type and trigger information */ intr_type = get_interrupt_type(irqp->airq_mps_intr_index); /* Figure out IOAPIC number and ILINE number */ if (APIC_IS_MSI_OR_MSIX_INDEX(irqp->airq_mps_intr_index)) (void) mdb_snprintf(ioapic_iline, 10, "- "); else { if (!irqp->airq_ioapicindex && !irqp->airq_intin_no) { if (strcmp(intr_type, "Fixed") == 0) (void) mdb_snprintf(ioapic_iline, 10, "0x%x/0x%x", irqp->airq_ioapicindex, irqp->airq_intin_no); else if (irqp->airq_mps_intr_index == RESERVE_INDEX) (void) mdb_snprintf(ioapic_iline, 10, "- "); else (void) mdb_snprintf(ioapic_iline, 10, " "); } else (void) mdb_snprintf(ioapic_iline, 10, "0x%x/0x%x", irqp->airq_ioapicindex, irqp->airq_intin_no); } evtchn[0] = '\0'; if (evtchnp != NULL) (void) mdb_snprintf(evtchn, 8, "%-7hd", *evtchnp); assigned_cpu = irqp->airq_temp_cpu; if (assigned_cpu == IRQ_UNINIT || assigned_cpu == IRQ_UNBOUND) assigned_cpu = irqp->airq_cpu; bus_type = irqp->airq_iflag.bustype; if (irqp->airq_mps_intr_index == RESERVE_INDEX) { (void) mdb_snprintf(cpu_assigned, 4, "all"); (void) mdb_snprintf(ipl, 3, "%d", avp->avh_hi_pri); } else { (void) mdb_snprintf(cpu_assigned, 4, "%d", assigned_cpu); (void) mdb_snprintf(ipl, 3, "%d", irqp->airq_ipl); } /* Print each interrupt entry */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%-4s", cpu_assigned); else mdb_printf("%-3d 0x%x %s%-3s %-6s %-3s %-6s %-4s%-3d %-9s ", i, irqp->airq_vector, evtchn, ipl, (bus_type ? businfo_array[bus_type] : " "), (level ? "Lvl" : "Edg"), intr_type, cpu_assigned, irqp->airq_share, ioapic_iline); /* If valid dip found; print driver name */ if (irqp->airq_dip) { (void) mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avp->avh_link); /* * Loop thru all the shared IRQs */ if (irqp->airq_share) interrupt_print_isr((uintptr_t)avhp.av_vector, (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip); for (j = 1; irqp->airq_mps_intr_index != FREE_INDEX && j < irqp->airq_share; j++) { if (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avhp.av_link) != -1) { mdb_printf(", "); interrupt_print_isr((uintptr_t)avhp.av_vector, (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip); } else { break; } } } else { if (irqp->airq_mps_intr_index == RESERVE_INDEX && !irqp->airq_share) { if (irqp->airq_vector == apic_pir_vect) { mdb_printf("pir_ipi"); } else { mdb_printf("poke_cpu"); } } else if (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avp->avh_link) != -1) { mdb_printf("%a", avhp.av_vector); } } mdb_printf("\n"); } void apix_interrupt_dump(apix_vector_t *vectp, apic_irq_t *irqp, struct autovec *avp, ushort_t *evtchnp, char level) { int j; int bus_type; char *intr_type; char irq[4]; char ioapic_iline[10]; char ipl[3]; char cpu_assigned[4]; char cpu_vector[10]; char evtchn[8]; /* If invalid vector state; continue */ if (vectp->v_state == APIX_STATE_FREED || vectp->v_state == APIX_STATE_OBSOLETED) return; /* use apic_interrupt_ipi_dump for IPIs */ if (vectp->v_type == APIX_TYPE_IPI) return; /* Figure out interrupt type and trigger information */ intr_type = get_apix_interrupt_type(vectp->v_type); /* Figure out IOAPIC number and ILINE number */ if (vectp->v_type != APIX_TYPE_FIXED) { level = 0; /* MSI/MSI-X are Edge trigger */ (void) mdb_snprintf(irq, 4, "- "); (void) mdb_snprintf(ioapic_iline, 10, "- "); if (vectp->v_type == APIX_TYPE_IPI) bus_type = BUSTYPE_NONE; else /* statically assign MSI/X with "PCI" */ bus_type = BUSTYPE_PCI; } else { (void) mdb_snprintf(irq, 4, "%d", vectp->v_inum); bus_type = irqp->airq_iflag.bustype; if (!irqp->airq_ioapicindex && !irqp->airq_intin_no) { if (strcmp(intr_type, "Fixed") == 0) (void) mdb_snprintf(ioapic_iline, 10, "0x%x/0x%x", irqp->airq_ioapicindex, irqp->airq_intin_no); else (void) mdb_snprintf(ioapic_iline, 10, "- "); } else (void) mdb_snprintf(ioapic_iline, 10, "0x%x/0x%x", irqp->airq_ioapicindex, irqp->airq_intin_no); } evtchn[0] = '\0'; if (evtchnp != NULL) (void) mdb_snprintf(evtchn, 8, "%-7hd", *evtchnp); (void) mdb_snprintf(cpu_assigned, 4, "%d", vectp->v_cpuid); (void) mdb_snprintf(cpu_vector, 10, "%d/0x%x", vectp->v_cpuid, vectp->v_vector); /* Loop all the shared vectors */ for (j = 0; j < vectp->v_share; ) { /* shared interrupts with one or more ISR removed afterwards */ if (avp->av_vector == NULL) { if (mdb_vread(avp, sizeof (struct autovec), (uintptr_t)avp->av_link) == -1) break; else continue; } (void) mdb_snprintf(ipl, 3, "%d", avp->av_prilevel); /* Print each interrupt entry */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%-4s", cpu_assigned); else mdb_printf("%-9s %-3s %s%-3s %-6s %-3s %-6s %-3d " "%-9s ", cpu_vector, irq, evtchn, ipl, (bus_type ? businfo_array[bus_type] : "-"), (level ? "Lvl" : "Edg"), intr_type, vectp->v_share, ioapic_iline); interrupt_print_isr((uintptr_t)avp->av_vector, (uintptr_t)avp->av_intarg1, (uintptr_t)avp->av_dip); mdb_printf("\n"); if (++j == vectp->v_share) break; /* done */ if (mdb_vread(avp, sizeof (struct autovec), (uintptr_t)avp->av_link) == -1) break; } } void apix_interrupt_ipi_dump(apix_vector_t *vectp, struct autovec *avp, ushort_t *evtchnp) { char *intr_type = "IPI"; char ioapic_iline[10]; char ipl[3]; char cpu_assigned[4]; char cpu_vector[10]; char evtchn[8]; /* If invalid vector state; continue */ if (vectp->v_state == APIX_STATE_FREED || vectp->v_state == APIX_STATE_OBSOLETED) return; if (vectp->v_type != APIX_TYPE_IPI) return; /* No IOAPIC number and ILINE number info */ (void) mdb_snprintf(ioapic_iline, 10, "- "); evtchn[0] = '\0'; if (evtchnp != NULL) (void) mdb_snprintf(evtchn, 8, "%-7hd", *evtchnp); /* IPI targeted ALL cpus */ mdb_snprintf(cpu_assigned, 4, "all"); (void) mdb_snprintf(cpu_vector, 10, "%s/0x%x", "all", vectp->v_vector); /* IPI is not shared interrupt, so we can get the IPL from v_pri */ (void) mdb_snprintf(ipl, 3, "%d", vectp->v_pri); /* Print each interrupt entry */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%-4s", cpu_assigned); else mdb_printf("%-9s %-3s %s%-3s %-6s %-3s %-6s %-3d %-9s ", cpu_vector, "- ", evtchn, ipl, "- ", "Edg", intr_type, vectp->v_share, ioapic_iline); if (!vectp->v_share) { if (vectp->v_vector == apic_pir_vect) { mdb_printf("pir_ipi"); } else { mdb_printf("poke_cpu"); } } else { mdb_printf("%a", avp->av_vector); } mdb_printf("\n"); } /* * 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Joyent, Inc. */ #ifndef _MDB_INTR_COMMON_H #define _MDB_INTR_COMMON_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include /* * Function prototypes */ void interrupt_help(void); void interrupt_print_isr(uintptr_t, uintptr_t, uintptr_t); void apic_interrupt_dump(apic_irq_t *, struct av_head *, int i, ushort_t *, char); void apix_interrupt_dump(apix_vector_t *, apic_irq_t *, struct autovec *, ushort_t *, char); void apix_interrupt_ipi_dump(apix_vector_t *, struct autovec *, ushort_t *); int ioapic(uintptr_t, uint_t, int, const mdb_arg_t *); int apic(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); void soft_interrupt_help(void); int soft_interrupt_dump(uintptr_t, uint_t, int, const mdb_arg_t *); /* * ::interrupts usage related defines and variables * -d and -i options are supported and saved in option_flags */ #define INTR_DISPLAY_DRVR_INST 0x1 /* -d option */ #define INTR_DISPLAY_INTRSTAT 0x2 /* -i option */ extern int option_flags; #define BUSTYPE_PCI 0x0d #define BUSTYPE_NONE 0x00 /* * gld_intr_addr is used to save address of gld_intr() ISR */ extern uintptr_t gld_intr_addr; /* cached the PIR ipi vector to differentiate it from poke_cpu */ extern int apic_pir_vect; #ifdef __cplusplus } #endif #endif /* _MDB_INTR_COMMON_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # include $(SRC)/Makefile.master include ../../../Makefile.subdirs # # 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2016, Joyent, Inc. # MODULE = pcplusmp.so MDBTGT = kvm MODSRCS = pcplusmp.c apic_common.c intr_common.c include ../../../../../Makefile.cmd include ../../../../../Makefile.cmd.64 include ../../../../intel/Makefile.amd64 include ../../../Makefile.i86pc include ../../../../Makefile.module MODSRCS_DIR = ../../common CPPFLAGS += -DMP -D_MACHDEP CPPFLAGS += -I../../../../common CPPFLAGS += -I../../common CPPFLAGS += -I$(SRC)/uts/intel CPPFLAGS += -I$(SRC)/uts/i86pc CERRWARN += -Wno-unused-function /* * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Joyent, Inc. */ #include "intr_common.h" #include /* * Globals */ static struct av_head avec_tbl[APIC_MAX_VECTOR+1]; static apic_irq_t *irq_tbl[APIC_MAX_VECTOR+1], airq; static char level_tbl[APIC_MAX_VECTOR+1]; /* * Dump interrupt information for pcplusmp PSM. */ /* ARGSUSED */ int interrupt_dump_apic(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; option_flags = 0; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags, 'i', MDB_OPT_SETBITS, INTR_DISPLAY_INTRSTAT, &option_flags, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&irq_tbl, "apic_irq_table") == -1) { mdb_warn("failed to read apic_irq_table"); return (DCMD_ERR); } if (mdb_readvar(&level_tbl, "apic_level_intr") == -1) { mdb_warn("failed to read apic_level_intr"); return (DCMD_ERR); } if (mdb_readvar(&avec_tbl, "autovect") == -1) { mdb_warn("failed to read autovect"); return (DCMD_ERR); } /* Print the header first */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%CPU "); else mdb_printf( "%IRQ Vect IPL Bus Trg Type CPU Share APIC/INT# "); mdb_printf("%s %\n", option_flags & INTR_DISPLAY_DRVR_INST ? "Driver Name(s)" : "ISR(s)"); /* Walk all the entries */ for (i = 0; i < APIC_MAX_VECTOR + 1; i++) { /* Read the entry */ if (mdb_vread(&airq, sizeof (apic_irq_t), (uintptr_t)irq_tbl[i]) == -1) continue; apic_interrupt_dump(&airq, &avec_tbl[i], i, NULL, level_tbl[i]); } return (DCMD_OK); } /* * MDB module linkage information: * * We declare a list of structures describing our dcmds, and a function * named _mdb_init to return a pointer to our module information. */ static const mdb_dcmd_t dcmds[] = { { "interrupts", "?[-di]", "print interrupts", interrupt_dump_apic, interrupt_help}, { "softint", "?[-d]", "print soft interrupts", soft_interrupt_dump, soft_interrupt_help}, #ifdef _KMDB { "apic", NULL, "print apic register contents", apic }, { "ioapic", NULL, "print ioapic register contents", ioapic }, #endif /* _KMDB */ { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_name("gld_intr", &sym) != -1) if (GELF_ST_TYPE(sym.st_info) == STT_FUNC) gld_intr_addr = (uintptr_t)sym.st_value; if (mdb_readvar(&apic_pir_vect, "apic_pir_vect") == -1) { apic_pir_vect = -1; } return (&modinfo); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # include $(SRC)/Makefile.master include ../../../Makefile.subdirs # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2019 Joyent, Inc. MODULE = unix.so MDBTGT = kvm MODSRCS = unix.c i86mmu.c xcall.c MODASMSRCS = unix_sup.S include ../../../../../Makefile.cmd include ../../../../../Makefile.cmd.64 include ../../../../intel/Makefile.amd64 include ../../../Makefile.i86pc include ../../../../Makefile.module CPPFLAGS += -DMP -D_MACHDEP CPPFLAGS += -I../../../../common CPPFLAGS += -I../../../../intel CPPFLAGS += -I$(SRC)/uts/i86pc CPPFLAGS += -I$(SRC)/uts/intel CERRWARN += -Wno-char-subscripts CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) CERRWARN += -Wno-unused-function /* * 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. * * Copyright 2019 Joyent, Inc. */ /* * This part of the file contains the mdb support for dcmds: * ::memseg_list * and walkers for: * memseg - a memseg list walker for ::memseg_list * */ #include #include #include #include #ifdef __xpv #include #endif #include #include #include #include #include #define VA_SIGN_BIT (1UL << 47) #define VA_LOW_BITS ((1UL << 48) - 1) #define VA_SIGN_EXTEND(va) ((((va) & VA_LOW_BITS) ^ VA_SIGN_BIT) - VA_SIGN_BIT) struct pfn2pp { pfn_t pfn; page_t *pp; }; static int do_va2pa(uintptr_t, struct as *, int, physaddr_t *, pfn_t *); static void init_mmu(void); int platform_vtop(uintptr_t addr, struct as *asp, physaddr_t *pap) { if (asp == NULL) return (DCMD_ERR); init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); return (do_va2pa(addr, asp, 0, pap, NULL)); } /* * ::memseg_list dcmd and walker to implement it. */ /*ARGSUSED*/ int memseg_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct memseg ms; if (!(flags & DCMD_ADDRSPEC)) { if (mdb_pwalk_dcmd("memseg", "memseg_list", 0, NULL, 0) == -1) { mdb_warn("can't walk memseg"); return (DCMD_ERR); } return (DCMD_OK); } if (DCMD_HDRSPEC(flags)) mdb_printf("%%?s %?s %?s %?s %?s%\n", "ADDR", "PAGES", "EPAGES", "BASE", "END"); if (mdb_vread(&ms, sizeof (struct memseg), addr) == -1) { mdb_warn("can't read memseg at %#lx", addr); return (DCMD_ERR); } mdb_printf("%0?lx %0?lx %0?lx %0?lx %0?lx\n", addr, ms.pages, ms.epages, ms.pages_base, ms.pages_end); return (DCMD_OK); } /* * walk the memseg structures */ int memseg_walk_init(mdb_walk_state_t *wsp) { if (wsp->walk_addr != 0) { mdb_warn("memseg only supports global walks\n"); return (WALK_ERR); } if (mdb_readvar(&wsp->walk_addr, "memsegs") == -1) { mdb_warn("symbol 'memsegs' not found"); return (WALK_ERR); } wsp->walk_data = mdb_alloc(sizeof (struct memseg), UM_SLEEP); return (WALK_NEXT); } int memseg_walk_step(mdb_walk_state_t *wsp) { int status; if (wsp->walk_addr == 0) { return (WALK_DONE); } if (mdb_vread(wsp->walk_data, sizeof (struct memseg), wsp->walk_addr) == -1) { mdb_warn("failed to read struct memseg at %p", wsp->walk_addr); return (WALK_DONE); } status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, wsp->walk_cbdata); wsp->walk_addr = (uintptr_t)(((struct memseg *)wsp->walk_data)->next); return (status); } void memseg_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (struct memseg)); } /* * Now HAT related dcmds. */ static struct hat *khat; /* value of kas.a_hat */ struct hat_mmu_info mmu; uintptr_t kernelbase; /* * stuff for i86xpv images */ static int is_xpv; static uintptr_t mfn_list_addr; /* kernel MFN list address */ uintptr_t xen_virt_start; /* address of mfn_to_pfn[] table */ ulong_t mfn_count; /* number of pfn's in the MFN list */ pfn_t *mfn_list; /* local MFN list copy */ /* * read mmu parameters from kernel */ static void init_mmu(void) { struct as kas; if (mmu.num_level != 0) return; if (mdb_readsym(&mmu, sizeof (mmu), "mmu") == -1) mdb_warn("Can't use HAT information before mmu_init()\n"); if (mdb_readsym(&kas, sizeof (kas), "kas") == -1) mdb_warn("Couldn't find kas - kernel's struct as\n"); if (mdb_readsym(&kernelbase, sizeof (kernelbase), "kernelbase") == -1) mdb_warn("Couldn't find kernelbase\n"); khat = kas.a_hat; /* * Is this a paravirtualized domain image? */ if (mdb_readsym(&mfn_list_addr, sizeof (mfn_list_addr), "mfn_list") == -1 || mdb_readsym(&xen_virt_start, sizeof (xen_virt_start), "xen_virt_start") == -1 || mdb_readsym(&mfn_count, sizeof (mfn_count), "mfn_count") == -1) { mfn_list_addr = 0; } is_xpv = mfn_list_addr != 0; #ifndef _KMDB /* * recreate the local mfn_list */ if (is_xpv) { size_t sz = mfn_count * sizeof (pfn_t); mfn_list = mdb_zalloc(sz, UM_SLEEP); if (mdb_vread(mfn_list, sz, (uintptr_t)mfn_list_addr) == -1) { mdb_warn("Failed to read MFN list\n"); mdb_free(mfn_list, sz); mfn_list = NULL; } } #endif } void free_mmu(void) { #ifdef __xpv if (mfn_list != NULL) mdb_free(mfn_list, mfn_count * sizeof (mfn_t)); #endif } #ifdef __xpv #ifdef _KMDB /* * Convert between MFNs and PFNs. Since we're in kmdb we can go directly * through the machine to phys mapping and the MFN list. */ pfn_t mdb_mfn_to_pfn(mfn_t mfn) { pfn_t pfn; mfn_t tmp; pfn_t *pfn_list; if (mfn_list_addr == 0) return (-(pfn_t)1); pfn_list = (pfn_t *)xen_virt_start; if (mdb_vread(&pfn, sizeof (pfn), (uintptr_t)(pfn_list + mfn)) == -1) return (-(pfn_t)1); if (mdb_vread(&tmp, sizeof (tmp), (uintptr_t)(mfn_list_addr + (pfn * sizeof (mfn_t)))) == -1) return (-(pfn_t)1); if (pfn >= mfn_count || tmp != mfn) return (-(pfn_t)1); return (pfn); } mfn_t mdb_pfn_to_mfn(pfn_t pfn) { mfn_t mfn; init_mmu(); if (mfn_list_addr == 0 || pfn >= mfn_count) return (-(mfn_t)1); if (mdb_vread(&mfn, sizeof (mfn), (uintptr_t)(mfn_list_addr + (pfn * sizeof (mfn_t)))) == -1) return (-(mfn_t)1); return (mfn); } #else /* _KMDB */ /* * Convert between MFNs and PFNs. Since a crash dump doesn't include the * MFN->PFN translation table (it's part of the hypervisor, not our image) * we do the MFN->PFN translation by searching the PFN->MFN (mfn_list) * table, if it's there. */ pfn_t mdb_mfn_to_pfn(mfn_t mfn) { pfn_t pfn; init_mmu(); if (mfn_list == NULL) return (-(pfn_t)1); for (pfn = 0; pfn < mfn_count; ++pfn) { if (mfn_list[pfn] != mfn) continue; return (pfn); } return (-(pfn_t)1); } mfn_t mdb_pfn_to_mfn(pfn_t pfn) { init_mmu(); if (mfn_list == NULL || pfn >= mfn_count) return (-(mfn_t)1); return (mfn_list[pfn]); } #endif /* _KMDB */ static paddr_t mdb_ma_to_pa(uint64_t ma) { pfn_t pfn = mdb_mfn_to_pfn(mmu_btop(ma)); if (pfn == -(pfn_t)1) return (-(paddr_t)1); return (mmu_ptob((paddr_t)pfn) | (ma & (MMU_PAGESIZE - 1))); } #else /* __xpv */ #define mdb_ma_to_pa(ma) (ma) #define mdb_mfn_to_pfn(mfn) (mfn) #define mdb_pfn_to_mfn(pfn) (pfn) #endif /* __xpv */ /* * ::mfntopfn dcmd translates hypervisor machine page number * to physical page number */ /*ARGSUSED*/ int mfntopfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pfn_t pfn; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("MFN missing\n"); return (DCMD_USAGE); } if ((pfn = mdb_mfn_to_pfn((pfn_t)addr)) == -(pfn_t)1) { mdb_warn("Invalid mfn %lr\n", (pfn_t)addr); return (DCMD_ERR); } mdb_printf("%lr\n", pfn); return (DCMD_OK); } /* * ::pfntomfn dcmd translates physical page number to * hypervisor machine page number */ /*ARGSUSED*/ int pfntomfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pfn_t mfn; if ((flags & DCMD_ADDRSPEC) == 0) { mdb_warn("PFN missing\n"); return (DCMD_USAGE); } if ((mfn = mdb_pfn_to_mfn((pfn_t)addr)) == -(pfn_t)1) { mdb_warn("Invalid pfn %lr\n", (pfn_t)addr); return (DCMD_ABORT); } mdb_printf("%lr\n", mfn); if (flags & DCMD_LOOP) mdb_set_dot(addr + 1); return (DCMD_OK); } static pfn_t pte2mfn(x86pte_t pte, uint_t level) { pfn_t mfn; if (level > 0 && (pte & PT_PAGESIZE)) mfn = mmu_btop(pte & PT_PADDR_LGPG); else mfn = mmu_btop(pte & PT_PADDR); return (mfn); } static int do_pte_dcmd(int level, uint64_t pte) { static char *attr[] = { "wrback", "wrthru", "uncached", "uncached", "wrback", "wrthru", "wrcombine", "uncached"}; int pat_index = 0; pfn_t mfn; mdb_printf("pte=0x%llr: ", pte); mfn = pte2mfn(pte, level); mdb_printf("%s=0x%lr ", is_xpv ? "mfn" : "pfn", mfn); if (PTE_GET(pte, mmu.pt_nx)) mdb_printf("noexec "); if (PTE_GET(pte, PT_NOCONSIST)) mdb_printf("noconsist "); if (PTE_GET(pte, PT_NOSYNC)) mdb_printf("nosync "); if (PTE_GET(pte, mmu.pt_global)) mdb_printf("global "); if (level > 0 && PTE_GET(pte, PT_PAGESIZE)) mdb_printf("largepage "); if (level > 0 && PTE_GET(pte, PT_MOD)) mdb_printf("mod "); if (level > 0 && PTE_GET(pte, PT_REF)) mdb_printf("ref "); if (PTE_GET(pte, PT_USER)) mdb_printf("user "); if (PTE_GET(pte, PT_WRITABLE)) mdb_printf("write "); /* * Report non-standard cacheability */ pat_index = 0; if (level > 0) { if (PTE_GET(pte, PT_PAGESIZE) && PTE_GET(pte, PT_PAT_LARGE)) pat_index += 4; } else { if (PTE_GET(pte, PT_PAT_4K)) pat_index += 4; } if (PTE_GET(pte, PT_NOCACHE)) pat_index += 2; if (PTE_GET(pte, PT_WRITETHRU)) pat_index += 1; if (pat_index != 0) mdb_printf("%s", attr[pat_index]); if (PTE_GET(pte, PT_VALID) == 0) mdb_printf(" !VALID "); mdb_printf("\n"); return (DCMD_OK); } /* * Print a PTE in more human friendly way. The PTE is assumed to be in * a level 0 page table, unless -l specifies another level. */ /*ARGSUSED*/ int pte_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint64_t level = 0; init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'l', MDB_OPT_UINT64, &level, NULL) != argc) return (DCMD_USAGE); if (level > mmu.max_level) { mdb_warn("invalid level %lu\n", level); return (DCMD_ERR); } if (addr == 0) return (DCMD_OK); return (do_pte_dcmd((int)level, addr)); } static size_t va2entry(htable_t *htable, uintptr_t addr) { size_t entry = (addr - htable->ht_vaddr); entry >>= mmu.level_shift[htable->ht_level]; return (entry & HTABLE_NUM_PTES(htable) - 1); } static x86pte_t get_pte(hat_t *hat, htable_t *htable, uintptr_t addr) { x86pte_t buf; if (htable->ht_flags & HTABLE_COPIED) { uintptr_t ptr = (uintptr_t)hat->hat_copied_ptes; ptr += va2entry(htable, addr) << mmu.pte_size_shift; return (*(x86pte_t *)ptr); } paddr_t paddr = mmu_ptob((paddr_t)htable->ht_pfn); paddr += va2entry(htable, addr) << mmu.pte_size_shift; if ((mdb_pread(&buf, mmu.pte_size, paddr)) == mmu.pte_size) return (buf); return (0); } static int do_va2pa(uintptr_t addr, struct as *asp, int print_level, physaddr_t *pap, pfn_t *mfnp) { struct as as; struct hat *hatp; struct hat hat; htable_t *ht; htable_t htable; uintptr_t base; int h; int level; int found = 0; x86pte_t pte; physaddr_t paddr; if (asp != NULL) { if (mdb_vread(&as, sizeof (as), (uintptr_t)asp) == -1) { mdb_warn("Couldn't read struct as\n"); return (DCMD_ERR); } hatp = as.a_hat; } else { hatp = khat; } /* * read the hat and its hash table */ if (mdb_vread(&hat, sizeof (hat), (uintptr_t)hatp) == -1) { mdb_warn("Couldn't read struct hat\n"); return (DCMD_ERR); } /* * read the htable hashtable */ for (level = 0; level <= mmu.max_level; ++level) { if (level == TOP_LEVEL(&hat)) base = 0; else base = addr & mmu.level_mask[level + 1]; for (h = 0; h < hat.hat_num_hash; ++h) { if (mdb_vread(&ht, sizeof (htable_t *), (uintptr_t)(hat.hat_ht_hash + h)) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } for (; ht != NULL; ht = htable.ht_next) { if (mdb_vread(&htable, sizeof (htable_t), (uintptr_t)ht) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } if (htable.ht_vaddr != base || htable.ht_level != level) continue; pte = get_pte(&hat, &htable, addr); if (print_level) { mdb_printf("\tlevel=%d htable=0x%p " "pte=0x%llr\n", level, ht, pte); } if (!PTE_ISVALID(pte)) { mdb_printf("Address %p is unmapped.\n", addr); return (DCMD_ERR); } if (found) continue; if (PTE_IS_LGPG(pte, level)) paddr = mdb_ma_to_pa(pte & PT_PADDR_LGPG); else paddr = mdb_ma_to_pa(pte & PT_PADDR); paddr += addr & mmu.level_offset[level]; if (pap != NULL) *pap = paddr; if (mfnp != NULL) *mfnp = pte2mfn(pte, level); found = 1; } } } if (!found) return (DCMD_ERR); return (DCMD_OK); } int va2pfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uintptr_t addrspace; char *addrspace_str = NULL; int piped = flags & DCMD_PIPE_OUT; pfn_t pfn; pfn_t mfn; int rc; init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); if (mdb_getopts(argc, argv, 'a', MDB_OPT_STR, &addrspace_str, NULL) != argc) return (DCMD_USAGE); if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); /* * parse the address space */ if (addrspace_str != NULL) addrspace = mdb_strtoull(addrspace_str); else addrspace = 0; rc = do_va2pa(addr, (struct as *)addrspace, !piped, NULL, &mfn); if (rc != DCMD_OK) return (rc); if ((pfn = mdb_mfn_to_pfn(mfn)) == -(pfn_t)1) { mdb_warn("Invalid mfn %lr\n", mfn); return (DCMD_ERR); } if (piped) { mdb_printf("0x%lr\n", pfn); return (DCMD_OK); } mdb_printf("Virtual address 0x%p maps pfn 0x%lr", addr, pfn); if (is_xpv) mdb_printf(" (mfn 0x%lr)", mfn); mdb_printf("\n"); return (DCMD_OK); } /* * Report all hat's that either use PFN as a page table or that map the page. */ static int do_report_maps(pfn_t pfn) { struct hat *hatp; struct hat hat; htable_t *ht; htable_t htable; uintptr_t base; int h; int level; int entry; x86pte_t pte; physaddr_t paddr; size_t len; /* * The hats are kept in a list with khat at the head. */ for (hatp = khat; hatp != NULL; hatp = hat.hat_next) { /* * read the hat and its hash table */ if (mdb_vread(&hat, sizeof (hat), (uintptr_t)hatp) == -1) { mdb_warn("Couldn't read struct hat\n"); return (DCMD_ERR); } /* * read the htable hashtable */ paddr = 0; for (h = 0; h < hat.hat_num_hash; ++h) { if (mdb_vread(&ht, sizeof (htable_t *), (uintptr_t)(hat.hat_ht_hash + h)) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } for (; ht != NULL; ht = htable.ht_next) { if (mdb_vread(&htable, sizeof (htable_t), (uintptr_t)ht) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } /* * only report kernel addresses once */ if (hatp != khat && htable.ht_vaddr >= kernelbase) continue; /* * Is the PFN a pagetable itself? */ if (htable.ht_pfn == pfn) { mdb_printf("Pagetable for " "hat=%p htable=%p\n", hatp, ht); continue; } /* * otherwise, examine page mappings */ level = htable.ht_level; if (level > mmu.max_page_level) continue; paddr = mmu_ptob((physaddr_t)htable.ht_pfn); for (entry = 0; entry < HTABLE_NUM_PTES(&htable); ++entry) { base = htable.ht_vaddr + entry * mmu.level_size[level]; /* * only report kernel addresses once */ if (hatp != khat && base >= kernelbase) continue; len = mdb_pread(&pte, mmu.pte_size, paddr + entry * mmu.pte_size); if (len != mmu.pte_size) return (DCMD_ERR); if ((pte & PT_VALID) == 0) continue; if (level == 0 || !(pte & PT_PAGESIZE)) pte &= PT_PADDR; else pte &= PT_PADDR_LGPG; if (mmu_btop(mdb_ma_to_pa(pte)) != pfn) continue; mdb_printf("hat=%p maps addr=%p\n", hatp, (caddr_t)base); } } } } return (DCMD_OK); } /* * given a PFN as its address argument, prints out the uses of it */ /*ARGSUSED*/ int report_maps_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pfn_t pfn; uint_t mflag = 0; init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'm', MDB_OPT_SETBITS, TRUE, &mflag, NULL) != argc) return (DCMD_USAGE); pfn = (pfn_t)addr; if (mflag) pfn = mdb_mfn_to_pfn(pfn); return (do_report_maps(pfn)); } static int do_ptable_dcmd(pfn_t pfn, uint64_t level) { struct hat *hatp; struct hat hat; htable_t *ht; htable_t htable; uintptr_t base; int h; int entry; uintptr_t pagesize; x86pte_t pte; physaddr_t paddr; size_t len; /* * The hats are kept in a list with khat at the head. */ for (hatp = khat; hatp != NULL; hatp = hat.hat_next) { /* * read the hat and its hash table */ if (mdb_vread(&hat, sizeof (hat), (uintptr_t)hatp) == -1) { mdb_warn("Couldn't read struct hat\n"); return (DCMD_ERR); } /* * read the htable hashtable */ paddr = 0; for (h = 0; h < hat.hat_num_hash; ++h) { if (mdb_vread(&ht, sizeof (htable_t *), (uintptr_t)(hat.hat_ht_hash + h)) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } for (; ht != NULL; ht = htable.ht_next) { if (mdb_vread(&htable, sizeof (htable_t), (uintptr_t)ht) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } /* * Is this the PFN for this htable */ if (htable.ht_pfn == pfn) goto found_it; } } } found_it: if (htable.ht_pfn == pfn) { mdb_printf("htable=%p\n", ht); if (level == (uint64_t)-1) { level = htable.ht_level; } else if (htable.ht_level != level) { mdb_warn("htable has level %d but forcing level %lu\n", htable.ht_level, level); } base = htable.ht_vaddr; pagesize = mmu.level_size[level]; } else { if (level == (uint64_t)-1) level = 0; mdb_warn("couldn't find matching htable, using level=%lu, " "base address=0x0\n", level); base = 0; pagesize = mmu.level_size[level]; } paddr = mmu_ptob((physaddr_t)pfn); for (entry = 0; entry < mmu.ptes_per_table; ++entry) { len = mdb_pread(&pte, mmu.pte_size, paddr + entry * mmu.pte_size); if (len != mmu.pte_size) return (DCMD_ERR); if (pte == 0) continue; mdb_printf("[%3d] va=0x%p ", entry, VA_SIGN_EXTEND(base + entry * pagesize)); do_pte_dcmd(level, pte); } return (DCMD_OK); } /* * Dump the page table at the given PFN */ /*ARGSUSED*/ int ptable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { pfn_t pfn; uint_t mflag = 0; uint64_t level = (uint64_t)-1; init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'm', MDB_OPT_SETBITS, TRUE, &mflag, 'l', MDB_OPT_UINT64, &level, NULL) != argc) return (DCMD_USAGE); if (level != (uint64_t)-1 && level > mmu.max_level) { mdb_warn("invalid level %lu\n", level); return (DCMD_ERR); } pfn = (pfn_t)addr; if (mflag) pfn = mdb_mfn_to_pfn(pfn); return (do_ptable_dcmd(pfn, level)); } static int do_htables_dcmd(hat_t *hatp) { struct hat hat; htable_t *ht; htable_t htable; int h; /* * read the hat and its hash table */ if (mdb_vread(&hat, sizeof (hat), (uintptr_t)hatp) == -1) { mdb_warn("Couldn't read struct hat\n"); return (DCMD_ERR); } /* * read the htable hashtable */ for (h = 0; h < hat.hat_num_hash; ++h) { if (mdb_vread(&ht, sizeof (htable_t *), (uintptr_t)(hat.hat_ht_hash + h)) == -1) { mdb_warn("Couldn't read htable ptr\\n"); return (DCMD_ERR); } for (; ht != NULL; ht = htable.ht_next) { mdb_printf("%p\n", ht); if (mdb_vread(&htable, sizeof (htable_t), (uintptr_t)ht) == -1) { mdb_warn("Couldn't read htable\n"); return (DCMD_ERR); } } } return (DCMD_OK); } /* * Dump the htables for the given hat */ /*ARGSUSED*/ int htables_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { hat_t *hat; init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); hat = (hat_t *)addr; return (do_htables_dcmd(hat)); } static uintptr_t entry2va(size_t *entries) { uintptr_t va = 0; for (level_t l = mmu.max_level; l >= 0; l--) va += entries[l] << mmu.level_shift[l]; return (VA_SIGN_EXTEND(va)); } static void ptmap_report(size_t *entries, uintptr_t start, boolean_t user, boolean_t writable, boolean_t wflag) { uint64_t curva = entry2va(entries); mdb_printf("mapped %s,%s range of %lu bytes: %a-%a\n", user ? "user" : "kernel", writable ? "writable" : "read-only", curva - start, start, curva - 1); if (wflag && start >= kernelbase) (void) mdb_call_dcmd("whatis", start, DCMD_ADDRSPEC, 0, NULL); } int ptmap_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { physaddr_t paddrs[MAX_NUM_LEVEL] = { 0, }; size_t entry[MAX_NUM_LEVEL] = { 0, }; uintptr_t start = (uintptr_t)-1; boolean_t writable = B_FALSE; boolean_t user = B_FALSE; boolean_t wflag = B_FALSE; level_t curlevel; if ((flags & DCMD_ADDRSPEC) == 0) return (DCMD_USAGE); if (mdb_getopts(argc, argv, 'w', MDB_OPT_SETBITS, TRUE, &wflag, NULL) != argc) return (DCMD_USAGE); init_mmu(); if (mmu.num_level == 0) return (DCMD_ERR); curlevel = mmu.max_level; paddrs[curlevel] = addr & MMU_PAGEMASK; for (;;) { physaddr_t pte_addr; x86pte_t pte; pte_addr = paddrs[curlevel] + (entry[curlevel] << mmu.pte_size_shift); if (mdb_pread(&pte, sizeof (pte), pte_addr) != sizeof (pte)) { mdb_warn("couldn't read pte at %p", pte_addr); return (DCMD_ERR); } if (PTE_GET(pte, PT_VALID) == 0) { if (start != (uintptr_t)-1) { ptmap_report(entry, start, user, writable, wflag); start = (uintptr_t)-1; } } else if (curlevel == 0 || PTE_GET(pte, PT_PAGESIZE)) { if (start == (uintptr_t)-1) { start = entry2va(entry); user = PTE_GET(pte, PT_USER); writable = PTE_GET(pte, PT_WRITABLE); } else if (user != PTE_GET(pte, PT_USER) || writable != PTE_GET(pte, PT_WRITABLE)) { ptmap_report(entry, start, user, writable, wflag); start = entry2va(entry); user = PTE_GET(pte, PT_USER); writable = PTE_GET(pte, PT_WRITABLE); } } else { /* Descend a level. */ physaddr_t pa = mmu_ptob(pte2mfn(pte, curlevel)); paddrs[--curlevel] = pa; entry[curlevel] = 0; continue; } while (++entry[curlevel] == mmu.ptes_per_table) { /* Ascend back up. */ entry[curlevel] = 0; if (curlevel == mmu.max_level) { if (start != (uintptr_t)-1) { ptmap_report(entry, start, user, writable, wflag); } goto out; } curlevel++; } } out: return (DCMD_OK); } /* * 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. * * Copyright 2018 Joyent, Inc. */ #ifndef _I86MMU_H #define _I86MMU_H #ifdef __cplusplus extern "C" { #endif extern int pte_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int report_maps_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int htables_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int ptable_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int ptmap_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int va2pfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int mfntopfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int pfntomfn_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int memseg_list(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv); extern int memseg_walk_init(mdb_walk_state_t *); extern int memseg_walk_step(mdb_walk_state_t *); extern void memseg_walk_fini(mdb_walk_state_t *); extern void free_mmu(void); #ifdef __cplusplus } #endif #endif /* _I86MMU_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "i86mmu.h" #include "unix_sup.h" #include #include #include #include #define TT_HDLR_WIDTH 17 /* apix only */ static apix_impl_t *d_apixs[NCPU]; static int use_apix = 0; static int ttrace_ttr_size_check(void) { mdb_ctf_id_t ttrtid; ssize_t ttr_size; if (mdb_ctf_lookup_by_name("trap_trace_rec_t", &ttrtid) != 0 || mdb_ctf_type_resolve(ttrtid, &ttrtid) != 0) { mdb_warn("failed to determine size of trap_trace_rec_t; " "non-TRAPTRACE kernel?\n"); return (0); } if ((ttr_size = mdb_ctf_type_size(ttrtid)) != sizeof (trap_trace_rec_t)) { /* * On Intel machines, this will happen when TTR_STACK_DEPTH * is changed. This code could be smarter, and could * dynamically adapt to different depths, but not until a * need for such adaptation is demonstrated. */ mdb_warn("size of trap_trace_rec_t (%d bytes) doesn't " "match expected %d\n", ttr_size, sizeof (trap_trace_rec_t)); return (0); } return (1); } int ttrace_walk_init(mdb_walk_state_t *wsp) { trap_trace_ctl_t *ttcp; size_t ttc_size = sizeof (trap_trace_ctl_t) * NCPU; int i; if (!ttrace_ttr_size_check()) return (WALK_ERR); ttcp = mdb_zalloc(ttc_size, UM_SLEEP); if (wsp->walk_addr != 0) { mdb_warn("ttrace only supports global walks\n"); return (WALK_ERR); } if (mdb_readsym(ttcp, ttc_size, "trap_trace_ctl") == -1) { mdb_warn("symbol 'trap_trace_ctl' not found; " "non-TRAPTRACE kernel?\n"); mdb_free(ttcp, ttc_size); return (WALK_ERR); } /* * We'll poach the ttc_current pointer (which isn't used for * anything) to store a pointer to our current TRAPTRACE record. * This allows us to only keep the array of trap_trace_ctl structures * as our walker state (ttc_current may be the only kernel data * structure member added exclusively to make writing the mdb walker * a little easier). */ for (i = 0; i < NCPU; i++) { trap_trace_ctl_t *ttc = &ttcp[i]; if (ttc->ttc_first == 0) continue; /* * Assign ttc_current to be the last completed record. * Note that the error checking (i.e. in the ttc_next == * ttc_first case) is performed in the step function. */ ttc->ttc_current = ttc->ttc_next - sizeof (trap_trace_rec_t); } wsp->walk_data = ttcp; return (WALK_NEXT); } int ttrace_walk_step(mdb_walk_state_t *wsp) { trap_trace_ctl_t *ttcp = wsp->walk_data, *ttc, *latest_ttc; trap_trace_rec_t rec; int rval, i, recsize = sizeof (trap_trace_rec_t); hrtime_t latest = 0; /* * Loop through the CPUs, looking for the latest trap trace record * (we want to walk through the trap trace records in reverse * chronological order). */ for (i = 0; i < NCPU; i++) { ttc = &ttcp[i]; if (ttc->ttc_current == 0) continue; if (ttc->ttc_current < ttc->ttc_first) ttc->ttc_current = ttc->ttc_limit - recsize; if (mdb_vread(&rec, sizeof (rec), ttc->ttc_current) == -1) { mdb_warn("couldn't read rec at %p", ttc->ttc_current); return (WALK_ERR); } if (rec.ttr_stamp > latest) { latest = rec.ttr_stamp; latest_ttc = ttc; } } if (latest == 0) return (WALK_DONE); ttc = latest_ttc; if (mdb_vread(&rec, sizeof (rec), ttc->ttc_current) == -1) { mdb_warn("couldn't read rec at %p", ttc->ttc_current); return (WALK_ERR); } rval = wsp->walk_callback(ttc->ttc_current, &rec, wsp->walk_cbdata); if (ttc->ttc_current == ttc->ttc_next) ttc->ttc_current = 0; else ttc->ttc_current -= sizeof (trap_trace_rec_t); return (rval); } void ttrace_walk_fini(mdb_walk_state_t *wsp) { mdb_free(wsp->walk_data, sizeof (trap_trace_ctl_t) * NCPU); } static int ttrace_syscall(trap_trace_rec_t *rec) { GElf_Sym sym; int sysnum = rec->ttr_sysnum; uintptr_t addr; struct sysent sys; mdb_printf("%-3x", sysnum); if (rec->ttr_sysnum > NSYSCALL) { mdb_printf(" %-*d", TT_HDLR_WIDTH, rec->ttr_sysnum); return (0); } if (mdb_lookup_by_name("sysent", &sym) == -1) { mdb_warn("\ncouldn't find 'sysent'"); return (-1); } addr = (uintptr_t)sym.st_value + sysnum * sizeof (struct sysent); if (addr >= (uintptr_t)sym.st_value + sym.st_size) { mdb_warn("\nsysnum %d out-of-range\n", sysnum); return (-1); } if (mdb_vread(&sys, sizeof (sys), addr) == -1) { mdb_warn("\nfailed to read sysent at %p", addr); return (-1); } mdb_printf(" %-*a", TT_HDLR_WIDTH, sys.sy_callc); return (0); } static int ttrace_interrupt(trap_trace_rec_t *rec) { GElf_Sym sym; uintptr_t addr; struct av_head hd; struct autovec av; switch (rec->ttr_regs.r_trapno) { case T_SOFTINT: mdb_printf("%-3s %-*s", "-", TT_HDLR_WIDTH, "(fakesoftint)"); return (0); default: break; } mdb_printf("%-3x ", rec->ttr_vector); if (mdb_lookup_by_name("autovect", &sym) == -1) { mdb_warn("\ncouldn't find 'autovect'"); return (-1); } addr = (uintptr_t)sym.st_value + rec->ttr_vector * sizeof (struct av_head); if (addr >= (uintptr_t)sym.st_value + sym.st_size) { mdb_warn("\nav_head for vec %x is corrupt\n", rec->ttr_vector); return (-1); } if (mdb_vread(&hd, sizeof (hd), addr) == -1) { mdb_warn("\ncouldn't read av_head for vec %x", rec->ttr_vector); return (-1); } if (hd.avh_link == NULL) { if (rec->ttr_ipl == XC_CPUPOKE_PIL) mdb_printf("%-*s", TT_HDLR_WIDTH, "(cpupoke)"); else mdb_printf("%-*s", TT_HDLR_WIDTH, "(spurious)"); } else { if (mdb_vread(&av, sizeof (av), (uintptr_t)hd.avh_link) == -1) { mdb_warn("couldn't read autovec at %p", (uintptr_t)hd.avh_link); } mdb_printf("%-*a", TT_HDLR_WIDTH, av.av_vector); } return (0); } static int ttrace_apix_interrupt(trap_trace_rec_t *rec) { struct autovec av; apix_impl_t apix; apix_vector_t apix_vector; switch (rec->ttr_regs.r_trapno) { case T_SOFTINT: mdb_printf("%-3s %-*s", "-", TT_HDLR_WIDTH, "(fakesoftint)"); return (0); default: break; } mdb_printf("%-3x ", rec->ttr_vector); /* Read the per CPU apix entry */ if (mdb_vread(&apix, sizeof (apix_impl_t), (uintptr_t)d_apixs[rec->ttr_cpuid]) == -1) { mdb_warn("\ncouldn't read apix[%d]", rec->ttr_cpuid); return (-1); } if (mdb_vread(&apix_vector, sizeof (apix_vector_t), (uintptr_t)apix.x_vectbl[rec->ttr_vector]) == -1) { mdb_warn("\ncouldn't read apix_vector_t[%d]", rec->ttr_vector); return (-1); } if (apix_vector.v_share == 0) { if (rec->ttr_ipl == XC_CPUPOKE_PIL) mdb_printf("%-*s", TT_HDLR_WIDTH, "(cpupoke)"); else mdb_printf("%-*s", TT_HDLR_WIDTH, "(spurious)"); } else { if (mdb_vread(&av, sizeof (struct autovec), (uintptr_t)(apix_vector.v_autovect)) == -1) { mdb_warn("couldn't read autovec at %p", (uintptr_t)apix_vector.v_autovect); } mdb_printf("%-*a", TT_HDLR_WIDTH, av.av_vector); } return (0); } static struct { int tt_trapno; char *tt_name; } ttrace_traps[] = { { T_ZERODIV, "divide-error" }, { T_SGLSTP, "debug-exception" }, { T_NMIFLT, "nmi-interrupt" }, { T_BPTFLT, "breakpoint" }, { T_OVFLW, "into-overflow" }, { T_BOUNDFLT, "bound-exceeded" }, { T_ILLINST, "invalid-opcode" }, { T_NOEXTFLT, "device-not-avail" }, { T_DBLFLT, "double-fault" }, { T_EXTOVRFLT, "segment-overrun" }, { T_TSSFLT, "invalid-tss" }, { T_SEGFLT, "segment-not-pres" }, { T_STKFLT, "stack-fault" }, { T_GPFLT, "general-protectn" }, { T_PGFLT, "page-fault" }, { T_EXTERRFLT, "error-fault" }, { T_ALIGNMENT, "alignment-check" }, { T_MCE, "machine-check" }, { T_SIMDFPE, "sse-exception" }, { T_DBGENTR, "debug-enter" }, { T_FASTTRAP, "fasttrap-0xd2" }, { T_SYSCALLINT, "syscall-0x91" }, { T_DTRACE_RET, "dtrace-ret" }, { T_SOFTINT, "softint" }, { T_INTERRUPT, "interrupt" }, { T_FAULT, "fault" }, { T_AST, "ast" }, { T_SYSCALL, "syscall" }, { 0, NULL } }; static int ttrace_trap(trap_trace_rec_t *rec) { int i; if (rec->ttr_regs.r_trapno == T_AST) mdb_printf("%-3s ", "-"); else mdb_printf("%-3x ", rec->ttr_regs.r_trapno); for (i = 0; ttrace_traps[i].tt_name != NULL; i++) { if (rec->ttr_regs.r_trapno == ttrace_traps[i].tt_trapno) break; } if (ttrace_traps[i].tt_name == NULL) mdb_printf("%-*s", TT_HDLR_WIDTH, "(unknown)"); else mdb_printf("%-*s", TT_HDLR_WIDTH, ttrace_traps[i].tt_name); return (0); } static void ttrace_intr_detail(trap_trace_rec_t *rec) { mdb_printf("\tirq %x ipl %d oldpri %d basepri %d\n", rec->ttr_vector, rec->ttr_ipl, rec->ttr_pri, rec->ttr_spl); } static struct { uchar_t t_marker; char *t_name; int (*t_hdlr)(trap_trace_rec_t *); } ttrace_hdlr[] = { { TT_SYSCALL, "sysc", ttrace_syscall }, { TT_SYSENTER, "syse", ttrace_syscall }, { TT_SYSC, "asys", ttrace_syscall }, { TT_SYSC64, "sc64", ttrace_syscall }, { TT_INTERRUPT, "intr", ttrace_interrupt }, { TT_TRAP, "trap", ttrace_trap }, { TT_EVENT, "evnt", ttrace_trap }, { 0, NULL, NULL } }; typedef struct ttrace_dcmd { processorid_t ttd_cpu; uint_t ttd_extended; uintptr_t ttd_kthread; trap_trace_ctl_t ttd_ttc[NCPU]; } ttrace_dcmd_t; #if defined(__amd64) #define DUMP(reg) #reg, regs->r_##reg #define THREEREGS " %3s: %16lx %3s: %16lx %3s: %16lx\n" static void ttrace_dumpregs(trap_trace_rec_t *rec) { struct regs *regs = &rec->ttr_regs; mdb_printf(THREEREGS, DUMP(rdi), DUMP(rsi), DUMP(rdx)); mdb_printf(THREEREGS, DUMP(rcx), DUMP(r8), DUMP(r9)); mdb_printf(THREEREGS, DUMP(rax), DUMP(rbx), DUMP(rbp)); mdb_printf(THREEREGS, DUMP(r10), DUMP(r11), DUMP(r12)); mdb_printf(THREEREGS, DUMP(r13), DUMP(r14), DUMP(r15)); mdb_printf(THREEREGS, DUMP(ds), DUMP(es), DUMP(fs)); mdb_printf(THREEREGS, DUMP(gs), "trp", regs->r_trapno, DUMP(err)); mdb_printf(THREEREGS, DUMP(rip), DUMP(cs), DUMP(rfl)); mdb_printf(THREEREGS, DUMP(rsp), DUMP(ss), "cr2", rec->ttr_cr2); mdb_printf(" %3s: %16lx %3s: %16lx\n", "fsb", regs->__r_fsbase, "gsb", regs->__r_gsbase); mdb_printf("\n"); } #else #define DUMP(reg) #reg, regs->r_##reg #define FOURREGS " %3s: %08x %3s: %08x %3s: %08x %3s: %08x\n" static void ttrace_dumpregs(trap_trace_rec_t *rec) { struct regs *regs = &rec->ttr_regs; mdb_printf(FOURREGS, DUMP(gs), DUMP(fs), DUMP(es), DUMP(ds)); mdb_printf(FOURREGS, DUMP(edi), DUMP(esi), DUMP(ebp), DUMP(esp)); mdb_printf(FOURREGS, DUMP(ebx), DUMP(edx), DUMP(ecx), DUMP(eax)); mdb_printf(FOURREGS, "trp", regs->r_trapno, DUMP(err), DUMP(pc), DUMP(cs)); mdb_printf(FOURREGS, DUMP(efl), "usp", regs->r_uesp, DUMP(ss), "cr2", rec->ttr_cr2); mdb_printf("\n"); } #endif /* __amd64 */ int ttrace_walk(uintptr_t addr, trap_trace_rec_t *rec, ttrace_dcmd_t *dcmd) { struct regs *regs = &rec->ttr_regs; processorid_t cpu = -1, i; for (i = 0; i < NCPU; i++) { if (addr >= dcmd->ttd_ttc[i].ttc_first && addr < dcmd->ttd_ttc[i].ttc_limit) { cpu = i; break; } } if (cpu == -1) { mdb_warn("couldn't find %p in any trap trace ctl\n", addr); return (WALK_ERR); } if (dcmd->ttd_cpu != -1 && cpu != dcmd->ttd_cpu) return (WALK_NEXT); if (dcmd->ttd_kthread != 0 && dcmd->ttd_kthread != rec->ttr_curthread) return (WALK_NEXT); mdb_printf("%3d %15llx ", cpu, rec->ttr_stamp); for (i = 0; ttrace_hdlr[i].t_hdlr != NULL; i++) { if (rec->ttr_marker != ttrace_hdlr[i].t_marker) continue; mdb_printf("%4s ", ttrace_hdlr[i].t_name); if (ttrace_hdlr[i].t_hdlr(rec) == -1) return (WALK_ERR); } mdb_printf(" %a\n", regs->r_pc); if (dcmd->ttd_extended == FALSE) return (WALK_NEXT); if (rec->ttr_marker == TT_INTERRUPT) ttrace_intr_detail(rec); else ttrace_dumpregs(rec); if (rec->ttr_sdepth > 0) { for (i = 0; i < rec->ttr_sdepth; i++) { if (i >= TTR_STACK_DEPTH) { mdb_printf("%17s*** invalid ttr_sdepth (is %d, " "should be <= %d)\n", " ", rec->ttr_sdepth, TTR_STACK_DEPTH); break; } mdb_printf("%17s %a()\n", " ", rec->ttr_stack[i]); } mdb_printf("\n"); } return (WALK_NEXT); } int ttrace(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { ttrace_dcmd_t dcmd; trap_trace_ctl_t *ttc = dcmd.ttd_ttc; trap_trace_rec_t rec; size_t ttc_size = sizeof (trap_trace_ctl_t) * NCPU; if (!ttrace_ttr_size_check()) return (WALK_ERR); bzero(&dcmd, sizeof (dcmd)); dcmd.ttd_cpu = -1; dcmd.ttd_extended = FALSE; if (mdb_readsym(ttc, ttc_size, "trap_trace_ctl") == -1) { mdb_warn("symbol 'trap_trace_ctl' not found; " "non-TRAPTRACE kernel?\n"); return (DCMD_ERR); } if (mdb_getopts(argc, argv, 'x', MDB_OPT_SETBITS, TRUE, &dcmd.ttd_extended, 't', MDB_OPT_UINTPTR, &dcmd.ttd_kthread, NULL) != argc) return (DCMD_USAGE); if (DCMD_HDRSPEC(flags)) { mdb_printf("%3s %15s %4s %2s %-*s%s\n", "CPU", "TIMESTAMP", "TYPE", "Vec", TT_HDLR_WIDTH, "HANDLER", " EIP"); } if (flags & DCMD_ADDRSPEC) { if (addr >= NCPU) { if (mdb_vread(&rec, sizeof (rec), addr) == -1) { mdb_warn("couldn't read trap trace record " "at %p", addr); return (DCMD_ERR); } if (ttrace_walk(addr, &rec, &dcmd) == WALK_ERR) return (DCMD_ERR); return (DCMD_OK); } dcmd.ttd_cpu = addr; } if (mdb_readvar(&use_apix, "apix_enable") == -1) { mdb_warn("failed to read apix_enable"); use_apix = 0; } if (use_apix) { if (mdb_readvar(&d_apixs, "apixs") == -1) { mdb_warn("\nfailed to read apixs."); return (DCMD_ERR); } /* change to apix ttrace interrupt handler */ ttrace_hdlr[4].t_hdlr = ttrace_apix_interrupt; } if (mdb_walk("ttrace", (mdb_walk_cb_t)ttrace_walk, &dcmd) == -1) { mdb_warn("couldn't walk 'ttrace'"); return (DCMD_ERR); } return (DCMD_OK); } /*ARGSUSED*/ int mutex_owner_init(mdb_walk_state_t *wsp) { return (WALK_NEXT); } int mutex_owner_step(mdb_walk_state_t *wsp) { uintptr_t addr = wsp->walk_addr; mutex_impl_t mtx; uintptr_t owner; kthread_t thr; if (mdb_vread(&mtx, sizeof (mtx), addr) == -1) return (WALK_ERR); if (!MUTEX_TYPE_ADAPTIVE(&mtx)) return (WALK_DONE); if ((owner = (uintptr_t)MUTEX_OWNER(&mtx)) == 0) return (WALK_DONE); if (mdb_vread(&thr, sizeof (thr), owner) != -1) (void) wsp->walk_callback(owner, &thr, wsp->walk_cbdata); return (WALK_DONE); } static void gate_desc_dump(gate_desc_t *gate, const char *label, int header) { const char *lastnm; uint_t lastval; char type[4]; switch (gate->sgd_type) { case SDT_SYSIGT: strcpy(type, "int"); break; case SDT_SYSTGT: strcpy(type, "trp"); break; case SDT_SYSTASKGT: strcpy(type, "tsk"); break; default: (void) mdb_snprintf(type, sizeof (type), "%3x", gate->sgd_type); } #if defined(__amd64) lastnm = "IST"; lastval = gate->sgd_ist; #else lastnm = "STK"; lastval = gate->sgd_stkcpy; #endif if (header) { mdb_printf("%*s%%-30s% %%-4s% %%3s% " "%%1s% %%3s% %%3s%\n", strlen(label), "", "HANDLER", "SEL", "DPL", "P", "TYP", lastnm); } mdb_printf("%s", label); if (gate->sgd_type == SDT_SYSTASKGT) mdb_printf("%-30s ", "-"); else mdb_printf("%-30a ", GATESEG_GETOFFSET(gate)); mdb_printf("%4x %d %c %3s %2x\n", gate->sgd_selector, gate->sgd_dpl, (gate->sgd_p ? '+' : ' '), type, lastval); } /*ARGSUSED*/ static int gate_desc(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { gate_desc_t gate; if (argc != 0 || !(flags & DCMD_ADDRSPEC)) return (DCMD_USAGE); if (mdb_vread(&gate, sizeof (gate_desc_t), addr) != sizeof (gate_desc_t)) { mdb_warn("failed to read gate descriptor at %p\n", addr); return (DCMD_ERR); } gate_desc_dump(&gate, "", DCMD_HDRSPEC(flags)); return (DCMD_OK); } /*ARGSUSED*/ static int idt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i; if (!(flags & DCMD_ADDRSPEC)) { GElf_Sym idt0_va; gate_desc_t *idt0; if (mdb_lookup_by_name("idt0", &idt0_va) < 0) { mdb_warn("failed to find VA of idt0"); return (DCMD_ERR); } addr = idt0_va.st_value; if (mdb_vread(&idt0, sizeof (idt0), addr) != sizeof (idt0)) { mdb_warn("failed to read idt0 at %p\n", addr); return (DCMD_ERR); } addr = (uintptr_t)idt0; } for (i = 0; i < NIDT; i++, addr += sizeof (gate_desc_t)) { gate_desc_t gate; char label[6]; if (mdb_vread(&gate, sizeof (gate_desc_t), addr) != sizeof (gate_desc_t)) { mdb_warn("failed to read gate descriptor at %p\n", addr); return (DCMD_ERR); } (void) mdb_snprintf(label, sizeof (label), "%3d: ", i); gate_desc_dump(&gate, label, i == 0); } return (DCMD_OK); } static void htables_help(void) { mdb_printf( "Given a (hat_t *), generates the list of all (htable_t *)s\n" "that correspond to that address space\n"); } static void report_maps_help(void) { mdb_printf( "Given a PFN, report HAT structures that map the page, or use\n" "the page as a pagetable.\n" "\n" "-m Interpret the PFN as an MFN (machine frame number)\n"); } static void ptable_help(void) { mdb_printf( "Given a PFN holding a page table, print its contents, and\n" "the address of the corresponding htable structure.\n" "\n" "-m Interpret the PFN as an MFN (machine frame number)\n" "-l force page table level (3 is top)\n"); } static void ptmap_help(void) { mdb_printf( "Report all mappings represented by the page table hierarchy\n" "rooted at the given cr3 value / physical address.\n" "\n" "-w run ::whatis on mapping start addresses\n"); } static const char *const scalehrtime_desc = "Scales a timestamp from ticks to nanoseconds. Unscaled timestamps\n" "are used as both a quick way of accumulating relative time (as for\n" "usage) and as a quick way of getting the absolute current time.\n" "These uses require slightly different scaling algorithms. By\n" "default, if a specified time is greater than half of the unscaled\n" "time at the last tick (that is, if the unscaled time represents\n" "more than half the time since boot), the timestamp is assumed to\n" "be absolute, and the scaling algorithm used mimics that which the\n" "kernel uses in gethrtime(). Otherwise, the timestamp is assumed to\n" "be relative, and the algorithm mimics scalehrtime(). This behavior\n" "can be overridden by forcing the unscaled time to be interpreted\n" "as relative (via -r) or absolute (via -a).\n"; static void scalehrtime_help(void) { mdb_printf("%s", scalehrtime_desc); } /* * NSEC_SHIFT is replicated here (it is not defined in a header file), * but for amusement, the reader is directed to the comment that explains * the rationale for this particular value on x86. Spoiler: the value is * selected to accommodate 60 MHz Pentiums! (And a confession: if the voice * in that comment sounds too familiar, it's because your author also wrote * that code -- some fifteen years prior to this writing in 2011...) */ #define NSEC_SHIFT 5 /*ARGSUSED*/ static int scalehrtime_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { uint32_t nsec_scale; hrtime_t tsc = addr, hrt, tsc_last, base, mult = 1; unsigned int *tscp = (unsigned int *)&tsc; uintptr_t scalehrtimef; uint64_t scale; GElf_Sym sym; int expected = !(flags & DCMD_ADDRSPEC); uint_t absolute = FALSE, relative = FALSE; if (mdb_getopts(argc, argv, 'a', MDB_OPT_SETBITS, TRUE, &absolute, 'r', MDB_OPT_SETBITS, TRUE, &relative, NULL) != argc - expected) return (DCMD_USAGE); if (absolute && relative) { mdb_warn("can't specify both -a and -r\n"); return (DCMD_USAGE); } if (expected == 1) { switch (argv[argc - 1].a_type) { case MDB_TYPE_STRING: tsc = mdb_strtoull(argv[argc - 1].a_un.a_str); break; case MDB_TYPE_IMMEDIATE: tsc = argv[argc - 1].a_un.a_val; break; default: return (DCMD_USAGE); } } if (mdb_readsym(&scalehrtimef, sizeof (scalehrtimef), "scalehrtimef") == -1) { mdb_warn("couldn't read 'scalehrtimef'"); return (DCMD_ERR); } if (mdb_lookup_by_name("tsc_scalehrtime", &sym) == -1) { mdb_warn("couldn't find 'tsc_scalehrtime'"); return (DCMD_ERR); } if (sym.st_value != scalehrtimef) { mdb_warn("::scalehrtime requires that scalehrtimef " "be set to tsc_scalehrtime\n"); return (DCMD_ERR); } if (mdb_readsym(&nsec_scale, sizeof (nsec_scale), "nsec_scale") == -1) { mdb_warn("couldn't read 'nsec_scale'"); return (DCMD_ERR); } if (mdb_readsym(&tsc_last, sizeof (tsc_last), "tsc_last") == -1) { mdb_warn("couldn't read 'tsc_last'"); return (DCMD_ERR); } if (mdb_readsym(&base, sizeof (base), "tsc_hrtime_base") == -1) { mdb_warn("couldn't read 'tsc_hrtime_base'"); return (DCMD_ERR); } /* * If our time is greater than half of tsc_last, we will take our * delta against tsc_last, convert it, and add that to (or subtract it * from) tsc_hrtime_base. This mimics what the kernel actually does * in gethrtime() (modulo the tsc_sync_tick_delta) and gets us a much * higher precision result than trying to convert a large tsc value. */ if (absolute || (tsc > (tsc_last >> 1) && !relative)) { if (tsc > tsc_last) { tsc = tsc - tsc_last; } else { tsc = tsc_last - tsc; mult = -1; } } else { base = 0; } scale = (uint64_t)nsec_scale; hrt = ((uint64_t)tscp[1] * scale) << NSEC_SHIFT; hrt += ((uint64_t)tscp[0] * scale) >> (32 - NSEC_SHIFT); mdb_printf("0x%llx\n", base + (hrt * mult)); return (DCMD_OK); } /* * The x86 feature set is implemented as a bitmap array. That bitmap array is * stored across a number of uchars based on the BT_SIZEOFMAP(NUM_X86_FEATURES) * macro. We have the names for each of these features in unix's text segment * so we do not have to duplicate them and instead just look them up. */ /*ARGSUSED*/ static int x86_featureset_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { void *fset; GElf_Sym sym; uintptr_t nptr; char name[128]; int ii; size_t sz = sizeof (uchar_t) * BT_SIZEOFMAP(NUM_X86_FEATURES); if (argc != 0) return (DCMD_USAGE); if (mdb_lookup_by_name("x86_feature_names", &sym) == -1) { mdb_warn("couldn't find x86_feature_names"); return (DCMD_ERR); } fset = mdb_zalloc(sz, UM_NOSLEEP); if (fset == NULL) { mdb_warn("failed to allocate memory for x86_featureset"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC) { if (mdb_vread(fset, sz, addr) != sz) { mdb_warn("failed to read x86_featureset from %p", addr); mdb_free(fset, sz); return (DCMD_ERR); } } else { if (mdb_readvar(fset, "x86_featureset") != sz) { mdb_warn("failed to read x86_featureset"); mdb_free(fset, sz); return (DCMD_ERR); } } for (ii = 0; ii < NUM_X86_FEATURES; ii++) { if (!BT_TEST((ulong_t *)fset, ii)) continue; if (mdb_vread(&nptr, sizeof (char *), sym.st_value + sizeof (void *) * ii) != sizeof (char *)) { mdb_warn("failed to read feature array %d", ii); mdb_free(fset, sz); return (DCMD_ERR); } if (mdb_readstr(name, sizeof (name), nptr) == -1) { mdb_printf("unknown feature 0x%x\n", ii); } else { mdb_printf("%s\n", name); } } mdb_free(fset, sz); return (DCMD_OK); } #ifdef _KMDB /* ARGSUSED */ static int sysregs_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { struct sysregs sregs = { 0 }; desctbr_t gdtr; boolean_t longmode = B_FALSE; #ifdef __amd64 longmode = B_TRUE; #endif sregs.sr_cr0 = kmdb_unix_getcr0(); sregs.sr_cr2 = kmdb_unix_getcr2(); sregs.sr_cr3 = kmdb_unix_getcr3(); sregs.sr_cr4 = kmdb_unix_getcr4(); kmdb_unix_getgdtr(&gdtr); sregs.sr_gdtr.d_base = gdtr.dtr_base; sregs.sr_gdtr.d_lim = gdtr.dtr_limit; mdb_x86_print_sysregs(&sregs, longmode); return (DCMD_OK); } #endif extern void xcall_help(void); extern int xcall_dcmd(uintptr_t, uint_t, int, const mdb_arg_t *); static const mdb_dcmd_t dcmds[] = { { "gate_desc", ":", "dump a gate descriptor", gate_desc }, { "idt", ":[-v]", "dump an IDT", idt }, { "ttrace", "[-x] [-t kthread]", "dump trap trace buffers", ttrace }, { "vatopfn", ":[-a as]", "translate address to physical page", va2pfn_dcmd }, { "report_maps", ":[-m]", "Given PFN, report mappings / page table usage", report_maps_dcmd, report_maps_help }, { "htables", "", "Given hat_t *, lists all its htable_t * values", htables_dcmd, htables_help }, { "ptable", ":[-lm]", "Given PFN, dump contents of a page table", ptable_dcmd, ptable_help }, { "ptmap", ":", "Given a cr3 value, dump all mappings", ptmap_dcmd, ptmap_help }, { "pte", ":[-l N]", "print human readable page table entry", pte_dcmd }, { "pfntomfn", ":", "convert physical page to hypervisor machine page", pfntomfn_dcmd }, { "mfntopfn", ":", "convert hypervisor machine page to physical page", mfntopfn_dcmd }, { "memseg_list", ":", "show memseg list", memseg_list }, { "scalehrtime", ":[-a|-r]", "scale an unscaled high-res time", scalehrtime_dcmd, scalehrtime_help }, { "x86_featureset", ":", "dump the x86_featureset vector", x86_featureset_dcmd }, { "xcall", ":", "print CPU cross-call state", xcall_dcmd, xcall_help }, #ifdef _KMDB { "sysregs", NULL, "dump system registers", sysregs_dcmd }, #endif { NULL } }; static const mdb_walker_t walkers[] = { { "ttrace", "walks trap trace buffers in reverse chronological order", ttrace_walk_init, ttrace_walk_step, ttrace_walk_fini }, { "mutex_owner", "walks the owner of a mutex", mutex_owner_init, mutex_owner_step }, { "memseg", "walk the memseg structures", memseg_walk_init, memseg_walk_step, memseg_walk_fini }, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, walkers }; const mdb_modinfo_t * _mdb_init(void) { return (&modinfo); } void _mdb_fini(void) { free_mmu(); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Joyent, Inc. */ /* * Support routines for the unix kmdb module */ #if defined(__lint) #include #else #include .file "unix_sup.s" ENTRY(kmdb_unix_getcr0) movq %cr0, %rax ret SET_SIZE(kmdb_unix_getcr0) ENTRY(kmdb_unix_getcr2) movq %cr2, %rax ret SET_SIZE(kmdb_unix_getcr2) ENTRY(kmdb_unix_getcr3) movq %cr3, %rax ret SET_SIZE(kmdb_unix_getcr3) ENTRY(kmdb_unix_getcr4) movq %cr4, %rax ret SET_SIZE(kmdb_unix_getcr4) ENTRY(kmdb_unix_getgdtr) sgdt (%rdi) ret SET_SIZE(kmdb_unix_getgdtr) #endif /* !__lint */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2018 Joyent, Inc. */ #ifndef _UNIX_SUP_H #define _UNIX_SUP_H /* * Support routines for unix. */ #include #include #ifdef __cplusplus extern "C" { #endif extern ulong_t kmdb_unix_getcr0(void); extern ulong_t kmdb_unix_getcr2(void); extern ulong_t kmdb_unix_getcr3(void); extern ulong_t kmdb_unix_getcr4(void); extern void kmdb_unix_getgdtr(desctbr_t *); #ifdef __cplusplus } #endif #endif /* _UNIX_SUP_H */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include typedef struct { uint32_t xc_work_cnt; struct xc_msg *xc_curmsg; struct xc_msg *xc_msgbox; xc_data_t xc_data; } mdb_xcall_machcpu_t; typedef struct { processorid_t cpu_id; mdb_xcall_machcpu_t cpu_m; } mdb_xcall_cpu_t; typedef struct { uint_t xd_flags; processorid_t xd_cpu_id; size_t xd_msg_index; struct xc_msg xd_msgs[NCPU]; } xcall_data_t; void xcall_help(void) { mdb_printf( "Print all active cross-calls where the given CPU is the master.\n" "The PEND column is ->xc_work_cnt, the pending message count -\n" "this includes both master and slave messages. For each\n" "cross call, the message type and the slave CPU ID are shown.\n"); } static int cpu_id_to_addr(processorid_t cpun, uintptr_t *addrp) { uintptr_t addr; GElf_Sym sym; if (mdb_lookup_by_name("cpu", &sym) == -1) { mdb_warn("failed to find symbol for 'cpu'"); return (-1); } if (cpun * sizeof (uintptr_t) > sym.st_size) return (-1); addr = (uintptr_t)sym.st_value + cpun * sizeof (uintptr_t); if (mdb_vread(&addr, sizeof (addr), addr) == -1) { mdb_warn("failed to read cpu[%lu]", cpun); return (-1); } if (addr != (uintptr_t)NULL) { *addrp = addr; return (0); } return (-1); } static int xcall_copy_msg(struct xc_msg *msg, xcall_data_t *data, boolean_t current) { if (data->xd_msg_index >= NCPU) { mdb_warn("ran out of msg space: %lu >= %lu\n", data->xd_msg_index, NCPU); return (-1); } bcopy(msg, &data->xd_msgs[data->xd_msg_index], sizeof (*msg)); /* * As we don't use .xc_next, store 'current' there. */ data->xd_msgs[data->xd_msg_index].xc_next = (void *)(uintptr_t)current; data->xd_msg_index++; return (0); } static int xcall_get_msgs(uintptr_t addr, const void *wdata, void *priv) { _NOTE(ARGUNUSED(wdata)); xcall_data_t *data = priv; mdb_xcall_cpu_t xcpu = { 0, }; struct xc_msg msg; uintptr_t msgaddr; if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t", addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1) return (WALK_ERR); if (xcpu.cpu_m.xc_curmsg != NULL) { msgaddr = (uintptr_t)xcpu.cpu_m.xc_curmsg; if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg)) return (WALK_ERR); if (msg.xc_master == data->xd_cpu_id) { if (data->xd_flags & DCMD_PIPE_OUT) mdb_printf("%p\n", msgaddr); else if (xcall_copy_msg(&msg, data, B_TRUE) != 0) return (WALK_ERR); } } for (msgaddr = (uintptr_t)xcpu.cpu_m.xc_msgbox; msgaddr != (uintptr_t)NULL; msgaddr = (uintptr_t)msg.xc_next) { if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg)) return (WALK_ERR); if (msg.xc_master != data->xd_cpu_id) continue; if (data->xd_flags & DCMD_PIPE_OUT) mdb_printf("%p\n", msgaddr); else if (xcall_copy_msg(&msg, data, B_FALSE) != 0) return (WALK_ERR); } return (WALK_NEXT); } static int print_xcall_msg(struct xc_msg *msg) { boolean_t current = (boolean_t)msg->xc_next; char indent[] = " "; const char *cmd; switch (msg->xc_command) { case XC_MSG_ASYNC: cmd = "ASYNC"; break; case XC_MSG_CALL: cmd = "CALL"; break; case XC_MSG_SYNC: cmd = "SYNC"; break; case XC_MSG_FREE:cmd = "FREE"; break; case XC_MSG_WAITING: cmd = "WAITING"; break; case XC_MSG_RELEASED: cmd = "RELEASED"; break; case XC_MSG_DONE: cmd = "DONE"; break; default: cmd = "?"; break; } mdb_printf("%s %s%-*s %-6u\n", indent, current ? "*" : "", 9 - current, cmd, msg->xc_slave); return (0); } /* * Show all xcall messages where the master is the given CPU. * * As non-free messages can be on the slave's ->xc_msgbox or ->xc_curmsg, we * need to walk across all of them to find each message where ->xc_master * is our CPU ID. */ int xcall_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_xcall_cpu_t xcpu = { 0, }; xcall_data_t data = { 0, }; if (mdb_getopts(argc, argv, NULL) != argc) return (DCMD_USAGE); /* * Yep, this will re-collect all the messages each time. Shrug. */ if (!(flags & DCMD_ADDRSPEC)) { if (mdb_pwalk_dcmd("cpu", "xcall", argc, argv, 0) == -1) { mdb_warn("can't walk CPUs"); return (DCMD_ERR); } return (DCMD_OK); } if (addr < NCPU && cpu_id_to_addr((processorid_t)addr, &addr) != 0) { mdb_warn("invalid CPU ID %lu\n", addr); return (DCMD_ERR); } if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t", addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1) { mdb_warn("couldn't read cpu 0x%lx", addr); return (DCMD_ERR); } data.xd_cpu_id = xcpu.cpu_id; data.xd_flags = flags; if (mdb_pwalk("cpu", xcall_get_msgs, &data, (uintptr_t)NULL) == -1) { mdb_warn("can't walk CPUs"); return (DCMD_ERR); } if (flags & DCMD_PIPE_OUT) return (DCMD_OK); if (DCMD_HDRSPEC(flags)) mdb_printf("%%3s %4s %s%\n", "CPU", "PEND", "HANDLER"); if (data.xd_msg_index == 0) { mdb_printf("%3d %4d -\n", xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt); return (DCMD_OK); } mdb_printf("%3d %4d %a(%a, %a, %a)\n", xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt, xcpu.cpu_m.xc_data.xc_func, xcpu.cpu_m.xc_data.xc_a1, xcpu.cpu_m.xc_data.xc_a2, xcpu.cpu_m.xc_data.xc_a3); if (!(flags & DCMD_PIPE_OUT)) mdb_printf(" %%-9s %-6s%\n", "COMMAND", "SLAVE"); for (size_t i = 0; i < data.xd_msg_index; i++) { if (print_xcall_msg(&data.xd_msgs[i]) != 0) return (DCMD_ERR); } return (DCMD_OK); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # include $(SRC)/Makefile.master include ../../../Makefile.subdirs # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2016 Joyent, Inc. MODULE = uppc.so MDBTGT = kvm MODSRCS = uppc.c intr_common.c include ../../../../../Makefile.cmd include ../../../../../Makefile.cmd.64 include ../../../../intel/Makefile.amd64 include ../../../Makefile.i86pc include ../../../../Makefile.module MODSRCS_DIR = ../../common CPPFLAGS += -DMP -D_MACHDEP CPPFLAGS += -I../../common CPPFLAGS += -I../../../../common CPPFLAGS += -I$(SRC)/uts/intel CPPFLAGS += -I$(SRC)/uts/i86pc CERRWARN += -Wno-unused-function /* * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ #include "intr_common.h" static struct av_head avec_tbl[APIC_MAX_VECTOR+1]; static uint16_t shared_tbl[MAX_ISA_IRQ + 1]; static char * interrupt_print_bus(uintptr_t dip_addr) { char bind_name[MAXPATHLEN + 1]; struct dev_info dev_info; if (mdb_vread(&dev_info, sizeof (dev_info), dip_addr) == -1) { mdb_warn("failed to read child dip"); return ("-"); } while (dev_info.devi_parent != 0) { if (mdb_vread(&dev_info, sizeof (dev_info), (uintptr_t)dev_info.devi_parent) == -1) break; (void) mdb_readstr(bind_name, sizeof (bind_name), (uintptr_t)dev_info.devi_binding_name); if (strcmp(bind_name, "isa") == 0) return ("ISA"); else if (strcmp(bind_name, "pci") == 0 || strcmp(bind_name, "npe") == 0) return ("PCI"); } return ("-"); } /* * uppc_interrupt_dump: * Dump uppc(4D) interrupt information. */ /* ARGSUSED */ int uppc_interrupt_dump(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { int i, j; boolean_t found = B_FALSE; struct autovec avhp; option_flags = 0; if (mdb_getopts(argc, argv, 'd', MDB_OPT_SETBITS, INTR_DISPLAY_DRVR_INST, &option_flags, 'i', MDB_OPT_SETBITS, INTR_DISPLAY_INTRSTAT, &option_flags, NULL) != argc) return (DCMD_USAGE); if (mdb_readvar(&avec_tbl, "autovect") == -1) { mdb_warn("failed to read autovect"); return (DCMD_ERR); } if (mdb_readvar(&shared_tbl, "uppc_irq_shared_table") == -1) { mdb_warn("failed to read uppc_irq_shared_table"); return (DCMD_ERR); } /* * By default, on all x86 systems ::interrupts from uppc gets * loaded first. For APIC systems the ::interrupts from either * apix or pcplusmp ought to be executed. Confusion stems as * these three modules export the same dcmd. */ for (i = 0; i < MAX_ISA_IRQ + 1; i++) if (shared_tbl[i]) { found = B_TRUE; break; } if (found == B_FALSE) { if (mdb_lookup_by_obj("apix", "apixs", NULL) == 0) { return (mdb_call_dcmd("apix`interrupts", addr, flags, argc, argv)); } else if (mdb_lookup_by_obj("pcplusmp", "apic_irq_table", NULL) == 0) { return (mdb_call_dcmd("pcplusmp`interrupts", addr, flags, argc, argv)); } } /* Print the header first */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("%CPU "); else mdb_printf("%IRQ Vector IPL(lo/hi) Bus Share "); mdb_printf("%s %\n", option_flags & INTR_DISPLAY_DRVR_INST ? "Driver Name(s)" : "ISR(s)"); /* Walk all the entries */ for (i = 0; i < MAX_ISA_IRQ + 1; i++) { /* Read the entry, if invalid continue */ if (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avec_tbl[i].avh_link) == -1) continue; /* Print each interrupt entry */ if (option_flags & INTR_DISPLAY_INTRSTAT) mdb_printf("cpu0\t"); else mdb_printf("%-3d 0x%2x %4d/%-2d %-4s %-3d ", i, i + PIC_VECTBASE, avec_tbl[i].avh_lo_pri, avec_tbl[i].avh_hi_pri, avhp.av_dip ? interrupt_print_bus((uintptr_t)avhp.av_dip) : " - ", shared_tbl[i]); if (shared_tbl[i]) interrupt_print_isr((uintptr_t)avhp.av_vector, (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip); for (j = 1; j < shared_tbl[i]; j++) { if (mdb_vread(&avhp, sizeof (struct autovec), (uintptr_t)avhp.av_link) != -1) { mdb_printf(", "); interrupt_print_isr((uintptr_t)avhp.av_vector, (uintptr_t)avhp.av_intarg1, (uintptr_t)avhp.av_dip); } else { break; } } mdb_printf("\n"); } return (DCMD_OK); } /* * MDB module linkage information: */ static const mdb_dcmd_t dcmds[] = { { "interrupts", "?[-di]", "print interrupts", uppc_interrupt_dump, interrupt_help}, { "softint", "?[-d]", "print soft interrupts", soft_interrupt_dump, soft_interrupt_help}, { NULL } }; static const mdb_modinfo_t modinfo = { MDB_API_VERSION, dcmds, NULL }; const mdb_modinfo_t * _mdb_init(void) { GElf_Sym sym; if (mdb_lookup_by_name("gld_intr", &sym) != -1) if (GELF_ST_TYPE(sym.st_info) == STT_FUNC) gld_intr_addr = (uintptr_t)sym.st_value; return (&modinfo); }