| /* Copyright 2013-2014 IBM Corp. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| * implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <skiboot.h> |
| #include <opal-api.h> |
| #include <pci.h> |
| #include <pci-cfg.h> |
| #include <pci-slot.h> |
| #include <opal-msg.h> |
| #include <timebase.h> |
| #include <timer.h> |
| |
| #define OPAL_PCICFG_ACCESS_READ(op, cb, type) \ |
| static int64_t opal_pci_config_##op(uint64_t phb_id, \ |
| uint64_t bus_dev_func, \ |
| uint64_t offset, type data) \ |
| { \ |
| struct phb *phb = pci_get_phb(phb_id); \ |
| int64_t rc; \ |
| \ |
| if (!opal_addr_valid((void *)data)) \ |
| return OPAL_PARAMETER; \ |
| \ |
| if (!phb) \ |
| return OPAL_PARAMETER; \ |
| phb_lock(phb); \ |
| rc = phb->ops->cfg_##cb(phb, bus_dev_func, offset, data); \ |
| phb_unlock(phb); \ |
| \ |
| return rc; \ |
| } |
| |
| #define OPAL_PCICFG_ACCESS_WRITE(op, cb, type) \ |
| static int64_t opal_pci_config_##op(uint64_t phb_id, \ |
| uint64_t bus_dev_func, \ |
| uint64_t offset, type data) \ |
| { \ |
| struct phb *phb = pci_get_phb(phb_id); \ |
| int64_t rc; \ |
| \ |
| if (!phb) \ |
| return OPAL_PARAMETER; \ |
| phb_lock(phb); \ |
| rc = phb->ops->cfg_##cb(phb, bus_dev_func, offset, data); \ |
| phb_unlock(phb); \ |
| \ |
| return rc; \ |
| } |
| |
| OPAL_PCICFG_ACCESS_READ(read_byte, read8, uint8_t *) |
| OPAL_PCICFG_ACCESS_READ(read_half_word, read16, uint16_t *) |
| OPAL_PCICFG_ACCESS_READ(read_word, read32, uint32_t *) |
| OPAL_PCICFG_ACCESS_WRITE(write_byte, write8, uint8_t) |
| OPAL_PCICFG_ACCESS_WRITE(write_half_word, write16, uint16_t) |
| OPAL_PCICFG_ACCESS_WRITE(write_word, write32, uint32_t) |
| |
| opal_call(OPAL_PCI_CONFIG_READ_BYTE, opal_pci_config_read_byte, 4); |
| opal_call(OPAL_PCI_CONFIG_READ_HALF_WORD, opal_pci_config_read_half_word, 4); |
| opal_call(OPAL_PCI_CONFIG_READ_WORD, opal_pci_config_read_word, 4); |
| opal_call(OPAL_PCI_CONFIG_WRITE_BYTE, opal_pci_config_write_byte, 4); |
| opal_call(OPAL_PCI_CONFIG_WRITE_HALF_WORD, opal_pci_config_write_half_word, 4); |
| opal_call(OPAL_PCI_CONFIG_WRITE_WORD, opal_pci_config_write_word, 4); |
| |
| static struct lock opal_eeh_evt_lock = LOCK_UNLOCKED; |
| static uint64_t opal_eeh_evt = 0; |
| |
| void opal_pci_eeh_set_evt(uint64_t phb_id) |
| { |
| lock(&opal_eeh_evt_lock); |
| opal_eeh_evt |= 1ULL << phb_id; |
| opal_update_pending_evt(OPAL_EVENT_PCI_ERROR, OPAL_EVENT_PCI_ERROR); |
| unlock(&opal_eeh_evt_lock); |
| } |
| |
| void opal_pci_eeh_clear_evt(uint64_t phb_id) |
| { |
| lock(&opal_eeh_evt_lock); |
| opal_eeh_evt &= ~(1ULL << phb_id); |
| if (!opal_eeh_evt) |
| opal_update_pending_evt(OPAL_EVENT_PCI_ERROR, 0); |
| unlock(&opal_eeh_evt_lock); |
| } |
| |
| static int64_t opal_pci_eeh_freeze_status(uint64_t phb_id, uint64_t pe_number, |
| uint8_t *freeze_state, |
| uint16_t *pci_error_type, |
| uint64_t *phb_status) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(freeze_state) || !opal_addr_valid(pci_error_type) |
| || !opal_addr_valid(phb_status)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->eeh_freeze_status) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| |
| if (phb_status) |
| prlog(PR_ERR, "PHB#%04llx: %s: deprecated PHB status\n", |
| phb_id, __func__); |
| |
| rc = phb->ops->eeh_freeze_status(phb, pe_number, freeze_state, |
| pci_error_type, NULL); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_EEH_FREEZE_STATUS, opal_pci_eeh_freeze_status, 5); |
| |
| static int64_t opal_pci_eeh_freeze_clear(uint64_t phb_id, uint64_t pe_number, |
| uint64_t eeh_action_token) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->eeh_freeze_clear) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->eeh_freeze_clear(phb, pe_number, eeh_action_token); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_EEH_FREEZE_CLEAR, opal_pci_eeh_freeze_clear, 3); |
| |
| static int64_t opal_pci_eeh_freeze_set(uint64_t phb_id, uint64_t pe_number, |
| uint64_t eeh_action_token) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->eeh_freeze_set) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->eeh_freeze_set(phb, pe_number, eeh_action_token); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_EEH_FREEZE_SET, opal_pci_eeh_freeze_set, 3); |
| |
| static int64_t opal_pci_err_inject(uint64_t phb_id, uint64_t pe_number, |
| uint32_t type, uint32_t func, |
| uint64_t addr, uint64_t mask) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops || !phb->ops->err_inject) |
| return OPAL_UNSUPPORTED; |
| |
| if (type != OPAL_ERR_INJECT_TYPE_IOA_BUS_ERR && |
| type != OPAL_ERR_INJECT_TYPE_IOA_BUS_ERR64) |
| return OPAL_PARAMETER; |
| |
| phb_lock(phb); |
| rc = phb->ops->err_inject(phb, pe_number, type, func, addr, mask); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_ERR_INJECT, opal_pci_err_inject, 6); |
| |
| static int64_t opal_pci_phb_mmio_enable(uint64_t phb_id, uint16_t window_type, |
| uint16_t window_num, uint16_t enable) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->phb_mmio_enable) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->phb_mmio_enable(phb, window_type, window_num, enable); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_PHB_MMIO_ENABLE, opal_pci_phb_mmio_enable, 4); |
| |
| static int64_t opal_pci_set_phb_mem_window(uint64_t phb_id, |
| uint16_t window_type, |
| uint16_t window_num, |
| uint64_t addr, |
| uint64_t pci_addr, |
| uint64_t size) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_phb_mem_window) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_phb_mem_window(phb, window_type, window_num, |
| addr, pci_addr, size); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PHB_MEM_WINDOW, opal_pci_set_phb_mem_window, 6); |
| |
| static int64_t opal_pci_map_pe_mmio_window(uint64_t phb_id, uint64_t pe_number, |
| uint16_t window_type, |
| uint16_t window_num, |
| uint16_t segment_num) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->map_pe_mmio_window) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->map_pe_mmio_window(phb, pe_number, window_type, |
| window_num, segment_num); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_MAP_PE_MMIO_WINDOW, opal_pci_map_pe_mmio_window, 5); |
| |
| static int64_t opal_pci_set_phb_table_memory(uint64_t phb_id __unused, |
| uint64_t rtt_addr __unused, |
| uint64_t ivt_addr __unused, |
| uint64_t ivt_len __unused, |
| uint64_t rej_array_addr __unused, |
| uint64_t peltv_addr __unused) |
| { |
| /* IODA2 (P8) stuff, TODO */ |
| return OPAL_UNSUPPORTED; |
| } |
| opal_call(OPAL_PCI_SET_PHB_TABLE_MEMORY, opal_pci_set_phb_table_memory, 6); |
| |
| static int64_t opal_pci_set_pe(uint64_t phb_id, uint64_t pe_number, |
| uint64_t bus_dev_func, uint8_t bus_compare, |
| uint8_t dev_compare, uint8_t func_compare, |
| uint8_t pe_action) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_pe) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_pe(phb, pe_number, bus_dev_func, bus_compare, |
| dev_compare, func_compare, pe_action); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PE, opal_pci_set_pe, 7); |
| |
| static int64_t opal_pci_set_peltv(uint64_t phb_id, uint32_t parent_pe, |
| uint32_t child_pe, uint8_t state) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_peltv) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_peltv(phb, parent_pe, child_pe, state); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PELTV, opal_pci_set_peltv, 4); |
| |
| static int64_t opal_pci_set_mve(uint64_t phb_id, uint32_t mve_number, |
| uint64_t pe_number) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_mve) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_mve(phb, mve_number, pe_number); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_MVE, opal_pci_set_mve, 3); |
| |
| static int64_t opal_pci_set_mve_enable(uint64_t phb_id, uint32_t mve_number, |
| uint32_t state) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_mve_enable) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_mve_enable(phb, mve_number, state); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_MVE_ENABLE, opal_pci_set_mve_enable, 3); |
| |
| static int64_t opal_pci_get_xive_reissue(uint64_t phb_id __unused, |
| uint32_t xive_number __unused, |
| uint8_t *p_bit __unused, |
| uint8_t *q_bit __unused) |
| { |
| /* IODA2 (P8) stuff, TODO */ |
| return OPAL_UNSUPPORTED; |
| } |
| opal_call(OPAL_PCI_GET_XIVE_REISSUE, opal_pci_get_xive_reissue, 4); |
| |
| static int64_t opal_pci_set_xive_reissue(uint64_t phb_id __unused, |
| uint32_t xive_number __unused, |
| uint8_t p_bit __unused, |
| uint8_t q_bit __unused) |
| { |
| /* IODA2 (P8) stuff, TODO */ |
| return OPAL_UNSUPPORTED; |
| } |
| opal_call(OPAL_PCI_SET_XIVE_REISSUE, opal_pci_set_xive_reissue, 4); |
| |
| static int64_t opal_pci_msi_eoi(uint64_t phb_id, |
| uint32_t hwirq) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->pci_msi_eoi) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->pci_msi_eoi(phb, hwirq); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_MSI_EOI, opal_pci_msi_eoi, 2); |
| |
| static int64_t opal_pci_tce_kill(uint64_t phb_id, |
| uint32_t kill_type, |
| uint64_t pe_number, uint32_t tce_size, |
| uint64_t dma_addr, uint32_t npages) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->tce_kill) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->tce_kill(phb, kill_type, pe_number, tce_size, |
| dma_addr, npages); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_TCE_KILL, opal_pci_tce_kill, 6); |
| |
| static int64_t opal_pci_set_xive_pe(uint64_t phb_id, uint64_t pe_number, |
| uint32_t xive_num) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_xive_pe) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_xive_pe(phb, pe_number, xive_num); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_XIVE_PE, opal_pci_set_xive_pe, 3); |
| |
| static int64_t opal_get_xive_source(uint64_t phb_id, uint32_t xive_num, |
| int32_t *interrupt_source_number) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(interrupt_source_number)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_xive_source) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->get_xive_source(phb, xive_num, interrupt_source_number); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_GET_XIVE_SOURCE, opal_get_xive_source, 3); |
| |
| static int64_t opal_get_msi_32(uint64_t phb_id, uint32_t mve_number, |
| uint32_t xive_num, uint8_t msi_range, |
| uint32_t *msi_address, uint32_t *message_data) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(msi_address) || !opal_addr_valid(message_data)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_msi_32) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->get_msi_32(phb, mve_number, xive_num, msi_range, |
| msi_address, message_data); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_GET_MSI_32, opal_get_msi_32, 6); |
| |
| static int64_t opal_get_msi_64(uint64_t phb_id, uint32_t mve_number, |
| uint32_t xive_num, uint8_t msi_range, |
| uint64_t *msi_address, uint32_t *message_data) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(msi_address) || !opal_addr_valid(message_data)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_msi_64) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->get_msi_64(phb, mve_number, xive_num, msi_range, |
| msi_address, message_data); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_GET_MSI_64, opal_get_msi_64, 6); |
| |
| static int64_t opal_pci_map_pe_dma_window(uint64_t phb_id, uint64_t pe_number, |
| uint16_t window_id, |
| uint16_t tce_levels, |
| uint64_t tce_table_addr, |
| uint64_t tce_table_size, |
| uint64_t tce_page_size) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->map_pe_dma_window) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->map_pe_dma_window(phb, pe_number, window_id, |
| tce_levels, tce_table_addr, |
| tce_table_size, tce_page_size); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW, opal_pci_map_pe_dma_window, 7); |
| |
| static int64_t opal_pci_map_pe_dma_window_real(uint64_t phb_id, |
| uint64_t pe_number, |
| uint16_t window_id, |
| uint64_t pci_start_addr, |
| uint64_t pci_mem_size) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->map_pe_dma_window_real) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->map_pe_dma_window_real(phb, pe_number, window_id, |
| pci_start_addr, pci_mem_size); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW_REAL, opal_pci_map_pe_dma_window_real, 5); |
| |
| static int64_t opal_pci_reset(uint64_t id, uint8_t reset_scope, |
| uint8_t assert_state) |
| { |
| struct pci_slot *slot = pci_slot_find(id); |
| struct phb *phb = slot ? slot->phb : NULL; |
| int64_t rc = OPAL_SUCCESS; |
| |
| if (!slot || !phb) |
| return OPAL_PARAMETER; |
| if (assert_state != OPAL_ASSERT_RESET && |
| assert_state != OPAL_DEASSERT_RESET) |
| return OPAL_PARAMETER; |
| |
| phb_lock(phb); |
| |
| switch(reset_scope) { |
| case OPAL_RESET_PHB_COMPLETE: |
| /* Complete reset is applicable to PHB slot only */ |
| if (!slot->ops.creset || slot->pd) { |
| rc = OPAL_UNSUPPORTED; |
| break; |
| } |
| |
| if (assert_state != OPAL_ASSERT_RESET) |
| break; |
| |
| rc = slot->ops.creset(slot); |
| if (rc < 0) |
| prlog(PR_ERR, "SLOT-%016llx: Error %lld on complete reset\n", |
| slot->id, rc); |
| break; |
| case OPAL_RESET_PCI_FUNDAMENTAL: |
| if (!slot->ops.freset) { |
| rc = OPAL_UNSUPPORTED; |
| break; |
| } |
| |
| /* We need do nothing on deassert time */ |
| if (assert_state != OPAL_ASSERT_RESET) |
| break; |
| |
| rc = slot->ops.freset(slot); |
| if (rc < 0) |
| prlog(PR_ERR, "SLOT-%016llx: Error %lld on fundamental reset\n", |
| slot->id, rc); |
| break; |
| case OPAL_RESET_PCI_HOT: |
| if (!slot->ops.hreset) { |
| rc = OPAL_UNSUPPORTED; |
| break; |
| } |
| |
| /* We need do nothing on deassert time */ |
| if (assert_state != OPAL_ASSERT_RESET) |
| break; |
| |
| rc = slot->ops.hreset(slot); |
| if (rc < 0) |
| prlog(PR_ERR, "SLOT-%016llx: Error %lld on hot reset\n", |
| slot->id, rc); |
| break; |
| case OPAL_RESET_PCI_IODA_TABLE: |
| /* It's allowed on PHB slot only */ |
| if (slot->pd || !phb->ops || !phb->ops->ioda_reset) { |
| rc = OPAL_UNSUPPORTED; |
| break; |
| } |
| |
| if (assert_state != OPAL_ASSERT_RESET) |
| break; |
| |
| rc = phb->ops->ioda_reset(phb, true); |
| break; |
| case OPAL_RESET_PHB_ERROR: |
| /* It's allowed on PHB slot only */ |
| if (slot->pd || !phb->ops || !phb->ops->papr_errinjct_reset) { |
| rc = OPAL_UNSUPPORTED; |
| break; |
| } |
| |
| if (assert_state != OPAL_ASSERT_RESET) |
| break; |
| |
| rc = phb->ops->papr_errinjct_reset(phb); |
| break; |
| default: |
| rc = OPAL_UNSUPPORTED; |
| } |
| phb_unlock(phb); |
| |
| return (rc > 0) ? tb_to_msecs(rc) : rc; |
| } |
| opal_call(OPAL_PCI_RESET, opal_pci_reset, 3); |
| |
| static int64_t opal_pci_reinit(uint64_t phb_id, |
| uint64_t reinit_scope, |
| uint64_t data) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops || !phb->ops->pci_reinit) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = phb->ops->pci_reinit(phb, reinit_scope, data); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_REINIT, opal_pci_reinit, 3); |
| |
| static int64_t opal_pci_poll(uint64_t id) |
| { |
| struct pci_slot *slot = pci_slot_find(id); |
| struct phb *phb = slot ? slot->phb : NULL; |
| int64_t rc; |
| |
| if (!slot || !phb) |
| return OPAL_PARAMETER; |
| if (!slot->ops.run_sm) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = slot->ops.run_sm(slot); |
| phb_unlock(phb); |
| |
| /* Return milliseconds for caller to sleep: round up */ |
| if (rc > 0) { |
| rc = tb_to_msecs(rc); |
| if (rc == 0) |
| rc = 1; |
| } |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_POLL, opal_pci_poll, 1); |
| |
| static int64_t opal_pci_get_presence_state(uint64_t id, uint64_t data) |
| { |
| struct pci_slot *slot = pci_slot_find(id); |
| struct phb *phb = slot ? slot->phb : NULL; |
| uint8_t *presence = (uint8_t *)data; |
| int64_t rc; |
| |
| if (!opal_addr_valid(presence)) |
| return OPAL_PARAMETER; |
| |
| if (!slot || !phb) |
| return OPAL_PARAMETER; |
| if (!slot->ops.get_presence_state) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = slot->ops.get_presence_state(slot, presence); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_GET_PRESENCE_STATE, opal_pci_get_presence_state, 2); |
| |
| static int64_t opal_pci_get_power_state(uint64_t id, uint64_t data) |
| { |
| struct pci_slot *slot = pci_slot_find(id); |
| struct phb *phb = slot ? slot->phb : NULL; |
| uint8_t *power_state = (uint8_t *)data; |
| int64_t rc; |
| |
| if (!opal_addr_valid(power_state)) |
| return OPAL_PARAMETER; |
| |
| if (!slot || !phb) |
| return OPAL_PARAMETER; |
| if (!slot->ops.get_power_state) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = slot->ops.get_power_state(slot, power_state); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_GET_POWER_STATE, opal_pci_get_power_state, 2); |
| |
| static void set_power_timer(struct timer *t __unused, void *data, |
| uint64_t now __unused) |
| { |
| struct pci_slot *slot = data; |
| struct phb *phb = slot->phb; |
| struct pci_device *pd = slot->pd; |
| struct dt_node *dn = pd->dn; |
| uint8_t link; |
| |
| switch (slot->state) { |
| case PCI_SLOT_STATE_SPOWER_START: |
| if (slot->retries-- == 0) { |
| pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); |
| opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, |
| slot->async_token, dn->phandle, |
| slot->power_state, OPAL_BUSY); |
| } else { |
| schedule_timer(&slot->timer, msecs_to_tb(10)); |
| } |
| |
| break; |
| case PCI_SLOT_STATE_SPOWER_DONE: |
| if (slot->power_state == OPAL_PCI_SLOT_POWER_OFF) { |
| pci_remove_bus(phb, &pd->children); |
| pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); |
| opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, |
| slot->async_token, dn->phandle, |
| OPAL_PCI_SLOT_POWER_OFF, OPAL_SUCCESS); |
| break; |
| } |
| |
| /* Power on */ |
| if (slot->ops.get_link_state(slot, &link) != OPAL_SUCCESS) |
| link = 0; |
| if (link) { |
| slot->ops.prepare_link_change(slot, true); |
| pci_scan_bus(phb, pd->secondary_bus, |
| pd->subordinate_bus, |
| &pd->children, pd, true); |
| pci_add_device_nodes(phb, &pd->children, dn, |
| &phb->lstate, 0); |
| pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); |
| opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, |
| slot->async_token, dn->phandle, |
| OPAL_PCI_SLOT_POWER_ON, OPAL_SUCCESS); |
| } else if (slot->retries-- == 0) { |
| pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL); |
| opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, |
| slot->async_token, dn->phandle, |
| OPAL_PCI_SLOT_POWER_ON, OPAL_BUSY); |
| } else { |
| schedule_timer(&slot->timer, msecs_to_tb(10)); |
| } |
| |
| break; |
| default: |
| prlog(PR_ERR, "PCI SLOT %016llx: Unexpected state 0x%08x\n", |
| slot->id, slot->state); |
| } |
| } |
| |
| static int64_t opal_pci_set_power_state(uint64_t async_token, |
| uint64_t id, |
| uint64_t data) |
| { |
| struct pci_slot *slot = pci_slot_find(id); |
| struct phb *phb = slot ? slot->phb : NULL; |
| struct pci_device *pd = slot ? slot->pd : NULL; |
| uint8_t *state = (uint8_t *)data; |
| int64_t rc; |
| |
| if (!slot || !phb) |
| return OPAL_PARAMETER; |
| |
| if (!opal_addr_valid(state)) |
| return OPAL_PARAMETER; |
| |
| phb_lock(phb); |
| switch (*state) { |
| case OPAL_PCI_SLOT_POWER_OFF: |
| if (!slot->ops.prepare_link_change || |
| !slot->ops.set_power_state) { |
| phb_unlock(phb); |
| return OPAL_UNSUPPORTED; |
| } |
| |
| slot->async_token = async_token; |
| slot->ops.prepare_link_change(slot, false); |
| rc = slot->ops.set_power_state(slot, PCI_SLOT_POWER_OFF); |
| break; |
| case OPAL_PCI_SLOT_POWER_ON: |
| if (!slot->ops.set_power_state || |
| !slot->ops.get_link_state) { |
| phb_unlock(phb); |
| return OPAL_UNSUPPORTED; |
| } |
| |
| slot->async_token = async_token; |
| rc = slot->ops.set_power_state(slot, PCI_SLOT_POWER_ON); |
| break; |
| case OPAL_PCI_SLOT_OFFLINE: |
| if (!pd) { |
| phb_unlock(phb); |
| return OPAL_PARAMETER; |
| } |
| |
| pci_remove_bus(phb, &pd->children); |
| phb_unlock(phb); |
| return OPAL_SUCCESS; |
| case OPAL_PCI_SLOT_ONLINE: |
| if (!pd) { |
| phb_unlock(phb); |
| return OPAL_PARAMETER; |
| } |
| pci_scan_bus(phb, pd->secondary_bus, pd->subordinate_bus, |
| &pd->children, pd, true); |
| pci_add_device_nodes(phb, &pd->children, pd->dn, |
| &phb->lstate, 0); |
| phb_unlock(phb); |
| return OPAL_SUCCESS; |
| default: |
| rc = OPAL_PARAMETER; |
| } |
| |
| /* |
| * OPAL_ASYNC_COMPLETION is returned when delay is needed to change |
| * the power state in the backend. When it can be finished without |
| * delay, OPAL_SUCCESS is returned. The PCI topology needs to be |
| * updated in both cases. |
| */ |
| if (rc == OPAL_ASYNC_COMPLETION) { |
| slot->retries = 500; |
| init_timer(&slot->timer, set_power_timer, slot); |
| schedule_timer(&slot->timer, msecs_to_tb(10)); |
| } else if (rc == OPAL_SUCCESS) { |
| if (*state == OPAL_PCI_SLOT_POWER_OFF) { |
| pci_remove_bus(phb, &pd->children); |
| } else { |
| slot->ops.prepare_link_change(slot, true); |
| pci_scan_bus(phb, pd->secondary_bus, |
| pd->subordinate_bus, &pd->children, pd, true); |
| pci_add_device_nodes(phb, &pd->children, pd->dn, |
| &phb->lstate, 0); |
| } |
| } |
| |
| phb_unlock(phb); |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_POWER_STATE, opal_pci_set_power_state, 3); |
| |
| static int64_t opal_pci_set_phb_tce_memory(uint64_t phb_id, |
| uint64_t tce_mem_addr, |
| uint64_t tce_mem_size) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_phb_tce_memory) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->set_phb_tce_memory(phb, tce_mem_addr, tce_mem_size); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PHB_TCE_MEMORY, opal_pci_set_phb_tce_memory, 3); |
| |
| static int64_t opal_pci_get_phb_diag_data(uint64_t phb_id, |
| void *diag_buffer, |
| uint64_t diag_buffer_len) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(diag_buffer)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_diag_data) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->get_diag_data(phb, diag_buffer, diag_buffer_len); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_GET_PHB_DIAG_DATA, opal_pci_get_phb_diag_data, 3); |
| |
| static int64_t opal_pci_get_phb_diag_data2(uint64_t phb_id, |
| void *diag_buffer, |
| uint64_t diag_buffer_len) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(diag_buffer)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_diag_data2) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| rc = phb->ops->get_diag_data2(phb, diag_buffer, diag_buffer_len); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_GET_PHB_DIAG_DATA2, opal_pci_get_phb_diag_data2, 3); |
| |
| static int64_t opal_pci_next_error(uint64_t phb_id, uint64_t *first_frozen_pe, |
| uint16_t *pci_error_type, uint16_t *severity) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(first_frozen_pe) || |
| !opal_addr_valid(pci_error_type) || !opal_addr_valid(severity)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->next_error) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| |
| opal_pci_eeh_clear_evt(phb_id); |
| rc = phb->ops->next_error(phb, first_frozen_pe, pci_error_type, |
| severity); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_NEXT_ERROR, opal_pci_next_error, 4); |
| |
| static int64_t opal_pci_eeh_freeze_status2(uint64_t phb_id, uint64_t pe_number, |
| uint8_t *freeze_state, |
| uint16_t *pci_error_type, |
| uint16_t *severity, |
| uint64_t *phb_status) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!opal_addr_valid(freeze_state) || !opal_addr_valid(pci_error_type) |
| || !opal_addr_valid(severity) || !opal_addr_valid(phb_status)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->eeh_freeze_status) |
| return OPAL_UNSUPPORTED; |
| phb_lock(phb); |
| |
| if (phb_status) |
| prlog(PR_ERR, "PHB#%04llx: %s: deprecated PHB status\n", |
| phb_id, __func__); |
| |
| rc = phb->ops->eeh_freeze_status(phb, pe_number, freeze_state, |
| pci_error_type, severity); |
| phb_unlock(phb); |
| |
| return rc; |
| } |
| opal_call(OPAL_PCI_EEH_FREEZE_STATUS2, opal_pci_eeh_freeze_status2, 6); |
| |
| static int64_t opal_pci_set_phb_capi_mode(uint64_t phb_id, uint64_t mode, uint64_t pe_number) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_capi_mode) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = phb->ops->set_capi_mode(phb, mode, pe_number); |
| phb_unlock(phb); |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PHB_CAPI_MODE, opal_pci_set_phb_capi_mode, 3); |
| |
| static int64_t opal_pci_set_p2p(uint64_t phbid_init, uint64_t phbid_target, |
| uint64_t desc, uint16_t pe_number) |
| { |
| struct phb *phb_init = pci_get_phb(phbid_init); |
| struct phb *phb_target = pci_get_phb(phbid_target); |
| |
| if (!phb_init || !phb_target) |
| return OPAL_PARAMETER; |
| /* |
| * Having the 2 devices under the same PHB may require tuning |
| * the configuration of intermediate switch(es), more easily |
| * done from linux. And it shouldn't require a PHB config |
| * change. |
| * Return an error for the time being. |
| */ |
| if (phb_init == phb_target) |
| return OPAL_UNSUPPORTED; |
| if (!phb_init->ops->set_p2p || !phb_target->ops->set_p2p) |
| return OPAL_UNSUPPORTED; |
| /* |
| * Loads would be supported on p9 if the 2 devices are under |
| * the same PHB, but we ruled it out above. |
| */ |
| if (desc & OPAL_PCI_P2P_LOAD) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb_init); |
| phb_init->ops->set_p2p(phb_init, OPAL_PCI_P2P_INITIATOR, desc, |
| pe_number); |
| phb_unlock(phb_init); |
| |
| phb_lock(phb_target); |
| phb_target->ops->set_p2p(phb_target, OPAL_PCI_P2P_TARGET, desc, |
| pe_number); |
| phb_unlock(phb_target); |
| return OPAL_SUCCESS; |
| } |
| opal_call(OPAL_PCI_SET_P2P, opal_pci_set_p2p, 4); |
| |
| static int64_t opal_pci_get_pbcq_tunnel_bar(uint64_t phb_id, uint64_t *addr) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| |
| if (!opal_addr_valid(addr)) |
| return OPAL_PARAMETER; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->get_tunnel_bar) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| phb->ops->get_tunnel_bar(phb, addr); |
| phb_unlock(phb); |
| return OPAL_SUCCESS; |
| } |
| opal_call(OPAL_PCI_GET_PBCQ_TUNNEL_BAR, opal_pci_get_pbcq_tunnel_bar, 2); |
| |
| static int64_t opal_pci_set_pbcq_tunnel_bar(uint64_t phb_id, uint64_t addr) |
| { |
| struct phb *phb = pci_get_phb(phb_id); |
| int64_t rc; |
| |
| if (!phb) |
| return OPAL_PARAMETER; |
| if (!phb->ops->set_tunnel_bar) |
| return OPAL_UNSUPPORTED; |
| |
| phb_lock(phb); |
| rc = phb->ops->set_tunnel_bar(phb, addr); |
| phb_unlock(phb); |
| return rc; |
| } |
| opal_call(OPAL_PCI_SET_PBCQ_TUNNEL_BAR, opal_pci_set_pbcq_tunnel_bar, 2); |