1047043c2SRobert Mustacchi /* 2047043c2SRobert Mustacchi * This file and its contents are supplied under the terms of the 3047043c2SRobert Mustacchi * Common Development and Distribution License ("CDDL"), version 1.0. 4047043c2SRobert Mustacchi * You may only use this file in accordance with the terms of version 5047043c2SRobert Mustacchi * 1.0 of the CDDL. 6047043c2SRobert Mustacchi * 7047043c2SRobert Mustacchi * A full copy of the text of the CDDL should have accompanied this 8047043c2SRobert Mustacchi * source. A copy of the CDDL is also available via the Internet at 9047043c2SRobert Mustacchi * http://www.illumos.org/license/CDDL. 10047043c2SRobert Mustacchi */ 11047043c2SRobert Mustacchi 12047043c2SRobert Mustacchi /* 13047043c2SRobert Mustacchi * Copyright 2019, Joyent, Inc. 14047043c2SRobert Mustacchi * Copyright 2020 Oxide Computer Company 15047043c2SRobert Mustacchi */ 16047043c2SRobert Mustacchi 17047043c2SRobert Mustacchi /* 18047043c2SRobert Mustacchi * Nexus Driver for AMD Zen family systems. The purpose of this driver is to 19047043c2SRobert Mustacchi * provide access to the following resources in a single, centralized fashion: 20047043c2SRobert Mustacchi * 21047043c2SRobert Mustacchi * - The per-chip Data Fabric 22047043c2SRobert Mustacchi * - The North Bridge 23047043c2SRobert Mustacchi * - The System Management Network (SMN) 24047043c2SRobert Mustacchi * 25047043c2SRobert Mustacchi * This is a nexus driver as once we have attached to all the requisite 26047043c2SRobert Mustacchi * components, we will enumerate child devices which consume this functionality. 27047043c2SRobert Mustacchi * 28047043c2SRobert Mustacchi * ------------------------ 29047043c2SRobert Mustacchi * Mapping Devices Together 30047043c2SRobert Mustacchi * ------------------------ 31047043c2SRobert Mustacchi * 32047043c2SRobert Mustacchi * The operating system needs to expose things like temperature sensors and DRAM 33047043c2SRobert Mustacchi * configuration registers in terms that are meaningful to the system such as 34047043c2SRobert Mustacchi * logical CPUs, cores, etc. This driver attaches to the PCI IDs that represent 35047043c2SRobert Mustacchi * the northbridge and data fabric; however, there are multiple PCI devices (one 36047043c2SRobert Mustacchi * per die) that exist. This driver does manage to map all of these three things 37047043c2SRobert Mustacchi * together; however, it requires some acrobatics. Unfortunately, there's no 38047043c2SRobert Mustacchi * direct way to map a northbridge to its corresponding die. However, we can map 39047043c2SRobert Mustacchi * a CPU die to a data fabric PCI device and a data fabric PCI device to a 40047043c2SRobert Mustacchi * corresponding northbridge PCI device. 41047043c2SRobert Mustacchi * 42047043c2SRobert Mustacchi * In current Zen based products, there is a direct mapping between processor 43047043c2SRobert Mustacchi * nodes and a data fabric PCI device. All of the devices are on PCI Bus 0 and 44047043c2SRobert Mustacchi * start from Device 0x18. Device 0x18 maps to processor node 0, 0x19 to 45047043c2SRobert Mustacchi * processor node 1, etc. This means that to map a logical CPU to a data fabric 46047043c2SRobert Mustacchi * device, we take its processor node id, add it to 0x18 and find the PCI device 47047043c2SRobert Mustacchi * that is on bus 0, device 0x18. As each data fabric device is attached based 48047043c2SRobert Mustacchi * on its PCI ID, we add it to the global list, amd_nbdf_dfs that is in the 49047043c2SRobert Mustacchi * amd_f17nbdf_t structure. 50047043c2SRobert Mustacchi * 51047043c2SRobert Mustacchi * The northbridge PCI device has a defined device and function, but the PCI bus 52047043c2SRobert Mustacchi * that it's on can vary. Each die has its own series of PCI buses that are 53047043c2SRobert Mustacchi * assigned to it and the northbridge PCI device is on the first of die-specific 54047043c2SRobert Mustacchi * PCI bus for each die. This also means that the northbridge will not show up 55047043c2SRobert Mustacchi * on PCI bus 0, which is the PCI bus that all of the data fabric devices are 56047043c2SRobert Mustacchi * on. While conventionally the northbridge with the lowest PCI bus value 57047043c2SRobert Mustacchi * would correspond to processor node zero, hardware does not guarantee that at 58047043c2SRobert Mustacchi * all. Because we don't want to be at the mercy of firmware, we don't rely on 59047043c2SRobert Mustacchi * this ordering, even though we have yet to find a system that deviates from 60047043c2SRobert Mustacchi * this scheme. 61047043c2SRobert Mustacchi * 62047043c2SRobert Mustacchi * One of the registers in the data fabric device's function 0 63047043c2SRobert Mustacchi * (AMDZEN_DF_F0_CFG_ADDR_CTL) happens to have the first PCI bus that is 64047043c2SRobert Mustacchi * associated with the processor node. This means that we can map a data fabric 65047043c2SRobert Mustacchi * device to a northbridge by finding the northbridge whose PCI bus matches the 66047043c2SRobert Mustacchi * value in the corresponding data fabric's AMDZEN_DF_F0_CFG_ADDR_CTL. 67047043c2SRobert Mustacchi * 68047043c2SRobert Mustacchi * We can map a northbridge to a data fabric device and a data fabric device to 69047043c2SRobert Mustacchi * a die. Because these are generally 1:1 mappings, there is a transitive 70047043c2SRobert Mustacchi * relationship and therefore we know which northbridge is associated with which 71047043c2SRobert Mustacchi * processor die. This is summarized in the following image: 72047043c2SRobert Mustacchi * 73047043c2SRobert Mustacchi * +-------+ +-----------------------------------+ +--------------+ 74047043c2SRobert Mustacchi * | Die 0 |--->| Data Fabric PCI BDF 0/18/0 |------->| Northbridge | 75047043c2SRobert Mustacchi * +-------+ | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 10 | | PCI 10/0/0 | 76047043c2SRobert Mustacchi * ... +-----------------------------------+ +--------------+ 77047043c2SRobert Mustacchi * +-------+ +------------------------------------+ +--------------+ 78047043c2SRobert Mustacchi * | Die n |---->| Data Fabric PCI BDF 0/18+n/0 |------->| Northbridge | 79047043c2SRobert Mustacchi * +-------+ | AMDZEN_DF_F0_CFG_ADDR_CTL: bus 133 | | PCI 133/0/0 | 80047043c2SRobert Mustacchi * +------------------------------------+ +--------------+ 81047043c2SRobert Mustacchi * 82047043c2SRobert Mustacchi * Note, the PCI buses used by the northbridges here are arbitrary. They do not 83047043c2SRobert Mustacchi * reflect the actual values by hardware; however, the bus/device/function (BDF) 84047043c2SRobert Mustacchi * of the data fabric accurately models hardware. All of the BDF values are in 85047043c2SRobert Mustacchi * hex. 86047043c2SRobert Mustacchi * 87047043c2SRobert Mustacchi * Starting with the Rome generation of processors (Family 17h Model 30-3Fh), 88047043c2SRobert Mustacchi * AMD has multiple northbridges that exist on a given die. All of these 89047043c2SRobert Mustacchi * northbridges share the same data fabric and system management network port. 90047043c2SRobert Mustacchi * From our perspective this means that some of the northbridge devices will be 91047043c2SRobert Mustacchi * redundant and that we will no longer have a 1:1 mapping between the 92047043c2SRobert Mustacchi * northbridge and the data fabric devices. Every data fabric will have a 93047043c2SRobert Mustacchi * northbridge, but not every northbridge will have a data fabric device mapped. 94047043c2SRobert Mustacchi * Because we're always trying to map from a die to a northbridge and not the 95047043c2SRobert Mustacchi * reverse, the fact that there are extra northbridge devices hanging around 96047043c2SRobert Mustacchi * that we don't know about shouldn't be a problem. 97047043c2SRobert Mustacchi * 98047043c2SRobert Mustacchi * ------------------------------- 99047043c2SRobert Mustacchi * Attach and Detach Complications 100047043c2SRobert Mustacchi * ------------------------------- 101047043c2SRobert Mustacchi * 102047043c2SRobert Mustacchi * Because we need to map different PCI devices together, this means that we 103047043c2SRobert Mustacchi * have multiple dev_info_t structures that we need to manage. Each of these is 104047043c2SRobert Mustacchi * independently attached and detached. While this is easily managed for attach, 105047043c2SRobert Mustacchi * it is not for detach. Each of these devices is a 'stub'. 106047043c2SRobert Mustacchi * 107047043c2SRobert Mustacchi * Once a device has been detached it will only come back if we have an active 108047043c2SRobert Mustacchi * minor node that will be accessed. This means that if they are detached, 109047043c2SRobert Mustacchi * nothing would ever cause them to be reattached. The system also doesn't 110047043c2SRobert Mustacchi * provide us a way or any guarantees around making sure that we're attached to 111047043c2SRobert Mustacchi * all such devices before we detach. As a result, unfortunately, it's easier to 112047043c2SRobert Mustacchi * basically have detach always fail. 113047043c2SRobert Mustacchi * 114047043c2SRobert Mustacchi * --------------- 115047043c2SRobert Mustacchi * Exposed Devices 116047043c2SRobert Mustacchi * --------------- 117047043c2SRobert Mustacchi * 118047043c2SRobert Mustacchi * Rather than try and have all of the different functions that could be 119047043c2SRobert Mustacchi * provided by one driver, we instead have created a nexus driver that will 120047043c2SRobert Mustacchi * itself try and load children. Children are all pseudo-device drivers that 121047043c2SRobert Mustacchi * provide different pieces of functionality that use this. 122047043c2SRobert Mustacchi */ 123047043c2SRobert Mustacchi 124047043c2SRobert Mustacchi #include <sys/modctl.h> 125047043c2SRobert Mustacchi #include <sys/conf.h> 126047043c2SRobert Mustacchi #include <sys/devops.h> 127047043c2SRobert Mustacchi #include <sys/ddi.h> 128047043c2SRobert Mustacchi #include <sys/sunddi.h> 129047043c2SRobert Mustacchi #include <sys/pci.h> 130047043c2SRobert Mustacchi #include <sys/sysmacros.h> 131047043c2SRobert Mustacchi #include <sys/sunndi.h> 132047043c2SRobert Mustacchi #include <sys/x86_archext.h> 133047043c2SRobert Mustacchi #include <sys/cpuvar.h> 134047043c2SRobert Mustacchi 135047043c2SRobert Mustacchi #include "amdzen.h" 136047043c2SRobert Mustacchi 137047043c2SRobert Mustacchi amdzen_t *amdzen_data; 138047043c2SRobert Mustacchi 139047043c2SRobert Mustacchi /* 140047043c2SRobert Mustacchi * Array of northbridge IDs that we care about. 141047043c2SRobert Mustacchi */ 142047043c2SRobert Mustacchi static const uint16_t amdzen_nb_ids[] = { 143047043c2SRobert Mustacchi /* Family 17h Ryzen, Epyc Models 00h-0fh (Zen uarch) */ 144047043c2SRobert Mustacchi 0x1450, 145becd642cSRobert Mustacchi /* Family 17h Raven Ridge, Kestrel, Dali Models 10h-2fh (Zen uarch) */ 146047043c2SRobert Mustacchi 0x15d0, 147e9abe9d6SRobert Mustacchi /* Family 17h/19h Rome, Milan, Matisse, Vermeer Zen 2/Zen 3 uarch */ 148becd642cSRobert Mustacchi 0x1480, 149becd642cSRobert Mustacchi /* Family 17h Renoir Models 60-6fh (Zen 2 uarch) */ 150becd642cSRobert Mustacchi 0x1630 151047043c2SRobert Mustacchi }; 152047043c2SRobert Mustacchi 153047043c2SRobert Mustacchi typedef struct { 154047043c2SRobert Mustacchi char *acd_name; 155047043c2SRobert Mustacchi amdzen_child_t acd_addr; 156047043c2SRobert Mustacchi } amdzen_child_data_t; 157047043c2SRobert Mustacchi 158047043c2SRobert Mustacchi static const amdzen_child_data_t amdzen_children[] = { 159047043c2SRobert Mustacchi { "smntemp", AMDZEN_C_SMNTEMP }, 160549e0fd3SRobert Mustacchi { "usmn", AMDZEN_C_USMN }, 161549e0fd3SRobert Mustacchi { "zen_udf", AMDZEN_C_ZEN_UDF } 162047043c2SRobert Mustacchi }; 163047043c2SRobert Mustacchi 164047043c2SRobert Mustacchi static uint32_t 165047043c2SRobert Mustacchi amdzen_stub_get32(amdzen_stub_t *stub, off_t reg) 166047043c2SRobert Mustacchi { 167047043c2SRobert Mustacchi return (pci_config_get32(stub->azns_cfgspace, reg)); 168047043c2SRobert Mustacchi } 169047043c2SRobert Mustacchi 170549e0fd3SRobert Mustacchi static uint64_t 171549e0fd3SRobert Mustacchi amdzen_stub_get64(amdzen_stub_t *stub, off_t reg) 172549e0fd3SRobert Mustacchi { 173549e0fd3SRobert Mustacchi return (pci_config_get64(stub->azns_cfgspace, reg)); 174549e0fd3SRobert Mustacchi } 175549e0fd3SRobert Mustacchi 176047043c2SRobert Mustacchi static void 177047043c2SRobert Mustacchi amdzen_stub_put32(amdzen_stub_t *stub, off_t reg, uint32_t val) 178047043c2SRobert Mustacchi { 179047043c2SRobert Mustacchi pci_config_put32(stub->azns_cfgspace, reg, val); 180047043c2SRobert Mustacchi } 181047043c2SRobert Mustacchi 182047043c2SRobert Mustacchi /* 183047043c2SRobert Mustacchi * Perform a targeted 32-bit indirect read to a specific instance and function. 184047043c2SRobert Mustacchi */ 185047043c2SRobert Mustacchi static uint32_t 186047043c2SRobert Mustacchi amdzen_df_read32(amdzen_t *azn, amdzen_df_t *df, uint8_t inst, uint8_t func, 187047043c2SRobert Mustacchi uint16_t reg) 188047043c2SRobert Mustacchi { 189047043c2SRobert Mustacchi uint32_t val; 190047043c2SRobert Mustacchi 191047043c2SRobert Mustacchi VERIFY(MUTEX_HELD(&azn->azn_mutex)); 192047043c2SRobert Mustacchi val = AMDZEN_DF_F4_FICAA_TARG_INST | AMDZEN_DF_F4_FICAA_SET_REG(reg) | 193047043c2SRobert Mustacchi AMDZEN_DF_F4_FICAA_SET_FUNC(func) | 194047043c2SRobert Mustacchi AMDZEN_DF_F4_FICAA_SET_INST(inst); 195047043c2SRobert Mustacchi amdzen_stub_put32(df->adf_funcs[4], AMDZEN_DF_F4_FICAA, val); 196047043c2SRobert Mustacchi return (amdzen_stub_get32(df->adf_funcs[4], AMDZEN_DF_F4_FICAD_LO)); 197047043c2SRobert Mustacchi } 198047043c2SRobert Mustacchi 199549e0fd3SRobert Mustacchi /* 200549e0fd3SRobert Mustacchi * Perform a targeted 64-bit indirect read to a specific instance and function. 201549e0fd3SRobert Mustacchi */ 202549e0fd3SRobert Mustacchi static uint64_t 203549e0fd3SRobert Mustacchi amdzen_df_read64(amdzen_t *azn, amdzen_df_t *df, uint8_t inst, uint8_t func, 204549e0fd3SRobert Mustacchi uint16_t reg) 205549e0fd3SRobert Mustacchi { 206549e0fd3SRobert Mustacchi uint32_t val; 207549e0fd3SRobert Mustacchi 208549e0fd3SRobert Mustacchi VERIFY(MUTEX_HELD(&azn->azn_mutex)); 209549e0fd3SRobert Mustacchi val = AMDZEN_DF_F4_FICAA_TARG_INST | AMDZEN_DF_F4_FICAA_SET_REG(reg) | 210549e0fd3SRobert Mustacchi AMDZEN_DF_F4_FICAA_SET_FUNC(func) | 211549e0fd3SRobert Mustacchi AMDZEN_DF_F4_FICAA_SET_INST(inst) | AMDZEN_DF_F4_FICAA_SET_64B; 212549e0fd3SRobert Mustacchi amdzen_stub_put32(df->adf_funcs[4], AMDZEN_DF_F4_FICAA, val); 213549e0fd3SRobert Mustacchi return (amdzen_stub_get64(df->adf_funcs[4], AMDZEN_DF_F4_FICAD_LO)); 214549e0fd3SRobert Mustacchi } 215549e0fd3SRobert Mustacchi 216549e0fd3SRobert Mustacchi 217047043c2SRobert Mustacchi static uint32_t 218047043c2SRobert Mustacchi amdzen_smn_read32(amdzen_t *azn, amdzen_df_t *df, uint32_t reg) 219047043c2SRobert Mustacchi { 220047043c2SRobert Mustacchi VERIFY(MUTEX_HELD(&azn->azn_mutex)); 221047043c2SRobert Mustacchi amdzen_stub_put32(df->adf_nb, AMDZEN_NB_SMN_ADDR, reg); 222047043c2SRobert Mustacchi return (amdzen_stub_get32(df->adf_nb, AMDZEN_NB_SMN_DATA)); 223047043c2SRobert Mustacchi } 224047043c2SRobert Mustacchi 225047043c2SRobert Mustacchi static amdzen_df_t * 226047043c2SRobert Mustacchi amdzen_df_find(amdzen_t *azn, uint_t dfno) 227047043c2SRobert Mustacchi { 228047043c2SRobert Mustacchi uint_t i; 229047043c2SRobert Mustacchi 230047043c2SRobert Mustacchi ASSERT(MUTEX_HELD(&azn->azn_mutex)); 231047043c2SRobert Mustacchi if (dfno >= azn->azn_ndfs) { 232047043c2SRobert Mustacchi return (NULL); 233047043c2SRobert Mustacchi } 234047043c2SRobert Mustacchi 235047043c2SRobert Mustacchi for (i = 0; i < azn->azn_ndfs; i++) { 236047043c2SRobert Mustacchi amdzen_df_t *df = &azn->azn_dfs[i]; 237047043c2SRobert Mustacchi if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0) { 238047043c2SRobert Mustacchi continue; 239047043c2SRobert Mustacchi } 240047043c2SRobert Mustacchi 241047043c2SRobert Mustacchi if (dfno == 0) { 242047043c2SRobert Mustacchi return (df); 243047043c2SRobert Mustacchi } 244047043c2SRobert Mustacchi dfno--; 245047043c2SRobert Mustacchi } 246047043c2SRobert Mustacchi 247047043c2SRobert Mustacchi return (NULL); 248047043c2SRobert Mustacchi } 249047043c2SRobert Mustacchi 250047043c2SRobert Mustacchi /* 251047043c2SRobert Mustacchi * Client functions that are used by nexus children. 252047043c2SRobert Mustacchi */ 253047043c2SRobert Mustacchi int 254047043c2SRobert Mustacchi amdzen_c_smn_read32(uint_t dfno, uint32_t reg, uint32_t *valp) 255047043c2SRobert Mustacchi { 256047043c2SRobert Mustacchi amdzen_df_t *df; 257047043c2SRobert Mustacchi amdzen_t *azn = amdzen_data; 258047043c2SRobert Mustacchi 259047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 260047043c2SRobert Mustacchi df = amdzen_df_find(azn, dfno); 261047043c2SRobert Mustacchi if (df == NULL) { 262047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 263047043c2SRobert Mustacchi return (ENOENT); 264047043c2SRobert Mustacchi } 265047043c2SRobert Mustacchi 266047043c2SRobert Mustacchi if ((df->adf_flags & AMDZEN_DF_F_FOUND_NB) == 0) { 267047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 268047043c2SRobert Mustacchi return (ENXIO); 269047043c2SRobert Mustacchi } 270047043c2SRobert Mustacchi 271047043c2SRobert Mustacchi *valp = amdzen_smn_read32(azn, df, reg); 272047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 273047043c2SRobert Mustacchi return (0); 274047043c2SRobert Mustacchi } 275047043c2SRobert Mustacchi 276047043c2SRobert Mustacchi uint_t 277047043c2SRobert Mustacchi amdzen_c_df_count(void) 278047043c2SRobert Mustacchi { 279047043c2SRobert Mustacchi uint_t ret; 280047043c2SRobert Mustacchi amdzen_t *azn = amdzen_data; 281047043c2SRobert Mustacchi 282047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 283047043c2SRobert Mustacchi ret = azn->azn_ndfs; 284047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 285047043c2SRobert Mustacchi return (ret); 286047043c2SRobert Mustacchi } 287047043c2SRobert Mustacchi 288549e0fd3SRobert Mustacchi int 289549e0fd3SRobert Mustacchi amdzen_c_df_read32(uint_t dfno, uint8_t inst, uint8_t func, 290549e0fd3SRobert Mustacchi uint16_t reg, uint32_t *valp) 291549e0fd3SRobert Mustacchi { 292549e0fd3SRobert Mustacchi amdzen_df_t *df; 293549e0fd3SRobert Mustacchi amdzen_t *azn = amdzen_data; 294549e0fd3SRobert Mustacchi 295549e0fd3SRobert Mustacchi mutex_enter(&azn->azn_mutex); 296549e0fd3SRobert Mustacchi df = amdzen_df_find(azn, dfno); 297549e0fd3SRobert Mustacchi if (df == NULL) { 298549e0fd3SRobert Mustacchi mutex_exit(&azn->azn_mutex); 299549e0fd3SRobert Mustacchi return (ENOENT); 300549e0fd3SRobert Mustacchi } 301549e0fd3SRobert Mustacchi 302549e0fd3SRobert Mustacchi *valp = amdzen_df_read32(azn, df, inst, func, reg); 303549e0fd3SRobert Mustacchi mutex_exit(&azn->azn_mutex); 304549e0fd3SRobert Mustacchi 305549e0fd3SRobert Mustacchi return (0); 306549e0fd3SRobert Mustacchi } 307549e0fd3SRobert Mustacchi 308549e0fd3SRobert Mustacchi int 309549e0fd3SRobert Mustacchi amdzen_c_df_read64(uint_t dfno, uint8_t inst, uint8_t func, 310549e0fd3SRobert Mustacchi uint16_t reg, uint64_t *valp) 311549e0fd3SRobert Mustacchi { 312549e0fd3SRobert Mustacchi amdzen_df_t *df; 313549e0fd3SRobert Mustacchi amdzen_t *azn = amdzen_data; 314549e0fd3SRobert Mustacchi 315549e0fd3SRobert Mustacchi mutex_enter(&azn->azn_mutex); 316549e0fd3SRobert Mustacchi df = amdzen_df_find(azn, dfno); 317549e0fd3SRobert Mustacchi if (df == NULL) { 318549e0fd3SRobert Mustacchi mutex_exit(&azn->azn_mutex); 319549e0fd3SRobert Mustacchi return (ENOENT); 320549e0fd3SRobert Mustacchi } 321549e0fd3SRobert Mustacchi 322549e0fd3SRobert Mustacchi *valp = amdzen_df_read64(azn, df, inst, func, reg); 323549e0fd3SRobert Mustacchi mutex_exit(&azn->azn_mutex); 324549e0fd3SRobert Mustacchi 325549e0fd3SRobert Mustacchi return (0); 326549e0fd3SRobert Mustacchi } 327047043c2SRobert Mustacchi 328047043c2SRobert Mustacchi static boolean_t 329047043c2SRobert Mustacchi amdzen_create_child(amdzen_t *azn, const amdzen_child_data_t *acd) 330047043c2SRobert Mustacchi { 331047043c2SRobert Mustacchi int ret; 332047043c2SRobert Mustacchi dev_info_t *child; 333047043c2SRobert Mustacchi 334047043c2SRobert Mustacchi if (ndi_devi_alloc(azn->azn_dip, acd->acd_name, 335047043c2SRobert Mustacchi (pnode_t)DEVI_SID_NODEID, &child) != NDI_SUCCESS) { 336549e0fd3SRobert Mustacchi dev_err(azn->azn_dip, CE_WARN, "!failed to allocate child " 337047043c2SRobert Mustacchi "dip for %s", acd->acd_name); 338047043c2SRobert Mustacchi return (B_FALSE); 339047043c2SRobert Mustacchi } 340047043c2SRobert Mustacchi 341047043c2SRobert Mustacchi ddi_set_parent_data(child, (void *)acd); 342047043c2SRobert Mustacchi if ((ret = ndi_devi_online(child, 0)) != NDI_SUCCESS) { 343549e0fd3SRobert Mustacchi dev_err(azn->azn_dip, CE_WARN, "!failed to online child " 344047043c2SRobert Mustacchi "dip %s: %d", acd->acd_name, ret); 345047043c2SRobert Mustacchi return (B_FALSE); 346047043c2SRobert Mustacchi } 347047043c2SRobert Mustacchi 348047043c2SRobert Mustacchi return (B_TRUE); 349047043c2SRobert Mustacchi } 350047043c2SRobert Mustacchi 351047043c2SRobert Mustacchi static boolean_t 352047043c2SRobert Mustacchi amdzen_map_dfs(amdzen_t *azn) 353047043c2SRobert Mustacchi { 354047043c2SRobert Mustacchi amdzen_stub_t *stub; 355047043c2SRobert Mustacchi 356047043c2SRobert Mustacchi ASSERT(MUTEX_HELD(&azn->azn_mutex)); 357047043c2SRobert Mustacchi 358047043c2SRobert Mustacchi for (stub = list_head(&azn->azn_df_stubs); stub != NULL; 359047043c2SRobert Mustacchi stub = list_next(&azn->azn_df_stubs, stub)) { 360047043c2SRobert Mustacchi amdzen_df_t *df; 361047043c2SRobert Mustacchi uint_t dfno; 362047043c2SRobert Mustacchi 363047043c2SRobert Mustacchi dfno = stub->azns_dev - AMDZEN_DF_FIRST_DEVICE; 364047043c2SRobert Mustacchi if (dfno > AMDZEN_MAX_DFS) { 365047043c2SRobert Mustacchi dev_err(stub->azns_dip, CE_WARN, "encountered df " 366047043c2SRobert Mustacchi "device with illegal DF PCI b/d/f: 0x%x/%x/%x", 367047043c2SRobert Mustacchi stub->azns_bus, stub->azns_dev, stub->azns_func); 368047043c2SRobert Mustacchi goto err; 369047043c2SRobert Mustacchi } 370047043c2SRobert Mustacchi 371047043c2SRobert Mustacchi df = &azn->azn_dfs[dfno]; 372047043c2SRobert Mustacchi 373047043c2SRobert Mustacchi if (stub->azns_func >= AMDZEN_MAX_DF_FUNCS) { 374047043c2SRobert Mustacchi dev_err(stub->azns_dip, CE_WARN, "encountered df " 375047043c2SRobert Mustacchi "device with illegal DF PCI b/d/f: 0x%x/%x/%x", 376047043c2SRobert Mustacchi stub->azns_bus, stub->azns_dev, stub->azns_func); 377047043c2SRobert Mustacchi goto err; 378047043c2SRobert Mustacchi } 379047043c2SRobert Mustacchi 380047043c2SRobert Mustacchi if (df->adf_funcs[stub->azns_func] != NULL) { 381047043c2SRobert Mustacchi dev_err(stub->azns_dip, CE_WARN, "encountered " 382047043c2SRobert Mustacchi "duplicate df device with DF PCI b/d/f: 0x%x/%x/%x", 383047043c2SRobert Mustacchi stub->azns_bus, stub->azns_dev, stub->azns_func); 384047043c2SRobert Mustacchi goto err; 385047043c2SRobert Mustacchi } 386047043c2SRobert Mustacchi df->adf_funcs[stub->azns_func] = stub; 387047043c2SRobert Mustacchi } 388047043c2SRobert Mustacchi 389047043c2SRobert Mustacchi return (B_TRUE); 390047043c2SRobert Mustacchi 391047043c2SRobert Mustacchi err: 392047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_DEVICE_ERROR; 393047043c2SRobert Mustacchi return (B_FALSE); 394047043c2SRobert Mustacchi } 395047043c2SRobert Mustacchi 396047043c2SRobert Mustacchi static boolean_t 397047043c2SRobert Mustacchi amdzen_check_dfs(amdzen_t *azn) 398047043c2SRobert Mustacchi { 399047043c2SRobert Mustacchi uint_t i; 400047043c2SRobert Mustacchi boolean_t ret = B_TRUE; 401047043c2SRobert Mustacchi 402047043c2SRobert Mustacchi for (i = 0; i < AMDZEN_MAX_DFS; i++) { 403047043c2SRobert Mustacchi amdzen_df_t *df = &azn->azn_dfs[i]; 404047043c2SRobert Mustacchi uint_t count = 0; 405047043c2SRobert Mustacchi 406047043c2SRobert Mustacchi /* 407047043c2SRobert Mustacchi * We require all platforms to have DFs functions 0-6. Not all 408047043c2SRobert Mustacchi * platforms have DF function 7. 409047043c2SRobert Mustacchi */ 410047043c2SRobert Mustacchi for (uint_t func = 0; func < AMDZEN_MAX_DF_FUNCS - 1; func++) { 411047043c2SRobert Mustacchi if (df->adf_funcs[func] != NULL) { 412047043c2SRobert Mustacchi count++; 413047043c2SRobert Mustacchi } 414047043c2SRobert Mustacchi } 415047043c2SRobert Mustacchi 416047043c2SRobert Mustacchi if (count == 0) 417047043c2SRobert Mustacchi continue; 418047043c2SRobert Mustacchi 419047043c2SRobert Mustacchi if (count != 7) { 420047043c2SRobert Mustacchi ret = B_FALSE; 421047043c2SRobert Mustacchi dev_err(azn->azn_dip, CE_WARN, "df %u devices " 422047043c2SRobert Mustacchi "incomplete", i); 423047043c2SRobert Mustacchi } else { 424047043c2SRobert Mustacchi df->adf_flags |= AMDZEN_DF_F_VALID; 425047043c2SRobert Mustacchi azn->azn_ndfs++; 426047043c2SRobert Mustacchi } 427047043c2SRobert Mustacchi } 428047043c2SRobert Mustacchi 429047043c2SRobert Mustacchi return (ret); 430047043c2SRobert Mustacchi } 431047043c2SRobert Mustacchi 432047043c2SRobert Mustacchi static const uint8_t amdzen_df_rome_ids[0x2b] = { 433047043c2SRobert Mustacchi 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23, 434047043c2SRobert Mustacchi 24, 25, 26, 27, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 435047043c2SRobert Mustacchi 44, 45, 46, 47, 48 436047043c2SRobert Mustacchi }; 437047043c2SRobert Mustacchi 438047043c2SRobert Mustacchi /* 439e9abe9d6SRobert Mustacchi * Check the first df entry to see if it belongs to Rome or Milan. If so, then 440e9abe9d6SRobert Mustacchi * it uses the disjoint ID space. 441e9abe9d6SRobert Mustacchi */ 442e9abe9d6SRobert Mustacchi static boolean_t 443e9abe9d6SRobert Mustacchi amdzen_is_rome_style(uint_t id) 444e9abe9d6SRobert Mustacchi { 445e9abe9d6SRobert Mustacchi return (id == 0x1490 || id == 0x1650); 446e9abe9d6SRobert Mustacchi } 447e9abe9d6SRobert Mustacchi 448e9abe9d6SRobert Mustacchi /* 449047043c2SRobert Mustacchi * Initialize our knowledge about a given series of nodes on the data fabric. 450047043c2SRobert Mustacchi */ 451047043c2SRobert Mustacchi static void 452047043c2SRobert Mustacchi amdzen_setup_df(amdzen_t *azn, amdzen_df_t *df) 453047043c2SRobert Mustacchi { 454047043c2SRobert Mustacchi uint_t i; 455047043c2SRobert Mustacchi uint32_t val; 456047043c2SRobert Mustacchi 457047043c2SRobert Mustacchi val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_CFG_ADDR_CTL); 458047043c2SRobert Mustacchi df->adf_nb_busno = AMDZEN_DF_F0_CFG_ADDR_CTL_BUS_NUM(val); 459047043c2SRobert Mustacchi val = amdzen_stub_get32(df->adf_funcs[0], AMDZEN_DF_F0_FBICNT); 460047043c2SRobert Mustacchi df->adf_nents = AMDZEN_DF_F0_FBICNT_COUNT(val); 461047043c2SRobert Mustacchi if (df->adf_nents == 0) 462047043c2SRobert Mustacchi return; 463047043c2SRobert Mustacchi df->adf_ents = kmem_zalloc(sizeof (amdzen_df_ent_t) * df->adf_nents, 464047043c2SRobert Mustacchi KM_SLEEP); 465047043c2SRobert Mustacchi 466047043c2SRobert Mustacchi for (i = 0; i < df->adf_nents; i++) { 467047043c2SRobert Mustacchi amdzen_df_ent_t *dfe = &df->adf_ents[i]; 468047043c2SRobert Mustacchi uint8_t inst = i; 469047043c2SRobert Mustacchi 470047043c2SRobert Mustacchi /* 471047043c2SRobert Mustacchi * Unfortunately, Rome uses a discontinuous instance ID pattern 472047043c2SRobert Mustacchi * while everything else we can find uses a contiguous instance 473047043c2SRobert Mustacchi * ID pattern. This means that for Rome, we need to adjust the 474047043c2SRobert Mustacchi * indexes that we iterate over, though the total number of 475047043c2SRobert Mustacchi * entries is right. 476047043c2SRobert Mustacchi */ 477e9abe9d6SRobert Mustacchi if (amdzen_is_rome_style(df->adf_funcs[0]->azns_did)) { 478047043c2SRobert Mustacchi if (inst > ARRAY_SIZE(amdzen_df_rome_ids)) { 479047043c2SRobert Mustacchi dev_err(azn->azn_dip, CE_WARN, "Rome family " 480047043c2SRobert Mustacchi "processor reported more ids than the PPR, " 481e9abe9d6SRobert Mustacchi "resetting %u to instance zero", inst); 482047043c2SRobert Mustacchi inst = 0; 483047043c2SRobert Mustacchi } else { 484047043c2SRobert Mustacchi inst = amdzen_df_rome_ids[inst]; 485047043c2SRobert Mustacchi } 486047043c2SRobert Mustacchi } 487047043c2SRobert Mustacchi 488047043c2SRobert Mustacchi dfe->adfe_drvid = inst; 489047043c2SRobert Mustacchi dfe->adfe_info0 = amdzen_df_read32(azn, df, inst, 0, 490047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0); 491047043c2SRobert Mustacchi dfe->adfe_info1 = amdzen_df_read32(azn, df, inst, 0, 492047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO1); 493047043c2SRobert Mustacchi dfe->adfe_info2 = amdzen_df_read32(azn, df, inst, 0, 494047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO2); 495047043c2SRobert Mustacchi dfe->adfe_info3 = amdzen_df_read32(azn, df, inst, 0, 496047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO3); 497047043c2SRobert Mustacchi dfe->adfe_syscfg = amdzen_df_read32(azn, df, inst, 1, 498047043c2SRobert Mustacchi AMDZEN_DF_F1_SYSCFG); 499047043c2SRobert Mustacchi dfe->adfe_mask0 = amdzen_df_read32(azn, df, inst, 1, 500047043c2SRobert Mustacchi AMDZEN_DF_F1_FIDMASK0); 501047043c2SRobert Mustacchi dfe->adfe_mask1 = amdzen_df_read32(azn, df, inst, 1, 502047043c2SRobert Mustacchi AMDZEN_DF_F1_FIDMASK1); 503047043c2SRobert Mustacchi 504047043c2SRobert Mustacchi dfe->adfe_type = AMDZEN_DF_F0_FBIINFO0_TYPE(dfe->adfe_info0); 505047043c2SRobert Mustacchi dfe->adfe_sdp_width = 506047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0_SDP_WIDTH(dfe->adfe_info0); 507047043c2SRobert Mustacchi if (AMDZEN_DF_F0_FBIINFO0_ENABLED(dfe->adfe_info0)) { 508047043c2SRobert Mustacchi dfe->adfe_flags |= AMDZEN_DFE_F_ENABLED; 509047043c2SRobert Mustacchi } 510047043c2SRobert Mustacchi dfe->adfe_fti_width = 511047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0_FTI_WIDTH(dfe->adfe_info0); 512047043c2SRobert Mustacchi dfe->adfe_sdp_count = 513047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0_SDP_PCOUNT(dfe->adfe_info0); 514047043c2SRobert Mustacchi dfe->adfe_fti_count = 515047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0_FTI_PCOUNT(dfe->adfe_info0); 516047043c2SRobert Mustacchi if (AMDZEN_DF_F0_FBIINFO0_HAS_MCA(dfe->adfe_info0)) { 517047043c2SRobert Mustacchi dfe->adfe_flags |= AMDZEN_DFE_F_MCA; 518047043c2SRobert Mustacchi } 519047043c2SRobert Mustacchi dfe->adfe_subtype = 520047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO0_SUBTYPE(dfe->adfe_info0); 521047043c2SRobert Mustacchi 522047043c2SRobert Mustacchi dfe->adfe_inst_id = 523047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO3_INSTID(dfe->adfe_info3); 524047043c2SRobert Mustacchi dfe->adfe_fabric_id = 525047043c2SRobert Mustacchi AMDZEN_DF_F0_FBIINFO3_FABID(dfe->adfe_info3); 526047043c2SRobert Mustacchi } 527047043c2SRobert Mustacchi 528047043c2SRobert Mustacchi df->adf_syscfg = amdzen_stub_get32(df->adf_funcs[1], 529047043c2SRobert Mustacchi AMDZEN_DF_F1_SYSCFG); 530047043c2SRobert Mustacchi df->adf_nodeid = AMDZEN_DF_F1_SYSCFG_NODEID(df->adf_syscfg); 531047043c2SRobert Mustacchi df->adf_mask0 = amdzen_stub_get32(df->adf_funcs[1], 532047043c2SRobert Mustacchi AMDZEN_DF_F1_FIDMASK0); 533047043c2SRobert Mustacchi df->adf_mask1 = amdzen_stub_get32(df->adf_funcs[1], 534047043c2SRobert Mustacchi AMDZEN_DF_F1_FIDMASK1); 535047043c2SRobert Mustacchi } 536047043c2SRobert Mustacchi 537047043c2SRobert Mustacchi static void 538047043c2SRobert Mustacchi amdzen_find_nb(amdzen_t *azn, amdzen_df_t *df) 539047043c2SRobert Mustacchi { 540047043c2SRobert Mustacchi amdzen_stub_t *stub; 541047043c2SRobert Mustacchi 542047043c2SRobert Mustacchi for (stub = list_head(&azn->azn_nb_stubs); stub != NULL; 543047043c2SRobert Mustacchi stub = list_next(&azn->azn_nb_stubs, stub)) { 544047043c2SRobert Mustacchi if (stub->azns_bus == df->adf_nb_busno) { 545047043c2SRobert Mustacchi df->adf_flags |= AMDZEN_DF_F_FOUND_NB; 546047043c2SRobert Mustacchi df->adf_nb = stub; 547047043c2SRobert Mustacchi return; 548047043c2SRobert Mustacchi } 549047043c2SRobert Mustacchi } 550047043c2SRobert Mustacchi } 551047043c2SRobert Mustacchi 552047043c2SRobert Mustacchi static void 553047043c2SRobert Mustacchi amdzen_nexus_init(void *arg) 554047043c2SRobert Mustacchi { 555047043c2SRobert Mustacchi uint_t i; 556047043c2SRobert Mustacchi amdzen_t *azn = arg; 557047043c2SRobert Mustacchi 558047043c2SRobert Mustacchi /* 559047043c2SRobert Mustacchi * First go through all of the stubs and assign the DF entries. 560047043c2SRobert Mustacchi */ 561047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 562047043c2SRobert Mustacchi if (!amdzen_map_dfs(azn) || !amdzen_check_dfs(azn)) { 563047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_MAP_ERROR; 564047043c2SRobert Mustacchi goto done; 565047043c2SRobert Mustacchi } 566047043c2SRobert Mustacchi 567047043c2SRobert Mustacchi for (i = 0; i < AMDZEN_MAX_DFS; i++) { 568047043c2SRobert Mustacchi amdzen_df_t *df = &azn->azn_dfs[i]; 569047043c2SRobert Mustacchi 570047043c2SRobert Mustacchi if ((df->adf_flags & AMDZEN_DF_F_VALID) == 0) 571047043c2SRobert Mustacchi continue; 572047043c2SRobert Mustacchi amdzen_setup_df(azn, df); 573047043c2SRobert Mustacchi amdzen_find_nb(azn, df); 574047043c2SRobert Mustacchi } 575047043c2SRobert Mustacchi 576047043c2SRobert Mustacchi /* 577047043c2SRobert Mustacchi * Not all children may be installed. As such, we do not treat the 578047043c2SRobert Mustacchi * failure of a child as fatal to the driver. 579047043c2SRobert Mustacchi */ 580047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 581047043c2SRobert Mustacchi for (i = 0; i < ARRAY_SIZE(amdzen_children); i++) { 582047043c2SRobert Mustacchi (void) amdzen_create_child(azn, &amdzen_children[i]); 583047043c2SRobert Mustacchi } 584047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 585047043c2SRobert Mustacchi 586047043c2SRobert Mustacchi done: 587047043c2SRobert Mustacchi azn->azn_flags &= ~AMDZEN_F_ATTACH_DISPATCHED; 588047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_ATTACH_COMPLETE; 589047043c2SRobert Mustacchi azn->azn_taskqid = TASKQID_INVALID; 590047043c2SRobert Mustacchi cv_broadcast(&azn->azn_cv); 591047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 592047043c2SRobert Mustacchi } 593047043c2SRobert Mustacchi 594047043c2SRobert Mustacchi static int 595047043c2SRobert Mustacchi amdzen_stub_scan_cb(dev_info_t *dip, void *arg) 596047043c2SRobert Mustacchi { 597047043c2SRobert Mustacchi amdzen_t *azn = arg; 598047043c2SRobert Mustacchi uint16_t vid, did; 599047043c2SRobert Mustacchi int *regs; 600047043c2SRobert Mustacchi uint_t nregs, i; 601047043c2SRobert Mustacchi boolean_t match = B_FALSE; 602047043c2SRobert Mustacchi 603047043c2SRobert Mustacchi if (dip == ddi_root_node()) { 604047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 605047043c2SRobert Mustacchi } 606047043c2SRobert Mustacchi 607047043c2SRobert Mustacchi /* 608047043c2SRobert Mustacchi * If a node in question is not a pci node, then we have no interest in 609047043c2SRobert Mustacchi * it as all the stubs that we care about are related to pci devices. 610047043c2SRobert Mustacchi */ 611047043c2SRobert Mustacchi if (strncmp("pci", ddi_get_name(dip), 3) != 0) { 612047043c2SRobert Mustacchi return (DDI_WALK_PRUNECHILD); 613047043c2SRobert Mustacchi } 614047043c2SRobert Mustacchi 615047043c2SRobert Mustacchi /* 616047043c2SRobert Mustacchi * If we can't get a device or vendor ID and prove that this is an AMD 617047043c2SRobert Mustacchi * part, then we don't care about it. 618047043c2SRobert Mustacchi */ 619047043c2SRobert Mustacchi vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 620047043c2SRobert Mustacchi "vendor-id", PCI_EINVAL16); 621047043c2SRobert Mustacchi did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 622047043c2SRobert Mustacchi "device-id", PCI_EINVAL16); 623047043c2SRobert Mustacchi if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) { 624047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 625047043c2SRobert Mustacchi } 626047043c2SRobert Mustacchi 627*9b0429a1SPu Wen if (vid != AMDZEN_PCI_VID_AMD && vid != AMDZEN_PCI_VID_HYGON) { 628047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 629047043c2SRobert Mustacchi } 630047043c2SRobert Mustacchi 631047043c2SRobert Mustacchi for (i = 0; i < ARRAY_SIZE(amdzen_nb_ids); i++) { 632047043c2SRobert Mustacchi if (amdzen_nb_ids[i] == did) { 633047043c2SRobert Mustacchi match = B_TRUE; 634047043c2SRobert Mustacchi } 635047043c2SRobert Mustacchi } 636047043c2SRobert Mustacchi 637047043c2SRobert Mustacchi if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 638047043c2SRobert Mustacchi "reg", ®s, &nregs) != DDI_PROP_SUCCESS) { 639047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 640047043c2SRobert Mustacchi } 641047043c2SRobert Mustacchi 642047043c2SRobert Mustacchi if (nregs == 0) { 643047043c2SRobert Mustacchi ddi_prop_free(regs); 644047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 645047043c2SRobert Mustacchi } 646047043c2SRobert Mustacchi 647047043c2SRobert Mustacchi if (PCI_REG_BUS_G(regs[0]) == AMDZEN_DF_BUSNO && 648047043c2SRobert Mustacchi PCI_REG_DEV_G(regs[0]) >= AMDZEN_DF_FIRST_DEVICE) { 649047043c2SRobert Mustacchi match = B_TRUE; 650047043c2SRobert Mustacchi } 651047043c2SRobert Mustacchi 652047043c2SRobert Mustacchi ddi_prop_free(regs); 653047043c2SRobert Mustacchi if (match) { 654047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 655047043c2SRobert Mustacchi azn->azn_nscanned++; 656047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 657047043c2SRobert Mustacchi } 658047043c2SRobert Mustacchi 659047043c2SRobert Mustacchi return (DDI_WALK_CONTINUE); 660047043c2SRobert Mustacchi } 661047043c2SRobert Mustacchi 662047043c2SRobert Mustacchi static void 663047043c2SRobert Mustacchi amdzen_stub_scan(void *arg) 664047043c2SRobert Mustacchi { 665047043c2SRobert Mustacchi amdzen_t *azn = arg; 666047043c2SRobert Mustacchi 667047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 668047043c2SRobert Mustacchi azn->azn_nscanned = 0; 669047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 670047043c2SRobert Mustacchi 671047043c2SRobert Mustacchi ddi_walk_devs(ddi_root_node(), amdzen_stub_scan_cb, azn); 672047043c2SRobert Mustacchi 673047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 674047043c2SRobert Mustacchi azn->azn_flags &= ~AMDZEN_F_SCAN_DISPATCHED; 675047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_SCAN_COMPLETE; 676047043c2SRobert Mustacchi 677047043c2SRobert Mustacchi if (azn->azn_nscanned == 0) { 678047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_UNSUPPORTED; 679047043c2SRobert Mustacchi azn->azn_taskqid = TASKQID_INVALID; 680047043c2SRobert Mustacchi cv_broadcast(&azn->azn_cv); 681047043c2SRobert Mustacchi } else if (azn->azn_npresent == azn->azn_nscanned) { 682047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_ATTACH_DISPATCHED; 683047043c2SRobert Mustacchi azn->azn_taskqid = taskq_dispatch(system_taskq, 684047043c2SRobert Mustacchi amdzen_nexus_init, azn, TQ_SLEEP); 685047043c2SRobert Mustacchi } 686047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 687047043c2SRobert Mustacchi } 688047043c2SRobert Mustacchi 689047043c2SRobert Mustacchi /* 690047043c2SRobert Mustacchi * Unfortunately we can't really let the stubs detach as we may need them to be 691047043c2SRobert Mustacchi * available for client operations. We may be able to improve this if we know 692047043c2SRobert Mustacchi * that the actual nexus is going away. However, as long as it's active, we need 693047043c2SRobert Mustacchi * all the stubs. 694047043c2SRobert Mustacchi */ 695047043c2SRobert Mustacchi int 696047043c2SRobert Mustacchi amdzen_detach_stub(dev_info_t *dip, ddi_detach_cmd_t cmd) 697047043c2SRobert Mustacchi { 698047043c2SRobert Mustacchi if (cmd == DDI_SUSPEND) { 699047043c2SRobert Mustacchi return (DDI_SUCCESS); 700047043c2SRobert Mustacchi } 701047043c2SRobert Mustacchi 702047043c2SRobert Mustacchi return (DDI_FAILURE); 703047043c2SRobert Mustacchi } 704047043c2SRobert Mustacchi 705047043c2SRobert Mustacchi int 706047043c2SRobert Mustacchi amdzen_attach_stub(dev_info_t *dip, ddi_attach_cmd_t cmd) 707047043c2SRobert Mustacchi { 708047043c2SRobert Mustacchi int *regs, reg; 709047043c2SRobert Mustacchi uint_t nregs, i; 710047043c2SRobert Mustacchi uint16_t vid, did; 711047043c2SRobert Mustacchi amdzen_stub_t *stub; 712047043c2SRobert Mustacchi amdzen_t *azn = amdzen_data; 713047043c2SRobert Mustacchi boolean_t valid = B_FALSE; 714047043c2SRobert Mustacchi boolean_t nb = B_FALSE; 715047043c2SRobert Mustacchi 716047043c2SRobert Mustacchi if (cmd == DDI_RESUME) { 717047043c2SRobert Mustacchi return (DDI_SUCCESS); 718047043c2SRobert Mustacchi } else if (cmd != DDI_ATTACH) { 719047043c2SRobert Mustacchi return (DDI_FAILURE); 720047043c2SRobert Mustacchi } 721047043c2SRobert Mustacchi 722047043c2SRobert Mustacchi /* 723047043c2SRobert Mustacchi * Make sure that the stub that we've been asked to attach is a pci type 724047043c2SRobert Mustacchi * device. If not, then there is no reason for us to proceed. 725047043c2SRobert Mustacchi */ 726047043c2SRobert Mustacchi if (strncmp("pci", ddi_get_name(dip), 3) != 0) { 727047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "asked to attach a bad AMD Zen nexus " 728047043c2SRobert Mustacchi "stub: %s", ddi_get_name(dip)); 729047043c2SRobert Mustacchi return (DDI_FAILURE); 730047043c2SRobert Mustacchi } 731047043c2SRobert Mustacchi vid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 732047043c2SRobert Mustacchi "vendor-id", PCI_EINVAL16); 733047043c2SRobert Mustacchi did = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 734047043c2SRobert Mustacchi "device-id", PCI_EINVAL16); 735047043c2SRobert Mustacchi if (vid == PCI_EINVAL16 || did == PCI_EINVAL16) { 736047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "failed to get PCI ID properties"); 737047043c2SRobert Mustacchi return (DDI_FAILURE); 738047043c2SRobert Mustacchi } 739047043c2SRobert Mustacchi 740*9b0429a1SPu Wen if (vid != AMDZEN_PCI_VID_AMD && vid != AMDZEN_PCI_VID_HYGON) { 741*9b0429a1SPu Wen dev_err(dip, CE_WARN, "expected vendor ID (0x%x), found 0x%x", 742*9b0429a1SPu Wen cpuid_getvendor(CPU) == X86_VENDOR_HYGON ? 743*9b0429a1SPu Wen AMDZEN_PCI_VID_HYGON : AMDZEN_PCI_VID_AMD, vid); 744047043c2SRobert Mustacchi return (DDI_FAILURE); 745047043c2SRobert Mustacchi } 746047043c2SRobert Mustacchi 747047043c2SRobert Mustacchi if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 748047043c2SRobert Mustacchi "reg", ®s, &nregs) != DDI_PROP_SUCCESS) { 749047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "failed to get 'reg' property"); 750047043c2SRobert Mustacchi return (DDI_FAILURE); 751047043c2SRobert Mustacchi } 752047043c2SRobert Mustacchi 753047043c2SRobert Mustacchi if (nregs == 0) { 754047043c2SRobert Mustacchi ddi_prop_free(regs); 755047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "missing 'reg' property values"); 756047043c2SRobert Mustacchi return (DDI_FAILURE); 757047043c2SRobert Mustacchi } 758047043c2SRobert Mustacchi reg = *regs; 759047043c2SRobert Mustacchi ddi_prop_free(regs); 760047043c2SRobert Mustacchi 761047043c2SRobert Mustacchi for (i = 0; i < ARRAY_SIZE(amdzen_nb_ids); i++) { 762047043c2SRobert Mustacchi if (amdzen_nb_ids[i] == did) { 763047043c2SRobert Mustacchi valid = B_TRUE; 764047043c2SRobert Mustacchi nb = B_TRUE; 765047043c2SRobert Mustacchi } 766047043c2SRobert Mustacchi } 767047043c2SRobert Mustacchi 768047043c2SRobert Mustacchi if (!valid && PCI_REG_BUS_G(reg) == AMDZEN_DF_BUSNO && 769047043c2SRobert Mustacchi PCI_REG_DEV_G(reg) >= AMDZEN_DF_FIRST_DEVICE) { 770047043c2SRobert Mustacchi valid = B_TRUE; 771047043c2SRobert Mustacchi nb = B_FALSE; 772047043c2SRobert Mustacchi } 773047043c2SRobert Mustacchi 774047043c2SRobert Mustacchi if (!valid) { 775047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "device %s didn't match the nexus list", 776047043c2SRobert Mustacchi ddi_get_name(dip)); 777047043c2SRobert Mustacchi return (DDI_FAILURE); 778047043c2SRobert Mustacchi } 779047043c2SRobert Mustacchi 780047043c2SRobert Mustacchi stub = kmem_alloc(sizeof (amdzen_stub_t), KM_SLEEP); 781047043c2SRobert Mustacchi if (pci_config_setup(dip, &stub->azns_cfgspace) != DDI_SUCCESS) { 782047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "failed to set up config space"); 783047043c2SRobert Mustacchi kmem_free(stub, sizeof (amdzen_stub_t)); 784047043c2SRobert Mustacchi return (DDI_FAILURE); 785047043c2SRobert Mustacchi } 786047043c2SRobert Mustacchi 787047043c2SRobert Mustacchi stub->azns_dip = dip; 788047043c2SRobert Mustacchi stub->azns_vid = vid; 789047043c2SRobert Mustacchi stub->azns_did = did; 790047043c2SRobert Mustacchi stub->azns_bus = PCI_REG_BUS_G(reg); 791047043c2SRobert Mustacchi stub->azns_dev = PCI_REG_DEV_G(reg); 792047043c2SRobert Mustacchi stub->azns_func = PCI_REG_FUNC_G(reg); 793047043c2SRobert Mustacchi ddi_set_driver_private(dip, stub); 794047043c2SRobert Mustacchi 795047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 796047043c2SRobert Mustacchi azn->azn_npresent++; 797047043c2SRobert Mustacchi if (nb) { 798047043c2SRobert Mustacchi list_insert_tail(&azn->azn_nb_stubs, stub); 799047043c2SRobert Mustacchi } else { 800047043c2SRobert Mustacchi list_insert_tail(&azn->azn_df_stubs, stub); 801047043c2SRobert Mustacchi } 802047043c2SRobert Mustacchi 803047043c2SRobert Mustacchi if ((azn->azn_flags & AMDZEN_F_TASKQ_MASK) == AMDZEN_F_SCAN_COMPLETE && 804047043c2SRobert Mustacchi azn->azn_nscanned == azn->azn_npresent) { 805047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_ATTACH_DISPATCHED; 806047043c2SRobert Mustacchi azn->azn_taskqid = taskq_dispatch(system_taskq, 807047043c2SRobert Mustacchi amdzen_nexus_init, azn, TQ_SLEEP); 808047043c2SRobert Mustacchi } 809047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 810047043c2SRobert Mustacchi 811047043c2SRobert Mustacchi return (DDI_SUCCESS); 812047043c2SRobert Mustacchi } 813047043c2SRobert Mustacchi 814047043c2SRobert Mustacchi static int 815047043c2SRobert Mustacchi amdzen_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, 816047043c2SRobert Mustacchi void *arg, void *result) 817047043c2SRobert Mustacchi { 818047043c2SRobert Mustacchi char buf[32]; 819047043c2SRobert Mustacchi dev_info_t *child; 820047043c2SRobert Mustacchi const amdzen_child_data_t *acd; 821047043c2SRobert Mustacchi 822047043c2SRobert Mustacchi switch (ctlop) { 823047043c2SRobert Mustacchi case DDI_CTLOPS_REPORTDEV: 824047043c2SRobert Mustacchi if (rdip == NULL) { 825047043c2SRobert Mustacchi return (DDI_FAILURE); 826047043c2SRobert Mustacchi } 827047043c2SRobert Mustacchi cmn_err(CE_CONT, "amdzen nexus: %s@%s, %s%d\n", 828047043c2SRobert Mustacchi ddi_node_name(rdip), ddi_get_name_addr(rdip), 829047043c2SRobert Mustacchi ddi_driver_name(rdip), ddi_get_instance(rdip)); 830047043c2SRobert Mustacchi break; 831047043c2SRobert Mustacchi case DDI_CTLOPS_INITCHILD: 832047043c2SRobert Mustacchi child = arg; 833047043c2SRobert Mustacchi if (child == NULL) { 834047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "!no child passed for " 835047043c2SRobert Mustacchi "DDI_CTLOPS_INITCHILD"); 836047043c2SRobert Mustacchi } 837047043c2SRobert Mustacchi 838047043c2SRobert Mustacchi acd = ddi_get_parent_data(child); 839047043c2SRobert Mustacchi if (acd == NULL) { 840047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "!missing child parent data"); 841047043c2SRobert Mustacchi return (DDI_FAILURE); 842047043c2SRobert Mustacchi } 843047043c2SRobert Mustacchi 844047043c2SRobert Mustacchi if (snprintf(buf, sizeof (buf), "%d", acd->acd_addr) >= 845047043c2SRobert Mustacchi sizeof (buf)) { 846047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "!failed to construct device " 847047043c2SRobert Mustacchi "addr due to overflow"); 848047043c2SRobert Mustacchi return (DDI_FAILURE); 849047043c2SRobert Mustacchi } 850047043c2SRobert Mustacchi 851047043c2SRobert Mustacchi ddi_set_name_addr(child, buf); 852047043c2SRobert Mustacchi break; 853047043c2SRobert Mustacchi case DDI_CTLOPS_UNINITCHILD: 854047043c2SRobert Mustacchi child = arg; 855047043c2SRobert Mustacchi if (child == NULL) { 856047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "!no child passed for " 857047043c2SRobert Mustacchi "DDI_CTLOPS_UNINITCHILD"); 858047043c2SRobert Mustacchi } 859047043c2SRobert Mustacchi 860047043c2SRobert Mustacchi ddi_set_name_addr(child, NULL); 861047043c2SRobert Mustacchi break; 862047043c2SRobert Mustacchi default: 863047043c2SRobert Mustacchi return (ddi_ctlops(dip, rdip, ctlop, arg, result)); 864047043c2SRobert Mustacchi } 865047043c2SRobert Mustacchi return (DDI_SUCCESS); 866047043c2SRobert Mustacchi } 867047043c2SRobert Mustacchi 868047043c2SRobert Mustacchi static int 869047043c2SRobert Mustacchi amdzen_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 870047043c2SRobert Mustacchi { 871047043c2SRobert Mustacchi amdzen_t *azn = amdzen_data; 872047043c2SRobert Mustacchi 873047043c2SRobert Mustacchi if (cmd == DDI_RESUME) { 874047043c2SRobert Mustacchi return (DDI_SUCCESS); 875047043c2SRobert Mustacchi } else if (cmd != DDI_ATTACH) { 876047043c2SRobert Mustacchi return (DDI_FAILURE); 877047043c2SRobert Mustacchi } 878047043c2SRobert Mustacchi 879047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 880047043c2SRobert Mustacchi if (azn->azn_dip != NULL) { 881047043c2SRobert Mustacchi dev_err(dip, CE_WARN, "driver is already attached!"); 882047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 883047043c2SRobert Mustacchi return (DDI_FAILURE); 884047043c2SRobert Mustacchi } 885047043c2SRobert Mustacchi 886047043c2SRobert Mustacchi azn->azn_dip = dip; 887047043c2SRobert Mustacchi azn->azn_taskqid = taskq_dispatch(system_taskq, amdzen_stub_scan, 888047043c2SRobert Mustacchi azn, TQ_SLEEP); 889047043c2SRobert Mustacchi azn->azn_flags |= AMDZEN_F_SCAN_DISPATCHED; 890047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 891047043c2SRobert Mustacchi 892047043c2SRobert Mustacchi return (DDI_SUCCESS); 893047043c2SRobert Mustacchi } 894047043c2SRobert Mustacchi 895047043c2SRobert Mustacchi static int 896047043c2SRobert Mustacchi amdzen_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 897047043c2SRobert Mustacchi { 898047043c2SRobert Mustacchi amdzen_t *azn = amdzen_data; 899047043c2SRobert Mustacchi 900047043c2SRobert Mustacchi if (cmd == DDI_SUSPEND) { 901047043c2SRobert Mustacchi return (DDI_SUCCESS); 902047043c2SRobert Mustacchi } else if (cmd != DDI_DETACH) { 903047043c2SRobert Mustacchi return (DDI_FAILURE); 904047043c2SRobert Mustacchi } 905047043c2SRobert Mustacchi 906047043c2SRobert Mustacchi mutex_enter(&azn->azn_mutex); 907047043c2SRobert Mustacchi while (azn->azn_taskqid != TASKQID_INVALID) { 908047043c2SRobert Mustacchi cv_wait(&azn->azn_cv, &azn->azn_mutex); 909047043c2SRobert Mustacchi } 910047043c2SRobert Mustacchi 911047043c2SRobert Mustacchi /* 912047043c2SRobert Mustacchi * If we've attached any stub drivers, e.g. this platform is important 913047043c2SRobert Mustacchi * for us, then we fail detach. 914047043c2SRobert Mustacchi */ 915047043c2SRobert Mustacchi if (!list_is_empty(&azn->azn_df_stubs) || 916047043c2SRobert Mustacchi !list_is_empty(&azn->azn_nb_stubs)) { 917047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 918047043c2SRobert Mustacchi return (DDI_FAILURE); 919047043c2SRobert Mustacchi } 920047043c2SRobert Mustacchi 921047043c2SRobert Mustacchi azn->azn_dip = NULL; 922047043c2SRobert Mustacchi mutex_exit(&azn->azn_mutex); 923047043c2SRobert Mustacchi 924047043c2SRobert Mustacchi return (DDI_SUCCESS); 925047043c2SRobert Mustacchi } 926047043c2SRobert Mustacchi 927047043c2SRobert Mustacchi static void 928047043c2SRobert Mustacchi amdzen_free(void) 929047043c2SRobert Mustacchi { 930047043c2SRobert Mustacchi if (amdzen_data == NULL) { 931047043c2SRobert Mustacchi return; 932047043c2SRobert Mustacchi } 933047043c2SRobert Mustacchi 934047043c2SRobert Mustacchi VERIFY(list_is_empty(&amdzen_data->azn_df_stubs)); 935047043c2SRobert Mustacchi list_destroy(&amdzen_data->azn_df_stubs); 936047043c2SRobert Mustacchi VERIFY(list_is_empty(&amdzen_data->azn_nb_stubs)); 937047043c2SRobert Mustacchi list_destroy(&amdzen_data->azn_nb_stubs); 938047043c2SRobert Mustacchi cv_destroy(&amdzen_data->azn_cv); 939047043c2SRobert Mustacchi mutex_destroy(&amdzen_data->azn_mutex); 940047043c2SRobert Mustacchi kmem_free(amdzen_data, sizeof (amdzen_t)); 941047043c2SRobert Mustacchi amdzen_data = NULL; 942047043c2SRobert Mustacchi } 943047043c2SRobert Mustacchi 944047043c2SRobert Mustacchi static void 945047043c2SRobert Mustacchi amdzen_alloc(void) 946047043c2SRobert Mustacchi { 947047043c2SRobert Mustacchi amdzen_data = kmem_zalloc(sizeof (amdzen_t), KM_SLEEP); 948047043c2SRobert Mustacchi mutex_init(&amdzen_data->azn_mutex, NULL, MUTEX_DRIVER, NULL); 949047043c2SRobert Mustacchi list_create(&amdzen_data->azn_df_stubs, sizeof (amdzen_stub_t), 950047043c2SRobert Mustacchi offsetof(amdzen_stub_t, azns_link)); 951047043c2SRobert Mustacchi list_create(&amdzen_data->azn_nb_stubs, sizeof (amdzen_stub_t), 952047043c2SRobert Mustacchi offsetof(amdzen_stub_t, azns_link)); 953047043c2SRobert Mustacchi cv_init(&amdzen_data->azn_cv, NULL, CV_DRIVER, NULL); 954047043c2SRobert Mustacchi } 955047043c2SRobert Mustacchi 956047043c2SRobert Mustacchi struct bus_ops amdzen_bus_ops = { 957047043c2SRobert Mustacchi .busops_rev = BUSO_REV, 958047043c2SRobert Mustacchi .bus_map = nullbusmap, 959047043c2SRobert Mustacchi .bus_dma_map = ddi_no_dma_map, 960047043c2SRobert Mustacchi .bus_dma_allochdl = ddi_no_dma_allochdl, 961047043c2SRobert Mustacchi .bus_dma_freehdl = ddi_no_dma_freehdl, 962047043c2SRobert Mustacchi .bus_dma_bindhdl = ddi_no_dma_bindhdl, 963047043c2SRobert Mustacchi .bus_dma_unbindhdl = ddi_no_dma_unbindhdl, 964047043c2SRobert Mustacchi .bus_dma_flush = ddi_no_dma_flush, 965047043c2SRobert Mustacchi .bus_dma_win = ddi_no_dma_win, 966047043c2SRobert Mustacchi .bus_dma_ctl = ddi_no_dma_mctl, 967047043c2SRobert Mustacchi .bus_prop_op = ddi_bus_prop_op, 968047043c2SRobert Mustacchi .bus_ctl = amdzen_bus_ctl 969047043c2SRobert Mustacchi }; 970047043c2SRobert Mustacchi 971047043c2SRobert Mustacchi static struct dev_ops amdzen_dev_ops = { 972047043c2SRobert Mustacchi .devo_rev = DEVO_REV, 973047043c2SRobert Mustacchi .devo_refcnt = 0, 974047043c2SRobert Mustacchi .devo_getinfo = nodev, 975047043c2SRobert Mustacchi .devo_identify = nulldev, 976047043c2SRobert Mustacchi .devo_probe = nulldev, 977047043c2SRobert Mustacchi .devo_attach = amdzen_attach, 978047043c2SRobert Mustacchi .devo_detach = amdzen_detach, 979047043c2SRobert Mustacchi .devo_reset = nodev, 980047043c2SRobert Mustacchi .devo_quiesce = ddi_quiesce_not_needed, 981047043c2SRobert Mustacchi .devo_bus_ops = &amdzen_bus_ops 982047043c2SRobert Mustacchi }; 983047043c2SRobert Mustacchi 984047043c2SRobert Mustacchi static struct modldrv amdzen_modldrv = { 985047043c2SRobert Mustacchi .drv_modops = &mod_driverops, 986047043c2SRobert Mustacchi .drv_linkinfo = "AMD Zen Nexus Driver", 987047043c2SRobert Mustacchi .drv_dev_ops = &amdzen_dev_ops 988047043c2SRobert Mustacchi }; 989047043c2SRobert Mustacchi 990047043c2SRobert Mustacchi static struct modlinkage amdzen_modlinkage = { 991047043c2SRobert Mustacchi .ml_rev = MODREV_1, 992047043c2SRobert Mustacchi .ml_linkage = { &amdzen_modldrv, NULL } 993047043c2SRobert Mustacchi }; 994047043c2SRobert Mustacchi 995047043c2SRobert Mustacchi int 996047043c2SRobert Mustacchi _init(void) 997047043c2SRobert Mustacchi { 998047043c2SRobert Mustacchi int ret; 999047043c2SRobert Mustacchi 1000*9b0429a1SPu Wen if (cpuid_getvendor(CPU) != X86_VENDOR_AMD && 1001*9b0429a1SPu Wen cpuid_getvendor(CPU) != X86_VENDOR_HYGON) { 1002047043c2SRobert Mustacchi return (ENOTSUP); 1003047043c2SRobert Mustacchi } 1004047043c2SRobert Mustacchi 1005047043c2SRobert Mustacchi if ((ret = mod_install(&amdzen_modlinkage)) == 0) { 1006047043c2SRobert Mustacchi amdzen_alloc(); 1007047043c2SRobert Mustacchi } 1008047043c2SRobert Mustacchi 1009047043c2SRobert Mustacchi return (ret); 1010047043c2SRobert Mustacchi } 1011047043c2SRobert Mustacchi 1012047043c2SRobert Mustacchi int 1013047043c2SRobert Mustacchi _info(struct modinfo *modinfop) 1014047043c2SRobert Mustacchi { 1015047043c2SRobert Mustacchi return (mod_info(&amdzen_modlinkage, modinfop)); 1016047043c2SRobert Mustacchi } 1017047043c2SRobert Mustacchi 1018047043c2SRobert Mustacchi int 1019047043c2SRobert Mustacchi _fini(void) 1020047043c2SRobert Mustacchi { 1021047043c2SRobert Mustacchi int ret; 1022047043c2SRobert Mustacchi 1023047043c2SRobert Mustacchi if ((ret = mod_remove(&amdzen_modlinkage)) == 0) { 1024047043c2SRobert Mustacchi amdzen_free(); 1025047043c2SRobert Mustacchi } 1026047043c2SRobert Mustacchi 1027047043c2SRobert Mustacchi return (ret); 1028047043c2SRobert Mustacchi } 1029