/* * 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. */ /* * px_msi.c */ #include <sys/types.h> #include <sys/kmem.h> #include <sys/conf.h> #include <sys/ddi.h> #include <sys/sunddi.h> #include <sys/sunndi.h> #include <sys/modctl.h> #include <sys/disp.h> #include <sys/stat.h> #include <sys/ddi_impldefs.h> #include <sys/pci_impl.h> #include "px_obj.h" static int px_msi_get_props(px_t *px_p); /* * msi_attach() */ int px_msi_attach(px_t *px_p) { dev_info_t *dip = px_p->px_dip; px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; ddi_irm_pool_t *irm_pool_p = NULL; ddi_irm_params_t irm_params; msinum_t msi_num; int i, ret; DBG(DBG_MSIQ, dip, "px_msi_attach\n"); mutex_init(&msi_state_p->msi_mutex, NULL, MUTEX_DRIVER, NULL); /* * Check for all MSI related properties and * save all information. */ if (px_msi_get_props(px_p) != DDI_SUCCESS) { px_msi_detach(px_p); return (DDI_FAILURE); } msi_state_p->msi_p = kmem_zalloc(msi_state_p->msi_cnt * sizeof (px_msi_t), KM_SLEEP); for (i = 0, msi_num = msi_state_p->msi_1st_msinum; i < msi_state_p->msi_cnt; i++, msi_num++) { msi_state_p->msi_p[i].msi_msinum = msi_num; msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; } /* * Create IRM pool to manage interrupt allocations. */ bzero(&irm_params, sizeof (ddi_irm_params_t)); irm_params.iparams_types = msi_state_p->msi_type; irm_params.iparams_total = msi_state_p->msi_cnt; irm_params.iparams_default = DDI_DEFAULT_MSIX_ALLOC; if (ndi_irm_create(dip, &irm_params, &irm_pool_p) == DDI_SUCCESS) { msi_state_p->msi_pool_p = irm_pool_p; } else { DBG(DBG_MSIQ, dip, "ndi_irm_create() failed\n"); } if ((ret = px_lib_msi_init(dip)) != DDI_SUCCESS) px_msi_detach(px_p); return (ret); } /* * msi_detach() */ void px_msi_detach(px_t *px_p) { dev_info_t *dip = px_p->px_dip; px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; DBG(DBG_MSIQ, dip, "px_msi_detach\n"); if (msi_state_p->msi_pool_p) { (void) ndi_irm_destroy(msi_state_p->msi_pool_p); } if (msi_state_p->msi_addr64 && msi_state_p->msi_mem_flg) { ndi_ra_free(dip, msi_state_p->msi_addr64, msi_state_p->msi_addr64_len, NDI_RA_TYPE_MEM, NDI_RA_PASS); } if (msi_state_p->msi_addr32 && msi_state_p->msi_mem_flg) { ndi_ra_free(dip, msi_state_p->msi_addr32, msi_state_p->msi_addr32_len, NDI_RA_TYPE_MEM, NDI_RA_PASS); pci_resource_destroy(dip); } if (msi_state_p->msi_p) { kmem_free(msi_state_p->msi_p, msi_state_p->msi_cnt * sizeof (px_msi_t)); } mutex_destroy(&msi_state_p->msi_mutex); bzero(&px_p->px_ib_p->ib_msi_state, sizeof (px_msi_state_t)); } /* * msi_alloc() */ /* ARGSUSED */ int px_msi_alloc(px_t *px_p, dev_info_t *rdip, int type, int inum, int msi_count, int flag, int *actual_msi_count_p) { px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; int first, count, i, n; DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " "type 0x%x inum 0x%x msi_count 0x%x\n", ddi_driver_name(rdip), ddi_get_instance(rdip), type, inum, msi_count); mutex_enter(&msi_state_p->msi_mutex); *actual_msi_count_p = 0; /* * MSI interrupts are allocated as contiguous ranges at * power of 2 boundaries from the start of the MSI array. */ if (type == DDI_INTR_TYPE_MSI) { /* Search for a range of available interrupts */ for (count = msi_count; count; count >>= 1) { for (first = 0; (first + count) < msi_state_p->msi_cnt; first += count) { for (i = first; i < (first + count); i++) { if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE) { break; } } if (i == (first + count)) { goto found_msi; } } DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: failed\n"); if (count > 1) { DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: " "Retry MSI allocation with new msi_count " "0x%x\n", count >> 1); } } found_msi: /* Set number of available interrupts */ *actual_msi_count_p = count; /* Check if successful, and enforce strict behavior */ if ((count == 0) || ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { mutex_exit(&msi_state_p->msi_mutex); return (DDI_EAGAIN); } /* Allocate the interrupts */ for (i = first; i < (first + count); i++, inum++) { msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; msi_state_p->msi_p[i].msi_dip = rdip; msi_state_p->msi_p[i].msi_inum = inum; } } /* * MSI-X interrupts are allocated from the end of the MSI * array. There are no concerns about power of 2 boundaries * and the allocated interrupts do not have to be contiguous. */ if (type == DDI_INTR_TYPE_MSIX) { /* Count available interrupts, up to count requested */ for (count = 0, i = (msi_state_p->msi_cnt - 1); i >= 0; i--) { if (msi_state_p->msi_p[i].msi_state == MSI_STATE_FREE) { if (count == 0) first = i; count++; if (count == msi_count) break; } } /* Set number of available interrupts */ *actual_msi_count_p = count; /* Check if successful, and enforce strict behavior */ if ((count == 0) || ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { mutex_exit(&msi_state_p->msi_mutex); return (DDI_EAGAIN); } /* Allocate the interrupts */ for (n = 0, i = first; n < count; i--) { if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE) continue; msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; msi_state_p->msi_p[i].msi_dip = rdip; msi_state_p->msi_p[i].msi_inum = inum; inum++; n++; } } DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " "msi_num 0x%x count 0x%x\n", ddi_driver_name(rdip), ddi_get_instance(rdip), first, count); mutex_exit(&msi_state_p->msi_mutex); return (DDI_SUCCESS); } /* * msi_free() */ int px_msi_free(px_t *px_p, dev_info_t *rdip, int inum, int msi_count) { px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; int i, n; DBG(DBG_R_MSIX, px_p->px_dip, "px_msi_free: rdip 0x%p " "inum 0x%x msi_count 0x%x\n", rdip, inum, msi_count); mutex_enter(&msi_state_p->msi_mutex); /* * Find and release the specified MSI/X numbers. * * Because the allocations are not always contiguous, perform * a full linear search of the MSI/X table looking for MSI/X * vectors owned by the device with inum values in the range * [inum .. (inum + msi_count - 1)]. */ for (i = 0, n = 0; (i < msi_state_p->msi_cnt) && (n < msi_count); i++) { if ((msi_state_p->msi_p[i].msi_dip == rdip) && (msi_state_p->msi_p[i].msi_inum >= inum) && (msi_state_p->msi_p[i].msi_inum < (inum + msi_count))) { msi_state_p->msi_p[i].msi_dip = NULL; msi_state_p->msi_p[i].msi_inum = 0; msi_state_p->msi_p[i].msi_msiq_id = 0; msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; n++; } } mutex_exit(&msi_state_p->msi_mutex); /* Fail if the MSI/X numbers were not found */ if (n < msi_count) return (DDI_FAILURE); return (DDI_SUCCESS); } /* * msi_get_msinum() */ int px_msi_get_msinum(px_t *px_p, dev_info_t *rdip, int inum, msinum_t *msi_num_p) { px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; int i; DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " "rdip 0x%p inum 0x%x\n", rdip, inum); mutex_enter(&msi_state_p->msi_mutex); for (i = 0; i < msi_state_p->msi_cnt; i++) { if ((msi_state_p->msi_p[i].msi_inum == inum) && (msi_state_p->msi_p[i].msi_dip == rdip)) { *msi_num_p = msi_state_p->msi_p[i].msi_msinum; DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " "inum 0x%x msi 0x%x\n", inum, *msi_num_p); mutex_exit(&msi_state_p->msi_mutex); return (DDI_SUCCESS); } } if (i >= msi_state_p->msi_cnt) DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " "no msi for inum 0x%x\n", inum); mutex_exit(&msi_state_p->msi_mutex); return (DDI_FAILURE); } /* * px_msi_get_props() */ static int px_msi_get_props(px_t *px_p) { dev_info_t *dip = px_p->px_dip; px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; int ret = DDI_SUCCESS; int length = sizeof (int); int *valuep = NULL; uint64_t msi_addr_hi, msi_addr_lo; uint64_t mem_answer, mem_alen; ndi_ra_request_t request; DBG(DBG_MSIQ, dip, "px_msi_get_props\n"); /* #msi */ msi_state_p->msi_cnt = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "#msi", PX_DEFAULT_MSI_CNT); DBG(DBG_MSIQ, dip, "obp: #msi=%d\n", msi_state_p->msi_cnt); /* msi-ranges: msi# field */ ret = ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, DDI_PROP_DONTPASS, "msi-ranges", (caddr_t)&valuep, &length); if (ret == DDI_PROP_SUCCESS) { msi_state_p->msi_1st_msinum = ((px_msi_ranges_t *)valuep)->msi_no; kmem_free(valuep, (size_t)length); } else msi_state_p->msi_1st_msinum = PX_DEFAULT_MSI_1ST_MSINUM; DBG(DBG_MSIQ, dip, "obp: msi_1st_msinum=%d\n", msi_state_p->msi_1st_msinum); /* msi-data-mask */ msi_state_p->msi_data_mask = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "msi-data-mask", PX_DEFAULT_MSI_DATA_MASK); DBG(DBG_MSIQ, dip, "obp: msi-data-mask=0x%x\n", msi_state_p->msi_data_mask); /* msi-data-width */ msi_state_p->msi_data_width = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "msix-data-width", PX_DEFAULT_MSI_DATA_WIDTH); DBG(DBG_MSIQ, dip, "obp: msix-data-width=%d\n", msi_state_p->msi_data_width); /* * Assume MSI is always supported, but also check if MSIX is supported */ if (msi_state_p->msi_data_width) { msi_state_p->msi_type = DDI_INTR_TYPE_MSI; if (msi_state_p->msi_data_width == PX_MSIX_WIDTH) msi_state_p->msi_type |= DDI_INTR_TYPE_MSIX; } /* msi-address-ranges */ ret = ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, DDI_PROP_DONTPASS, "msi-address-ranges", (caddr_t)&valuep, &length); if (ret == DDI_PROP_SUCCESS) { msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr32_hi; msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr32_lo; msi_state_p->msi_addr32 = (msi_addr_hi << 32) | msi_addr_lo; msi_state_p->msi_addr32_len = ((px_msi_address_ranges_t *)valuep)->msi_addr32_len; msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr64_hi; msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr64_lo; msi_state_p->msi_addr64 = (msi_addr_hi << 32) | msi_addr_lo; msi_state_p->msi_addr64_len = ((px_msi_address_ranges_t *)valuep)->msi_addr64_len; kmem_free(valuep, (size_t)length); msi_state_p->msi_mem_flg = B_FALSE; DBG(DBG_MSIQ, dip, "obp: msi_addr32=0x%llx\n", msi_state_p->msi_addr32); DBG(DBG_MSIQ, dip, "obp: msi_addr64=0x%llx\n", msi_state_p->msi_addr64); return (ret); } /* * If msi-address-ranges property does not exist in OBP, Fire * driver will need to allocate memory. * * Allocate 64KB of memory from unused PCI-E address space for the MSI * transactions and program MSI 32-bit address register. * * This register is used by the Fire hardware to compare against the * address of incoming PCI-E 32-bit addressed memory write commands. * If the address matches bits 31:16 then PCI-E command is considered * to be MSI transaction. * * pci_resource_setup() is called in context of PCI hotplug * initialization. * * Setup resource maps for this bus node. */ if (pci_resource_setup(dip) != NDI_SUCCESS) { DBG(DBG_MSIQ, dip, "px_msi_getprops: dip=%s%d" "pci_resource_setup failed\n", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_FAILURE); } msi_state_p->msi_mem_flg = B_TRUE; /* * Reserve PCI MEM 32 resources to perform 32 bit MSI transactions. */ bzero((caddr_t)&request, sizeof (ndi_ra_request_t)); request.ra_flags = NDI_RA_ALLOC_BOUNDED; request.ra_boundbase = 0; request.ra_boundlen = PX_MSI_4GIG_LIMIT; request.ra_len = PX_MSI_ADDR_LEN; request.ra_align_mask = 0; if (ndi_ra_alloc(dip, &request, &mem_answer, &mem_alen, NDI_RA_TYPE_MEM, NDI_RA_PASS) != NDI_SUCCESS) { DBG(DBG_MSIQ, dip, "px_msi_getprops: Failed to allocate " "64KB mem\n"); return (DDI_FAILURE); } msi_state_p->msi_addr32 = mem_answer; msi_state_p->msi_addr32_len = mem_alen; DBG(DBG_MSIQ, dip, "px_msi_getprops: 32 Addr 0x%llx\n", msi_state_p->msi_addr32); /* * Reserve PCI MEM 64 resources to perform 64 bit MSI transactions. * * NOTE: * * Currently OBP do not export any "available" property or range in * the MEM64 space. Hence ndi_ra_alloc() request will return failure. * So, for time being ignore this failure. */ bzero((caddr_t)&request, sizeof (ndi_ra_request_t)); request.ra_flags = NDI_RA_ALLOC_BOUNDED; request.ra_boundbase = PX_MSI_4GIG_LIMIT + 1; request.ra_boundlen = PX_MSI_4GIG_LIMIT; request.ra_len = PX_MSI_ADDR_LEN; request.ra_align_mask = 0; if (ndi_ra_alloc(dip, &request, &mem_answer, &mem_alen, NDI_RA_TYPE_MEM, NDI_RA_PASS) != NDI_SUCCESS) { DBG(DBG_MSIQ, dip, "px_msi_getprops: Failed to allocate " "64KB mem\n"); return (DDI_SUCCESS); } msi_state_p->msi_addr64 = mem_answer; msi_state_p->msi_addr64_len = mem_alen; DBG(DBG_MSIQ, dip, "px_msi_getprops: 64 Addr 0x%llx\n", msi_state_p->msi_addr64); return (DDI_SUCCESS); }