sonic-buildimage/platform/broadcom/saibcm-modules/sdklt/linux/bde/ngbde_intr.c
vmittal-msft 04b9ce8e32
[BCMSAI] Update BCMSAI debian to 6.0.0.10 with 6.5.23 SDK, and opennsl module to 6.5.23 (#9046)
Manual verification on switch (TH3 device)
admin@str2-xxxxx-01:~$ bcmcmd bsv
bsv
BRCM SAI ver: [6.0.0.10], OCP SAI ver: [1.9.1], SDK ver: [sdk-6.5.23]
drivshell>

admin@str2-xxxxx-01:~$ bcmcmd version
version
Broadcom Command Monitor: Copyright (c) 1998-2021 Broadcom
Release: sdk-6.5.23 built 20211020 (Wed Oct 20 06:52:58 2021)
From root@fedbbfdbee81:/__w/2/s/output/x86-xgsall-deb/hsdk
Platform: X86
OS: Unix (Posix)
Chips:
BCM56640_A0,
BCM56850_A0,
BCM56340_A0,
BCM56960_A0, BCM56860_A0,

   BCM56970_A0, BCM56870_A0,
   BCM56980_A0, BCM56980_B0,
  
   BCM56370_A0, BCM56275_A0, BCM56770_A0,
Chips:
BCM56780_A0, BCM56782_A0, BCM56784_A0, BCM56785_A0,
BCM56786_A0, BCM56787_A0, BCM56788_A0, BCM56789_A0,
BCM56880_A0, BCM56880_B0, BCM56881_A0, BCM56881_B0,
BCM56883_A0, BCM56883_B0, BCM56990_A0, BCM56990_B0,
BCM56991_B0, BCM56992_B0, BCM56996_A0, BCM56996_B0,
BCM56997_A0, BCM56997_B0

Variant drivers:
BCM56780_A0_CNA_1_2_10, BCM56780_A0_DNA_2_7_6_0, BCM56880_A0_CNA_1_2_9, BCM56880_A0_DNA_4_9_5_0
PHYs: BCM5400, BCM54182, BCM54185, BCM54180,
BCM54140, BCM54192, BCM54195, BCM54190,
BCM54194, BCM54210, BCM54220, BCM54280,
BCM54282, BCM54240, BCM54285, BCM5428X,
BCM54290, BCM54292, BCM54294, BCM54295,
BCM54296, BCM56160-GPHY, BCM53540-GPHY, BCM56275-GPHY,
BCM8750, BCM8752, BCM8754, BCM84740,
BCM84164, BCM84758, BCM84780, BCM84784,
BCM84318, BCM84328, Sesto, BCM82780,
copper sfp

drivshell>
2021-10-28 00:12:32 -07:00

582 lines
14 KiB
C

/*! \file ngbde_intr.c
*
* API for controlling a thread-based user-mode interrupt handler.
*
*/
/*
* $Copyright: Copyright 2018-2021 Broadcom. All rights reserved.
* The term 'Broadcom' refers to Broadcom Inc. and/or its subsidiaries.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* A copy of the GNU General Public License version 2 (GPLv2) can
* be found in the LICENSES folder.$
*/
#include <ngbde.h>
/*! \cond */
static int intr_debug = 0;
module_param(intr_debug, int, 0);
MODULE_PARM_DESC(intr_debug,
"Interrupt debug output enable (default 0).");
/*! \endcond */
static int
ngbde_intr_shared_write32(struct ngbde_dev_s *sd, struct ngbde_intr_ctrl_s *ic,
uint32_t reg_offs, uint32_t reg_val, uint32_t shr_mask)
{
unsigned long flags;
struct ngbde_shr_reg_s *sr;
int idx;
sr = NULL;
for (idx = 0; idx < NGBDE_NUM_INTR_SHR_REGS_MAX; idx++) {
if (sd->intr_shr_reg[idx].reg_offs == 0) {
/* If not found, then we add a new entry */
sd->intr_shr_reg[idx].reg_offs = reg_offs;
}
if (sd->intr_shr_reg[idx].reg_offs == reg_offs) {
sr = &sd->intr_shr_reg[idx];
break;
}
}
if (sr == NULL) {
return -1;
}
spin_lock_irqsave(&sd->lock, flags);
sr->cur_val &= ~shr_mask;
sr->cur_val |= (reg_val & shr_mask);
NGBDE_IOWRITE32(sr->cur_val, ic->iomem + reg_offs);
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
/*!
* \brief Interrupt handler for user mode thread.
*
* This function will determine whether a user-mode interrupt has
* occurred by reading the configured interrupt status and mask
* registers.
*
* If an interrupt has occurred, any waiting user-mode thread is woken
* up.
*
* \param [in] ic Interrupt control information.
*
* \retval 1 One or more user mode interrupts occurred.
* \retval 0 No user mode interrupts occurred.
*/
static int
ngbde_user_isr(ngbde_intr_ctrl_t *ic)
{
int idx;
int active_interrupts = 0;
uint32_t stat = 0, mask = 0;
uint32_t kmask;
/* Check if any enabled interrupts are active */
for (idx = 0; idx < ic->num_regs; idx++) {
ngbde_irq_reg_t *ir = &ic->regs[idx];
/* Get mask of all kernel interrupt sources for this register address */
kmask = ir->kmask;
stat = NGBDE_IOREAD32(&ic->iomem[ir->status_reg]);
if (!ir->status_is_masked) {
/* Get enabled interrupts by applying mask register */
mask = NGBDE_IOREAD32(&ic->iomem[ir->mask_reg]);
stat &= mask;
}
if (stat & ~kmask) {
active_interrupts = 1;
break;
}
}
/* No active interrupts to service */
if (!active_interrupts) {
return 0;
}
/* Disable (mask off) all interrupts */
for (idx = 0; idx < ic->num_regs; idx++) {
ngbde_irq_reg_t *ir = &ic->regs[idx];
/* Get mask of all kernel interrupt sources for this register address */
kmask = ir->kmask;
if (kmask == 0xffffffff) {
/* Kernel driver owns all interrupts in this register */
continue;
}
if (ir->mask_w1tc) {
/* Clear all interrupt bits which are not in kmask */
NGBDE_IOWRITE32(~kmask, &ic->iomem[ir->mask_reg]);
continue;
}
if (kmask) {
/* Synchronized write */
struct ngbde_dev_s *sd = ngbde_swdev_get(ic->kdev);
if (ngbde_intr_shared_write32(sd, ic, ir->mask_reg, 0, ~kmask) < 0) {
printk(KERN_WARNING
"%s: Failed to write shared register for device %d\n",
MOD_NAME, ic->kdev);
/* Fall back to normal write to ensure interrupts are masked */
NGBDE_IOWRITE32(0, &ic->iomem[ir->mask_reg]);
}
} else {
NGBDE_IOWRITE32(0, &ic->iomem[ir->mask_reg]);
}
}
atomic_set(&ic->run_user_thread, 1);
wake_up_interruptible(&ic->user_thread_wq);
return 1;
}
/*!
* \brief Interrupt handler for kernel driver.
*
* Typically used by the KNET driver.
*
* \param [in] ic Interrupt control information.
*
* \retval 1 One or more kernel mode interrupts occurred.
* \retval 0 No kernel mode interrupts occurred.
*/
static int
ngbde_kernel_isr(ngbde_intr_ctrl_t *ic)
{
if (ic->isr_func) {
return ic->isr_func(ic->isr_data);
}
return 0;
}
/*!
* \brief Interrupt handler for kernel driver.
*
* Typically used by the EDK driver.
*
* \param [in] ic Interrupt control information.
*
* \retval 1 One or more kernel mode interrupts occurred.
* \retval 0 No kernel mode interrupts occurred.
*/
static int
ngbde_kernel_isr2(ngbde_intr_ctrl_t *ic)
{
if (ic->isr2_func) {
return ic->isr2_func(ic->isr2_data);
}
return 0;
}
/*!
* \brief Acknowledge interrupt
*
* \param [in] data Interrupt control information
*
* \retval 0
*/
static int
ngbde_intr_ack(ngbde_intr_ctrl_t *ic)
{
struct ngbde_dev_s *sd = ngbde_swdev_get(ic->kdev);
struct ngbde_intr_ack_reg_s *ar = &ic->intr_ack;
if (sd->use_msi) {
if (ar->flags & NGBDE_INTR_ACK_F_PAXB) {
ngbde_paxb_write32(sd, ar->ack_reg, ar->ack_val);
} else {
ngbde_pio_write32(sd, ar->ack_reg, ar->ack_val);
}
}
return 0;
}
/*!
* \brief Linux ISR
*
* Will call the user-mode interrupts handler and optionally also a
* kernel mode interrupt handler (typically KNET).
*
* \param [in] irq_num Interrupt vector from kernel.
* \param [in] data Interrupt control information
*
* \retval IRQ_NONE Interrupt not recognized.
* \retval IRQ_HANDLED Interrupt recognized and handled (masked off).
*/
static irqreturn_t
ngbde_isr(int irq_num, void *data)
{
struct ngbde_intr_ctrl_s *ic = (struct ngbde_intr_ctrl_s *)data;
irqreturn_t rv = IRQ_NONE;
ngbde_intr_ack(ic);
if (ngbde_kernel_isr2(ic)) {
rv = IRQ_HANDLED;
}
if (ngbde_user_isr(ic)) {
rv = IRQ_HANDLED;
}
if (ngbde_kernel_isr(ic)) {
rv = IRQ_HANDLED;
}
return rv;
}
int
ngbde_intr_connect(int kdev, unsigned int irq_num)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
unsigned long irq_flags;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (ic->irq_active) {
return 0;
}
if (sd->irq_line >= 0) {
if (sd->pio_mem == NULL) {
printk(KERN_WARNING "%s: No memory-mapped I/O for device %d\n",
MOD_NAME, kdev);
return -1;
}
ic->kdev = kdev;
ic->iomem = sd->pio_mem;
if (sd->iio_mem) {
if (intr_debug) {
printk("INTR: Using dedicated interrupt controller\n");
}
ic->iomem = sd->iio_mem;
}
init_waitqueue_head(&ic->user_thread_wq);
atomic_set(&ic->run_user_thread, 0);
irq_flags = IRQF_SHARED;
ic->irq_vect = sd->irq_line;
/*
* The pci_enable_msi function must be called after enabling
* BAR0_PAXB_OARR_FUNC0_MSI_PAGE, otherwise, MSI interrupts
* cannot be triggered!
*/
if (sd->use_msi) {
if (pci_enable_msi(sd->pci_dev) == 0) {
irq_flags = 0;
ic->irq_vect = sd->pci_dev->irq;
if (intr_debug) {
printk("INTR: Enabled MSI interrupts\n");
}
} else {
printk(KERN_WARNING "%s: Failed to enable MSI for device %d\n",
MOD_NAME, kdev);
sd->use_msi = 0;
}
}
if (intr_debug) {
printk("INTR: Request IRQ %d\n", ic->irq_vect);
}
if (request_irq(ic->irq_vect, ngbde_isr, irq_flags, MOD_NAME, ic) < 0) {
printk(KERN_WARNING "%s: Could not get IRQ %d for device %d\n",
MOD_NAME, ic->irq_vect, kdev);
return -1;
}
ic->irq_active = 1;
}
return 0;
}
int
ngbde_intr_disconnect(int kdev, unsigned int irq_num)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (!ic->irq_active) {
return 0;
}
if (ic->isr_func) {
printk(KERN_WARNING "%s: Disconnecting IRQ %d blocked by kernel ISR\n",
MOD_NAME, irq_num);
return 0;
}
if (ic->irq_vect >= 0) {
free_irq(ic->irq_vect, ic);
if (sd->use_msi) {
pci_disable_msi(sd->pci_dev);
}
ic->irq_active = 0;
}
return 0;
}
void
ngbde_intr_cleanup(void)
{
struct ngbde_dev_s *swdev;
unsigned int num_swdev, idx, irq_num;
ngbde_swdev_get_all(&swdev, &num_swdev);
for (idx = 0; idx < num_swdev; idx++) {
for (irq_num = 0; irq_num < NGBDE_NUM_IRQS_MAX; irq_num++) {
ngbde_intr_disconnect(idx, irq_num);
}
}
}
int
ngbde_intr_wait(int kdev, unsigned int irq_num)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (!ic->irq_active) {
return 0;
}
wait_event_interruptible(ic->user_thread_wq,
atomic_read(&ic->run_user_thread) != 0);
atomic_set(&ic->run_user_thread, 0);
return 0;
}
int
ngbde_intr_stop(int kdev, unsigned int irq_num)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (!ic->irq_active) {
return 0;
}
/* Wake up user thread */
atomic_set(&ic->run_user_thread, 1);
wake_up_interruptible(&ic->user_thread_wq);
return 0;
}
int
ngbde_intr_regs_clr(int kdev, unsigned int irq_num)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (ic->irq_active) {
/* Do not clear configuration with interrupt connected */
return 0;
}
ic->num_regs = 0;
memset(ic->regs, 0, sizeof(ic->regs));
return 0;
}
int
ngbde_intr_reg_add(int kdev, unsigned int irq_num,
struct ngbde_irq_reg_s *ireg)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
struct ngbde_irq_reg_s *ir;
unsigned int idx;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (ic->irq_active) {
/*
* If the interrupt is connected, then we only update the
* kernel mask for existing entries.
*/
for (idx = 0; idx < ic->num_regs; idx++) {
ir = &ic->regs[idx];
if (ir->status_reg == ireg->status_reg &&
ir->mask_reg == ireg->mask_reg) {
ir->kmask = ireg->kmask;
if (intr_debug) {
printk("INTR: Updating interrupt register "
"0x%08x/0x%08x (0x%08x)\n",
ir->status_reg, ir->mask_reg, ir->kmask);
}
return 0;
}
}
return -1;
}
if (ic->num_regs >= NGBDE_NUM_IRQ_REGS_MAX) {
return -1;
}
ir = &ic->regs[ic->num_regs++];
memcpy(ir, ireg, sizeof (*ir));
if (intr_debug) {
printk("INTR: Adding interrupt register 0x%08x/0x%08x (0x%08x)\n",
ir->status_reg, ir->mask_reg, ir->kmask);
}
return ic->num_regs;
}
int
ngbde_intr_ack_reg_add(int kdev, unsigned int irq_num,
struct ngbde_intr_ack_reg_s *ackreg)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
struct ngbde_intr_ack_reg_s *ar;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
if (ic->irq_active) {
/* Ignore request if interrupt is connected */
return 0;
}
ar = &ic->intr_ack;
memcpy(ar, ackreg, sizeof (*ar));
if (intr_debug) {
printk("INTR: Adding interrupt ACK register 0x%08x/0x%08x (0x%08x)\n",
ar->ack_reg, ar->ack_val, ar->flags);
}
return 0;
}
int
ngbde_intr_mask_write(int kdev, unsigned int irq_num, int kapi,
uint32_t status_reg, uint32_t mask_val)
{
struct ngbde_dev_s *sd;
struct ngbde_intr_ctrl_s *ic;
struct ngbde_irq_reg_s *ir;
unsigned int idx;
uint32_t bmask;
sd = ngbde_swdev_get(kdev);
if (!sd) {
return -1;
}
if (irq_num >= NGBDE_NUM_IRQS_MAX) {
return -1;
}
ic = &sd->intr_ctrl[irq_num];
ir = ic->regs;
for (idx = 0; idx < ic->num_regs; idx++) {
if (ir->status_reg == status_reg) {
bmask = kapi ? ir->kmask : ~ir->kmask;
ngbde_intr_shared_write32(sd, ic, ir->mask_reg, mask_val, bmask);
return 0;
}
ir++;
}
return -1;
}