blob: 09c811276aef8a9159baa40fa7685936059ac3f2 [file] [log] [blame] [edit]
/**
* Copyright (c) 2012-2012 Quantenna Communications, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/version.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/delay.h>
#include "qdpc_config.h"
#include "qdpc_debug.h"
#include "qdpc_init.h"
#include "qdpc_regs.h"
#include "qdpc_platform.h"
#include "topaz_vnet.h"
#define QDPC_TOPAZ_IMG "topaz-linux.lzma.img"
#define QDPC_TOPAZ_UBOOT "u-boot.bin"
#define MAX_IMG_NUM 2
#define EP_BOOT_FROM_FLASH 0
#ifndef MEMORY_START_ADDRESS
#define MEMORY_START_ADDRESS virt_to_bus((void *)PAGE_OFFSET)
#endif
static unsigned int tlp_mps = 256;
module_param(tlp_mps, uint, 0644);
MODULE_PARM_DESC(tlp_mps, "Default PCIe Max_Payload_Size");
/*
* Define EP state during host suspend
* 0 = EP does not power off
* 1 = EP power off
*/
#define EP_SUSPEND_MODE_RUNNING 0
#define EP_SUSPEND_MODE_PWR_OFF 1
static unsigned int suspend_mode = EP_SUSPEND_MODE_RUNNING;
module_param(suspend_mode, uint, 0644);
MODULE_PARM_DESC(suspend_mode, "Default suspend behavior");
static unsigned int suspend_flag = 0;
/* Quantenna PCIE vendor and device identifiers */
static struct pci_device_id qdpc_pcie_ids[] = {
{PCI_DEVICE(QDPC_VENDOR_ID, QDPC_DEVICE_ID),},
{0,}
};
MODULE_DEVICE_TABLE(pci, qdpc_pcie_ids);
static int qdpc_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id);
static void qdpc_pcie_remove(struct pci_dev *pdev);
static int qdpc_boot_thread(void *data);
static void qdpc_nl_recv_msg(struct sk_buff *skb);
int qdpc_init_netdev(struct net_device **net_dev, struct pci_dev *pdev);
pci_ers_result_t qdpc_pcie_slot_reset(struct pci_dev *dev);
static void qdpc_pcie_shutdown(struct pci_dev *pdev);
char qdpc_pcie_driver_name[] = "qdpc_host";
static struct pci_error_handlers qdpc_err_hdl = {
.slot_reset = qdpc_pcie_slot_reset,
};
static struct pci_driver qdpc_pcie_driver = {
.name = qdpc_pcie_driver_name,
.id_table = qdpc_pcie_ids,
.probe = qdpc_pcie_probe,
.remove = qdpc_pcie_remove,
#ifdef CONFIG_QTN_PM
.suspend = qdpc_pcie_suspend,
.resume = qdpc_pcie_resume,
#endif
.err_handler = &qdpc_err_hdl,
.shutdown = qdpc_pcie_shutdown,
};
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
struct netlink_kernel_cfg qdpc_netlink_cfg = {
.groups = 0,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
.flags = 0,
#endif
.input = qdpc_nl_recv_msg,
.cb_mutex = NULL,
.bind = NULL,
};
#endif
struct sock *qdpc_nl_sk = NULL;
int qdpc_clntPid = 0;
unsigned int (*qdpc_pci_readl)(void *addr) = qdpc_readl;
void (*qdpc_pci_writel)(unsigned int val, void *addr) = qdpc_writel;
static int qdpc_bootpoll(struct vmac_priv *p, uint32_t state)
{
while (!kthread_should_stop() && (qdpc_isbootstate(p,state) == 0)) {
if (qdpc_booterror(p))
return -1;
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(QDPC_SCHED_TIMEOUT);
}
return 0;
}
static void booterror(qdpc_pcie_bda_t *bda)
{
if (PCIE_BDA_TARGET_FWLOAD_ERR & qdpc_pci_readl(&bda->bda_flags))
printk("EP boot from download firmware failed!\n");
else if (PCIE_BDA_TARGET_FBOOT_ERR & qdpc_pci_readl(&bda->bda_flags))
printk("EP boot from flash failed! Please check if there is usable image in Target flash.\n");
else
printk("EP boot get in error, dba flag: 0x%x\n", qdpc_pci_readl(&bda->bda_flags));
}
static void qdpc_pci_endian_detect(struct vmac_priv *priv)
{
__iomem qdpc_pcie_bda_t *bda = priv->bda;
volatile uint32_t pci_endian;
writel(QDPC_PCI_ENDIAN_DETECT_DATA, &bda->bda_pci_endian);
mmiowb();
writel(QDPC_PCI_ENDIAN_VALID_STATUS, &bda->bda_pci_pre_status);
while (readl(&bda->bda_pci_post_status) != QDPC_PCI_ENDIAN_VALID_STATUS) {
if (kthread_should_stop())
break;
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(QDPC_SCHED_TIMEOUT);
}
pci_endian = readl(&bda->bda_pci_endian);
if (pci_endian == QDPC_PCI_LITTLE_ENDIAN) {
qdpc_pci_readl = qdpc_readl;
qdpc_pci_writel = qdpc_writel;
printk("PCI memory is little endian\n");
} else if (pci_endian == QDPC_PCI_BIG_ENDIAN) {
qdpc_pci_readl = qdpc_le32_readl;
qdpc_pci_writel = qdpc_le32_writel;
printk("PCI memory is big endian\n");
} else {
qdpc_pci_readl = qdpc_readl;
qdpc_pci_writel = qdpc_writel;
printk("PCI memory endian value:%08x is invalid - using little endian\n", pci_endian);
}
/* Clear endian flags */
writel(0, &bda->bda_pci_pre_status);
writel(0, &bda->bda_pci_post_status);
writel(0, &bda->bda_pci_endian);
}
static void qdpc_pci_dma_offset_reset(struct vmac_priv *priv)
{
__iomem qdpc_pcie_bda_t *bda = priv->bda;
uint32_t dma_offset;
/* Get EP Mapping address */
dma_offset = readl(&bda->bda_dma_offset);
if ((dma_offset & PCIE_DMA_OFFSET_ERROR_MASK) != PCIE_DMA_OFFSET_ERROR) {
printk("DMA offset : 0x%08x, no need to reset the value.\n", dma_offset);
return;
}
dma_offset &= ~PCIE_DMA_OFFSET_ERROR_MASK;
printk("EP map start addr : 0x%08x, Host memory start : 0x%08x\n",
dma_offset, (unsigned int)MEMORY_START_ADDRESS);
/* Reset DMA offset in bda */
dma_offset -= MEMORY_START_ADDRESS;
writel(dma_offset, &bda->bda_dma_offset);
}
static int qdpc_firmware_load(struct pci_dev *pdev, struct vmac_priv *priv, const char *name)
{
#define DMABLOCKSIZE (1 * 1024 * 1024)
#define NBLOCKS(size) ((size)/(DMABLOCKSIZE) + (((size)%(DMABLOCKSIZE) > 0) ? 1 : 0))
int result = SUCCESS;
const struct firmware *fw;
__iomem qdpc_pcie_bda_t *bda = priv->bda;
/* Request compressed firmware from user space */
if ((result = request_firmware(&fw, name, &pdev->dev)) == -ENOENT) {
/*
* No firmware found in the firmware directory, skip firmware downloading process
* boot from flash directly on target
*/
printk( "no firmware found skip fw downloading\n");
qdpc_pcie_posted_write((PCIE_BDA_HOST_NOFW_ERR |
qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags);
return FAILURE;
} else if (result == SUCCESS) {
uint32_t nblocks = NBLOCKS(fw->size);
uint32_t remaining = fw->size;
uint32_t count;
uint32_t dma_offset = qdpc_pci_readl(&bda->bda_dma_offset);
void *data =(void *) __get_free_pages(GFP_KERNEL | GFP_DMA,
get_order(DMABLOCKSIZE));
const uint8_t *curdata = fw->data;
dma_addr_t handle = 0;
if (!data) {
printk(KERN_ERR "Allocation failed for memory size[%u] Download firmware failed!\n", DMABLOCKSIZE);
release_firmware(fw);
qdpc_pcie_posted_write((PCIE_BDA_HOST_MEMALLOC_ERR |
qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags);
return FAILURE;
}
handle = pci_map_single(priv->pdev, data ,DMABLOCKSIZE, PCI_DMA_TODEVICE);
if (!handle) {
printk("Pci map for memory data block 0x%p error, Download firmware failed!\n", data);
free_pages((unsigned long)data, get_order(DMABLOCKSIZE));
release_firmware(fw);
qdpc_pcie_posted_write((PCIE_BDA_HOST_MEMMAP_ERR |
qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags);
return FAILURE;
}
qdpc_setbootstate(priv, QDPC_BDA_FW_HOST_LOAD);
qdpc_bootpoll(priv, QDPC_BDA_FW_EP_RDY);
/* Start loading firmware */
for (count = 0 ; count < nblocks; count++)
{
uint32_t size = (remaining > DMABLOCKSIZE) ? DMABLOCKSIZE : remaining;
memcpy(data, curdata, size);
/* flush dcache */
pci_dma_sync_single_for_device(priv->pdev, handle ,size, PCI_DMA_TODEVICE);
qdpc_pcie_posted_write(handle + dma_offset, &bda->bda_img);
qdpc_pcie_posted_write(size, &bda->bda_img_size);
printk("FW Data[%u]: VA:0x%p PA:0x%p Sz=%u..\n", count, (void *)curdata, (void *)handle, size);
qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_RDY);
qdpc_bootpoll(priv, QDPC_BDA_FW_BLOCK_DONE);
remaining = (remaining < size) ? remaining : (remaining - size);
curdata += size;
printk("done!\n");
}
pci_unmap_single(priv->pdev,handle, DMABLOCKSIZE, PCI_DMA_TODEVICE);
/* Mark end of block */
qdpc_pcie_posted_write(0, &bda->bda_img);
qdpc_pcie_posted_write(0, &bda->bda_img_size);
qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_RDY);
qdpc_bootpoll(priv, QDPC_BDA_FW_BLOCK_DONE);
qdpc_setbootstate(priv, QDPC_BDA_FW_BLOCK_END);
PRINT_INFO("Image. Sz:%u State:0x%x\n", (uint32_t)fw->size, qdpc_pci_readl(&bda->bda_bootstate));
qdpc_bootpoll(priv, QDPC_BDA_FW_LOAD_DONE);
free_pages((unsigned long)data, get_order(DMABLOCKSIZE));
release_firmware(fw);
PRINT_INFO("Image downloaded....!\n");
} else {
PRINT_ERROR("Failed to load firmware:%d\n", result);
return result;
}
return result;
}
static void qdpc_pcie_dev_init(struct vmac_priv *priv, struct pci_dev *pdev, struct net_device *ndev)
{
SET_NETDEV_DEV(ndev, &pdev->dev);
priv->pdev = pdev;
priv->ndev = ndev;
pci_set_drvdata(pdev, ndev);
}
static void qdpc_tune_pcie_mps(struct pci_dev *pdev, int pos)
{
struct pci_dev *parent = NULL;
int ppos = 0;
uint32_t dev_cap, pcap;
uint16_t dev_ctl, pctl;
unsigned int mps = tlp_mps;
#define BIT_TO_MPS(m) (1 << ((m) + 7))
if (pdev->bus && pdev->bus->self) {
parent = pdev->bus->self;
if (likely(parent)) {
ppos = pci_find_capability(parent, PCI_CAP_ID_EXP);
if (ppos) {
pci_read_config_dword(parent, ppos + PCI_EXP_DEVCAP, &pcap);
pci_read_config_dword(pdev, pos + PCI_EXP_DEVCAP, &dev_cap);
printk(KERN_INFO "parent cap:%u, dev cap:%u\n",\
BIT_TO_MPS(pcap & PCI_EXP_DEVCAP_PAYLOAD), BIT_TO_MPS(dev_cap & PCI_EXP_DEVCAP_PAYLOAD));
mps = min(BIT_TO_MPS(dev_cap & PCI_EXP_DEVCAP_PAYLOAD), BIT_TO_MPS(pcap & PCI_EXP_DEVCAP_PAYLOAD));
}
}
}
mps = 128;
printk(KERN_INFO"Setting MPS to %u\n", mps);
/*
* Set Max_Payload_Size
* Max_Payload_Size_in_effect = 1 << ( ( (dev_ctl >> 5) & 0x07) + 7);
*/
mps = (((mps >> 7) - 1) << 5);
pci_read_config_word(pdev, pos + PCI_EXP_DEVCTL, &dev_ctl);
dev_ctl = ((dev_ctl & ~PCI_EXP_DEVCTL_PAYLOAD) | mps);
pci_write_config_word(pdev, pos + PCI_EXP_DEVCTL, dev_ctl);
if (parent && ppos) {
pci_read_config_word(parent, pos + PCI_EXP_DEVCTL, &pctl);
pctl = ((pctl & ~PCI_EXP_DEVCTL_PAYLOAD) | mps);
pci_write_config_word(parent, pos + PCI_EXP_DEVCTL, pctl);
}
}
static struct net_device *g_ndev = NULL;
static int qdpc_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct vmac_priv *priv = NULL;
struct net_device *ndev = NULL;
int result = SUCCESS;
int pos;
/* When system boots up, the state of pdev is not saved on the entry of this function.
* Data structure pci_dev will keep in use when module is removed and re-installed.
* So we can call pci_restore_sate() to recover its previous configuration space.
*/
if (pdev->state_saved == true) {
printk("Recovery: restore saved state\n");
pci_restore_state(pdev);
}
/* Save "fresh poweron" state including BAR address, etc. So the state can be
* used for recovery next time.
*/
pci_save_state(pdev);
/* Allocate device structure */
if (!(ndev = vmac_alloc_ndev()))
return -ENOMEM;
g_ndev = ndev;
priv = netdev_priv(ndev);
qdpc_pcie_dev_init(priv, pdev, ndev);
/* allocate netlink data buffer */
priv->nl_buf = kmalloc(VMAC_NL_BUF_SIZE, GFP_KERNEL);
if (!priv->nl_buf) {
result = -ENOMEM;
goto out;
}
/* Check if the device has PCI express capability */
pos = pci_find_capability(pdev, PCI_CAP_ID_EXP);
if (!pos) {
PRINT_ERROR(KERN_ERR "The device %x does not have PCI Express capability\n",
pdev->device);
result = -ENOSYS;
goto out;
} else {
PRINT_DBG(KERN_INFO "The device %x has PCI Express capability\n", pdev->device);
}
#ifdef CPTCFG_QUANTENNA_PCIE_HOST_MPS_FIX
qdpc_tune_pcie_mps(pdev, pos);
#endif
/* Wake up the device if it is in suspended state and allocate IO,
* memory regions and IRQ if not
*/
if (pci_enable_device(pdev)) {
PRINT_ERROR(KERN_ERR "Failed to initialize PCI device with device ID %x\n",
pdev->device);
result = -EIO;
goto out;
} else {
PRINT_DBG(KERN_INFO "Initialized PCI device with device ID %x\n", pdev->device);
}
/*
* Check if the PCI device can support DMA addressing properly.
* The mask gives the bits that the device can address
*/
pci_set_master(pdev);
/* Initialize PCIE layer */
if (( result = qdpc_pcie_init_intr_and_mem(priv)) < 0) {
PRINT_DBG("Interrupt & Memory Initialization failed \n");
goto release_memory;
}
if (!!(result = vmac_net_init(pdev))) {
PRINT_DBG("Vmac netdev init fail\n");
goto free_mem_interrupt;
}
/* Create and start the thread to initiate the INIT Handshake*/
priv->init_thread = kthread_run(qdpc_boot_thread, priv, "qdpc_init_thread");
if (priv->init_thread == NULL) {
PRINT_ERROR("Init thread creation failed \n");
goto free_mem_interrupt;
}
/* Create netlink & register with kernel */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
priv->nl_socket = netlink_kernel_create(&init_net,
QDPC_NETLINK_RPC_PCI_CLNT, &qdpc_netlink_cfg);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
priv->nl_socket = netlink_kernel_create(&init_net,
QDPC_NETLINK_RPC_PCI_CLNT, THIS_MODULE, &qdpc_netlink_cfg);
#else
priv->nl_socket = netlink_kernel_create(&init_net,
QDPC_NETLINK_RPC_PCI_CLNT, 0, qdpc_nl_recv_msg,
NULL, THIS_MODULE);
#endif
if (priv->nl_socket) {
return SUCCESS;
}
PRINT_ERROR(KERN_ALERT "Error creating netlink socket.\n");
result = FAILURE;
free_mem_interrupt:
qdpc_pcie_free_mem(pdev);
qdpc_free_interrupt(pdev);
release_memory:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
/* Releasing the memory region if any error occured */
pci_clear_master(pdev);
#endif
pci_disable_device(pdev);
out:
kfree(priv->nl_buf);
free_netdev(ndev);
/* Any failure in probe, so it can directly return in remove */
pci_set_drvdata(pdev, NULL);
return result;
}
static void qdpc_pcie_remove(struct pci_dev *pdev)
{
struct net_device *ndev = pci_get_drvdata(pdev);
struct vmac_priv *vmp;
if (ndev == NULL)
return;
vmp = netdev_priv(ndev);
vmp->ep_ready = 0;
if (vmp->init_thread)
kthread_stop(vmp->init_thread);
if (vmp->nl_socket)
netlink_kernel_release(vmp->nl_socket);
kfree(vmp->nl_buf);
vmac_clean(ndev);
qdpc_free_interrupt(pdev);
qdpc_pcie_free_mem(pdev);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
pci_clear_master(pdev);
#endif
pci_disable_device(pdev);
writel(TOPAZ_SET_INT(IPC_RESET_EP), (volatile void *)(vmp->ep_ipc_reg));
qdpc_unmap_iomem(vmp);
free_netdev(ndev);
g_ndev = NULL;
return;
}
static inline int qdpc_pcie_set_power_state(struct pci_dev *pdev, pci_power_t state)
{
uint16_t pmcsr;
pci_read_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET, &pmcsr);
switch (state) {
case PCI_D0:
pci_write_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET,(pmcsr & ~PCI_PM_CTRL_STATE_MASK) | PCI_D0);
break;
case PCI_D3hot:
pci_write_config_word(pdev, TOPAZ_PCI_PM_CTRL_OFFSET,(pmcsr & ~PCI_PM_CTRL_STATE_MASK) | (PCI_D3hot | PCI_PM_CTRL_PME_ENABLE));
break;
default:
return -EINVAL;
}
return 0;
}
int qdpc_pcie_suspend(struct pci_dev *pdev, pm_message_t state)
{
struct net_device *ndev = pci_get_drvdata(pdev);
struct vmac_priv *priv;
if (ndev == NULL)
return -EINVAL;
priv = netdev_priv(ndev);
if (le32_to_cpu(*priv->ep_pmstate) == PCI_D3hot) {
return 0;
}
printk("%s start power management suspend\n", qdpc_pcie_driver_name);
/* Set ep not ready to drop packets in low power mode */
priv->ep_ready = 0;
ndev->flags &= ~IFF_RUNNING;
*priv->ep_pmstate = cpu_to_le32(PCI_D3hot);
barrier();
writel(TOPAZ_SET_INT(IPC_EP_PM_CTRL), (volatile void *)(priv->ep_ipc_reg));
msleep(100);
pci_save_state(pdev);
pci_disable_device(pdev);
qdpc_pcie_set_power_state(pdev, PCI_D3hot);
if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF)
suspend_flag = 1;
return 0;
}
int qdpc_pcie_resume(struct pci_dev *pdev)
{
struct net_device *ndev = pci_get_drvdata(pdev);
struct vmac_priv *priv;
int ret;
if (ndev == NULL) {
ret = -EINVAL;
goto out;
}
priv = netdev_priv(ndev);
if (le32_to_cpu(*priv->ep_pmstate) == PCI_D0) {
ret = 0;
goto out;
}
printk("%s start power management resume\n", qdpc_pcie_driver_name);
ret = pci_enable_device(pdev);
if (ret) {
PRINT_ERROR("%s: pci_enable_device failed on resume\n", __func__);
goto out;
}
pci_restore_state(pdev);
pdev->state_saved = true;
qdpc_pcie_set_power_state(pdev, PCI_D0);
{
*priv->ep_pmstate = cpu_to_le32(PCI_D0);
barrier();
writel(TOPAZ_SET_INT(IPC_EP_PM_CTRL), (volatile void *)(priv->ep_ipc_reg));
msleep(5000);
}
if ( (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) &&
(pdev->driver && pdev->driver->err_handler && pdev->driver->err_handler->slot_reset) ) {
printk("slot_reset in %s(), Device name: %s\n", __FUNCTION__, dev_name(&pdev->dev));
if(pdev->driver->err_handler->slot_reset(pdev) == PCI_ERS_RESULT_RECOVERED)
printk("Recovery OK\n");
else {
printk("Recovery Error");
ret = -EINVAL;
goto out;
}
}
/* Set ep_ready to resume tx traffic */
priv->ep_ready = 1;
ndev->flags |= IFF_RUNNING;
out:
if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF)
suspend_flag = 0;
return ret;
}
static int __init qdpc_init_module(void)
{
int ret;
PRINT_DBG(KERN_INFO "Quantenna pcie driver initialization\n");
if (qdpc_platform_init()) {
PRINT_ERROR("Platform initilization failed \n");
ret = FAILURE;
return ret;
}
/* Register the pci driver with device*/
if ((ret = pci_register_driver(&qdpc_pcie_driver)) < 0 ) {
PRINT_ERROR("Could not register the driver to pci : %d\n", ret);
ret = -ENODEV;
return ret;
}
return ret;
}
static void __exit qdpc_exit_module(void)
{
/* Release netlink */
qdpc_platform_exit();
/* Unregister the pci driver with the device */
pci_unregister_driver(&qdpc_pcie_driver);
return;
}
void qdpc_recovery_clean(struct pci_dev *pdev, struct net_device *ndev)
{
struct vmac_priv *vmp;
vmp = netdev_priv(ndev);
vmp->ep_ready = 0;
if (vmp->init_thread) {
kthread_stop(vmp->init_thread);
vmp->init_thread = NULL;
}
vmac_recovery_clean(ndev);
pci_disable_device(pdev);
return;
}
int qdpc_recovery_reinit(struct pci_dev *pdev, struct net_device *ndev)
{
struct vmac_priv *priv = NULL;
if (suspend_mode == EP_SUSPEND_MODE_PWR_OFF && suspend_flag)
suspend_flag = 0;
else {
if (pdev->state_saved == true) {
pci_restore_state(pdev);
pdev->state_saved = true;
} else {
printk("Recovery Error: No saved state\n");
goto out;
}
}
if (pci_enable_device(pdev)) {
printk("Recovery Error: Failed to enable PCI device\n");
goto out;
}
priv = netdev_priv(ndev);
if (vmac_recovery_init(priv, ndev) == ENOMEM) {
printk("Recovery Error: Not enough memory\n");
goto qdpc_recovery_err_0;
}
priv->init_thread = kthread_run(qdpc_boot_thread, priv, "qdpc_init_thread");
if (priv->init_thread == NULL) {
printk("Recovery Error: Thread creation failed \n");
goto qdpc_recovery_err_0;
}
return SUCCESS;
qdpc_recovery_err_0:
pci_disable_device(pdev);
out:
return -1;
}
static int qdpc_recovery_access_check(struct pci_dev *pdev)
{
uint32_t val = 0;
pci_read_config_dword(pdev, QDPC_VENDOR_ID_OFFSET, &val);
if (val == ((QDPC_DEVICE_ID << 16) | QDPC_VENDOR_ID)) {
printk("%s: PCIe read access check: Pass\n", __func__);
return 0;
} else {
printk("%s: PCIe read access check: Fail: VENDOR_ID read error: 0x%08x\n", __func__, val);
return -1;
}
}
int qdpc_pcie_recovery(struct pci_dev *pdev)
{
struct net_device *ndev = pci_get_drvdata(pdev);
qdpc_recovery_clean(pdev, ndev);
/* Wait EP link up. If this function is called at hardirq context where 10s
* delay is not allowed, please replace with link up check at Root Complex's
* status register.
*/
mdelay(10000);
if (qdpc_recovery_access_check(pdev) != 0)
return -1;
/* Re-allocate and initialize data structure */
qdpc_recovery_reinit(pdev, ndev);
return 0;
}
pci_ers_result_t qdpc_pcie_slot_reset(struct pci_dev *dev)
{
if (qdpc_pcie_recovery(dev) == 0)
return PCI_ERS_RESULT_RECOVERED;
else
return PCI_ERS_RESULT_DISCONNECT;
}
static void qdpc_pcie_shutdown(struct pci_dev *pdev)
{
qdpc_pcie_remove(pdev);
return;
}
static int qdpc_bringup_fw(struct vmac_priv *priv)
{
__iomem qdpc_pcie_bda_t *bda = priv->bda;
uint32_t bdaflg;
char *fwname;
qdpc_pci_endian_detect(priv);
qdpc_pci_dma_offset_reset(priv);
printk("Setting HOST ready...\n");
qdpc_setbootstate(priv, QDPC_BDA_FW_HOST_RDY);
qdpc_bootpoll(priv, QDPC_BDA_FW_TARGET_RDY);
bdaflg = qdpc_pci_readl(&bda->bda_flags);
if ((PCIE_BDA_FLASH_PRESENT & bdaflg) && EP_BOOT_FROM_FLASH) {
printk("EP have fw in flash, boot from flash\n");
qdpc_pcie_posted_write((PCIE_BDA_FLASH_BOOT |
qdpc_pci_readl(&bda->bda_flags)), &bda->bda_flags);
qdpc_setbootstate(priv, QDPC_BDA_FW_TARGET_BOOT);
qdpc_bootpoll(priv, QDPC_BDA_FW_FLASH_BOOT);
goto fw_start;
}
bdaflg &= PCIE_BDA_XMIT_UBOOT;
fwname = bdaflg ? QDPC_TOPAZ_UBOOT : QDPC_TOPAZ_IMG;
qdpc_setbootstate(priv, QDPC_BDA_FW_TARGET_BOOT);
printk("EP FW load request...\n");
qdpc_bootpoll(priv, QDPC_BDA_FW_LOAD_RDY);
printk("Start download Firmware %s...\n", fwname);
if (qdpc_firmware_load(priv->pdev, priv, fwname)){
printk("Failed to download firmware.\n");
priv->init_thread = NULL;
do_exit(-1);
}
fw_start:
qdpc_setbootstate(priv, QDPC_BDA_FW_START);
printk("Start booting EP...\n");
if (bdaflg != PCIE_BDA_XMIT_UBOOT) {
if (qdpc_bootpoll(priv,QDPC_BDA_FW_CONFIG)) {
booterror(bda);
priv->init_thread = NULL;
do_exit(-1);
}
printk("EP boot successful, starting config...\n");
/* Save target-side MSI address for later enable/disable irq*/
priv->dma_msi_imwr = readl(QDPC_BAR_VADDR(priv->dmareg_bar, TOPAZ_IMWR_DONE_ADDRLO_OFFSET));
priv->dma_msi_dummy = virt_to_bus(&priv->dma_msi_data) + qdpc_pci_readl(&bda->bda_dma_offset);
priv->ep_pciecfg0_val = readl(QDPC_BAR_VADDR(priv->sysctl_bar, TOPAZ_PCIE_CFG0_OFFSET));
qdpc_setbootstate(priv, QDPC_BDA_FW_RUN);
qdpc_bootpoll(priv,QDPC_BDA_FW_RUNNING);
priv->ep_ready = 1;
}
return (int)bdaflg;
}
static int qdpc_boot_done(struct vmac_priv *priv)
{
struct net_device *ndev;
ndev = priv->ndev;
PRINT_INFO("Connection established with Target BBIC4 board\n");
priv->init_thread = NULL;
do_exit(0);
}
static int qdpc_boot_thread(void *data)
{
struct vmac_priv *priv = (struct vmac_priv *)data;
int i;
for (i = 0; i < MAX_IMG_NUM; i++) {
if (qdpc_bringup_fw(priv) <= 0)
break;
}
qdpc_boot_done(priv);
return 0;
}
static void qdpc_nl_recv_msg(struct sk_buff *skb)
{
struct vmac_priv *priv = netdev_priv(g_ndev);
struct nlmsghdr *nlh = (struct nlmsghdr*)skb->data;
struct sk_buff *skb2;
unsigned int data_len;
unsigned int offset;
qdpc_cmd_hdr_t *cmd_hdr;
uint16_t rpc_type;
/* Parsing the netlink message */
PRINT_DBG(KERN_INFO "%s line %d Netlink received pid:%d, size:%d, type:%d\n",
__FUNCTION__, __LINE__, nlh->nlmsg_pid, nlh->nlmsg_len, nlh->nlmsg_type);
switch (nlh->nlmsg_type) {
case QDPC_NL_TYPE_CLNT_STR_REG:
case QDPC_NL_TYPE_CLNT_LIB_REG:
if (nlh->nlmsg_type == QDPC_NL_TYPE_CLNT_STR_REG)
priv->str_call_nl_pid = nlh->nlmsg_pid;
else
priv->lib_call_nl_pid = nlh->nlmsg_pid;
return;
case QDPC_NL_TYPE_CLNT_STR_REQ:
case QDPC_NL_TYPE_CLNT_LIB_REQ:
break;
default:
PRINT_DBG(KERN_INFO "%s line %d Netlink Invalid type %d\n",
__FUNCTION__, __LINE__, nlh->nlmsg_type);
return;
}
/*
* make new skbs; Fragment if necessary.
* The original skb will be freed in netlink_unicast_kernel,
* we hold the new skbs until DMA transfer is done
*/
offset = sizeof(struct nlmsghdr);
data_len = nlh->nlmsg_len;
while (data_len > 0) {
unsigned int len = min_t(unsigned int, data_len, priv->ndev->mtu);
unsigned int skb2_len = len + sizeof(qdpc_cmd_hdr_t);
skb2 = alloc_skb(skb2_len, GFP_ATOMIC);
if (!skb2) {
printk(KERN_INFO "%s: skb alloc failed\n", __func__);
return;
}
data_len -= len;
rpc_type = nlh->nlmsg_type & QDPC_RPC_TYPE_MASK;
rpc_type |= (data_len > 0 ? QDPC_RPC_TYPE_FRAG : 0);
cmd_hdr = (qdpc_cmd_hdr_t *)skb2->data;
memcpy(cmd_hdr->dst_magic, QDPC_NETLINK_DST_MAGIC, ETH_ALEN);
memcpy(cmd_hdr->src_magic, QDPC_NETLINK_SRC_MAGIC, ETH_ALEN);
cmd_hdr->type = __constant_htons(QDPC_APP_NETLINK_TYPE);
cmd_hdr->len = htons((uint16_t)len);
cmd_hdr->rpc_type = htons(rpc_type);
cmd_hdr->total_len = htons((uint16_t)(nlh->nlmsg_len));
memcpy((uint8_t *)(cmd_hdr + 1), skb->data + offset, len);
offset += len;
skb_put(skb2, skb2_len);
skb_reset_mac_header(skb2);
skb_reset_network_header(skb2);
skb2->protocol = __constant_htons(QDPC_APP_NETLINK_TYPE);
skb2->dev = priv->ndev;
dev_queue_xmit(skb2);
}
}
module_init(qdpc_init_module);
module_exit(qdpc_exit_module);