fm10k/src/fm10k_uio.c
2020-10-20 20:06:27 +02:00

210 lines
5.7 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2013 - 2018 Intel Corporation. */
#include "fm10k.h"
static inline struct fm10k_intfc *to_fm10k_intfc(struct uio_info *uio)
{
return container_of(uio, struct fm10k_intfc, uio);
}
static irqreturn_t fm10k_msix_uio(int __always_unused irq, void *data)
{
struct uio_info *uio = (struct uio_info *)data;
/* clear the interrupt notification */
fm10k_write_reg(&to_fm10k_intfc(uio)->hw, FM10K_EICR,
FM10K_EICR_SWITCHINTERRUPT);
uio_event_notify(uio);
return IRQ_HANDLED;
}
/**
* fm10k_uio_set_irq - enable or disable uio irq
* @interface: pointer to private device structure
* @on: boolean indicating whether to enable or disable the IRQ
**/
static void fm10k_uio_set_irq(struct fm10k_intfc *interface, bool on)
{
struct msix_entry *entry = &interface->msix_entries[FM10K_UIO_VECTOR];
struct fm10k_hw *hw = &interface->hw;
u32 itr = FM10K_ITR_AUTOMASK;
itr |= on ? FM10K_ITR_MASK_CLEAR : FM10K_ITR_MASK_SET;
fm10k_write_reg(hw, FM10K_ITR(entry->entry), itr);
}
/**
* fm10k_uio_irq_task - manages the UIO irq out of band
* @work: pointer to work_struct containing data
*
* This work item is called by the uio_irqcontrol, to enable the interrupt
* request. We do it out of band so that we can re-arm ourselves until the
* device isn't resetting. This ensures that reset (which clears our
* interrupt and re-enables us) doesn't race with us.
**/
static void fm10k_uio_irq_task(struct work_struct *work)
{
struct fm10k_intfc *interface;
interface = container_of(work, struct fm10k_intfc, uio_task);
/* if the interface is resetting, just re-queue */
if (test_bit(__FM10K_RESETTING, interface->state)) {
queue_work(fm10k_workqueue, &interface->uio_task);
return;
}
/* we aren't resetting, so we can arm the interrupt */
fm10k_uio_set_irq(interface, interface->uio_int_enable);
}
static int fm10k_uio_irqcontrol(struct uio_info *uio, s32 irq_on)
{
struct fm10k_intfc *interface = to_fm10k_intfc(uio);
/* save the interrupt state for later */
interface->uio_int_enable = irq_on ? true : false;
/* queue our task to enable the interrupt */
queue_work(fm10k_workqueue, &interface->uio_task);
return 0;
}
int fm10k_uio_request_irq(struct fm10k_intfc *interface)
{
struct msix_entry *entry = &interface->msix_entries[FM10K_UIO_VECTOR];
struct uio_info *uio = &interface->uio;
struct fm10k_hw *hw = &interface->hw;
int err;
if (!test_bit(FM10K_FLAG_UIO_REGISTERED, interface->flags))
return 0;
/* request the IRQ */
err = request_irq(entry->vector, fm10k_msix_uio, 0, uio->name, uio);
if (err)
return err;
/* restore the interrupt state */
fm10k_uio_set_irq(interface, interface->uio_int_enable);
/* enable interrupt with no moderation */
fm10k_write_reg(hw, FM10K_INT_MAP(fm10k_int_switch_event),
FM10K_INT_MAP_IMMEDIATE | entry->entry);
/* Enable bits in EIMR register */
fm10k_write_reg(hw, FM10K_EIMR, FM10K_EIMR_ENABLE(SWITCHINTERRUPT));
return 0;
}
void fm10k_uio_free_irq(struct fm10k_intfc *interface)
{
struct uio_info *uio = &interface->uio;
struct fm10k_hw *hw = &interface->hw;
struct msix_entry *entry;
if (!test_bit(FM10K_FLAG_UIO_REGISTERED, interface->flags))
return;
/* no uio IRQ to free if MSI-X is not enabled */
if (!interface->msix_entries)
return;
entry = &interface->msix_entries[FM10K_UIO_VECTOR];
/* Disable bits in EIMR register */
fm10k_write_reg(hw, FM10K_EIMR, FM10K_EIMR_DISABLE(SWITCHINTERRUPT));
/* disable the interrupt */
fm10k_write_reg(hw, FM10K_INT_MAP(fm10k_int_switch_event),
FM10K_INT_MAP_DISABLE);
/* mask interrupt to prevent any remaining events */
fm10k_write_reg(hw, FM10K_ITR(entry->entry), FM10K_ITR_MASK_SET);
/* flush disables to guarantee no further interrupts */
fm10k_write_flush(hw);
/* free the IRQ */
free_irq(entry->vector, uio);
}
int fm10k_uio_probe(struct fm10k_intfc *interface)
{
struct msix_entry *entry = &interface->msix_entries[FM10K_UIO_VECTOR];
struct uio_info *uio = &interface->uio;
struct fm10k_hw *hw = &interface->hw;
int err;
/* Verify if BAR4 access is allowed, if not do nothing */
if (!interface->sw_addr)
return 0;
/* initialize uio task */
INIT_WORK(&interface->uio_task, fm10k_uio_irq_task);
/* set driver name and version */
uio->name = fm10k_driver_name;
uio->version = fm10k_driver_version;
/* We handle the IRQs so set the irq type to custom */
uio->irq = UIO_IRQ_CUSTOM;
/* add basic controls for mapping memory and controlling interrupts */
uio->irqcontrol = fm10k_uio_irqcontrol;
/* Add BAR4 as a region to be memory mapped */
uio->mem[0].addr = pci_resource_start(interface->pdev, 4);
uio->mem[0].size = pci_resource_len(interface->pdev, 4);
uio->mem[0].internal_addr = interface->sw_addr;
uio->mem[0].memtype = UIO_MEM_PHYS;
/* register UIO device */
err = uio_register_device(&interface->pdev->dev, uio);
if (err)
return err;
/* add MSI-X interrupt configuration */
err = request_irq(interface->msix_entries[FM10K_UIO_VECTOR].vector,
fm10k_msix_uio, 0, uio->name, uio);
if (err) {
uio_unregister_device(uio);
return err;
}
/* start interrupt with vector masked */
interface->uio_int_enable = false;
fm10k_uio_set_irq(interface, interface->uio_int_enable);
/* enable interrupt with no moderation */
fm10k_write_reg(hw, FM10K_INT_MAP(fm10k_int_switch_event),
FM10K_INT_MAP_IMMEDIATE | entry->entry);
/* Enable bits in EIMR register */
fm10k_write_reg(hw, FM10K_EIMR, FM10K_EIMR_ENABLE(SWITCHINTERRUPT));
set_bit(FM10K_FLAG_UIO_REGISTERED, interface->flags);
return 0;
}
void fm10k_uio_remove(struct fm10k_intfc *interface)
{
struct uio_info *uio = &interface->uio;
fm10k_uio_free_irq(interface);
if (!test_and_clear_bit(FM10K_FLAG_UIO_REGISTERED, interface->flags))
return;
uio_unregister_device(uio);
cancel_work_sync(&interface->uio_task);
}