cregit-Linux how code gets into the kernel

Release 4.17 drivers/atm/solos-pci.c

Directory: drivers/atm
/*
 * Driver for the Solos PCI ADSL2+ card, designed to support Linux by
 *  Traverse Technologies -- http://www.traverse.com.au/
 *  Xrio Limited          -- http://www.xrio.com/
 *
 *
 * Copyright © 2008 Traverse Technologies
 * Copyright © 2008 Intel Corporation
 *
 * Authors: Nathan Williams <nathan@traverse.com.au>
 *          David Woodhouse <dwmw2@infradead.org>
 *          Treker Chen <treker@xrio.com>
 *
 * 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.
 */


#define DEBUG

#define VERBOSE_DEBUG

#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/skbuff.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/firmware.h>
#include <linux/ctype.h>
#include <linux/swab.h>
#include <linux/slab.h>


#define VERSION "1.04"

#define DRIVER_VERSION 0x01

#define PTAG "solos-pci"


#define CONFIG_RAM_SIZE	128

#define FLAGS_ADDR	0x7C

#define IRQ_EN_ADDR	0x78

#define FPGA_VER	0x74

#define IRQ_CLEAR	0x70

#define WRITE_FLASH	0x6C

#define PORTS		0x68

#define FLASH_BLOCK	0x64

#define FLASH_BUSY	0x60

#define FPGA_MODE	0x5C

#define FLASH_MODE	0x58

#define GPIO_STATUS	0x54

#define DRIVER_VER	0x50

#define TX_DMA_ADDR(port)	(0x40 + (4 * (port)))

#define RX_DMA_ADDR(port)	(0x30 + (4 * (port)))


#define DATA_RAM_SIZE	32768

#define BUF_SIZE	2048

#define OLD_BUF_SIZE	4096 
/* For FPGA versions <= 2*/
/* Old boards use ATMEL AD45DB161D flash */

#define ATMEL_FPGA_PAGE	528 
/* FPGA flash page size*/

#define ATMEL_SOLOS_PAGE	512 
/* Solos flash page size*/

#define ATMEL_FPGA_BLOCK	(ATMEL_FPGA_PAGE * 8) 
/* FPGA block size*/

#define ATMEL_SOLOS_BLOCK	(ATMEL_SOLOS_PAGE * 8) 
/* Solos block size*/
/* Current boards use M25P/M25PE SPI flash */

#define SPI_FLASH_BLOCK	(256 * 64)


#define RX_BUF(card, nr) ((card->buffers) + (nr)*(card->buffer_size)*2)

#define TX_BUF(card, nr) ((card->buffers) + (nr)*(card->buffer_size)*2 + (card->buffer_size))

#define FLASH_BUF ((card->buffers) + 4*(card->buffer_size)*2)


#define RX_DMA_SIZE	2048


#define FPGA_VERSION(a,b) (((a) << 8) + (b))

#define LEGACY_BUFFERS	2

#define DMA_SUPPORTED	4


static int reset = 0;

static int atmdebug = 0;

static int firmware_upgrade = 0;

static int fpga_upgrade = 0;

static int db_firmware_upgrade = 0;

static int db_fpga_upgrade = 0;


struct pkt_hdr {
	
__le16 size;
	
__le16 vpi;
	
__le16 vci;
	
__le16 type;
};


struct solos_skb_cb {
	
struct atm_vcc *vcc;
	
uint32_t dma_addr;
};



#define SKB_CB(skb)		((struct solos_skb_cb *)skb->cb)


#define PKT_DATA	0

#define PKT_COMMAND	1

#define PKT_POPEN	3

#define PKT_PCLOSE	4

#define PKT_STATUS	5


struct solos_card {
	
void __iomem *config_regs;
	
void __iomem *buffers;
	
int nr_ports;
	
int tx_mask;
	
struct pci_dev *dev;
	
struct atm_dev *atmdev[4];
	
struct tasklet_struct tlet;
	
spinlock_t tx_lock;
	
spinlock_t tx_queue_lock;
	
spinlock_t cli_queue_lock;
	
spinlock_t param_queue_lock;
	
struct list_head param_queue;
	
struct sk_buff_head tx_queue[4];
	
struct sk_buff_head cli_queue[4];
	
struct sk_buff *tx_skb[4];
	
struct sk_buff *rx_skb[4];
	
unsigned char *dma_bounce;
	
wait_queue_head_t param_wq;
	
wait_queue_head_t fw_wq;
	
int using_dma;
	
int dma_alignment;
	
int fpga_version;
	
int buffer_size;
	
int atmel_flash;
};



struct solos_param {
	
struct list_head list;
	
pid_t pid;
	
int port;
	
struct sk_buff *response;
};


#define SOLOS_CHAN(atmdev) ((int)(unsigned long)(atmdev)->phy_data)

MODULE_AUTHOR("Traverse Technologies <support@traverse.com.au>");
MODULE_DESCRIPTION("Solos PCI driver");
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");
MODULE_FIRMWARE("solos-FPGA.bin");
MODULE_FIRMWARE("solos-Firmware.bin");
MODULE_FIRMWARE("solos-db-FPGA.bin");
MODULE_PARM_DESC(reset, "Reset Solos chips on startup");
MODULE_PARM_DESC(atmdebug, "Print ATM data");
MODULE_PARM_DESC(firmware_upgrade, "Initiate Solos firmware upgrade");
MODULE_PARM_DESC(fpga_upgrade, "Initiate FPGA upgrade");
MODULE_PARM_DESC(db_firmware_upgrade, "Initiate daughter board Solos firmware upgrade");
MODULE_PARM_DESC(db_fpga_upgrade, "Initiate daughter board FPGA upgrade");
module_param(reset, int, 0444);
module_param(atmdebug, int, 0644);
module_param(firmware_upgrade, int, 0444);
module_param(fpga_upgrade, int, 0444);
module_param(db_firmware_upgrade, int, 0444);
module_param(db_fpga_upgrade, int, 0444);

static void fpga_queue(struct solos_card *card, int port, struct sk_buff *skb,
		       struct atm_vcc *vcc);
static uint32_t fpga_tx(struct solos_card *);
static irqreturn_t solos_irq(int irq, void *dev_id);
static struct atm_vcc* find_vcc(struct atm_dev *dev, short vpi, int vci);
static int atm_init(struct solos_card *, struct device *);
static void atm_remove(struct solos_card *);
static int send_command(struct solos_card *card, int dev, const char *buf, size_t size);
static void solos_bh(unsigned long);
static int print_buffer(struct sk_buff *buf);


static inline void solos_pop(struct atm_vcc *vcc, struct sk_buff *skb) { if (vcc->pop) vcc->pop(vcc, skb); else dev_kfree_skb_any(skb); }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse38100.00%1100.00%
Total38100.00%1100.00%


static ssize_t solos_param_show(struct device *dev, struct device_attribute *attr, char *buf) { struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); struct solos_card *card = atmdev->dev_data; struct solos_param prm; struct sk_buff *skb; struct pkt_hdr *header; int buflen; buflen = strlen(attr->attr.name) + 10; skb = alloc_skb(sizeof(*header) + buflen, GFP_KERNEL); if (!skb) { dev_warn(&card->dev->dev, "Failed to allocate sk_buff in solos_param_show()\n"); return -ENOMEM; } header = skb_put(skb, sizeof(*header)); buflen = snprintf((void *)&header[1], buflen - 1, "L%05d\n%s\n", current->pid, attr->attr.name); skb_put(skb, buflen); header->size = cpu_to_le16(buflen); header->vpi = cpu_to_le16(0); header->vci = cpu_to_le16(0); header->type = cpu_to_le16(PKT_COMMAND); prm.pid = current->pid; prm.response = NULL; prm.port = SOLOS_CHAN(atmdev); spin_lock_irq(&card->param_queue_lock); list_add(&prm.list, &card->param_queue); spin_unlock_irq(&card->param_queue_lock); fpga_queue(card, prm.port, skb, NULL); wait_event_timeout(card->param_wq, prm.response, 5 * HZ); spin_lock_irq(&card->param_queue_lock); list_del(&prm.list); spin_unlock_irq(&card->param_queue_lock); if (!prm.response) return -EIO; buflen = prm.response->len; memcpy(buf, prm.response->data, buflen); kfree_skb(prm.response); return buflen; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse345100.00%2100.00%
Total345100.00%2100.00%


static ssize_t solos_param_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); struct solos_card *card = atmdev->dev_data; struct solos_param prm; struct sk_buff *skb; struct pkt_hdr *header; int buflen; ssize_t ret; buflen = strlen(attr->attr.name) + 11 + count; skb = alloc_skb(sizeof(*header) + buflen, GFP_KERNEL); if (!skb) { dev_warn(&card->dev->dev, "Failed to allocate sk_buff in solos_param_store()\n"); return -ENOMEM; } header = skb_put(skb, sizeof(*header)); buflen = snprintf((void *)&header[1], buflen - 1, "L%05d\n%s\n%s\n", current->pid, attr->attr.name, buf); skb_put(skb, buflen); header->size = cpu_to_le16(buflen); header->vpi = cpu_to_le16(0); header->vci = cpu_to_le16(0); header->type = cpu_to_le16(PKT_COMMAND); prm.pid = current->pid; prm.response = NULL; prm.port = SOLOS_CHAN(atmdev); spin_lock_irq(&card->param_queue_lock); list_add(&prm.list, &card->param_queue); spin_unlock_irq(&card->param_queue_lock); fpga_queue(card, prm.port, skb, NULL); wait_event_timeout(card->param_wq, prm.response, 5 * HZ); spin_lock_irq(&card->param_queue_lock); list_del(&prm.list); spin_unlock_irq(&card->param_queue_lock); skb = prm.response; if (!skb) return -EIO; buflen = skb->len; /* Sometimes it has a newline, sometimes it doesn't. */ if (skb->data[buflen - 1] == '\n') buflen--; if (buflen == 2 && !strncmp(skb->data, "OK", 2)) ret = count; else if (buflen == 5 && !strncmp(skb->data, "ERROR", 5)) ret = -EIO; else { /* We know we have enough space allocated for this; we allocated it ourselves */ skb->data[buflen] = 0; dev_warn(&card->dev->dev, "Unexpected parameter response: '%s'\n", skb->data); ret = -EIO; } kfree_skb(skb); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse440100.00%3100.00%
Total440100.00%3100.00%


static char *next_string(struct sk_buff *skb) { int i = 0; char *this = skb->data; for (i = 0; i < skb->len; i++) { if (this[i] == '\n') { this[i] = 0; skb_pull(skb, i + 1); return this; } if (!isprint(this[i])) return NULL; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse89100.00%2100.00%
Total89100.00%2100.00%

/* * Status packet has fields separated by \n, starting with a version number * for the information therein. Fields are.... * * packet version * RxBitRate (version >= 1) * TxBitRate (version >= 1) * State (version >= 1) * LocalSNRMargin (version >= 1) * LocalLineAttn (version >= 1) */
static int process_status(struct solos_card *card, int port, struct sk_buff *skb) { char *str, *state_str, *snr, *attn; int ver, rate_up, rate_down, err; if (!card->atmdev[port]) return -ENODEV; str = next_string(skb); if (!str) return -EIO; err = kstrtoint(str, 10, &ver); if (err) { dev_warn(&card->dev->dev, "Unexpected status interrupt version\n"); return err; } if (ver < 1) { dev_warn(&card->dev->dev, "Unexpected status interrupt version %d\n", ver); return -EIO; } str = next_string(skb); if (!str) return -EIO; if (!strcmp(str, "ERROR")) { dev_dbg(&card->dev->dev, "Status packet indicated Solos error on port %d (starting up?)\n", port); return 0; } err = kstrtoint(str, 10, &rate_down); if (err) return err; str = next_string(skb); if (!str) return -EIO; err = kstrtoint(str, 10, &rate_up); if (err) return err; state_str = next_string(skb); if (!state_str) return -EIO; /* Anything but 'Showtime' is down */ if (strcmp(state_str, "Showtime")) { atm_dev_signal_change(card->atmdev[port], ATM_PHY_SIG_LOST); dev_info(&card->dev->dev, "Port %d: %s\n", port, state_str); return 0; } snr = next_string(skb); if (!snr) return -EIO; attn = next_string(skb); if (!attn) return -EIO; dev_info(&card->dev->dev, "Port %d: %s @%d/%d kb/s%s%s%s%s\n", port, state_str, rate_down/1000, rate_up/1000, snr[0]?", SNR ":"", snr, attn[0]?", Attn ":"", attn); card->atmdev[port]->link_rate = rate_down / 424; atm_dev_signal_change(card->atmdev[port], ATM_PHY_SIG_FOUND); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse34387.06%666.67%
Corentin Labbe4210.66%111.11%
Karl Hiramoto82.03%111.11%
Julia Lawall10.25%111.11%
Total394100.00%9100.00%


static int process_command(struct solos_card *card, int port, struct sk_buff *skb) { struct solos_param *prm; unsigned long flags; int cmdpid; int found = 0, err; if (skb->len < 7) return 0; if (skb->data[0] != 'L' || !isdigit(skb->data[1]) || !isdigit(skb->data[2]) || !isdigit(skb->data[3]) || !isdigit(skb->data[4]) || !isdigit(skb->data[5]) || skb->data[6] != '\n') return 0; err = kstrtoint(&skb->data[1], 10, &cmdpid); if (err) return err; spin_lock_irqsave(&card->param_queue_lock, flags); list_for_each_entry(prm, &card->param_queue, list) { if (prm->port == port && prm->pid == cmdpid) { prm->response = skb; skb_pull(skb, 7); wake_up(&card->param_wq); found = 1; break; } } spin_unlock_irqrestore(&card->param_queue_lock, flags); return found; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse21393.83%266.67%
Corentin Labbe146.17%133.33%
Total227100.00%3100.00%


static ssize_t console_show(struct device *dev, struct device_attribute *attr, char *buf) { struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); struct solos_card *card = atmdev->dev_data; struct sk_buff *skb; unsigned int len; spin_lock(&card->cli_queue_lock); skb = skb_dequeue(&card->cli_queue[SOLOS_CHAN(atmdev)]); spin_unlock(&card->cli_queue_lock); if(skb == NULL) return sprintf(buf, "No data.\n"); len = skb->len; memcpy(buf, skb->data, len); kfree_skb(skb); return len; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse11491.94%150.00%
Jiri Slaby108.06%150.00%
Total124100.00%2100.00%


static int send_command(struct solos_card *card, int dev, const char *buf, size_t size) { struct sk_buff *skb; struct pkt_hdr *header; if (size > (BUF_SIZE - sizeof(*header))) { dev_dbg(&card->dev->dev, "Command is too big. Dropping request\n"); return 0; } skb = alloc_skb(size + sizeof(*header), GFP_ATOMIC); if (!skb) { dev_warn(&card->dev->dev, "Failed to allocate sk_buff in send_command()\n"); return 0; } header = skb_put(skb, sizeof(*header)); header->size = cpu_to_le16(size); header->vpi = cpu_to_le16(0); header->vci = cpu_to_le16(0); header->type = cpu_to_le16(PKT_COMMAND); skb_put_data(skb, buf, size); fpga_queue(card, dev, skb, NULL); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
David Woodhouse14181.98%133.33%
Simon Farnsworth3017.44%133.33%
Johannes Berg10.58%133.33%
Total172100.00%3100.00%


static ssize_t console_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct atm_dev *atmdev = container_of(dev, struct atm_dev, class_dev); struct solos_card *card = atmdev->dev_data; int err; err = send_command(card, SOLOS_CHAN(atmdev), buf, count); return err?:count; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Farnsworth5879.45%150.00%
Nathan Williams1520.55%150.00%
Total73100.00%2100.00%

struct geos_gpio_attr { struct device_attribute attr; int offset; }; #define SOLOS_GPIO_ATTR(_name, _mode, _show, _store, _offset) \ struct geos_gpio_attr gpio_attr_##_name = { \ .attr = __ATTR(_name, _mode, _show, _store), \ .offset = _offset }
static ssize_t geos_gpio_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct pci_dev *pdev = to_pci_dev(dev); struct geos_gpio_attr *gattr = container_of(attr, struct geos_gpio_attr, attr); struct solos_card *card = pci_get_drvdata(pdev); uint32_t data32; if (count != 1 && (count != 2 || buf[1] != '\n')) return -EINVAL; spin_lock_irq(&card->param_queue_lock); data32 = ioread32(card->config_regs + GPIO_STATUS); if (buf[0] == '1') { data32 |= 1 &l