mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-27 19:18:07 +02:00
9482 lines
246 KiB
Diff
9482 lines
246 KiB
Diff
From 942f9ce3dd8dde01c501f7d7840700637eb2d285 Mon Sep 17 00:00:00 2001
|
|
From: Xiangfu <xiangfu@openmobilefree.net>
|
|
Date: Tue, 5 Jun 2012 11:32:52 +0800
|
|
Subject: [PATCH 2/3] 001
|
|
|
|
---
|
|
drivers/ieee802154/adf7242.c | 1034 +++++++++++++++++++++++++++++++++++++++
|
|
drivers/ieee802154/at86rf230.c | 872 +++++++++++++++++++++++++++++++++
|
|
drivers/ieee802154/at86rf230.h | 211 ++++++++
|
|
drivers/ieee802154/cc2420.c | 859 ++++++++++++++++++++++++++++++++
|
|
drivers/ieee802154/fakelb.c | 311 ++++++++++++
|
|
drivers/ieee802154/serial.c | 1047 ++++++++++++++++++++++++++++++++++++++++
|
|
drivers/ieee802154/spi_atben.c | 421 ++++++++++++++++
|
|
drivers/ieee802154/spi_atusb.c | 751 ++++++++++++++++++++++++++++
|
|
include/linux/if_ieee802154.h | 6 +
|
|
include/linux/spi/at86rf230.h | 34 ++
|
|
include/net/mac802154.h | 156 ++++++
|
|
net/mac802154/Kconfig | 24 +
|
|
net/mac802154/Makefile | 6 +
|
|
net/mac802154/beacon.c | 285 +++++++++++
|
|
net/mac802154/beacon_hash.c | 106 ++++
|
|
net/mac802154/beacon_hash.h | 41 ++
|
|
net/mac802154/mac802154.h | 126 +++++
|
|
net/mac802154/mac_cmd.c | 365 ++++++++++++++
|
|
net/mac802154/main.c | 283 +++++++++++
|
|
net/mac802154/mib.c | 249 ++++++++++
|
|
net/mac802154/mib.h | 35 ++
|
|
net/mac802154/monitor.c | 117 +++++
|
|
net/mac802154/rx.c | 117 +++++
|
|
net/mac802154/scan.c | 203 ++++++++
|
|
net/mac802154/smac.c | 128 +++++
|
|
net/mac802154/tx.c | 106 ++++
|
|
net/mac802154/wpan.c | 631 ++++++++++++++++++++++++
|
|
net/zigbee/Kconfig | 7 +
|
|
net/zigbee/Makefile | 5 +
|
|
net/zigbee/af_zigbee.c | 285 +++++++++++
|
|
net/zigbee/dgram.c | 401 +++++++++++++++
|
|
31 files changed, 9222 insertions(+)
|
|
create mode 100644 drivers/ieee802154/adf7242.c
|
|
create mode 100644 drivers/ieee802154/at86rf230.c
|
|
create mode 100644 drivers/ieee802154/at86rf230.h
|
|
create mode 100644 drivers/ieee802154/cc2420.c
|
|
create mode 100644 drivers/ieee802154/fakelb.c
|
|
create mode 100644 drivers/ieee802154/serial.c
|
|
create mode 100644 drivers/ieee802154/spi_atben.c
|
|
create mode 100644 drivers/ieee802154/spi_atusb.c
|
|
create mode 100644 include/linux/if_ieee802154.h
|
|
create mode 100644 include/linux/spi/at86rf230.h
|
|
create mode 100644 include/net/mac802154.h
|
|
create mode 100644 net/mac802154/Kconfig
|
|
create mode 100644 net/mac802154/Makefile
|
|
create mode 100644 net/mac802154/beacon.c
|
|
create mode 100644 net/mac802154/beacon_hash.c
|
|
create mode 100644 net/mac802154/beacon_hash.h
|
|
create mode 100644 net/mac802154/mac802154.h
|
|
create mode 100644 net/mac802154/mac_cmd.c
|
|
create mode 100644 net/mac802154/main.c
|
|
create mode 100644 net/mac802154/mib.c
|
|
create mode 100644 net/mac802154/mib.h
|
|
create mode 100644 net/mac802154/monitor.c
|
|
create mode 100644 net/mac802154/rx.c
|
|
create mode 100644 net/mac802154/scan.c
|
|
create mode 100644 net/mac802154/smac.c
|
|
create mode 100644 net/mac802154/tx.c
|
|
create mode 100644 net/mac802154/wpan.c
|
|
create mode 100644 net/zigbee/Kconfig
|
|
create mode 100644 net/zigbee/Makefile
|
|
create mode 100644 net/zigbee/af_zigbee.c
|
|
create mode 100644 net/zigbee/dgram.c
|
|
|
|
diff --git a/drivers/ieee802154/adf7242.c b/drivers/ieee802154/adf7242.c
|
|
new file mode 100644
|
|
index 0000000..b578a55
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/adf7242.c
|
|
@@ -0,0 +1,1034 @@
|
|
+/*
|
|
+ * Analog Devices ADF7242 Low-Power IEEE 802.15.4 Transceiver
|
|
+ *
|
|
+ * Copyright 2009-2010 Analog Devices Inc.
|
|
+ *
|
|
+ * Licensed under the GPL-2 or later.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/firmware.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/adf7242.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+/*
|
|
+ * DEBUG LEVEL
|
|
+ * 0 OFF
|
|
+ * 1 INFO
|
|
+ * 2 INFO + TRACE
|
|
+ */
|
|
+
|
|
+#define ADF_DEBUG 0
|
|
+#define DBG(n, args...) do { if (ADF_DEBUG >= (n)) pr_debug(args); } while (0)
|
|
+
|
|
+#define FIRMWARE "adf7242_firmware.bin"
|
|
+#define MAX_POLL_LOOPS 10
|
|
+
|
|
+/* All Registers */
|
|
+
|
|
+#define REG_EXT_CTRL 0x100 /* RW External LNA/PA and internal PA control configuration bits */
|
|
+#define REG_TX_FSK_TEST 0x101 /* RW TX FSK test mode configuration */
|
|
+#define REG_CCA1 0x105 /* RW RSSI threshold for CCA */
|
|
+#define REG_CCA2 0x106 /* RW CCA mode configuration */
|
|
+#define REG_BUFFERCFG 0x107 /* RW RX_BUFFER overwrite control */
|
|
+#define REG_PKT_CFG 0x108 /* RW FCS evaluation configuration */
|
|
+#define REG_DELAYCFG0 0x109 /* RW RC_RX command to SFD or sync word search delay */
|
|
+#define REG_DELAYCFG1 0x10A /* RW RC_TX command to TX state */
|
|
+#define REG_DELAYCFG2 0x10B /* RW Mac delay extention */
|
|
+#define REG_SYNC_WORD0 0x10C /* RW sync word bits [7:0] of [23:0] */
|
|
+#define REG_SYNC_WORD1 0x10D /* RW sync word bits [15:8] of [23:0] */
|
|
+#define REG_SYNC_WORD2 0x10E /* RW sync word bits [23:16] of [23:0] */
|
|
+#define REG_SYNC_CONFIG 0x10F /* RW sync word configuration */
|
|
+#define REG_RC_CFG 0x13E /* RW RX / TX packet configuration */
|
|
+#define REG_RC_VAR44 0x13F /* RW RESERVED */
|
|
+#define REG_CH_FREQ0 0x300 /* RW Channel Frequency Settings - Low Byte */
|
|
+#define REG_CH_FREQ1 0x301 /* RW Channel Frequency Settings - Middle Byte */
|
|
+#define REG_CH_FREQ2 0x302 /* RW Channel Frequency Settings - 2 MSBs */
|
|
+#define REG_TX_FD 0x304 /* RW TX Frequency Deviation Register */
|
|
+#define REG_DM_CFG0 0x305 /* RW RX Discriminator BW Register */
|
|
+#define REG_TX_M 0x306 /* RW TX Mode Register */
|
|
+#define REG_RX_M 0x307 /* RW RX Mode Register */
|
|
+#define REG_RRB 0x30C /* R RSSI Readback Register */
|
|
+#define REG_LRB 0x30D /* R Link Quality Readback Register */
|
|
+#define REG_DR0 0x30E /* RW bits [15:8] of [15:0] for data rate setting */
|
|
+#define REG_DR1 0x30F /* RW bits [7:0] of [15:0] for data rate setting */
|
|
+#define REG_PRAMPG 0x313 /* RW RESERVED */
|
|
+#define REG_TXPB 0x314 /* RW TX Packet Storage Base Address */
|
|
+#define REG_RXPB 0x315 /* RW RX Packet Storage Base Address */
|
|
+#define REG_TMR_CFG0 0x316 /* RW Wake up Timer Configuration Register - High Byte */
|
|
+#define REG_TMR_CFG1 0x317 /* RW Wake up Timer Configuration Register - Low Byte */
|
|
+#define REG_TMR_RLD0 0x318 /* RW Wake up Timer Value Register - High Byte */
|
|
+#define REG_TMR_RLD1 0x319 /* RW Wake up Timer Value Register - Low Byte */
|
|
+#define REG_TMR_CTRL 0x31A /* RW Wake up Timer Timeout flag */
|
|
+#define REG_PD_AUX 0x31E /* RW Battmon enable */
|
|
+#define REG_GP_CFG 0x32C /* RW GPIO Configuration */
|
|
+#define REG_GP_OUT 0x32D /* RW GPIO Configuration */
|
|
+#define REG_GP_IN 0x32E /* R GPIO Configuration */
|
|
+#define REG_SYNT 0x335 /* RW bandwidth calibration timers */
|
|
+#define REG_CAL_CFG 0x33D /* RW Calibration Settings */
|
|
+#define REG_SYNT_CAL 0x371 /* RW Oscillator and Doubler Configuration */
|
|
+#define REG_IIRF_CFG 0x389 /* RW BB Filter Decimation Rate */
|
|
+#define REG_CDR_CFG 0x38A /* RW CDR kVCO */
|
|
+#define REG_DM_CFG1 0x38B /* RW Postdemodulator Filter */
|
|
+#define REG_AGCSTAT 0x38E /* R RXBB Ref Osc Calibration Engine Readback */
|
|
+#define REG_RXCAL0 0x395 /* RW RX BB filter tuning, LSB */
|
|
+#define REG_RXCAL1 0x396 /* RW RX BB filter tuning, MSB */
|
|
+#define REG_RXFE_CFG 0x39B /* RW RXBB Ref Osc & RXFE Calibration */
|
|
+#define REG_PA_RR 0x3A7 /* RW Set PA ramp rate */
|
|
+#define REG_PA_CFG 0x3A8 /* RW PA enable */
|
|
+#define REG_EXTPA_CFG 0x3A9 /* RW External PA BIAS DAC */
|
|
+#define REG_EXTPA_MSC 0x3AA /* RW PA Bias Mode */
|
|
+#define REG_ADC_RBK 0x3AE /* R Readback temp */
|
|
+#define REG_AGC_CFG1 0x3B2 /* RW GC Parameters */
|
|
+#define REG_AGC_MAX 0x3B4 /* RW Slew rate */
|
|
+#define REG_AGC_CFG2 0x3B6 /* RW RSSI Parameters */
|
|
+#define REG_AGC_CFG3 0x3B7 /* RW RSSI Parameters */
|
|
+#define REG_AGC_CFG4 0x3B8 /* RW RSSI Parameters */
|
|
+#define REG_AGC_CFG5 0x3B9 /* RW RSSI & NDEC Parameters */
|
|
+#define REG_AGC_CFG6 0x3BA /* RW NDEC Parameters */
|
|
+#define REG_OCL_CFG1 0x3C4 /* RW OCL System Parameters */
|
|
+#define REG_IRQ1_EN0 0x3C7 /* RW Interrupt Mask set bits [7:0] of [15:0] for IRQ1 */
|
|
+#define REG_IRQ1_EN1 0x3C8 /* RW Interrupt Mask set bits [15:8] of [15:0] for IRQ1 */
|
|
+#define REG_IRQ2_EN0 0x3C9 /* RW Interrupt Mask set bits [7:0] of [15:0] for IRQ2 */
|
|
+#define REG_IRQ2_EN1 0x3CA /* RW Interrupt Mask set bits [15:8] of [15:0] for IRQ2 */
|
|
+#define REG_IRQ1_SRC0 0x3CB /* RW Interrupt Source bits [7:0] of [15:0] for IRQ */
|
|
+#define REG_IRQ1_SRC1 0x3CC /* RW Interrupt Source bits [15:8] of [15:0] for IRQ */
|
|
+#define REG_OCL_BW0 0x3D2 /* RW OCL System Parameters */
|
|
+#define REG_OCL_BW1 0x3D3 /* RW OCL System Parameters */
|
|
+#define REG_OCL_BW2 0x3D4 /* RW OCL System Parameters */
|
|
+#define REG_OCL_BW3 0x3D5 /* RW OCL System Parameters */
|
|
+#define REG_OCL_BW4 0x3D6 /* RW OCL System Parameters */
|
|
+#define REG_OCL_BWS 0x3D7 /* RW OCL System Parameters */
|
|
+#define REG_OCL_CFG13 0x3E0 /* RW OCL System Parameters */
|
|
+#define REG_GP_DRV 0x3E3 /* RW I/O pads Configuration and bg trim */
|
|
+#define REG_BM_CFG 0x3E6 /* RW Battery Monitor Threshold Voltage setting */
|
|
+#define REG_SFD_15_4 0x3F4 /* RW Option to set non standard SFD */
|
|
+#define REG_AFC_CFG 0x3F7 /* RW AFC mode and polarity */
|
|
+#define REG_AFC_KI_KP 0x3F8 /* RW AFC ki and kp */
|
|
+#define REG_AFC_RANGE 0x3F9 /* RW AFC range */
|
|
+#define REG_AFC_READ 0x3FA /* RW Readback frequency error */
|
|
+
|
|
+#define REG_PAN_ID0 0x112
|
|
+#define REG_PAN_ID1 0x113
|
|
+#define REG_SHORT_ADDR_0 0x114
|
|
+#define REG_SHORT_ADDR_1 0x115
|
|
+#define REG_IEEE_ADDR_0 0x116
|
|
+#define REG_IEEE_ADDR_1 0x117
|
|
+#define REG_IEEE_ADDR_2 0x118
|
|
+#define REG_IEEE_ADDR_3 0x119
|
|
+#define REG_IEEE_ADDR_4 0x11A
|
|
+#define REG_IEEE_ADDR_5 0x11B
|
|
+#define REG_IEEE_ADDR_6 0x11C
|
|
+#define REG_IEEE_ADDR_7 0x11D
|
|
+#define REG_FFILT_CFG 0x11E
|
|
+#define REG_AUTO_CFG 0x11F
|
|
+#define REG_AUTO_TX1 0x120
|
|
+#define REG_AUTO_TX2 0x121
|
|
+#define REG_AUTO_STATUS 0x122
|
|
+
|
|
+/* REG_FFILT_CFG */
|
|
+#define ACCEPT_BEACON_FRAMES (1 << 0)
|
|
+#define ACCEPT_DATA_FRAMES (1 << 1)
|
|
+#define ACCEPT_ACK_FRAMES (1 << 2)
|
|
+#define ACCEPT_MACCMD_FRAMES (1 << 3)
|
|
+#define ACCEPT_RESERVED_FRAMES (1 << 4)
|
|
+#define ACCEPT_ALL_ADDRESS (1 << 5)
|
|
+
|
|
+/* REG_AUTO_CFG */
|
|
+#define AUTO_ACK_FRAMEPEND (1 << 0)
|
|
+#define IS_PANCOORD (1 << 1)
|
|
+#define RX_AUTO_ACK_EN (1 << 3)
|
|
+#define CSMA_CA_RX_TURNAROUND (1 << 4)
|
|
+
|
|
+/* REG_AUTO_TX1 */
|
|
+#define MAX_FRAME_RETRIES(x) ((x) & 0xF)
|
|
+#define MAX_CCA_RETRIES(x) (((x) & 0x7) << 4)
|
|
+
|
|
+/* REG_AUTO_TX2 */
|
|
+#define CSMA_MAX_BE(x) ((x) & 0xF)
|
|
+#define CSMA_MIN_BE(x) (((x) & 0xF) << 4)
|
|
+
|
|
+#define CMD_SPI_NOP 0xFF /* No operation. Use for dummy writes */
|
|
+#define CMD_SPI_PKT_WR 0x10 /* Write telegram to the Packet RAM starting from the TX packet base address pointer tx_packet_base */
|
|
+#define CMD_SPI_PKT_RD 0x30 /* Read telegram from the Packet RAM starting from RX packet base address pointer rxpb.rx_packet_base */
|
|
+#define CMD_SPI_MEM_WR(x) (0x18 + (x >> 8)) /* Write data to MCR or Packet RAM sequentially */
|
|
+#define CMD_SPI_MEM_RD(x) (0x38 + (x >> 8)) /* Read data from MCR or Packet RAM sequentially */
|
|
+#define CMD_SPI_MEMR_WR(x) (0x08 + (x >> 8)) /* Write data to MCR or Packet RAM as random block */
|
|
+#define CMD_SPI_MEMR_RD(x) (0x28 + (x >> 8)) /* Read data from MCR or Packet RAM as random block */
|
|
+#define CMD_SPI_PRAM_WR 0x1E /* Write data sequentially to current PRAM page selected */
|
|
+#define CMD_RC_SLEEP 0xB1 /* Invoke transition of radio controller into SLEEP state */
|
|
+#define CMD_RC_IDLE 0xB2 /* Invoke transition of radio controller into IDLE state */
|
|
+#define CMD_RC_PHY_RDY 0xB3 /* Invoke transition of radio controller into PHY_RDY state */
|
|
+#define CMD_RC_RX 0xB4 /* Invoke transition of radio controller into RX state */
|
|
+#define CMD_RC_TX 0xB5 /* Invoke transition of radio controller into TX state */
|
|
+#define CMD_RC_MEAS 0xB6 /* Invoke transition of radio controller into MEAS state */
|
|
+#define CMD_RC_CCA 0xB7 /* Invoke Clear channel assessment */
|
|
+#define CMD_RC_CSMACA 0xC1 /* initiates CSMA-CA channel access sequence and frame transmission */
|
|
+
|
|
+/* STATUS */
|
|
+
|
|
+#define STAT_SPI_READY (1 << 7)
|
|
+#define STAT_IRQ_STATUS (1 << 6)
|
|
+#define STAT_RC_READY (1 << 5)
|
|
+#define STAT_CCA_RESULT (1 << 4)
|
|
+#define RC_STATUS_IDLE 1
|
|
+#define RC_STATUS_MEAS 2
|
|
+#define RC_STATUS_PHY_RDY 3
|
|
+#define RC_STATUS_RX 4
|
|
+#define RC_STATUS_TX 5
|
|
+#define RC_STATUS_MASK 0xF
|
|
+
|
|
+/* AUTO_STATUS */
|
|
+
|
|
+#define SUCCESS 0
|
|
+#define SUCCESS_DATPEND 1
|
|
+#define FAILURE_CSMACA 2
|
|
+#define FAILURE_NOACK 3
|
|
+#define AUTO_STATUS_MASK 0x3
|
|
+
|
|
+#define PRAM_PAGESIZE 256
|
|
+
|
|
+/* IRQ1 */
|
|
+
|
|
+#define IRQ_CCA_COMPLETE (1 << 0)
|
|
+#define IRQ_SFD_RX (1 << 1)
|
|
+#define IRQ_SFD_TX (1 << 2)
|
|
+#define IRQ_RX_PKT_RCVD (1 << 3)
|
|
+#define IRQ_TX_PKT_SENT (1 << 4)
|
|
+#define IRQ_FRAME_VALID (1 << 5)
|
|
+#define IRQ_ADDRESS_VALID (1 << 6)
|
|
+#define IRQ_CSMA_CA (1 << 7)
|
|
+
|
|
+#define AUTO_TX_TURNAROUND (1 << 3)
|
|
+#define ADDON_EN (1 << 4)
|
|
+
|
|
+struct adf7242_local {
|
|
+ struct spi_device *spi;
|
|
+ struct adf7242_platform_data *pdata;
|
|
+ struct work_struct irqwork;
|
|
+ struct completion tx_complete;
|
|
+ struct ieee802154_dev *dev;
|
|
+ struct mutex bmux;
|
|
+ spinlock_t lock;
|
|
+ unsigned irq_disabled:1; /* P: lock */
|
|
+ unsigned is_tx:1; /* P: lock */
|
|
+ unsigned mode;
|
|
+ unsigned tx_irq;
|
|
+ int tx_stat;
|
|
+ u8 buf[3];
|
|
+};
|
|
+
|
|
+static int adf7242_status(struct adf7242_local *lp, u8 *stat)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ u8 buf_tx[1], buf_rx[1];
|
|
+
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 1,
|
|
+ .tx_buf = buf_tx,
|
|
+ .rx_buf = buf_rx,
|
|
+ };
|
|
+
|
|
+ buf_tx[0] = CMD_SPI_NOP;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ *stat = buf_rx[0];
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_wait_ready(struct adf7242_local *lp)
|
|
+{
|
|
+ u8 stat;
|
|
+ int cnt = 0;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ do {
|
|
+ adf7242_status(lp, &stat);
|
|
+ cnt++;
|
|
+ } while (!(stat & STAT_RC_READY) && (cnt < MAX_POLL_LOOPS));
|
|
+
|
|
+ DBG(2, "%s :Exit loops=%d\n", __func__, cnt);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_wait_status(struct adf7242_local *lp, int status)
|
|
+{
|
|
+ u8 stat;
|
|
+ int cnt = 0;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ do {
|
|
+ adf7242_status(lp, &stat);
|
|
+ stat &= RC_STATUS_MASK;
|
|
+ cnt++;
|
|
+ } while ((stat != status) && (cnt < MAX_POLL_LOOPS));
|
|
+
|
|
+ DBG(2, "%s :Exit loops=%d\n", __func__, cnt);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_write_fbuf(struct adf7242_local *lp, u8 *data, u8 len)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = len,
|
|
+ .tx_buf = data,
|
|
+ };
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ adf7242_wait_ready(lp);
|
|
+
|
|
+ buf[0] = CMD_SPI_PKT_WR;
|
|
+ buf[1] = len + 2;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_read_fbuf(struct adf7242_local *lp,
|
|
+ u8 *data, u8 *len, u8 *lqi)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 3,
|
|
+ .tx_buf = buf,
|
|
+ .rx_buf = buf,
|
|
+
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = *len,
|
|
+ .rx_buf = data,
|
|
+ };
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ adf7242_wait_ready(lp);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ buf[0] = CMD_SPI_PKT_RD;
|
|
+ buf[1] = CMD_SPI_NOP;
|
|
+ buf[2] = 0; /* PHR */
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+
|
|
+ if (!status) {
|
|
+ *lqi = data[buf[2] - 1];
|
|
+ *len = buf[2]; /* PHR */
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_read_reg(struct adf7242_local *lp,
|
|
+ u16 addr, u8 *data)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ u8 buf_tx[4], buf_rx[4];
|
|
+
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 4,
|
|
+ .tx_buf = buf_tx,
|
|
+ .rx_buf = buf_rx,
|
|
+ };
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ adf7242_wait_ready(lp);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ buf_tx[0] = CMD_SPI_MEM_RD(addr);
|
|
+ buf_tx[1] = addr;
|
|
+ buf_tx[2] = CMD_SPI_NOP;
|
|
+ buf_tx[3] = CMD_SPI_NOP;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+
|
|
+ if (!status)
|
|
+ *data = buf_rx[3];
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_write_reg(struct adf7242_local *lp,
|
|
+ u16 addr, u8 data)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ u8 buf_tx[4];
|
|
+
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 3,
|
|
+ .tx_buf = buf_tx,
|
|
+ };
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ adf7242_wait_ready(lp);
|
|
+
|
|
+ buf_tx[0] = CMD_SPI_MEM_WR(addr);
|
|
+ buf_tx[1] = addr;
|
|
+ buf_tx[2] = data;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_cmd(struct adf7242_local *lp, u8 cmd)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ u8 buf_tx[1];
|
|
+
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 1,
|
|
+ .tx_buf = buf_tx,
|
|
+ };
|
|
+
|
|
+ DBG(2, "%s :Enter CMD=0x%X\n", __func__, cmd);
|
|
+ adf7242_wait_ready(lp);
|
|
+
|
|
+ buf_tx[0] = cmd;
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_upload_firmware(struct adf7242_local *lp,
|
|
+ u8 *data, u16 len)
|
|
+{
|
|
+ int status, i, page = 0;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_buf = {};
|
|
+ u8 buf[2];
|
|
+
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+ };
|
|
+
|
|
+ buf[0] = CMD_SPI_PRAM_WR;
|
|
+ buf[1] = 0;
|
|
+
|
|
+ for (i = len; i >= 0 ; i -= PRAM_PAGESIZE) {
|
|
+ adf7242_write_reg(lp, REG_PRAMPG, page);
|
|
+
|
|
+ xfer_buf.len = i >= PRAM_PAGESIZE ? PRAM_PAGESIZE : i,
|
|
+ xfer_buf.tx_buf = &data[page * PRAM_PAGESIZE],
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ page++;
|
|
+ }
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int adf7242_ed(struct ieee802154_dev *dev, u8 *level)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+#if 0
|
|
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
|
|
+ adf7242_cmd(lp, CMD_RC_CCA);
|
|
+ adf7242_wait_status(lp, RC_STATUS_PHY_RDY);
|
|
+#else
|
|
+ udelay(128);
|
|
+#endif
|
|
+ ret = adf7242_read_reg(lp, REG_RRB, level);
|
|
+ adf7242_cmd(lp, CMD_RC_RX);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int adf7242_start(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ ret = adf7242_cmd(lp, CMD_RC_RX);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void adf7242_stop(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+}
|
|
+
|
|
+static int adf7242_channel(struct ieee802154_dev *dev, int page, int channel)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+ unsigned long freq;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ DBG(1, "%s :Channel=%d\n", __func__, channel);
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ BUG_ON(page != 0);
|
|
+ BUG_ON(channel < 11);
|
|
+ BUG_ON(channel > 26);
|
|
+
|
|
+ freq = (2405 + 5 * (channel - 11)) * 100;
|
|
+
|
|
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_CH_FREQ0, freq);
|
|
+ adf7242_write_reg(lp, REG_CH_FREQ1, freq >> 8);
|
|
+ adf7242_write_reg(lp, REG_CH_FREQ2, freq >> 16);
|
|
+
|
|
+ adf7242_cmd(lp, CMD_RC_RX);
|
|
+
|
|
+ dev->phy->current_channel = channel;
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_set_hw_addr_filt(struct ieee802154_dev *dev,
|
|
+ struct ieee802154_hw_addr_filt *filt,
|
|
+ unsigned long changed)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+ u8 reg;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+ DBG(1, "%s :Changed=0x%lX\n", __func__, changed);
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ if (changed & IEEE802515_IEEEADDR_CHANGED) {
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_0, filt->ieee_addr[7]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_1, filt->ieee_addr[6]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_2, filt->ieee_addr[5]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_3, filt->ieee_addr[4]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_4, filt->ieee_addr[3]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_5, filt->ieee_addr[2]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_6, filt->ieee_addr[1]);
|
|
+ adf7242_write_reg(lp, REG_IEEE_ADDR_7, filt->ieee_addr[0]);
|
|
+ }
|
|
+
|
|
+ if (changed & IEEE802515_SADDR_CHANGED) {
|
|
+ adf7242_write_reg(lp, REG_SHORT_ADDR_0, filt->short_addr);
|
|
+ adf7242_write_reg(lp, REG_SHORT_ADDR_1, filt->short_addr >> 8);
|
|
+ }
|
|
+
|
|
+ if (changed & IEEE802515_PANID_CHANGED) {
|
|
+ adf7242_write_reg(lp, REG_PAN_ID0, filt->pan_id);
|
|
+ adf7242_write_reg(lp, REG_PAN_ID1, filt->pan_id >> 8);
|
|
+ }
|
|
+
|
|
+ if (changed & IEEE802515_PANC_CHANGED) {
|
|
+ adf7242_read_reg(lp, REG_AUTO_CFG, ®);
|
|
+ if (filt->pan_coord)
|
|
+ reg |= IS_PANCOORD;
|
|
+ else
|
|
+ reg &= ~IS_PANCOORD;
|
|
+ adf7242_write_reg(lp, REG_AUTO_CFG, reg);
|
|
+ }
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct adf7242_local *lp = dev->priv;
|
|
+ int ret;
|
|
+ unsigned long flags;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ BUG_ON(lp->is_tx);
|
|
+ lp->is_tx = 1;
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+
|
|
+ ret = adf7242_write_fbuf(lp, skb->data, skb->len);
|
|
+ if (ret)
|
|
+ goto err_rx;
|
|
+
|
|
+ if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA) {
|
|
+ ret = adf7242_cmd(lp, CMD_RC_PHY_RDY);
|
|
+ ret |= adf7242_cmd(lp, CMD_RC_CSMACA);
|
|
+ } else {
|
|
+ ret = adf7242_cmd(lp, CMD_RC_TX);
|
|
+ }
|
|
+
|
|
+ if (ret)
|
|
+ goto err_rx;
|
|
+
|
|
+ ret = wait_for_completion_interruptible(&lp->tx_complete);
|
|
+
|
|
+ if (ret < 0)
|
|
+ goto err_rx;
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+ return ret;
|
|
+
|
|
+err_rx:
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ lp->is_tx = 0;
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int adf7242_rx(struct adf7242_local *lp)
|
|
+{
|
|
+ u8 len = 128;
|
|
+ u8 lqi = 0;
|
|
+ int ret;
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ skb = alloc_skb(len, GFP_KERNEL);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ret = adf7242_read_fbuf(lp, skb_put(skb, len), &len, &lqi);
|
|
+
|
|
+ adf7242_cmd(lp, CMD_RC_RX);
|
|
+
|
|
+ skb_trim(skb, len - 2); /* We do not put RSSI/LQI or CRC into the frame */
|
|
+
|
|
+ if (len < 2) {
|
|
+ kfree_skb(skb);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ieee802154_rx_irqsafe(lp->dev, skb, lqi);
|
|
+
|
|
+ DBG(1, "%s: %d %d %x\n", __func__, ret, len, lqi);
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct ieee802154_ops adf7242_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .xmit = adf7242_xmit,
|
|
+ .ed = adf7242_ed,
|
|
+ .set_channel = adf7242_channel,
|
|
+ .set_hw_addr_filt = adf7242_set_hw_addr_filt,
|
|
+ .start = adf7242_start,
|
|
+ .stop = adf7242_stop,
|
|
+};
|
|
+
|
|
+static void adf7242_irqwork(struct work_struct *work)
|
|
+{
|
|
+ struct adf7242_local *lp =
|
|
+ container_of(work, struct adf7242_local, irqwork);
|
|
+ u8 irq1, auto_stat = 0, stat = 0;
|
|
+ int ret;
|
|
+ unsigned long flags;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ ret = adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1);
|
|
+
|
|
+ DBG(1, "%s IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n", __func__, irq1,
|
|
+ irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "",
|
|
+ irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "",
|
|
+ irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "",
|
|
+ irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "",
|
|
+ irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "",
|
|
+ irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "",
|
|
+ irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "",
|
|
+ irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : "");
|
|
+
|
|
+ adf7242_status(lp, &stat);
|
|
+
|
|
+ DBG(1, "%s STATUS = %X:\n%s\n%s%s%s%s%s\n", __func__, stat,
|
|
+ stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY",
|
|
+ (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "",
|
|
+ (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "",
|
|
+ (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "",
|
|
+ (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "",
|
|
+ (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : "");
|
|
+
|
|
+ adf7242_write_reg(lp, REG_IRQ1_SRC1, irq1);
|
|
+
|
|
+ if (irq1 & IRQ_RX_PKT_RCVD) {
|
|
+
|
|
+ /* Wait until ACK is processed */
|
|
+ if ((lp->mode & ADF_IEEE802154_HW_AACK) &&
|
|
+ ((stat & RC_STATUS_MASK) != RC_STATUS_PHY_RDY))
|
|
+ adf7242_wait_status(lp, RC_STATUS_PHY_RDY);
|
|
+
|
|
+ adf7242_rx(lp);
|
|
+ }
|
|
+
|
|
+ if (irq1 & lp->tx_irq) {
|
|
+
|
|
+ if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA) {
|
|
+ adf7242_read_reg(lp, REG_AUTO_STATUS, &auto_stat);
|
|
+ auto_stat &= AUTO_STATUS_MASK;
|
|
+
|
|
+ DBG(1, "%s AUTO_STATUS = %X:\n%s%s%s%s\n",
|
|
+ __func__, auto_stat,
|
|
+ auto_stat == SUCCESS ? "SUCCESS" : "",
|
|
+ auto_stat == SUCCESS_DATPEND ? "SUCCESS_DATPEND" : "",
|
|
+ auto_stat == FAILURE_CSMACA ? "FAILURE_CSMACA" : "",
|
|
+ auto_stat == FAILURE_NOACK ? "FAILURE_NOACK" : "");
|
|
+
|
|
+ /* save CSMA-CA completion status */
|
|
+ lp->tx_stat = auto_stat;
|
|
+ }
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ if (lp->is_tx) {
|
|
+ lp->is_tx = 0;
|
|
+ complete(&lp->tx_complete);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+
|
|
+ /* in case we just received a frame we are already in PHY_RX */
|
|
+
|
|
+ if (!(irq1 & IRQ_RX_PKT_RCVD))
|
|
+ adf7242_cmd(lp, CMD_RC_RX);
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ if (lp->irq_disabled) {
|
|
+ lp->irq_disabled = 0;
|
|
+ enable_irq(lp->spi->irq);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+}
|
|
+
|
|
+static irqreturn_t adf7242_isr(int irq, void *data)
|
|
+{
|
|
+ struct adf7242_local *lp = data;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ spin_lock(&lp->lock);
|
|
+ if (!lp->irq_disabled) {
|
|
+ disable_irq_nosync(irq);
|
|
+ lp->irq_disabled = 1;
|
|
+ }
|
|
+ spin_unlock(&lp->lock);
|
|
+
|
|
+ schedule_work(&lp->irqwork);
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+
|
|
+static int adf7242_hw_init(struct adf7242_local *lp)
|
|
+{
|
|
+ int ret;
|
|
+ const struct firmware *fw;
|
|
+
|
|
+ DBG(2, "%s :Enter\n", __func__);
|
|
+
|
|
+ adf7242_cmd(lp, CMD_RC_IDLE);
|
|
+
|
|
+ if (lp->mode) {
|
|
+ /* get ADF7242 addon firmware
|
|
+ * build this driver as module
|
|
+ * and place under /lib/firmware/adf7242_firmware.bin
|
|
+ */
|
|
+ ret = request_firmware(&fw, FIRMWARE, &lp->spi->dev);
|
|
+ if (ret) {
|
|
+ dev_err(&lp->spi->dev,
|
|
+ "request_firmware() failed with %i\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ adf7242_upload_firmware(lp, (u8 *) fw->data, fw->size);
|
|
+ release_firmware(fw);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_FFILT_CFG,
|
|
+ ACCEPT_BEACON_FRAMES |
|
|
+ ACCEPT_DATA_FRAMES |
|
|
+ ACCEPT_ACK_FRAMES |
|
|
+ ACCEPT_MACCMD_FRAMES |
|
|
+ (lp->mode & ADF_IEEE802154_PROMISCUOUS_MODE ?
|
|
+ ACCEPT_ALL_ADDRESS : 0) |
|
|
+ ACCEPT_RESERVED_FRAMES);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_AUTO_TX1,
|
|
+ MAX_FRAME_RETRIES(lp->pdata->max_frame_retries) |
|
|
+ MAX_CCA_RETRIES(lp->pdata->max_cca_retries));
|
|
+
|
|
+ adf7242_write_reg(lp, REG_AUTO_TX2,
|
|
+ CSMA_MAX_BE(lp->pdata->max_csma_be) |
|
|
+ CSMA_MIN_BE(lp->pdata->min_csma_be));
|
|
+
|
|
+ adf7242_write_reg(lp, REG_AUTO_CFG,
|
|
+ (lp->mode & ADF_IEEE802154_HW_AACK ?
|
|
+ RX_AUTO_ACK_EN : 0));
|
|
+ }
|
|
+
|
|
+ adf7242_write_reg(lp, REG_PKT_CFG, lp->mode ? ADDON_EN : 0);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_EXTPA_MSC, 0xF1);
|
|
+ adf7242_write_reg(lp, REG_RXFE_CFG, 0x1D);
|
|
+ adf7242_write_reg(lp, REG_IRQ1_EN0, 0);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_IRQ1_EN1, IRQ_RX_PKT_RCVD | lp->tx_irq);
|
|
+
|
|
+ adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF);
|
|
+ adf7242_write_reg(lp, REG_IRQ1_SRC0, 0xFF);
|
|
+
|
|
+ adf7242_cmd(lp, CMD_RC_PHY_RDY);
|
|
+
|
|
+ DBG(2, "%s :Exit\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_suspend(struct spi_device *spi, pm_message_t message)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int adf7242_resume(struct spi_device *spi)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t adf7242_show(struct device *dev,
|
|
+ struct device_attribute *devattr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct adf7242_local *lp = dev_get_drvdata(dev);
|
|
+ u8 stat;
|
|
+
|
|
+ adf7242_status(lp, &stat);
|
|
+
|
|
+ return sprintf(buf, "STATUS = %X:\n%s\n%s%s%s%s%s\n", stat,
|
|
+ stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY",
|
|
+ (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "",
|
|
+ (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "",
|
|
+ (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "",
|
|
+ (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "",
|
|
+ (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : "");
|
|
+
|
|
+}
|
|
+static DEVICE_ATTR(status, 0664, adf7242_show, NULL);
|
|
+
|
|
+static struct attribute *adf7242_attributes[] = {
|
|
+ &dev_attr_status.attr,
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static const struct attribute_group adf7242_attr_group = {
|
|
+ .attrs = adf7242_attributes,
|
|
+};
|
|
+
|
|
+static int __devinit adf7242_probe(struct spi_device *spi)
|
|
+{
|
|
+ struct adf7242_platform_data *pdata = spi->dev.platform_data;
|
|
+ struct ieee802154_dev *dev;
|
|
+ struct adf7242_local *lp;
|
|
+ int ret;
|
|
+
|
|
+ if (!spi->irq) {
|
|
+ dev_err(&spi->dev, "no IRQ specified\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (!pdata) {
|
|
+ dev_err(&spi->dev, "no platform data?\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ dev = ieee802154_alloc_device(sizeof(*lp), &adf7242_ops);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ lp = dev->priv;
|
|
+ lp->dev = dev;
|
|
+ lp->spi = spi;
|
|
+ lp->pdata = pdata;
|
|
+ lp->mode = pdata->mode;
|
|
+
|
|
+ dev->priv = lp;
|
|
+ dev->parent = &spi->dev;
|
|
+ dev->extra_tx_headroom = 0;
|
|
+ /* We do support only 2.4 Ghz */
|
|
+
|
|
+ dev->phy->channels_supported[0] = 0x7FFF800;
|
|
+
|
|
+ if (!lp->mode) {
|
|
+ adf7242_ops.set_hw_addr_filt = NULL;
|
|
+ lp->tx_irq = IRQ_TX_PKT_SENT;
|
|
+ } else {
|
|
+ if ((lp->mode & ADF_IEEE802154_PROMISCUOUS_MODE) &&
|
|
+ (lp->mode & ADF_IEEE802154_HW_AACK))
|
|
+ lp->mode &= ~ADF_IEEE802154_HW_AACK;
|
|
+
|
|
+ if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA)
|
|
+ lp->tx_irq = IRQ_CSMA_CA;
|
|
+ else
|
|
+ lp->tx_irq = IRQ_TX_PKT_SENT;
|
|
+ }
|
|
+
|
|
+ dev->flags = IEEE802154_HW_OMIT_CKSUM |
|
|
+ (lp->mode & ADF_IEEE802154_HW_AACK ?
|
|
+ IEEE802154_HW_AACK : 0);
|
|
+
|
|
+ mutex_init(&lp->bmux);
|
|
+ INIT_WORK(&lp->irqwork, adf7242_irqwork);
|
|
+ spin_lock_init(&lp->lock);
|
|
+ init_completion(&lp->tx_complete);
|
|
+
|
|
+ spi_set_drvdata(spi, lp);
|
|
+
|
|
+ ret = adf7242_hw_init(lp);
|
|
+ if (ret)
|
|
+ goto err_hw_init;
|
|
+
|
|
+ ret = request_irq(spi->irq, adf7242_isr, IRQF_TRIGGER_HIGH,
|
|
+ dev_name(&spi->dev), lp);
|
|
+ if (ret)
|
|
+ goto err_hw_init;
|
|
+
|
|
+ ret = ieee802154_register_device(lp->dev);
|
|
+ if (ret)
|
|
+ goto err_irq;
|
|
+
|
|
+ dev_set_drvdata(&spi->dev, lp);
|
|
+
|
|
+ ret = sysfs_create_group(&spi->dev.kobj, &adf7242_attr_group);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ dev_info(&spi->dev, "mac802154 IRQ-%d registered\n", spi->irq);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+out:
|
|
+ ieee802154_unregister_device(lp->dev);
|
|
+err_irq:
|
|
+ free_irq(spi->irq, lp);
|
|
+ flush_work(&lp->irqwork);
|
|
+err_hw_init:
|
|
+ spi_set_drvdata(spi, NULL);
|
|
+ mutex_destroy(&lp->bmux);
|
|
+ ieee802154_free_device(lp->dev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __devexit adf7242_remove(struct spi_device *spi)
|
|
+{
|
|
+ struct adf7242_local *lp = spi_get_drvdata(spi);
|
|
+
|
|
+ ieee802154_unregister_device(lp->dev);
|
|
+ free_irq(spi->irq, lp);
|
|
+ flush_work(&lp->irqwork);
|
|
+ spi_set_drvdata(spi, NULL);
|
|
+ mutex_destroy(&lp->bmux);
|
|
+ ieee802154_free_device(lp->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct spi_driver adf7242_driver = {
|
|
+ .driver = {
|
|
+ .name = "adf7242",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .probe = adf7242_probe,
|
|
+ .remove = __devexit_p(adf7242_remove),
|
|
+ .suspend = adf7242_suspend,
|
|
+ .resume = adf7242_resume,
|
|
+};
|
|
+
|
|
+static int __init adf7242_init(void)
|
|
+{
|
|
+ return spi_register_driver(&adf7242_driver);
|
|
+}
|
|
+module_init(adf7242_init);
|
|
+
|
|
+static void __exit adf7242_exit(void)
|
|
+{
|
|
+ spi_unregister_driver(&adf7242_driver);
|
|
+}
|
|
+module_exit(adf7242_exit);
|
|
+
|
|
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
|
+MODULE_DESCRIPTION("ADF7242 Transceiver Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/ieee802154/at86rf230.c b/drivers/ieee802154/at86rf230.c
|
|
new file mode 100644
|
|
index 0000000..8da82cf
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/at86rf230.c
|
|
@@ -0,0 +1,872 @@
|
|
+/*
|
|
+ * AT86RF230/RF231 driver
|
|
+ *
|
|
+ * Copyright (C) 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
|
|
+ */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/at86rf230.h>
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#endif
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "at86rf230.h"
|
|
+
|
|
+
|
|
+struct at86rf230_local {
|
|
+ struct spi_device *spi;
|
|
+ int rstn, slp_tr, dig2;
|
|
+ void (*reset)(void *reset_data);
|
|
+ void *reset_data;
|
|
+
|
|
+ u8 part;
|
|
+ u8 vers;
|
|
+
|
|
+ u8 buf[2];
|
|
+ struct mutex bmux;
|
|
+
|
|
+ struct work_struct irqwork;
|
|
+ struct completion tx_complete;
|
|
+
|
|
+ struct ieee802154_dev *dev;
|
|
+
|
|
+ volatile unsigned is_tx:1;
|
|
+};
|
|
+
|
|
+
|
|
+static int
|
|
+__at86rf230_write(struct at86rf230_local *lp, u8 addr, u8 data)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+ };
|
|
+
|
|
+ buf[0] = (addr & CMD_REG_MASK) | CMD_REG | CMD_WRITE;
|
|
+ buf[1] = data;
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+__at86rf230_read_subreg(struct at86rf230_local *lp,
|
|
+ u8 addr, u8 mask, int shift, u8 *data)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+ .rx_buf = buf,
|
|
+ };
|
|
+
|
|
+ buf[0] = (addr & CMD_REG_MASK) | CMD_REG;
|
|
+ buf[1] = 0xff;
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+
|
|
+ if (status == 0)
|
|
+ *data = buf[1];
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_read_subreg(struct at86rf230_local *lp,
|
|
+ u8 addr, u8 mask, int shift, u8 *data)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = __at86rf230_read_subreg(lp, addr, mask, shift, data);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_write_subreg(struct at86rf230_local *lp,
|
|
+ u8 addr, u8 mask, int shift, u8 data)
|
|
+{
|
|
+ int status;
|
|
+ u8 val;
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ status = __at86rf230_read_subreg(lp, addr, 0xff, 0, &val);
|
|
+ if (status)
|
|
+ goto out;
|
|
+
|
|
+ val &= ~mask;
|
|
+ val |= (data << shift) & mask;
|
|
+
|
|
+ status = __at86rf230_write(lp, addr, val);
|
|
+out:
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_write_fbuf(struct at86rf230_local *lp, u8 *data, u8 len)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = len,
|
|
+ .tx_buf = data,
|
|
+ };
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ buf[0] = CMD_WRITE | CMD_FB;
|
|
+ buf[1] = len + 2; /* 2 bytes for CRC that isn't written */
|
|
+
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_read_fbuf(struct at86rf230_local *lp, u8 *data, u8 *len, u8 *lqi)
|
|
+{
|
|
+ u8 *buf = lp->buf;
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+ .rx_buf = buf,
|
|
+ };
|
|
+ struct spi_transfer xfer_head1 = {
|
|
+ .len = 2,
|
|
+ .tx_buf = buf,
|
|
+ .rx_buf = buf,
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = 0,
|
|
+ .rx_buf = data,
|
|
+ };
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+
|
|
+ buf[0] = CMD_FB;
|
|
+ buf[1] = 0x00;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+
|
|
+ if (buf[1] & 0x80) {
|
|
+ dev_err(&lp->spi->dev, "invalid PHR 0x%02x\n", buf[1]);
|
|
+ status = -EIO;
|
|
+ goto fail;
|
|
+ }
|
|
+ if (buf[1] >= *len) {
|
|
+ dev_err(&lp->spi->dev, "PHR 0x%02x >= buffer %d bytes\n",
|
|
+ buf[1], *len);
|
|
+ status = -EMSGSIZE;
|
|
+ goto fail;
|
|
+ }
|
|
+ xfer_buf.len = *(buf + 1) + 1;
|
|
+ *len = buf[1];
|
|
+
|
|
+ buf[0] = CMD_FB;
|
|
+ buf[1] = 0x00;
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head1, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", buf[1]);
|
|
+
|
|
+ if (!status) {
|
|
+ if (lqi && *len > lp->buf[1])
|
|
+ *lqi = data[lp->buf[1]];
|
|
+ }
|
|
+
|
|
+fail:
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_ed(struct ieee802154_dev *dev, u8 *level)
|
|
+{
|
|
+ pr_debug("%s\n", __func__);
|
|
+ might_sleep();
|
|
+ BUG_ON(!level);
|
|
+ *level = 0xbe;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_state(struct ieee802154_dev *dev, int state)
|
|
+{
|
|
+ struct at86rf230_local *lp = dev->priv;
|
|
+ int rc;
|
|
+ u8 val;
|
|
+ u8 desired_status;
|
|
+
|
|
+ pr_debug("%s %d\n", __func__/*, priv->cur_state*/, state);
|
|
+ might_sleep();
|
|
+
|
|
+ if (state == STATE_FORCE_TX_ON)
|
|
+ desired_status = STATE_TX_ON;
|
|
+ else if (state == STATE_FORCE_TRX_OFF)
|
|
+ desired_status = STATE_TRX_OFF;
|
|
+ else
|
|
+ desired_status = state;
|
|
+
|
|
+ do {
|
|
+ rc = at86rf230_read_subreg(lp, SR_TRX_STATUS, &val);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+ pr_debug("%s val1 = %x\n", __func__, val);
|
|
+ } while (val == STATE_TRANSITION_IN_PROGRESS);
|
|
+
|
|
+ if (val == desired_status)
|
|
+ return 0;
|
|
+
|
|
+ /* state is equal to phy states */
|
|
+ rc = at86rf230_write_subreg(lp, SR_TRX_CMD, state);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+
|
|
+ do {
|
|
+ rc = at86rf230_read_subreg(lp, SR_TRX_STATUS, &val);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+ pr_debug("%s val2 = %x\n", __func__, val);
|
|
+ } while (val == STATE_TRANSITION_IN_PROGRESS);
|
|
+
|
|
+
|
|
+ if (val == desired_status)
|
|
+ return 0;
|
|
+ if (state == STATE_RX_ON && val == STATE_BUSY_RX)
|
|
+ return 0;
|
|
+
|
|
+ pr_err("%s unexpected state change: %d, asked for %d\n", __func__,
|
|
+ val, state);
|
|
+ return -EBUSY;
|
|
+
|
|
+err:
|
|
+ pr_err("%s error: %d\n", __func__, rc);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_start(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct at86rf230_local *lp = dev->priv;
|
|
+ u8 rc;
|
|
+
|
|
+ rc = at86rf230_write_subreg(lp, SR_RX_SAFE_MODE, 1);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ return at86rf230_state(dev, STATE_RX_ON);
|
|
+}
|
|
+
|
|
+static void
|
|
+at86rf230_stop(struct ieee802154_dev *dev)
|
|
+{
|
|
+ at86rf230_state(dev, STATE_FORCE_TRX_OFF);
|
|
+}
|
|
+
|
|
+static int
|
|
+at86rf230_channel(struct ieee802154_dev *dev, int page, int channel)
|
|
+{
|
|
+ struct at86rf230_local *lp = dev->priv;
|
|
+ int rc;
|
|
+
|
|
+ pr_debug("%s %d\n", __func__, channel);
|
|
+ might_sleep();
|
|
+
|
|
+ BUG_ON(page != 0);
|
|
+ BUG_ON(channel < 11);
|
|
+ BUG_ON(channel > 26);
|
|
+
|
|
+ rc = at86rf230_write_subreg(lp, SR_CHANNEL, channel);
|
|
+ msleep(1); /* Wait for PLL */
|
|
+ dev->phy->current_channel = channel;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* FIXME:
|
|
+ * This function currently is a mess. It uses flush_work to guard
|
|
+ * against concurrent irqwork, etc. One has to use mutexes intead. */
|
|
+static int
|
|
+at86rf230_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct at86rf230_local *lp = dev->priv;
|
|
+ int rc;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ BUG_ON(lp->is_tx);
|
|
+ INIT_COMPLETION(lp->tx_complete);
|
|
+
|
|
+ rc = at86rf230_state(dev, STATE_FORCE_TX_ON);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+
|
|
+ synchronize_irq(lp->spi->irq);
|
|
+ flush_work(&lp->irqwork);
|
|
+
|
|
+ lp->is_tx = 1;
|
|
+
|
|
+ rc = at86rf230_write_fbuf(lp, skb->data, skb->len);
|
|
+ if (rc)
|
|
+ goto err_rx;
|
|
+
|
|
+ if (gpio_is_valid(lp->slp_tr)) {
|
|
+ gpio_set_value(lp->slp_tr, 1);
|
|
+ udelay(80); /* > 62.5 */
|
|
+ gpio_set_value(lp->slp_tr, 0);
|
|
+ } else {
|
|
+ rc = at86rf230_write_subreg(lp, SR_TRX_CMD, STATE_BUSY_TX);
|
|
+ if (rc)
|
|
+ goto err_rx;
|
|
+ }
|
|
+
|
|
+ /* FIXME: the logic is really strange here. Datasheet doesn't
|
|
+ * provide us enough info about behaviour in such cases.
|
|
+ * Basically either we were interrupted here, or we have lost
|
|
+ * the interrupt. Most probably this should be changed to
|
|
+ * wait_for_completion_timeout() and handle it's results
|
|
+ */
|
|
+ rc = wait_for_completion_interruptible(&lp->tx_complete);
|
|
+ if (rc < 0)
|
|
+ goto err_state;
|
|
+
|
|
+ lp->is_tx = 0;
|
|
+
|
|
+ rc = at86rf230_start(dev);
|
|
+ return rc;
|
|
+
|
|
+err_state:
|
|
+ /* try to recover from possibly problematic state */
|
|
+ at86rf230_state(dev, STATE_FORCE_TX_ON);
|
|
+ synchronize_irq(lp->spi->irq);
|
|
+ flush_work(&lp->irqwork);
|
|
+ lp->is_tx = 0;
|
|
+err_rx:
|
|
+ at86rf230_start(dev);
|
|
+err:
|
|
+ if (rc)
|
|
+ pr_err("%s error: %d\n", __func__, rc);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int at86rf230_rx(struct at86rf230_local *lp)
|
|
+{
|
|
+ u8 len = 128, lqi = 0;
|
|
+ int rc, rc2;
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ skb = alloc_skb(len, GFP_KERNEL);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* FIXME: process return status */
|
|
+ rc = at86rf230_write_subreg(lp, SR_RX_PDT_DIS, 1);
|
|
+ rc2 = at86rf230_read_fbuf(lp, skb_put(skb, len), &len, &lqi);
|
|
+ rc = at86rf230_write_subreg(lp, SR_RX_SAFE_MODE, 1);
|
|
+ rc = at86rf230_write_subreg(lp, SR_RX_PDT_DIS, 0);
|
|
+ if (rc2 < 0)
|
|
+ goto err_fbuf;
|
|
+
|
|
+ if (len < 2)
|
|
+ goto err;
|
|
+
|
|
+ skb_trim(skb, len-2); /* We do not put CRC into the frame */
|
|
+
|
|
+
|
|
+ ieee802154_rx_irqsafe(lp->dev, skb, lqi);
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "READ_FBUF: %d %d %x\n", rc, len, lqi);
|
|
+
|
|
+ return 0;
|
|
+err:
|
|
+ pr_debug("%s: received frame is too small\n", __func__);
|
|
+
|
|
+err_fbuf:
|
|
+ kfree_skb(skb);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static struct ieee802154_ops at86rf230_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .xmit = at86rf230_xmit,
|
|
+ .ed = at86rf230_ed,
|
|
+ .set_channel = at86rf230_channel,
|
|
+ .start = at86rf230_start,
|
|
+ .stop = at86rf230_stop,
|
|
+};
|
|
+
|
|
+static void at86rf230_irqwork(struct work_struct *work)
|
|
+{
|
|
+ struct at86rf230_local *lp =
|
|
+ container_of(work, struct at86rf230_local, irqwork);
|
|
+ u8 status = 0, val;
|
|
+ int rc;
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "IRQ Worker\n");
|
|
+
|
|
+ do {
|
|
+ rc = at86rf230_read_subreg(lp, RG_IRQ_STATUS, 0xff, 0, &val);
|
|
+ status |= val;
|
|
+ dev_dbg(&lp->spi->dev, "IRQ Status: %02x\n", status);
|
|
+
|
|
+ status &= ~IRQ_PLL_LOCK; /* ignore */
|
|
+ status &= ~IRQ_RX_START; /* ignore */
|
|
+ status &= ~IRQ_AMI; /* ignore */
|
|
+ status &= ~IRQ_TRX_UR; /* FIXME: possibly handle ???*/
|
|
+
|
|
+ if (status & IRQ_TRX_END) {
|
|
+ status &= ~IRQ_TRX_END;
|
|
+ if (lp->is_tx)
|
|
+ complete(&lp->tx_complete);
|
|
+ else
|
|
+ at86rf230_rx(lp);
|
|
+ }
|
|
+
|
|
+ } while (status != 0);
|
|
+
|
|
+ enable_irq(lp->spi->irq);
|
|
+}
|
|
+
|
|
+static irqreturn_t at86rf230_isr(int irq, void *data)
|
|
+{
|
|
+ struct at86rf230_local *lp = data;
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "IRQ!\n");
|
|
+
|
|
+ disable_irq_nosync(irq);
|
|
+ schedule_work(&lp->irqwork);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+
|
|
+static int at86rf230_hw_init(struct at86rf230_local *lp)
|
|
+{
|
|
+ u8 status;
|
|
+ int rc;
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_TRX_STATUS, &status);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ dev_info(&lp->spi->dev, "Status: %02x\n", status);
|
|
+ if (status == STATE_P_ON) {
|
|
+ rc = at86rf230_write_subreg(lp, SR_TRX_CMD, STATE_TRX_OFF);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ msleep(1);
|
|
+ rc = at86rf230_read_subreg(lp, SR_TRX_STATUS, &status);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ dev_info(&lp->spi->dev, "Status: %02x\n", status);
|
|
+ }
|
|
+
|
|
+ rc = at86rf230_write_subreg(lp, SR_IRQ_MASK,
|
|
+ /*IRQ_TRX_UR | IRQ_CCA_ED | IRQ_TRX_END | IRQ_PLL_UNL | IRQ_PLL_LOCK*/ 0xff);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ /* CLKM changes are applied immediately */
|
|
+ rc = at86rf230_write_subreg(lp, SR_CLKM_SHA_SEL, 0x00);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ /* Turn CLKM Off */
|
|
+ rc = at86rf230_write_subreg(lp, SR_CLKM_CTRL, 0x00);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ msleep(100);
|
|
+
|
|
+ rc = at86rf230_write_subreg(lp, SR_TRX_CMD, STATE_TX_ON);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ msleep(1);
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_TRX_STATUS, &status);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ dev_info(&lp->spi->dev, "Status: %02x\n", status);
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_DVDD_OK, &status);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ if (!status) {
|
|
+ dev_err(&lp->spi->dev, "DVDD error\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_AVDD_OK, &status);
|
|
+ if (rc)
|
|
+ return rc;
|
|
+ if (!status) {
|
|
+ dev_err(&lp->spi->dev, "AVDD error\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int at86rf230_suspend(struct spi_device *spi, pm_message_t message)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int at86rf230_resume(struct spi_device *spi)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_OF
|
|
+static int at86rf230_fill_data(struct spi_device *spi)
|
|
+{
|
|
+ struct device_node *np = spi->dev.of_node;
|
|
+ struct at86rf230_local *lp = spi_get_drvdata(spi);
|
|
+ struct at86rf230_platform_data *pdata = spi->dev.platform_data;
|
|
+ enum of_gpio_flags gpio_flags;
|
|
+
|
|
+ if (pdata) {
|
|
+ lp->rstn = pdata->rstn;
|
|
+ lp->slp_tr = pdata->slp_tr;
|
|
+ lp->dig2 = pdata->dig2;
|
|
+ lp->reset = pdata->reset;
|
|
+ lp->reset_data = pdata->reset_data;
|
|
+
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (!np) {
|
|
+ dev_err(&spi->dev, "no platform_data and no node data\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ lp->rstn = of_get_gpio_flags(np, 0, &gpio_flags);
|
|
+ if (!gpio_is_valid(lp->rstn)) {
|
|
+ dev_err(&spi->dev, "no RSTN GPIO!\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ lp->slp_tr = of_get_gpio_flags(np, 1, &gpio_flags);
|
|
+ lp->dig2 = of_get_gpio_flags(np, 2, &gpio_flags);
|
|
+
|
|
+ lp->reset = NULL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#else
|
|
+static int at86rf230_fill_data(struct spi_device *spi)
|
|
+{
|
|
+ struct at86rf230_local *lp = spi_get_drvdata(spi);
|
|
+ struct at86rf230_platform_data *pdata = spi->dev.platform_data;
|
|
+
|
|
+ if (!pdata) {
|
|
+ dev_err(&spi->dev, "no platform_data\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ lp->rstn = pdata->rstn;
|
|
+ lp->slp_tr = pdata->slp_tr;
|
|
+ lp->dig2 = pdata->dig2;
|
|
+ lp->reset = pdata->reset;
|
|
+ lp->reset_data = pdata->reset_data;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int __devinit at86rf230_probe(struct spi_device *spi)
|
|
+{
|
|
+ struct ieee802154_dev *dev;
|
|
+ struct at86rf230_local *lp;
|
|
+ u8 man_id_0, man_id_1;
|
|
+ int rc;
|
|
+ const char *chip;
|
|
+ int supported = 0;
|
|
+
|
|
+ if (spi->irq < 0) {
|
|
+ dev_err(&spi->dev, "no IRQ specified\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev = ieee802154_alloc_device(sizeof(*lp), &at86rf230_ops);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ lp = dev->priv;
|
|
+ lp->dev = dev;
|
|
+
|
|
+ lp->spi = spi;
|
|
+
|
|
+ dev->priv = lp;
|
|
+ dev->parent = &spi->dev;
|
|
+ dev->extra_tx_headroom = 0;
|
|
+ /* We do support only 2.4 Ghz */
|
|
+ dev->phy->channels_supported[0] = 0x7FFF800;
|
|
+ dev->flags = IEEE802154_HW_OMIT_CKSUM;
|
|
+
|
|
+ mutex_init(&lp->bmux);
|
|
+ INIT_WORK(&lp->irqwork, at86rf230_irqwork);
|
|
+ init_completion(&lp->tx_complete);
|
|
+
|
|
+ spi_set_drvdata(spi, lp);
|
|
+
|
|
+ rc = at86rf230_fill_data(spi);
|
|
+ if (rc)
|
|
+ goto err_fill;
|
|
+
|
|
+ if (gpio_is_valid(lp->rstn)) {
|
|
+ rc = gpio_request(lp->rstn, "rstn");
|
|
+ if (rc)
|
|
+ goto err_rstn;
|
|
+ }
|
|
+
|
|
+ if (gpio_is_valid(lp->slp_tr)) {
|
|
+ rc = gpio_request(lp->slp_tr, "slp_tr");
|
|
+ if (rc)
|
|
+ goto err_slp_tr;
|
|
+ }
|
|
+
|
|
+ if (gpio_is_valid(lp->rstn)) {
|
|
+ rc = gpio_direction_output(lp->rstn, 1);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+ }
|
|
+
|
|
+ if (gpio_is_valid(lp->slp_tr)) {
|
|
+ rc = gpio_direction_output(lp->slp_tr, 0);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+ }
|
|
+
|
|
+ /* Reset */
|
|
+ if (lp->reset)
|
|
+ lp->reset(lp->reset_data);
|
|
+ else {
|
|
+ msleep(1);
|
|
+ gpio_set_value(lp->rstn, 0);
|
|
+ msleep(1);
|
|
+ gpio_set_value(lp->rstn, 1);
|
|
+ msleep(1);
|
|
+ }
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_MAN_ID_0, &man_id_0);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+ rc = at86rf230_read_subreg(lp, SR_MAN_ID_1, &man_id_1);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+
|
|
+ if (man_id_1 != 0x00 || man_id_0 != 0x1f) {
|
|
+ dev_err(&spi->dev, "Non-Atmel device found (MAN_ID:"
|
|
+ "%02x %02x)\n", man_id_1, man_id_0);
|
|
+ rc = -EINVAL;
|
|
+ goto err_gpio_dir;
|
|
+ }
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_PART_NUM, &lp->part);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+
|
|
+ rc = at86rf230_read_subreg(lp, SR_VERSION_NUM, &lp->vers);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+
|
|
+ switch (lp->part) {
|
|
+ case 2:
|
|
+ chip = "at86rf230";
|
|
+ /* supported = 1; FIXME: should be easy to support; */
|
|
+ break;
|
|
+ case 3:
|
|
+ chip = "at86rf231";
|
|
+ supported = 1;
|
|
+ break;
|
|
+ default:
|
|
+ chip = "UNKNOWN";
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ dev_info(&spi->dev, "Detected %s chip version %d\n", chip, lp->vers);
|
|
+ if (!supported) {
|
|
+ rc = -ENOTSUPP;
|
|
+ goto err_gpio_dir;
|
|
+ }
|
|
+
|
|
+ rc = at86rf230_hw_init(lp);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+
|
|
+ rc = request_irq(spi->irq, at86rf230_isr, IRQF_SHARED,
|
|
+ dev_name(&spi->dev), lp);
|
|
+ if (rc)
|
|
+ goto err_gpio_dir;
|
|
+
|
|
+ dev_dbg(&spi->dev, "registered at86rf230\n");
|
|
+
|
|
+ rc = ieee802154_register_device(lp->dev);
|
|
+ if (rc)
|
|
+ goto err_irq;
|
|
+
|
|
+ return rc;
|
|
+
|
|
+err_irq:
|
|
+ disable_irq(spi->irq);
|
|
+ flush_work(&lp->irqwork);
|
|
+ free_irq(spi->irq, lp);
|
|
+err_gpio_dir:
|
|
+ if (gpio_is_valid(lp->slp_tr))
|
|
+ gpio_free(lp->slp_tr);
|
|
+err_slp_tr:
|
|
+ if (gpio_is_valid(lp->rstn))
|
|
+ gpio_free(lp->rstn);
|
|
+err_rstn:
|
|
+err_fill:
|
|
+ spi_set_drvdata(spi, NULL);
|
|
+ mutex_destroy(&lp->bmux);
|
|
+ ieee802154_free_device(lp->dev);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int __devexit at86rf230_remove(struct spi_device *spi)
|
|
+{
|
|
+ struct at86rf230_local *lp = spi_get_drvdata(spi);
|
|
+
|
|
+ /*
|
|
+ * @@@ this looks wrong - what if a frame arrives before
|
|
+ * disable_irq ? -- wa
|
|
+ */
|
|
+ ieee802154_unregister_device(lp->dev);
|
|
+
|
|
+ disable_irq(spi->irq);
|
|
+ flush_work(&lp->irqwork);
|
|
+ free_irq(spi->irq, lp);
|
|
+
|
|
+ if (gpio_is_valid(lp->slp_tr))
|
|
+ gpio_free(lp->slp_tr);
|
|
+ if (gpio_is_valid(lp->rstn))
|
|
+ gpio_free(lp->rstn);
|
|
+
|
|
+ spi_set_drvdata(spi, NULL);
|
|
+ mutex_destroy(&lp->bmux);
|
|
+ ieee802154_free_device(lp->dev);
|
|
+
|
|
+ dev_dbg(&spi->dev, "unregistered at86rf230\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct spi_driver at86rf230_driver = {
|
|
+ .driver = {
|
|
+ .name = "at86rf230",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .probe = at86rf230_probe,
|
|
+ .remove = __devexit_p(at86rf230_remove),
|
|
+ .suspend = at86rf230_suspend,
|
|
+ .resume = at86rf230_resume,
|
|
+};
|
|
+
|
|
+static int __init at86rf230_init(void)
|
|
+{
|
|
+ return spi_register_driver(&at86rf230_driver);
|
|
+}
|
|
+module_init(at86rf230_init);
|
|
+
|
|
+static void __exit at86rf230_exit(void)
|
|
+{
|
|
+ spi_unregister_driver(&at86rf230_driver);
|
|
+}
|
|
+module_exit(at86rf230_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("AT86RF230 Transceiver Driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/ieee802154/at86rf230.h b/drivers/ieee802154/at86rf230.h
|
|
new file mode 100644
|
|
index 0000000..fcb0e11
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/at86rf230.h
|
|
@@ -0,0 +1,211 @@
|
|
+/*
|
|
+ * AT86RF230/RF231 register and protocol definitions
|
|
+ *
|
|
+ * Copyright (C) 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
|
|
+ */
|
|
+
|
|
+#ifndef ATR86F230_H
|
|
+#define ATR86F230_H
|
|
+
|
|
+#define RG_TRX_STATUS (0x01)
|
|
+#define SR_TRX_STATUS 0x01, 0x1f, 0
|
|
+#define SR_RESERVED_01_3 0x01, 0x20, 5
|
|
+#define SR_CCA_STATUS 0x01, 0x40, 6
|
|
+#define SR_CCA_DONE 0x01, 0x80, 7
|
|
+#define RG_TRX_STATE (0x02)
|
|
+#define SR_TRX_CMD 0x02, 0x1f, 0
|
|
+#define SR_TRAC_STATUS 0x02, 0xe0, 5
|
|
+#define RG_TRX_CTRL_0 (0x03)
|
|
+#define SR_CLKM_CTRL 0x03, 0x07, 0
|
|
+#define SR_CLKM_SHA_SEL 0x03, 0x08, 3
|
|
+#define SR_PAD_IO_CLKM 0x03, 0x30, 4
|
|
+#define SR_PAD_IO 0x03, 0xc0, 6
|
|
+#define RG_TRX_CTRL_1 (0x04)
|
|
+#define SR_IRQ_POLARITY 0x04, 0x01, 0
|
|
+#define SR_IRQ_MASK_MODE 0x04, 0x02, 1
|
|
+#define SR_SPI_CMD_MODE 0x04, 0x0c, 2
|
|
+#define SR_RX_BL_CTRL 0x04, 0x10, 4
|
|
+#define SR_TX_AUTO_CRC_ON 0x04, 0x20, 5
|
|
+#define SR_IRQ_2_EXT_EN 0x04, 0x40, 6
|
|
+#define SR_PA_EXT_EN 0x04, 0x80, 7
|
|
+#define RG_PHY_TX_PWR (0x05)
|
|
+#define SR_TX_PWR 0x05, 0x0f, 0
|
|
+#define SR_PA_LT 0x05, 0x30, 4
|
|
+#define SR_PA_BUF_LT 0x05, 0xc0, 6
|
|
+#define RG_PHY_RSSI (0x06)
|
|
+#define SR_RSSI 0x06, 0x1f, 0
|
|
+#define SR_RND_VALUE 0x06, 0x60, 5
|
|
+#define SR_RX_CRC_VALID 0x06, 0x80, 7
|
|
+#define RG_PHY_ED_LEVEL (0x07)
|
|
+#define SR_ED_LEVEL 0x07, 0xff, 0
|
|
+#define RG_PHY_CC_CCA (0x08)
|
|
+#define SR_CHANNEL 0x08, 0x1f, 0
|
|
+#define SR_CCA_MODE 0x08, 0x60, 5
|
|
+#define SR_CCA_REQUEST 0x08, 0x80, 7
|
|
+#define RG_CCA_THRES (0x09)
|
|
+#define SR_CCA_ED_THRES 0x09, 0x0f, 0
|
|
+#define SR_RESERVED_09_1 0x09, 0xf0, 4
|
|
+#define RG_RX_CTRL (0x0a)
|
|
+#define SR_PDT_THRES 0x0a, 0x0f, 0
|
|
+#define SR_RESERVED_0a_1 0x0a, 0xf0, 4
|
|
+#define RG_SFD_VALUE (0x0b)
|
|
+#define SR_SFD_VALUE 0x0b, 0xff, 0
|
|
+#define RG_TRX_CTRL_2 (0x0c)
|
|
+#define SR_OQPSK_DATA_RATE 0x0c, 0x03, 0
|
|
+#define SR_RESERVED_0c_2 0x0c, 0x7c, 2
|
|
+#define SR_RX_SAFE_MODE 0x0c, 0x80, 7
|
|
+#define RG_ANT_DIV (0x0d)
|
|
+#define SR_ANT_CTRL 0x0d, 0x03, 0
|
|
+#define SR_ANT_EXT_SW_EN 0x0d, 0x04, 2
|
|
+#define SR_ANT_DIV_EN 0x0d, 0x08, 3
|
|
+#define SR_RESERVED_0d_2 0x0d, 0x70, 4
|
|
+#define SR_ANT_SEL 0x0d, 0x80, 7
|
|
+#define RG_IRQ_MASK (0x0e)
|
|
+#define SR_IRQ_MASK 0x0e, 0xff, 0
|
|
+#define RG_IRQ_STATUS (0x0f)
|
|
+#define SR_IRQ_0_PLL_LOCK 0x0f, 0x01, 0
|
|
+#define SR_IRQ_1_PLL_UNLOCK 0x0f, 0x02, 1
|
|
+#define SR_IRQ_2_RX_START 0x0f, 0x04, 2
|
|
+#define SR_IRQ_3_TRX_END 0x0f, 0x08, 3
|
|
+#define SR_IRQ_4_CCA_ED_DONE 0x0f, 0x10, 4
|
|
+#define SR_IRQ_5_AMI 0x0f, 0x20, 5
|
|
+#define SR_IRQ_6_TRX_UR 0x0f, 0x40, 6
|
|
+#define SR_IRQ_7_BAT_LOW 0x0f, 0x80, 7
|
|
+#define RG_VREG_CTRL (0x10)
|
|
+#define SR_RESERVED_10_6 0x10, 0x03, 0
|
|
+#define SR_DVDD_OK 0x10, 0x04, 2
|
|
+#define SR_DVREG_EXT 0x10, 0x08, 3
|
|
+#define SR_RESERVED_10_3 0x10, 0x30, 4
|
|
+#define SR_AVDD_OK 0x10, 0x40, 6
|
|
+#define SR_AVREG_EXT 0x10, 0x80, 7
|
|
+#define RG_BATMON (0x11)
|
|
+#define SR_BATMON_VTH 0x11, 0x0f, 0
|
|
+#define SR_BATMON_HR 0x11, 0x10, 4
|
|
+#define SR_BATMON_OK 0x11, 0x20, 5
|
|
+#define SR_RESERVED_11_1 0x11, 0xc0, 6
|
|
+#define RG_XOSC_CTRL (0x12)
|
|
+#define SR_XTAL_TRIM 0x12, 0x0f, 0
|
|
+#define SR_XTAL_MODE 0x12, 0xf0, 4
|
|
+#define RG_RX_SYN (0x15)
|
|
+#define SR_RX_PDT_LEVEL 0x15, 0x0f, 0
|
|
+#define SR_RESERVED_15_2 0x15, 0x70, 4
|
|
+#define SR_RX_PDT_DIS 0x15, 0x80, 7
|
|
+#define RG_XAH_CTRL_1 (0x17)
|
|
+#define SR_RESERVED_17_8 0x17, 0x01, 0
|
|
+#define SR_AACK_PROM_MODE 0x17, 0x02, 1
|
|
+#define SR_AACK_ACK_TIME 0x17, 0x04, 2
|
|
+#define SR_RESERVED_17_5 0x17, 0x08, 3
|
|
+#define SR_AACK_UPLD_RES_FT 0x17, 0x10, 4
|
|
+#define SR_AACK_FLTR_RES_FT 0x17, 0x20, 5
|
|
+#define SR_RESERVED_17_2 0x17, 0x40, 6
|
|
+#define SR_RESERVED_17_1 0x17, 0x80, 7
|
|
+#define RG_FTN_CTRL (0x18)
|
|
+#define SR_RESERVED_18_2 0x18, 0x7f, 0
|
|
+#define SR_FTN_START 0x18, 0x80, 7
|
|
+#define RG_PLL_CF (0x1a)
|
|
+#define SR_RESERVED_1a_2 0x1a, 0x7f, 0
|
|
+#define SR_PLL_CF_START 0x1a, 0x80, 7
|
|
+#define RG_PLL_DCU (0x1b)
|
|
+#define SR_RESERVED_1b_3 0x1b, 0x3f, 0
|
|
+#define SR_RESERVED_1b_2 0x1b, 0x40, 6
|
|
+#define SR_PLL_DCU_START 0x1b, 0x80, 7
|
|
+#define RG_PART_NUM (0x1c)
|
|
+#define SR_PART_NUM 0x1c, 0xff, 0
|
|
+#define RG_VERSION_NUM (0x1d)
|
|
+#define SR_VERSION_NUM 0x1d, 0xff, 0
|
|
+#define RG_MAN_ID_0 (0x1e)
|
|
+#define SR_MAN_ID_0 0x1e, 0xff, 0
|
|
+#define RG_MAN_ID_1 (0x1f)
|
|
+#define SR_MAN_ID_1 0x1f, 0xff, 0
|
|
+#define RG_SHORT_ADDR_0 (0x20)
|
|
+#define SR_SHORT_ADDR_0 0x20, 0xff, 0
|
|
+#define RG_SHORT_ADDR_1 (0x21)
|
|
+#define SR_SHORT_ADDR_1 0x21, 0xff, 0
|
|
+#define RG_PAN_ID_0 (0x22)
|
|
+#define SR_PAN_ID_0 0x22, 0xff, 0
|
|
+#define RG_PAN_ID_1 (0x23)
|
|
+#define SR_PAN_ID_1 0x23, 0xff, 0
|
|
+#define RG_IEEE_ADDR_0 (0x24)
|
|
+#define SR_IEEE_ADDR_0 0x24, 0xff, 0
|
|
+#define RG_IEEE_ADDR_1 (0x25)
|
|
+#define SR_IEEE_ADDR_1 0x25, 0xff, 0
|
|
+#define RG_IEEE_ADDR_2 (0x26)
|
|
+#define SR_IEEE_ADDR_2 0x26, 0xff, 0
|
|
+#define RG_IEEE_ADDR_3 (0x27)
|
|
+#define SR_IEEE_ADDR_3 0x27, 0xff, 0
|
|
+#define RG_IEEE_ADDR_4 (0x28)
|
|
+#define SR_IEEE_ADDR_4 0x28, 0xff, 0
|
|
+#define RG_IEEE_ADDR_5 (0x29)
|
|
+#define SR_IEEE_ADDR_5 0x29, 0xff, 0
|
|
+#define RG_IEEE_ADDR_6 (0x2a)
|
|
+#define SR_IEEE_ADDR_6 0x2a, 0xff, 0
|
|
+#define RG_IEEE_ADDR_7 (0x2b)
|
|
+#define SR_IEEE_ADDR_7 0x2b, 0xff, 0
|
|
+#define RG_XAH_CTRL_0 (0x2c)
|
|
+#define SR_SLOTTED_OPERATION 0x2c, 0x01, 0
|
|
+#define SR_MAX_CSMA_RETRIES 0x2c, 0x0e, 1
|
|
+#define SR_MAX_FRAME_RETRIES 0x2c, 0xf0, 4
|
|
+#define RG_CSMA_SEED_0 (0x2d)
|
|
+#define SR_CSMA_SEED_0 0x2d, 0xff, 0
|
|
+#define RG_CSMA_SEED_1 (0x2e)
|
|
+#define SR_CSMA_SEED_1 0x2e, 0x07, 0
|
|
+#define SR_AACK_I_AM_COORD 0x2e, 0x08, 3
|
|
+#define SR_AACK_DIS_ACK 0x2e, 0x10, 4
|
|
+#define SR_AACK_SET_PD 0x2e, 0x20, 5
|
|
+#define SR_AACK_FVN_MODE 0x2e, 0xc0, 6
|
|
+#define RG_CSMA_BE (0x2f)
|
|
+#define SR_MIN_BE 0x2f, 0x0f, 0
|
|
+#define SR_MAX_BE 0x2f, 0xf0, 4
|
|
+
|
|
+#define CMD_REG 0x80
|
|
+#define CMD_REG_MASK 0x3f
|
|
+#define CMD_WRITE 0x40
|
|
+#define CMD_FB 0x20
|
|
+
|
|
+#define IRQ_BAT_LOW (1 << 7)
|
|
+#define IRQ_TRX_UR (1 << 6)
|
|
+#define IRQ_AMI (1 << 5)
|
|
+#define IRQ_CCA_ED (1 << 4)
|
|
+#define IRQ_TRX_END (1 << 3)
|
|
+#define IRQ_RX_START (1 << 2)
|
|
+#define IRQ_PLL_UNL (1 << 1)
|
|
+#define IRQ_PLL_LOCK (1 << 0)
|
|
+
|
|
+#define STATE_P_ON 0x00 /* BUSY */
|
|
+#define STATE_BUSY_RX 0x01
|
|
+#define STATE_BUSY_TX 0x02
|
|
+#define STATE_FORCE_TRX_OFF 0x03
|
|
+#define STATE_FORCE_TX_ON 0x04 /* IDLE */
|
|
+/* 0x05 */ /* INVALID_PARAMETER */
|
|
+#define STATE_RX_ON 0x06
|
|
+/* 0x07 */ /* SUCCESS */
|
|
+#define STATE_TRX_OFF 0x08
|
|
+#define STATE_TX_ON 0x09
|
|
+/* 0x0a - 0x0e */ /* 0x0a - UNSUPPORTED_ATTRIBUTE */
|
|
+#define STATE_SLEEP 0x0F
|
|
+#define STATE_BUSY_RX_AACK 0x11
|
|
+#define STATE_BUSY_TX_ARET 0x12
|
|
+#define STATE_BUSY_RX_AACK_ON 0x16
|
|
+#define STATE_BUSY_TX_ARET_ON 0x19
|
|
+#define STATE_RX_ON_NOCLK 0x1C
|
|
+#define STATE_RX_AACK_ON_NOCLK 0x1D
|
|
+#define STATE_BUSY_RX_AACK_NOCLK 0x1E
|
|
+#define STATE_TRANSITION_IN_PROGRESS 0x1F
|
|
+
|
|
+#endif /* !ATR86F230_H */
|
|
diff --git a/drivers/ieee802154/cc2420.c b/drivers/ieee802154/cc2420.c
|
|
new file mode 100644
|
|
index 0000000..50761de
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/cc2420.c
|
|
@@ -0,0 +1,859 @@
|
|
+/*
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Author: Jonathan Cameron <jic23@cam.ac.uk>
|
|
+ *
|
|
+ * Modified 2010: xue liu <liuxuenetmail@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/cc2420.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/irq.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#define CC2420_WRITEREG(x) (x)
|
|
+#define CC2420_READREG(x) (0x40 | x)
|
|
+#define CC2420_RAMADDR(x) ((x & 0x7F) | 0x80)
|
|
+#define CC2420_RAMBANK(x) ((x >> 1) & 0xc0)
|
|
+#define CC2420_WRITERAM(x) (x)
|
|
+#define CC2420_READRAM(x) (0x20 | x)
|
|
+
|
|
+#define CC2420_FREQ_MASK 0x3FF
|
|
+#define CC2420_ADR_DECODE_MASK 0x0B00
|
|
+#define CC2420_FIFOP_THR_MASK 0x003F
|
|
+#define CC2420_CRC_MASK 0x80
|
|
+#define CC2420_RSSI_MASK 0x7F
|
|
+#define CC2420_FSMSTATE_MASK 0x2F
|
|
+
|
|
+#define CC2420_MANFIDLOW 0x233D
|
|
+#define CC2420_MANFIDHIGH 0x3000 /* my chip appears to version 3 - broaden this with testing */
|
|
+
|
|
+#define RSSI_OFFSET 45
|
|
+
|
|
+#define STATE_PDOWN 0
|
|
+#define STATE_IDLE 1
|
|
+#define STATE_RX_CALIBRATE 2
|
|
+#define STATE_RX_CALIBRATE2 40
|
|
+
|
|
+#define STATE_RX_SFD_SEARCH_MIN 3
|
|
+#define STATE_RX_SFD_SEARCH_MAX 6
|
|
+#define STATE_RX_FRAME 16
|
|
+#define STATE_RX_FRAME2 40
|
|
+#define STATE_RX_WAIT 14
|
|
+#define STATE_RX_OVERFLOW 17
|
|
+#define STATE_TX_ACK_CALIBRATE 48
|
|
+#define STATE_TX_ACK_PREAMBLE_MIN 49
|
|
+#define STATE_TX_ACK_PREAMBLE_MAX 51
|
|
+#define STATE_TX_ACK_MIN 52
|
|
+#define STATE_TX_ACK_MAX 54
|
|
+#define STATE_TX_CALIBRATE 32
|
|
+#define STATE_TX_PREAMBLE_MIN 34
|
|
+#define STATE_TX_PREAMBLE_MAX 36
|
|
+#define STATE_TX_FRAME_MIN 37
|
|
+#define STATE_TX_FRAME_MAX 39
|
|
+#define STATE_TX_UNDERFLOW 56
|
|
+
|
|
+struct cc2420_local {
|
|
+ struct cc2420_platform_data *pdata;
|
|
+ struct spi_device *spi;
|
|
+ struct ieee802154_dev *dev;
|
|
+ u8 *buf;
|
|
+ struct mutex bmux;
|
|
+ int fifop_irq;
|
|
+ int sfd_irq;
|
|
+ struct work_struct fifop_irqwork;
|
|
+ struct work_struct sfd_irqwork;
|
|
+ spinlock_t lock;
|
|
+ unsigned irq_disabled:1;/* P:lock */
|
|
+ unsigned is_tx:1; /* P:lock */
|
|
+
|
|
+ struct completion tx_complete;
|
|
+};
|
|
+static int cc2420_get_status(struct cc2420_local *lp, u8 *status)
|
|
+{
|
|
+ int ret;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 1,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_WRITEREG(CC2420_SNOP);
|
|
+ dev_vdbg(&lp->spi->dev, "get status command buf[0] = %02x\n", lp->buf[0]);
|
|
+ ret = spi_sync(lp->spi, &msg);
|
|
+ if (!ret)
|
|
+ *status = lp->buf[0];
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+static int cc2420_cmd_strobe(struct cc2420_local *lp,
|
|
+ u8 addr)
|
|
+{
|
|
+ int ret;
|
|
+ u8 status = 0xf;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 1,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_WRITEREG(addr);
|
|
+ dev_vdbg(&lp->spi->dev, "cmd strobe buf[0] = %02x\n", lp->buf[0]);
|
|
+ ret = spi_sync(lp->spi, &msg);
|
|
+ if (!ret)
|
|
+ status = lp->buf[0];
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]);
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cc2420_read_16_bit_reg(struct cc2420_local *lp,
|
|
+ u8 addr, u16 *data)
|
|
+{
|
|
+ int ret;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 3,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_READREG(addr);
|
|
+ dev_vdbg(&lp->spi->dev, "readreg addr buf[0] = %02x\n", lp->buf[0]);
|
|
+ ret = spi_sync(lp->spi, &msg);
|
|
+ dev_dbg(&lp->spi->dev, "status = %d\n", ret);
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ dev_dbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]);
|
|
+ if (!ret)
|
|
+ *data = ((u16) (lp->buf[1]) << 8) | lp->buf[2];
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cc2420_write_16_bit_reg_partial(struct cc2420_local *lp,
|
|
+ u8 addr, u16 data, u16 mask)
|
|
+{
|
|
+ int ret;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer = {
|
|
+ .len = 3,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ dev_dbg(&lp->spi->dev, "data = %x\n", data);
|
|
+ dev_dbg(&lp->spi->dev, "mask = %x\n", mask);
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer, &msg);
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_READREG(addr);
|
|
+ dev_vdbg(&lp->spi->dev, "read addr buf[0] = %02x\n", lp->buf[0]);
|
|
+ ret = spi_sync(lp->spi, &msg);
|
|
+ if (ret)
|
|
+ goto err_ret;
|
|
+ dev_dbg(&lp->spi->dev, "read buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]);
|
|
+
|
|
+ lp->buf[0] = CC2420_WRITEREG(addr);
|
|
+
|
|
+ lp->buf[1] &= ~(mask >> 8);
|
|
+ lp->buf[2] &= ~(mask & 0xFF);
|
|
+ lp->buf[1] |= (mask >> 8) & (data >> 8);
|
|
+ lp->buf[2] |= (mask & 0xFF) & (data & 0xFF);
|
|
+ dev_vdbg(&lp->spi->dev, "writereg addr buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]);
|
|
+ ret = spi_sync(lp->spi, &msg);
|
|
+ if (ret)
|
|
+ goto err_ret;
|
|
+ dev_dbg(&lp->spi->dev, "return status buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]);
|
|
+
|
|
+err_ret:
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cc2420_channel(struct ieee802154_dev *dev, int page, int channel)
|
|
+{
|
|
+ struct cc2420_local *lp = dev->priv;
|
|
+ int ret;
|
|
+
|
|
+ might_sleep();
|
|
+ dev_dbg(&lp->spi->dev, "trying to set channel\n");
|
|
+
|
|
+ BUG_ON(page != 0);
|
|
+ BUG_ON(channel < CC2420_MIN_CHANNEL);
|
|
+ BUG_ON(channel > CC2420_MAX_CHANNEL);
|
|
+
|
|
+ ret = cc2420_write_16_bit_reg_partial(lp, CC2420_FSCTRL, 357 + 5*(channel - 11), CC2420_FREQ_MASK);
|
|
+
|
|
+ dev->phy->current_channel = channel;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cc2420_write_ram(struct cc2420_local *lp, u16 addr, u8 len, u8 *data)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = len,
|
|
+ .tx_buf = data,
|
|
+ };
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_RAMADDR(addr);
|
|
+ lp->buf[1] = CC2420_WRITERAM(CC2420_RAMBANK(addr));
|
|
+ dev_dbg(&lp->spi->dev, "write ram addr buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "ram bank buf[1] = %02x\n", lp->buf[1]);
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_dbg(&lp->spi->dev, "spi status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_dbg(&lp->spi->dev, "cc2420 status = %02x\n", lp->buf[0]);
|
|
+ dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int cc2420_write_txfifo(struct cc2420_local *lp, u8 *data, u8 len)
|
|
+{
|
|
+ int status;
|
|
+ /* Length byte must include FCS even if calculated in hardware */
|
|
+ int len_byte = len + 2;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 1,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ struct spi_transfer xfer_len = {
|
|
+ .len = 1,
|
|
+ .tx_buf = &len_byte,
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = len,
|
|
+ .tx_buf = data,
|
|
+ };
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_WRITEREG(CC2420_TXFIFO);
|
|
+ dev_vdbg(&lp->spi->dev, "TX_FIFO addr buf[0] = %02x\n", lp->buf[0]);
|
|
+
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_len, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]);
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int
|
|
+cc2420_read_rxfifo(struct cc2420_local *lp, u8 *data, u8 *len, u8 *lqi)
|
|
+{
|
|
+ int status;
|
|
+ struct spi_message msg;
|
|
+ struct spi_transfer xfer_head = {
|
|
+ .len = 2,
|
|
+ .tx_buf = lp->buf,
|
|
+ .rx_buf = lp->buf,
|
|
+ };
|
|
+ struct spi_transfer xfer_buf = {
|
|
+ .len = *len,
|
|
+ .rx_buf = data,
|
|
+ };
|
|
+
|
|
+ mutex_lock(&lp->bmux);
|
|
+ lp->buf[0] = CC2420_READREG(CC2420_RXFIFO);
|
|
+ lp->buf[1] = 0x00;
|
|
+ dev_vdbg(&lp->spi->dev, "read rxfifo buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]);
|
|
+ spi_message_init(&msg);
|
|
+ spi_message_add_tail(&xfer_head, &msg);
|
|
+ spi_message_add_tail(&xfer_buf, &msg);
|
|
+
|
|
+ status = spi_sync(lp->spi, &msg);
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ if (msg.status)
|
|
+ status = msg.status;
|
|
+ dev_vdbg(&lp->spi->dev, "status = %d\n", status);
|
|
+ dev_vdbg(&lp->spi->dev, "return status buf[0] = %02x\n", lp->buf[0]);
|
|
+ dev_vdbg(&lp->spi->dev, "length buf[1] = %02x\n", lp->buf[1]);
|
|
+
|
|
+ *lqi = data[lp->buf[1] - 1] & 0x7f;
|
|
+ *len = lp->buf[1]; /* it should be less than 130 */
|
|
+
|
|
+ mutex_unlock(&lp->bmux);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+
|
|
+static int cc2420_tx(struct ieee802154_dev *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct cc2420_local *lp = dev->priv;
|
|
+ int rc;
|
|
+ unsigned long flags;
|
|
+ u8 status = 0;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ rc = cc2420_cmd_strobe(lp, CC2420_SFLUSHTX);
|
|
+ if (rc)
|
|
+ goto err_rx;
|
|
+ rc = cc2420_write_txfifo(lp, skb->data, skb->len);
|
|
+ if (rc)
|
|
+ goto err_rx;
|
|
+
|
|
+ /* TODO: test CCA pin */
|
|
+
|
|
+ rc = cc2420_get_status(lp, &status);
|
|
+ if (rc)
|
|
+ goto err_rx;
|
|
+
|
|
+ if (status & CC2420_STATUS_TX_UNDERFLOW) {
|
|
+ dev_err(&lp->spi->dev, "cc2420 tx underflow!\n");
|
|
+ goto err_rx;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ BUG_ON(lp->is_tx);
|
|
+ lp->is_tx = 1;
|
|
+ INIT_COMPLETION(lp->tx_complete);
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+
|
|
+ rc = cc2420_cmd_strobe(lp, CC2420_STXONCCA);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+
|
|
+ rc = wait_for_completion_interruptible(&lp->tx_complete);
|
|
+ if (rc < 0)
|
|
+ goto err;
|
|
+
|
|
+ cc2420_cmd_strobe(lp, CC2420_SFLUSHTX);
|
|
+ cc2420_cmd_strobe(lp, CC2420_SRXON);
|
|
+
|
|
+ return rc;
|
|
+
|
|
+err:
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ lp->is_tx = 0;
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+err_rx:
|
|
+ cc2420_cmd_strobe(lp, CC2420_SFLUSHTX);
|
|
+ cc2420_cmd_strobe(lp, CC2420_SRXON);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int cc2420_rx(struct cc2420_local *lp)
|
|
+{
|
|
+ u8 len = 128;
|
|
+ u8 lqi = 0; /* link quality */
|
|
+ int rc;
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ skb = alloc_skb(len, GFP_KERNEL);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ rc = cc2420_read_rxfifo(lp, skb_put(skb, len), &len, &lqi);
|
|
+ if (len < 2) {
|
|
+ kfree_skb(skb);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Clip last two bytes. When using hardware FCS they get replaced with
|
|
+ * correlation value, FCS flag and RSSI value */
|
|
+ skb_trim(skb, len-2);
|
|
+
|
|
+ ieee802154_rx_irqsafe(lp->dev, skb, lqi);
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "RXFIFO: %d %d %x\n", rc, len, lqi);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+cc2420_set_hw_addr_filt(struct ieee802154_dev *dev,
|
|
+ struct ieee802154_hw_addr_filt *filt,
|
|
+ unsigned long changed)
|
|
+{
|
|
+ struct cc2420_local *lp = dev->priv;
|
|
+ u16 reg;
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ if (changed & IEEE802515_IEEEADDR_CHANGED)
|
|
+ cc2420_write_ram(lp, CC2420_RAM_IEEEADR,
|
|
+ IEEE802154_ALEN,
|
|
+ filt->ieee_addr);
|
|
+
|
|
+ if (changed & IEEE802515_SADDR_CHANGED) {
|
|
+ u8 short_addr[2];
|
|
+ short_addr[0] = filt->short_addr & 0xff;/* LSB */
|
|
+ short_addr[1] = filt->short_addr >> 8; /* MSB */
|
|
+ cc2420_write_ram(lp, CC2420_RAM_SHORTADR,
|
|
+ sizeof(short_addr),
|
|
+ short_addr);
|
|
+ }
|
|
+
|
|
+ if (changed & IEEE802515_PANID_CHANGED) {
|
|
+ u8 panid[2];
|
|
+ panid[0] = filt->pan_id & 0xff; /* LSB */
|
|
+ panid[1] = filt->pan_id >> 8; /* MSB */
|
|
+ cc2420_write_ram(lp, CC2420_RAM_PANID,
|
|
+ sizeof(panid),
|
|
+ panid);
|
|
+ }
|
|
+
|
|
+ if (changed & IEEE802515_PANC_CHANGED) {
|
|
+ cc2420_read_16_bit_reg(lp, CC2420_MDMCTRL0, ®);
|
|
+ if (filt->pan_coord)
|
|
+ reg |= 1 << CC2420_MDMCTRL0_PANCRD;
|
|
+ else
|
|
+ reg &= ~(1 << CC2420_MDMCTRL0_PANCRD);
|
|
+ cc2420_write_16_bit_reg_partial(lp, CC2420_MDMCTRL0,
|
|
+ reg, 1 << CC2420_MDMCTRL0_PANCRD);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int cc2420_ed(struct ieee802154_dev *dev, u8 *level)
|
|
+{
|
|
+ struct cc2420_local *lp = dev->priv;
|
|
+ u16 rssi;
|
|
+ int ret;
|
|
+ dev_dbg(&lp->spi->dev, "ed called\n");
|
|
+
|
|
+ ret = cc2420_read_16_bit_reg(lp, CC2420_RSSI, &rssi);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* P = RSSI_VAL + RSSI_OFFSET[dBm] */
|
|
+ *level = (rssi & CC2420_RSSI_MASK) + RSSI_OFFSET;
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int cc2420_start(struct ieee802154_dev *dev)
|
|
+{
|
|
+ return cc2420_cmd_strobe(dev->priv, CC2420_SRXON);
|
|
+}
|
|
+
|
|
+static void cc2420_stop(struct ieee802154_dev *dev)
|
|
+{
|
|
+ cc2420_cmd_strobe(dev->priv, CC2420_SRFOFF);
|
|
+}
|
|
+
|
|
+static struct ieee802154_ops cc2420_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .xmit = cc2420_tx,
|
|
+ .ed = cc2420_ed,
|
|
+ .start = cc2420_start,
|
|
+ .stop = cc2420_stop,
|
|
+ .set_channel = cc2420_channel,
|
|
+ .set_hw_addr_filt = cc2420_set_hw_addr_filt,
|
|
+};
|
|
+
|
|
+static int cc2420_register(struct cc2420_local *lp)
|
|
+{
|
|
+ int ret = -ENOMEM;
|
|
+ lp->dev = ieee802154_alloc_device(sizeof(*lp), &cc2420_ops);
|
|
+ if (!lp->dev)
|
|
+ goto err_ret;
|
|
+
|
|
+ lp->dev->priv = lp;
|
|
+ lp->dev->parent = &lp->spi->dev;
|
|
+ //look this up.
|
|
+ lp->dev->extra_tx_headroom = 0;
|
|
+ //and this
|
|
+ //lp->dev->channel_mask = 0x7ff;
|
|
+ //and more.
|
|
+
|
|
+ /* We do support only 2.4 Ghz */
|
|
+ lp->dev->phy->channels_supported[0] = 0x7FFF800;
|
|
+ lp->dev->flags = IEEE802154_HW_OMIT_CKSUM;
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "registered cc2420\n");
|
|
+ ret = ieee802154_register_device(lp->dev);
|
|
+ if (ret)
|
|
+ goto err_free_device;
|
|
+
|
|
+ return 0;
|
|
+err_free_device:
|
|
+ ieee802154_free_device(lp->dev);
|
|
+err_ret:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void cc2420_unregister(struct cc2420_local *lp)
|
|
+{
|
|
+ ieee802154_unregister_device(lp->dev);
|
|
+ //check this is needed
|
|
+ ieee802154_free_device(lp->dev);
|
|
+}
|
|
+
|
|
+static irqreturn_t cc2420_isr(int irq, void *data)
|
|
+{
|
|
+ struct cc2420_local *lp = data;
|
|
+
|
|
+ spin_lock(&lp->lock);
|
|
+ if (!lp->irq_disabled) {
|
|
+ disable_irq_nosync(irq);
|
|
+ lp->irq_disabled = 1;
|
|
+ }
|
|
+ spin_unlock(&lp->lock);
|
|
+
|
|
+ if (irq == lp->sfd_irq)
|
|
+ schedule_work(&lp->sfd_irqwork);
|
|
+
|
|
+ if (irq == lp->fifop_irq)
|
|
+ schedule_work(&lp->fifop_irqwork);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void cc2420_fifop_irqwork(struct work_struct *work)
|
|
+{
|
|
+ struct cc2420_local *lp
|
|
+ = container_of(work, struct cc2420_local, fifop_irqwork);
|
|
+ unsigned long flags;
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "fifop interrupt received\n");
|
|
+
|
|
+ if (gpio_get_value(lp->pdata->fifo))
|
|
+ cc2420_rx(lp);
|
|
+ else
|
|
+ dev_err(&lp->spi->dev, "rxfifo overflow\n");
|
|
+
|
|
+ cc2420_cmd_strobe(lp, CC2420_SFLUSHRX);
|
|
+ cc2420_cmd_strobe(lp, CC2420_SFLUSHRX);
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ if (lp->irq_disabled) {
|
|
+ lp->irq_disabled = 0;
|
|
+ enable_irq(lp->fifop_irq);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+}
|
|
+
|
|
+static void cc2420_sfd_irqwork(struct work_struct *work)
|
|
+{
|
|
+ struct cc2420_local *lp
|
|
+ = container_of(work, struct cc2420_local, sfd_irqwork);
|
|
+ unsigned long flags;
|
|
+
|
|
+ dev_dbg(&lp->spi->dev, "sfd interrupt received\n");
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ if (lp->is_tx) {
|
|
+ lp->is_tx = 0;
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+ complete(&lp->tx_complete);
|
|
+ } else {
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&lp->lock, flags);
|
|
+ if (lp->irq_disabled) {
|
|
+ lp->irq_disabled = 0;
|
|
+ enable_irq(lp->sfd_irq);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&lp->lock, flags);
|
|
+}
|
|
+
|
|
+static int cc2420_hw_init(struct cc2420_local *lp)
|
|
+{
|
|
+ int ret;
|
|
+ u16 state;
|
|
+ u8 status = 0xff;
|
|
+ int timeout = 500; /* 500us delay */
|
|
+ ret = cc2420_read_16_bit_reg(lp, CC2420_FSMSTATE, &state);
|
|
+ if (ret)
|
|
+ goto error_ret;
|
|
+ /* reset has occured prior to this, so there should be no other option */
|
|
+ if (state != STATE_PDOWN) {
|
|
+ ret = -EINVAL;
|
|
+ goto error_ret;
|
|
+ }
|
|
+ ret = cc2420_cmd_strobe(lp, CC2420_SXOSCON);
|
|
+ if (ret)
|
|
+ goto error_ret;
|
|
+
|
|
+ do {
|
|
+ ret = cc2420_get_status(lp, &status);
|
|
+ if (ret)
|
|
+ goto error_ret;
|
|
+ if (timeout-- <= 0) {
|
|
+ dev_err(&lp->spi->dev, "oscillator start failed!\n");
|
|
+ return ret;
|
|
+ }
|
|
+ udelay(1);
|
|
+ } while (!(status & CC2420_STATUS_XOSC16M_STABLE));
|
|
+
|
|
+ dev_info(&lp->spi->dev, "oscillator succesfully brought up\n");
|
|
+
|
|
+ return 0;
|
|
+error_ret:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __devinit cc2420_probe(struct spi_device *spi)
|
|
+{
|
|
+ int ret;
|
|
+ u16 manidl, manidh;
|
|
+ struct cc2420_local *lp = kzalloc(sizeof *lp, GFP_KERNEL);
|
|
+ if (!lp) {
|
|
+ ret = -ENOMEM;
|
|
+ goto error_ret;
|
|
+ }
|
|
+
|
|
+ lp->pdata = spi->dev.platform_data;
|
|
+ if (!lp->pdata) {
|
|
+ dev_err(&spi->dev, "no platform data\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_free_local;
|
|
+ }
|
|
+ spi_set_drvdata(spi, lp);
|
|
+ mutex_init(&lp->bmux);
|
|
+ INIT_WORK(&lp->fifop_irqwork, cc2420_fifop_irqwork);
|
|
+ INIT_WORK(&lp->sfd_irqwork, cc2420_sfd_irqwork);
|
|
+ spin_lock_init(&lp->lock);
|
|
+ init_completion(&lp->tx_complete);
|
|
+
|
|
+ lp->spi = spi;
|
|
+ lp->buf = kmalloc(3*sizeof *lp->buf, GFP_KERNEL);
|
|
+ if (!lp->buf) {
|
|
+ ret = -ENOMEM;
|
|
+ goto err_free_local;
|
|
+ }
|
|
+
|
|
+ /* Request all the gpio's */
|
|
+ ret = gpio_request(lp->pdata->fifo, "fifo");
|
|
+ if (ret)
|
|
+ goto err_free_buf;
|
|
+ ret = gpio_request(lp->pdata->cca, "cca");
|
|
+ if (ret)
|
|
+ goto err_free_gpio_fifo;
|
|
+#if 0
|
|
+ /* This is causing problems as fifop is gpio 0 ? */
|
|
+ ret = gpio_request(lp->pdata->fifop, "fifop");
|
|
+ if (ret)
|
|
+ goto err_free_gpio_cca;
|
|
+#endif
|
|
+ ret = gpio_request(lp->pdata->sfd, "sfd");
|
|
+ if (ret)
|
|
+ goto err_free_gpio_fifop;
|
|
+ ret = gpio_request(lp->pdata->reset, "reset");
|
|
+ if (ret)
|
|
+ goto err_free_gpio_sfd;
|
|
+ ret = gpio_request(lp->pdata->vreg, "vreg");
|
|
+ if (ret)
|
|
+ goto err_free_gpio_reset;
|
|
+ /* Configure the gpios appropriately */
|
|
+
|
|
+ /* Enable the voltage regulator */
|
|
+ ret = gpio_direction_output(lp->pdata->vreg, 1);
|
|
+ if (ret)
|
|
+ goto err_free_gpio_reset;
|
|
+ udelay(600); /* Time for regulator to power up */
|
|
+ /* Toggle the reset */
|
|
+ ret = gpio_direction_output(lp->pdata->reset, 0);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+ udelay(10); /* no idea how long this should be? */
|
|
+ ret = gpio_direction_output(lp->pdata->reset, 1);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+ udelay(10);
|
|
+
|
|
+ ret = gpio_direction_input(lp->pdata->cca);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+ ret = gpio_direction_input(lp->pdata->fifo);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+ ret = gpio_direction_input(lp->pdata->fifop);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+ ret = gpio_direction_input(lp->pdata->sfd);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+
|
|
+
|
|
+ /* Check this is actually a cc2420 */
|
|
+ ret = cc2420_read_16_bit_reg(lp, CC2420_MANFIDL, &manidl);
|
|
+ if (ret)
|
|
+ goto err_free_gpio_vreg;
|
|
+ ret = cc2420_read_16_bit_reg(lp, CC2420_MANFIDH, &manidh);
|
|
+ if (ret)
|
|
+ goto err_free_gpio_vreg;
|
|
+ if (manidh != CC2420_MANFIDHIGH || manidl != CC2420_MANFIDLOW) {
|
|
+ dev_err(&spi->dev, "Incorrect manufacturer id %x%x\n", manidh, manidl);
|
|
+ ret = -ENODEV;
|
|
+ goto err_free_gpio_vreg;
|
|
+ }
|
|
+ /* TODO: make it more readable */
|
|
+ dev_info(&lp->spi->dev, "Found Chipcon CC2420\n");
|
|
+ dev_info(&lp->spi->dev, "Manufacturer ID:%x Version:%x Partnum:%x\n",
|
|
+ manidl & 0x0FFF, manidh >> 12, manidl >> 12);
|
|
+
|
|
+ ret = cc2420_hw_init(lp);
|
|
+ if (ret)
|
|
+ goto err_disable_vreg;
|
|
+
|
|
+ lp->fifop_irq = gpio_to_irq(lp->pdata->fifop);
|
|
+ lp->sfd_irq = gpio_to_irq(lp->pdata->sfd);
|
|
+
|
|
+ ret = request_irq(lp->fifop_irq,
|
|
+ cc2420_isr,
|
|
+ IRQF_TRIGGER_RISING | IRQF_SHARED,
|
|
+ dev_name(&spi->dev),
|
|
+ lp);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "could not get fifop irq?\n");
|
|
+ goto err_free_fifop_irq;
|
|
+ }
|
|
+
|
|
+ ret = request_irq(lp->sfd_irq,
|
|
+ cc2420_isr,
|
|
+ IRQF_TRIGGER_FALLING,
|
|
+ dev_name(&spi->dev),
|
|
+ lp);
|
|
+ if (ret) {
|
|
+ dev_err(&spi->dev, "could not get sfd irq?\n");
|
|
+ goto err_free_sfd_irq;
|
|
+ }
|
|
+
|
|
+ dev_info(&lp->spi->dev, "Set fifo threshold to 127\n");
|
|
+ cc2420_write_16_bit_reg_partial(lp, CC2420_IOCFG0, 127, CC2420_FIFOP_THR_MASK);
|
|
+ ret = cc2420_register(lp);
|
|
+ if (ret)
|
|
+ goto err_free_sfd_irq;
|
|
+
|
|
+ return 0;
|
|
+err_free_sfd_irq:
|
|
+ free_irq(lp->sfd_irq, lp);
|
|
+err_free_fifop_irq:
|
|
+ free_irq(lp->fifop_irq, lp);
|
|
+err_disable_vreg:
|
|
+ gpio_set_value(lp->pdata->vreg, 0);
|
|
+err_free_gpio_vreg:
|
|
+ gpio_free(lp->pdata->vreg);
|
|
+err_free_gpio_reset:
|
|
+ gpio_free(lp->pdata->reset);
|
|
+err_free_gpio_sfd:
|
|
+ gpio_free(lp->pdata->sfd);
|
|
+err_free_gpio_fifop:
|
|
+ gpio_free(lp->pdata->fifop);
|
|
+//err_free_gpio_cca:
|
|
+// gpio_free(lp->pdata->cca);
|
|
+err_free_gpio_fifo:
|
|
+ gpio_free(lp->pdata->fifo);
|
|
+err_free_buf:
|
|
+ kfree(lp->buf);
|
|
+err_free_local:
|
|
+ kfree(lp);
|
|
+error_ret:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __devexit cc2420_remove(struct spi_device *spi)
|
|
+{
|
|
+ struct cc2420_local *lp = spi_get_drvdata(spi);
|
|
+
|
|
+ cc2420_unregister(lp);
|
|
+ free_irq(lp->fifop_irq, lp);
|
|
+ free_irq(lp->sfd_irq, lp);
|
|
+ flush_work(&lp->fifop_irqwork);
|
|
+ flush_work(&lp->sfd_irqwork);
|
|
+ gpio_free(lp->pdata->vreg);
|
|
+ gpio_free(lp->pdata->reset);
|
|
+ gpio_free(lp->pdata->sfd);
|
|
+ gpio_free(lp->pdata->fifop);
|
|
+ gpio_free(lp->pdata->cca);
|
|
+ gpio_free(lp->pdata->fifo);
|
|
+ kfree(lp->buf);
|
|
+ kfree(lp);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct spi_driver cc2420_driver = {
|
|
+ .driver = {
|
|
+ .name = "cc2420",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .probe = cc2420_probe,
|
|
+ .remove = __devexit_p(cc2420_remove),
|
|
+};
|
|
+
|
|
+static int __init cc2420_init(void)
|
|
+{
|
|
+ return spi_register_driver(&cc2420_driver);
|
|
+}
|
|
+module_init(cc2420_init);
|
|
+
|
|
+static void __exit cc2420_exit(void)
|
|
+{
|
|
+ spi_unregister_driver(&cc2420_driver);
|
|
+}
|
|
+module_exit(cc2420_exit);
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/ieee802154/fakelb.c b/drivers/ieee802154/fakelb.c
|
|
new file mode 100644
|
|
index 0000000..ae6e53f
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/fakelb.c
|
|
@@ -0,0 +1,311 @@
|
|
+/*
|
|
+ * Loopback IEEE 802.15.4 interface
|
|
+ *
|
|
+ * Copyright 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+struct fake_dev_priv {
|
|
+ struct ieee802154_dev *dev;
|
|
+
|
|
+ struct list_head list;
|
|
+ struct fake_priv *fake;
|
|
+
|
|
+ unsigned int working:1;
|
|
+};
|
|
+
|
|
+struct fake_priv {
|
|
+ struct list_head list;
|
|
+ rwlock_t lock;
|
|
+};
|
|
+
|
|
+static int
|
|
+hw_ed(struct ieee802154_dev *dev, u8 *level)
|
|
+{
|
|
+ pr_debug("%s\n", __func__);
|
|
+ might_sleep();
|
|
+ BUG_ON(!level);
|
|
+ *level = 0xbe;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+hw_channel(struct ieee802154_dev *dev, int page, int channel)
|
|
+{
|
|
+ pr_debug("%s %d\n", __func__, channel);
|
|
+ might_sleep();
|
|
+ dev->phy->current_page = page;
|
|
+ dev->phy->current_channel = channel;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void
|
|
+hw_deliver(struct fake_dev_priv *priv, struct sk_buff *skb)
|
|
+{
|
|
+ struct sk_buff *newskb;
|
|
+
|
|
+ if (!priv->working)
|
|
+ return;
|
|
+
|
|
+ newskb = pskb_copy(skb, GFP_ATOMIC);
|
|
+
|
|
+ ieee802154_rx_irqsafe(priv->dev, newskb, 0xcc);
|
|
+}
|
|
+
|
|
+static int
|
|
+hw_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct fake_dev_priv *priv = dev->priv;
|
|
+ struct fake_priv *fake = priv->fake;
|
|
+
|
|
+ might_sleep();
|
|
+
|
|
+ read_lock_bh(&fake->lock);
|
|
+ if (priv->list.next == priv->list.prev) {
|
|
+ /* we are the only one device */
|
|
+ hw_deliver(priv, skb);
|
|
+ } else {
|
|
+ struct fake_dev_priv *dp;
|
|
+ list_for_each_entry(dp, &priv->fake->list, list)
|
|
+ if (dp != priv &&
|
|
+ dp->dev->phy->current_channel ==
|
|
+ priv->dev->phy->current_channel)
|
|
+ hw_deliver(dp, skb);
|
|
+ }
|
|
+ read_unlock_bh(&fake->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+hw_start(struct ieee802154_dev *dev) {
|
|
+ struct fake_dev_priv *priv = dev->priv;
|
|
+
|
|
+ if (priv->working)
|
|
+ return -EBUSY;
|
|
+
|
|
+ priv->working = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void
|
|
+hw_stop(struct ieee802154_dev *dev) {
|
|
+ struct fake_dev_priv *priv = dev->priv;
|
|
+
|
|
+ priv->working = 0;
|
|
+}
|
|
+
|
|
+static struct ieee802154_ops fake_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .xmit = hw_xmit,
|
|
+ .ed = hw_ed,
|
|
+ .set_channel = hw_channel,
|
|
+ .start = hw_start,
|
|
+ .stop = hw_stop,
|
|
+};
|
|
+
|
|
+static int ieee802154fake_add_priv(struct device *dev, struct fake_priv *fake)
|
|
+{
|
|
+ struct fake_dev_priv *priv;
|
|
+ int err = -ENOMEM;
|
|
+ struct ieee802154_dev *ieee;
|
|
+
|
|
+ ieee = ieee802154_alloc_device(sizeof(*priv), &fake_ops);
|
|
+ if (!dev)
|
|
+ goto err_alloc_dev;
|
|
+
|
|
+ priv = ieee->priv;
|
|
+ priv->dev = ieee;
|
|
+
|
|
+ /* 868 MHz BPSK 802.15.4-2003 */
|
|
+ ieee->phy->channels_supported[0] |= 1;
|
|
+ /* 915 MHz BPSK 802.15.4-2003 */
|
|
+ ieee->phy->channels_supported[0] |= 0x7fe;
|
|
+ /* 2.4 GHz O-QPSK 802.15.4-2003 */
|
|
+ ieee->phy->channels_supported[0] |= 0x7FFF800;
|
|
+ /* 868 MHz ASK 802.15.4-2006 */
|
|
+ ieee->phy->channels_supported[1] |= 1;
|
|
+ /* 915 MHz ASK 802.15.4-2006 */
|
|
+ ieee->phy->channels_supported[1] |= 0x7fe;
|
|
+ /* 868 MHz O-QPSK 802.15.4-2006 */
|
|
+ ieee->phy->channels_supported[2] |= 1;
|
|
+ /* 915 MHz O-QPSK 802.15.4-2006 */
|
|
+ ieee->phy->channels_supported[2] |= 0x7fe;
|
|
+ /* 2.4 GHz CSS 802.15.4a-2007 */
|
|
+ ieee->phy->channels_supported[3] |= 0x3fff;
|
|
+ /* UWB Sub-gigahertz 802.15.4a-2007 */
|
|
+ ieee->phy->channels_supported[4] |= 1;
|
|
+ /* UWB Low band 802.15.4a-2007 */
|
|
+ ieee->phy->channels_supported[4] |= 0x1e;
|
|
+ /* UWB High band 802.15.4a-2007 */
|
|
+ ieee->phy->channels_supported[4] |= 0xffe0;
|
|
+ /* 750 MHz O-QPSK 802.15.4c-2009 */
|
|
+ ieee->phy->channels_supported[5] |= 0xf;
|
|
+ /* 750 MHz MPSK 802.15.4c-2009 */
|
|
+ ieee->phy->channels_supported[5] |= 0xf0;
|
|
+ /* 950 MHz BPSK 802.15.4d-2009 */
|
|
+ ieee->phy->channels_supported[6] |= 0x3ff;
|
|
+ /* 950 MHz GFSK 802.15.4d-2009 */
|
|
+ ieee->phy->channels_supported[6] |= 0x3ffc00;
|
|
+
|
|
+
|
|
+ INIT_LIST_HEAD(&priv->list);
|
|
+ priv->fake = fake;
|
|
+
|
|
+ ieee->parent = dev;
|
|
+
|
|
+ err = ieee802154_register_device(ieee);
|
|
+ if (err)
|
|
+ goto err_reg;
|
|
+
|
|
+ write_lock_bh(&fake->lock);
|
|
+ list_add_tail(&priv->list, &fake->list);
|
|
+ write_unlock_bh(&fake->lock);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_reg:
|
|
+ ieee802154_free_device(priv->dev);
|
|
+err_alloc_dev:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void ieee802154fake_del_priv(struct fake_dev_priv *priv)
|
|
+{
|
|
+ write_lock_bh(&priv->fake->lock);
|
|
+ list_del(&priv->list);
|
|
+ write_unlock_bh(&priv->fake->lock);
|
|
+
|
|
+ ieee802154_unregister_device(priv->dev);
|
|
+ ieee802154_free_device(priv->dev);
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+adddev_store(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t n)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct fake_priv *priv = platform_get_drvdata(pdev);
|
|
+ int err;
|
|
+
|
|
+ err = ieee802154fake_add_priv(dev, priv);
|
|
+ if (err)
|
|
+ return err;
|
|
+ return n;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(adddev, 0200, NULL, adddev_store);
|
|
+
|
|
+static struct attribute *fake_attrs[] = {
|
|
+ &dev_attr_adddev.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static struct attribute_group fake_group = {
|
|
+ .name = NULL /* fake */,
|
|
+ .attrs = fake_attrs,
|
|
+};
|
|
+
|
|
+
|
|
+static int __devinit ieee802154fake_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct fake_priv *priv;
|
|
+ struct fake_dev_priv *dp;
|
|
+
|
|
+ int err = -ENOMEM;
|
|
+ priv = kzalloc(sizeof(struct fake_priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ goto err_alloc;
|
|
+
|
|
+ INIT_LIST_HEAD(&priv->list);
|
|
+ rwlock_init(&priv->lock);
|
|
+
|
|
+ err = sysfs_create_group(&pdev->dev.kobj, &fake_group);
|
|
+ if (err)
|
|
+ goto err_grp;
|
|
+
|
|
+ err = ieee802154fake_add_priv(&pdev->dev, priv);
|
|
+ if (err < 0)
|
|
+ goto err_slave;
|
|
+
|
|
+ platform_set_drvdata(pdev, priv);
|
|
+ dev_info(&pdev->dev, "Added ieee802154 hardware\n");
|
|
+ return 0;
|
|
+
|
|
+err_slave:
|
|
+ list_for_each_entry(dp, &priv->list, list)
|
|
+ ieee802154fake_del_priv(dp);
|
|
+ sysfs_remove_group(&pdev->dev.kobj, &fake_group);
|
|
+err_grp:
|
|
+ kfree(priv);
|
|
+err_alloc:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int __devexit ieee802154fake_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct fake_priv *priv = platform_get_drvdata(pdev);
|
|
+ struct fake_dev_priv *dp, *temp;
|
|
+
|
|
+ list_for_each_entry_safe(dp, temp, &priv->list, list)
|
|
+ ieee802154fake_del_priv(dp);
|
|
+ sysfs_remove_group(&pdev->dev.kobj, &fake_group);
|
|
+ kfree(priv);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_device *ieee802154fake_dev;
|
|
+
|
|
+static struct platform_driver ieee802154fake_driver = {
|
|
+ .probe = ieee802154fake_probe,
|
|
+ .remove = __devexit_p(ieee802154fake_remove),
|
|
+ .driver = {
|
|
+ .name = "ieee802154fakelb",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+};
|
|
+
|
|
+static __init int fake_init(void)
|
|
+{
|
|
+ ieee802154fake_dev = platform_device_register_simple(
|
|
+ "ieee802154fakelb", -1, NULL, 0);
|
|
+ return platform_driver_register(&ieee802154fake_driver);
|
|
+}
|
|
+
|
|
+static __exit void fake_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&ieee802154fake_driver);
|
|
+ platform_device_unregister(ieee802154fake_dev);
|
|
+}
|
|
+
|
|
+module_init(fake_init);
|
|
+module_exit(fake_exit);
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_AUTHOR("Dmitry Eremin-Solenikov, Sergey Lapin");
|
|
+
|
|
+
|
|
diff --git a/drivers/ieee802154/serial.c b/drivers/ieee802154/serial.c
|
|
new file mode 100644
|
|
index 0000000..6981d0e
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/serial.c
|
|
@@ -0,0 +1,1047 @@
|
|
+/*
|
|
+ * ZigBee TTY line discipline.
|
|
+ *
|
|
+ * Provides interface between ZigBee stack and IEEE 802.15.4 compatible
|
|
+ * firmware over serial line. Communication protocol is described below.
|
|
+ *
|
|
+ * Copyright (C) 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ * Maxim Osipov <maxim.osipov@siemens.com>
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/tty.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/sched.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+
|
|
+/* NOTE: be sure to use here the same values as in the firmware */
|
|
+#define START_BYTE1 'z'
|
|
+#define START_BYTE2 'b'
|
|
+#define MAX_DATA_SIZE 127
|
|
+
|
|
+#define IDLE_MODE 0x00
|
|
+#define RX_MODE 0x02
|
|
+#define TX_MODE 0x03
|
|
+#define FORCE_TRX_OFF 0xF0
|
|
+
|
|
+#define STATUS_SUCCESS 0
|
|
+#define STATUS_RX_ON 1
|
|
+#define STATUS_TX_ON 2
|
|
+#define STATUS_TRX_OFF 3
|
|
+#define STATUS_IDLE 4
|
|
+#define STATUS_BUSY 5
|
|
+#define STATUS_BUSY_RX 6
|
|
+#define STATUS_BUSY_TX 7
|
|
+#define STATUS_ERR 8
|
|
+
|
|
+#define STATUS_WAIT ((u8) -1) /* waiting for the answer */
|
|
+
|
|
+/* We re-use PPP ioctl for our purposes */
|
|
+#define PPPIOCGUNIT _IOR('t', 86, int) /* get ppp unit number */
|
|
+
|
|
+/*
|
|
+ * The following messages are used to control ZigBee firmware.
|
|
+ * All communication has request/response format,
|
|
+ * except of asynchronous incoming data stream (DATA_RECV_* messages).
|
|
+ */
|
|
+enum {
|
|
+ NO_ID = 0, /* means no pending id */
|
|
+
|
|
+ /* Driver to Firmware */
|
|
+ CMD_OPEN = 0x01, /* u8 id */
|
|
+ CMD_CLOSE = 0x02, /* u8 id */
|
|
+ CMD_SET_CHANNEL = 0x04, /* u8 id, u8 channel */
|
|
+ CMD_ED = 0x05, /* u8 id */
|
|
+ CMD_CCA = 0x06, /* u8 id */
|
|
+ CMD_SET_STATE = 0x07, /* u8 id, u8 flag */
|
|
+ DATA_XMIT_BLOCK = 0x09, /* u8 id, u8 len, u8 data[len] */
|
|
+ RESP_RECV_BLOCK = 0x0b, /* u8 id, u8 status */
|
|
+ CMD_ADDRESS = 0x0d, /* u8 id */
|
|
+
|
|
+ /* Firmware to Driver */
|
|
+ RESP_OPEN = 0x81, /* u8 id, u8 status */
|
|
+ RESP_CLOSE = 0x82, /* u8 id, u8 status */
|
|
+ RESP_SET_CHANNEL = 0x84, /* u8 id, u8 status */
|
|
+ RESP_ED = 0x85, /* u8 id, u8 status, u8 level */
|
|
+ RESP_CCA = 0x86, /* u8 id, u8 status */
|
|
+ RESP_SET_STATE = 0x87, /* u8 id, u8 status */
|
|
+ RESP_XMIT_BLOCK = 0x89, /* u8 id, u8 status */
|
|
+ DATA_RECV_BLOCK = 0x8b, /* u8 id, u8 lq, u8 len, u8 data[len] */
|
|
+ RESP_ADDRESS = 0x8d, /* u8 id, u8 status, u8 u8 u8 u8 u8 u8 u8 u8 address */
|
|
+};
|
|
+
|
|
+enum {
|
|
+ STATE_WAIT_START1,
|
|
+ STATE_WAIT_START2,
|
|
+ STATE_WAIT_COMMAND,
|
|
+ STATE_WAIT_PARAM1,
|
|
+ STATE_WAIT_PARAM2,
|
|
+ STATE_WAIT_DATA
|
|
+};
|
|
+
|
|
+struct zb_device {
|
|
+ /* Relative devices */
|
|
+ struct tty_struct *tty;
|
|
+ struct ieee802154_dev *dev;
|
|
+
|
|
+ /* locks the ldisc for the command */
|
|
+ struct mutex mutex;
|
|
+
|
|
+ /* command completition */
|
|
+ wait_queue_head_t wq;
|
|
+ u8 status;
|
|
+ u8 ed;
|
|
+
|
|
+ /* Internal state */
|
|
+ struct completion open_done;
|
|
+ unsigned char opened;
|
|
+ u8 pending_id;
|
|
+ unsigned int pending_size;
|
|
+ u8 *pending_data;
|
|
+ /* FIXME: WE NEED LOCKING!!! */
|
|
+
|
|
+ /* Command (rx) processing */
|
|
+ int state;
|
|
+ unsigned char id;
|
|
+ unsigned char param1;
|
|
+ unsigned char param2;
|
|
+ unsigned char index;
|
|
+ unsigned char data[MAX_DATA_SIZE];
|
|
+};
|
|
+
|
|
+/*****************************************************************************
|
|
+ * ZigBee serial device protocol handling
|
|
+ *****************************************************************************/
|
|
+static int _open_dev(struct zb_device *zbdev);
|
|
+
|
|
+static int
|
|
+_send_pending_data(struct zb_device *zbdev)
|
|
+{
|
|
+ struct tty_struct *tty;
|
|
+
|
|
+ BUG_ON(!zbdev);
|
|
+ tty = zbdev->tty;
|
|
+ if (!tty)
|
|
+ return -ENODEV;
|
|
+
|
|
+ zbdev->status = STATUS_WAIT;
|
|
+
|
|
+ /* Debug info */
|
|
+ printk(KERN_INFO "%s, %d bytes\n", __func__,
|
|
+ zbdev->pending_size);
|
|
+#ifdef DEBUG
|
|
+ print_hex_dump_bytes("send_pending_data ", DUMP_PREFIX_NONE,
|
|
+ zbdev->pending_data, zbdev->pending_size);
|
|
+#endif
|
|
+
|
|
+ if (tty->driver->ops->write(tty, zbdev->pending_data,
|
|
+ zbdev->pending_size) != zbdev->pending_size) {
|
|
+ printk(KERN_ERR "%s: device write failed\n", __func__);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+send_cmd(struct zb_device *zbdev, u8 id)
|
|
+{
|
|
+ u8 len = 0;
|
|
+ /* 4 because of 2 start bytes, id and optional extra */
|
|
+ u8 buf[4];
|
|
+
|
|
+ /* Check arguments */
|
|
+ BUG_ON(!zbdev);
|
|
+
|
|
+ if (!zbdev->opened) {
|
|
+ if (!_open_dev(zbdev))
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ pr_debug("%s(): id = %u\n", __func__, id);
|
|
+ if (zbdev->pending_size) {
|
|
+ printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
|
|
+ __func__, zbdev->pending_id);
|
|
+ BUG();
|
|
+ }
|
|
+
|
|
+ /* Prepare a message */
|
|
+ buf[len++] = START_BYTE1;
|
|
+ buf[len++] = START_BYTE2;
|
|
+ buf[len++] = id;
|
|
+
|
|
+ zbdev->pending_id = id;
|
|
+ zbdev->pending_size = len;
|
|
+ zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
|
|
+ if (!zbdev->pending_data) {
|
|
+ printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
|
|
+ zbdev->pending_id = 0;
|
|
+ zbdev->pending_size = 0;
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ memcpy(zbdev->pending_data, buf, len);
|
|
+
|
|
+ return _send_pending_data(zbdev);
|
|
+}
|
|
+
|
|
+static int
|
|
+send_cmd2(struct zb_device *zbdev, u8 id, u8 extra)
|
|
+{
|
|
+ u8 len = 0;
|
|
+ /* 4 because of 2 start bytes, id and optional extra */
|
|
+ u8 buf[4];
|
|
+
|
|
+ /* Check arguments */
|
|
+ BUG_ON(!zbdev);
|
|
+
|
|
+ if (!zbdev->opened) {
|
|
+ if (!_open_dev(zbdev))
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ pr_debug("%s(): id = %u\n", __func__, id);
|
|
+ if (zbdev->pending_size) {
|
|
+ printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
|
|
+ __func__, zbdev->pending_id);
|
|
+ BUG();
|
|
+ }
|
|
+
|
|
+ /* Prepare a message */
|
|
+ buf[len++] = START_BYTE1;
|
|
+ buf[len++] = START_BYTE2;
|
|
+ buf[len++] = id;
|
|
+ buf[len++] = extra;
|
|
+
|
|
+ zbdev->pending_id = id;
|
|
+ zbdev->pending_size = len;
|
|
+ zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
|
|
+ if (!zbdev->pending_data) {
|
|
+ printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
|
|
+ zbdev->pending_id = 0;
|
|
+ zbdev->pending_size = 0;
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ memcpy(zbdev->pending_data, buf, len);
|
|
+
|
|
+ return _send_pending_data(zbdev);
|
|
+}
|
|
+
|
|
+static int
|
|
+send_block(struct zb_device *zbdev, u8 len, u8 *data)
|
|
+{
|
|
+ u8 i = 0, buf[4]; /* 4 because of 2 start bytes, id and len */
|
|
+
|
|
+ /* Check arguments */
|
|
+ BUG_ON(!zbdev);
|
|
+
|
|
+ if (!zbdev->opened) {
|
|
+ if (!_open_dev(zbdev))
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+
|
|
+ pr_debug("%s(): id = %u\n", __func__, DATA_XMIT_BLOCK);
|
|
+ if (zbdev->pending_size) {
|
|
+ printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
|
|
+ __func__, zbdev->pending_id);
|
|
+ BUG();
|
|
+ }
|
|
+
|
|
+ /* Prepare a message */
|
|
+ buf[i++] = START_BYTE1;
|
|
+ buf[i++] = START_BYTE2;
|
|
+ buf[i++] = DATA_XMIT_BLOCK;
|
|
+ buf[i++] = len;
|
|
+
|
|
+ zbdev->pending_id = DATA_XMIT_BLOCK;
|
|
+ zbdev->pending_size = i + len;
|
|
+ zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
|
|
+ if (!zbdev->pending_data) {
|
|
+ printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
|
|
+ zbdev->pending_id = 0;
|
|
+ zbdev->pending_size = 0;
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ memcpy(zbdev->pending_data, buf, i);
|
|
+ memcpy(zbdev->pending_data + i, data, len);
|
|
+
|
|
+ return _send_pending_data(zbdev);
|
|
+}
|
|
+
|
|
+static void
|
|
+cleanup(struct zb_device *zbdev)
|
|
+{
|
|
+ zbdev->state = STATE_WAIT_START1;
|
|
+ zbdev->id = 0;
|
|
+ zbdev->param1 = 0;
|
|
+ zbdev->param2 = 0;
|
|
+ zbdev->index = 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+is_command(unsigned char c)
|
|
+{
|
|
+ switch (c) {
|
|
+ /* ids we can get here: */
|
|
+ case RESP_OPEN:
|
|
+ case RESP_CLOSE:
|
|
+ case RESP_SET_CHANNEL:
|
|
+ case RESP_ED:
|
|
+ case RESP_CCA:
|
|
+ case RESP_SET_STATE:
|
|
+ case RESP_XMIT_BLOCK:
|
|
+ case DATA_RECV_BLOCK:
|
|
+ case RESP_ADDRESS:
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+_match_pending_id(struct zb_device *zbdev)
|
|
+{
|
|
+ return ((CMD_OPEN == zbdev->pending_id &&
|
|
+ RESP_OPEN == zbdev->id) ||
|
|
+ (CMD_CLOSE == zbdev->pending_id &&
|
|
+ RESP_CLOSE == zbdev->id) ||
|
|
+ (CMD_SET_CHANNEL == zbdev->pending_id &&
|
|
+ RESP_SET_CHANNEL == zbdev->id) ||
|
|
+ (CMD_ED == zbdev->pending_id &&
|
|
+ RESP_ED == zbdev->id) ||
|
|
+ (CMD_CCA == zbdev->pending_id &&
|
|
+ RESP_CCA == zbdev->id) ||
|
|
+ (CMD_SET_STATE == zbdev->pending_id &&
|
|
+ RESP_SET_STATE == zbdev->id) ||
|
|
+ (DATA_XMIT_BLOCK == zbdev->pending_id &&
|
|
+ RESP_XMIT_BLOCK == zbdev->id) ||
|
|
+ (DATA_RECV_BLOCK == zbdev->id) ||
|
|
+ (CMD_ADDRESS == zbdev->pending_id &&
|
|
+ RESP_ADDRESS == zbdev->id));
|
|
+}
|
|
+
|
|
+static void serial_net_rx(struct zb_device *zbdev)
|
|
+{
|
|
+ /* zbdev->param1 is LQI
|
|
+ * zbdev->param2 is length of data
|
|
+ * zbdev->data is data itself
|
|
+ */
|
|
+ struct sk_buff *skb;
|
|
+ skb = alloc_skb(zbdev->param2, GFP_ATOMIC);
|
|
+ skb_put(skb, zbdev->param2);
|
|
+ skb_copy_to_linear_data(skb, zbdev->data, zbdev->param2);
|
|
+ ieee802154_rx_irqsafe(zbdev->dev, skb, zbdev->param1);
|
|
+}
|
|
+
|
|
+static void
|
|
+process_command(struct zb_device *zbdev)
|
|
+{
|
|
+ /* Command processing */
|
|
+ if (!_match_pending_id(zbdev))
|
|
+ return;
|
|
+
|
|
+ if (RESP_OPEN == zbdev->id && STATUS_SUCCESS == zbdev->param1) {
|
|
+ zbdev->opened = 1;
|
|
+ pr_debug("Opened device\n");
|
|
+ complete(&zbdev->open_done);
|
|
+ /* Input is not processed during output, so
|
|
+ * using completion is not possible during output.
|
|
+ * so we need to handle open as any other command
|
|
+ * and hope for best
|
|
+ */
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!zbdev->opened)
|
|
+ return;
|
|
+
|
|
+ zbdev->pending_id = 0;
|
|
+ kfree(zbdev->pending_data);
|
|
+ zbdev->pending_data = NULL;
|
|
+ zbdev->pending_size = 0;
|
|
+ if (zbdev->id != DATA_RECV_BLOCK) {
|
|
+ /* XXX: w/around for old FW, REMOVE */
|
|
+ if (zbdev->param1 == STATUS_IDLE)
|
|
+ zbdev->status = STATUS_SUCCESS;
|
|
+ else
|
|
+ zbdev->status = zbdev->param1;
|
|
+ }
|
|
+
|
|
+ switch (zbdev->id) {
|
|
+ case RESP_ED:
|
|
+ zbdev->ed = zbdev->param2;
|
|
+ break;
|
|
+ case DATA_RECV_BLOCK:
|
|
+ pr_debug("Received block, lqi %02x, len %02x\n",
|
|
+ zbdev->param1, zbdev->param2);
|
|
+ /* zbdev->param1 is LQ, zbdev->param2 is length */
|
|
+ serial_net_rx(zbdev);
|
|
+ break;
|
|
+ case RESP_ADDRESS:
|
|
+ pr_debug("Received address, %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
+ zbdev->data[0], zbdev->data[1], zbdev->data[2], zbdev->data[3],
|
|
+ zbdev->data[4], zbdev->data[5], zbdev->data[6], zbdev->data[7]);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ wake_up(&zbdev->wq);
|
|
+}
|
|
+
|
|
+static void
|
|
+process_char(struct zb_device *zbdev, unsigned char c)
|
|
+{
|
|
+ /* Data processing */
|
|
+ switch (zbdev->state) {
|
|
+ case STATE_WAIT_START1:
|
|
+ if (START_BYTE1 == c)
|
|
+ zbdev->state = STATE_WAIT_START2;
|
|
+ break;
|
|
+
|
|
+ case STATE_WAIT_START2:
|
|
+ if (START_BYTE2 == c)
|
|
+ zbdev->state = STATE_WAIT_COMMAND;
|
|
+ else
|
|
+ cleanup(zbdev);
|
|
+ break;
|
|
+
|
|
+ case STATE_WAIT_COMMAND:
|
|
+ if (is_command(c)) {
|
|
+ zbdev->id = c;
|
|
+ zbdev->state = STATE_WAIT_PARAM1;
|
|
+ } else {
|
|
+ cleanup(zbdev);
|
|
+ printk(KERN_ERR "%s, unexpected command id: %x\n",
|
|
+ __func__, c);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case STATE_WAIT_PARAM1:
|
|
+ zbdev->param1 = c;
|
|
+ if ((RESP_ED == zbdev->id) || (DATA_RECV_BLOCK == zbdev->id))
|
|
+ zbdev->state = STATE_WAIT_PARAM2;
|
|
+ else if (RESP_ADDRESS == zbdev->id) {
|
|
+ zbdev->param2 = 8;
|
|
+ zbdev->state = STATE_WAIT_DATA;
|
|
+ } else {
|
|
+ process_command(zbdev);
|
|
+ cleanup(zbdev);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case STATE_WAIT_PARAM2:
|
|
+ zbdev->param2 = c;
|
|
+ if (RESP_ED == zbdev->id) {
|
|
+ process_command(zbdev);
|
|
+ cleanup(zbdev);
|
|
+ } else if (DATA_RECV_BLOCK == zbdev->id)
|
|
+ zbdev->state = STATE_WAIT_DATA;
|
|
+ else
|
|
+ cleanup(zbdev);
|
|
+ break;
|
|
+
|
|
+ case STATE_WAIT_DATA:
|
|
+ if (zbdev->index < sizeof(zbdev->data)) {
|
|
+ zbdev->data[zbdev->index] = c;
|
|
+ zbdev->index++;
|
|
+ /*
|
|
+ * Pending data is received,
|
|
+ * param2 is length for DATA_RECV_BLOCK
|
|
+ */
|
|
+ if (zbdev->index == zbdev->param2) {
|
|
+ process_command(zbdev);
|
|
+ cleanup(zbdev);
|
|
+ }
|
|
+ } else {
|
|
+ printk(KERN_ERR "%s(): data size is greater "
|
|
+ "than buffer available\n", __func__);
|
|
+ cleanup(zbdev);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ cleanup(zbdev);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*****************************************************************************
|
|
+ * Device operations for IEEE 802.15.4 PHY side interface ZigBee stack
|
|
+ *****************************************************************************/
|
|
+
|
|
+static int _open_dev(struct zb_device *zbdev)
|
|
+{
|
|
+ int retries;
|
|
+ u8 len = 0;
|
|
+ /* 4 because of 2 start bytes, id and optional extra */
|
|
+ u8 buf[4];
|
|
+
|
|
+ /* Check arguments */
|
|
+ BUG_ON(!zbdev);
|
|
+ if (zbdev->opened)
|
|
+ return 1;
|
|
+
|
|
+ pr_debug("%s()\n", __func__);
|
|
+ if (zbdev->pending_size) {
|
|
+ printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
|
|
+ __func__, zbdev->pending_id);
|
|
+ BUG();
|
|
+ }
|
|
+
|
|
+ /* Prepare a message */
|
|
+ buf[len++] = START_BYTE1;
|
|
+ buf[len++] = START_BYTE2;
|
|
+ buf[len++] = CMD_OPEN;
|
|
+
|
|
+ zbdev->pending_id = CMD_OPEN;
|
|
+ zbdev->pending_size = len;
|
|
+ zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
|
|
+ if (!zbdev->pending_data) {
|
|
+ printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
|
|
+ zbdev->pending_id = 0;
|
|
+ zbdev->pending_size = 0;
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ memcpy(zbdev->pending_data, buf, len);
|
|
+
|
|
+ retries = 5;
|
|
+ while (!zbdev->opened && retries) {
|
|
+ if (_send_pending_data(zbdev) != 0)
|
|
+ return 0;
|
|
+
|
|
+ /* 3 second before retransmission */
|
|
+ wait_for_completion_interruptible_timeout(
|
|
+ &zbdev->open_done, msecs_to_jiffies(1000));
|
|
+ --retries;
|
|
+ }
|
|
+
|
|
+ zbdev->pending_id = 0;
|
|
+ kfree(zbdev->pending_data);
|
|
+ zbdev->pending_data = NULL;
|
|
+ zbdev->pending_size = 0;
|
|
+
|
|
+ if (zbdev->opened) {
|
|
+ printk(KERN_INFO "Opened connection to device\n");
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Valid channels: 1-16 */
|
|
+static int
|
|
+ieee802154_serial_set_channel(struct ieee802154_dev *dev, int page, int channel)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int ret = 0;
|
|
+
|
|
+ pr_debug("%s %d\n", __func__, channel);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ BUG_ON(page != 0);
|
|
+ /* Our channels are actually from 11 to 26
|
|
+ * We have IEEE802.15.4 channel no from 0 to 26.
|
|
+ * channels 0-10 are not valid for us */
|
|
+ BUG_ON(channel < 11 || channel > 26);
|
|
+ /* ... but our crappy firmware numbers channels from 1 to 16
|
|
+ * which is a mystery. We suould enforce that using PIB API
|
|
+ * but additional checking here won't kill, and gcc will
|
|
+ * optimize this stuff anyway. */
|
|
+ BUG_ON((channel - 10) < 1 && (channel - 10) > 16);
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return -EINTR;
|
|
+ ret = send_cmd2(zbdev, CMD_SET_CHANNEL, channel - 10);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS)
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ ret = -EINTR;
|
|
+
|
|
+ if (!ret)
|
|
+ zbdev->dev->phy->current_channel = channel;
|
|
+out:
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int
|
|
+ieee802154_serial_ed(struct ieee802154_dev *dev, u8 *level)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int ret = 0;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return -EINTR;
|
|
+
|
|
+ ret = send_cmd(zbdev, CMD_ED);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ *level = zbdev->ed;
|
|
+ if (zbdev->status != STATUS_SUCCESS)
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ ret = -ETIMEDOUT;
|
|
+out:
|
|
+
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int
|
|
+ieee802154_serial_address(struct ieee802154_dev *dev, u8 addr[IEEE802154_ALEN])
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int ret = 0;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return -EINTR;
|
|
+
|
|
+ ret = send_cmd(zbdev, CMD_ADDRESS);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ memcpy(addr, zbdev->data, sizeof addr);
|
|
+ if (zbdev->status != STATUS_SUCCESS)
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ ret = -ETIMEDOUT;
|
|
+out:
|
|
+
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int
|
|
+ieee802154_serial_start(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int ret = 0;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return -EINTR;
|
|
+
|
|
+ ret = send_cmd2(zbdev, CMD_SET_STATE, RX_MODE);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS)
|
|
+ ret = -EBUSY;
|
|
+ } else
|
|
+ ret = -ETIMEDOUT;
|
|
+out:
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void
|
|
+ieee802154_serial_stop(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return;
|
|
+
|
|
+
|
|
+ if (send_cmd2(zbdev, CMD_SET_STATE, FORCE_TRX_OFF) != 0)
|
|
+ goto out;
|
|
+
|
|
+ wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000));
|
|
+out:
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+}
|
|
+
|
|
+static int
|
|
+ieee802154_serial_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int ret;
|
|
+
|
|
+ pr_debug("%s\n", __func__);
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s: wrong phy\n", __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mutex_lock_interruptible(&zbdev->mutex))
|
|
+ return -EINTR;
|
|
+
|
|
+ ret = send_cmd(zbdev, CMD_CCA);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS) {
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = send_cmd2(zbdev, CMD_SET_STATE, TX_MODE);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS) {
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = send_block(zbdev, skb->len, skb->data);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS) {
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ ret = send_cmd2(zbdev, CMD_SET_STATE, RX_MODE);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ if (wait_event_interruptible_timeout(zbdev->wq,
|
|
+ zbdev->status != STATUS_WAIT,
|
|
+ msecs_to_jiffies(1000)) > 0) {
|
|
+ if (zbdev->status != STATUS_SUCCESS) {
|
|
+ ret = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ } else {
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+out:
|
|
+
|
|
+ mutex_unlock(&zbdev->mutex);
|
|
+ pr_debug("%s end\n", __func__);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*****************************************************************************
|
|
+ * Line discipline interface for IEEE 802.15.4 serial device
|
|
+ *****************************************************************************/
|
|
+
|
|
+static struct ieee802154_ops serial_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .xmit = ieee802154_serial_xmit,
|
|
+ .ed = ieee802154_serial_ed,
|
|
+ .set_channel = ieee802154_serial_set_channel,
|
|
+ .start = ieee802154_serial_start,
|
|
+ .stop = ieee802154_serial_stop,
|
|
+ .ieee_addr = ieee802154_serial_address,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Called when a tty is put into ZB line discipline. Called in process context.
|
|
+ * Returns 0 on success.
|
|
+ */
|
|
+static int
|
|
+ieee802154_tty_open(struct tty_struct *tty)
|
|
+{
|
|
+ struct zb_device *zbdev = tty->disc_data;
|
|
+ struct ieee802154_dev *dev;
|
|
+ int err;
|
|
+
|
|
+ pr_debug("Openning ldisc\n");
|
|
+ if (!capable(CAP_NET_ADMIN))
|
|
+ return -EPERM;
|
|
+
|
|
+ if (tty->disc_data)
|
|
+ return -EBUSY;
|
|
+
|
|
+ dev = ieee802154_alloc_device(sizeof(*zbdev), &serial_ops);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ zbdev = dev->priv;
|
|
+ zbdev->dev = dev;
|
|
+
|
|
+ mutex_init(&zbdev->mutex);
|
|
+ init_completion(&zbdev->open_done);
|
|
+ init_waitqueue_head(&zbdev->wq);
|
|
+
|
|
+ dev->extra_tx_headroom = 0;
|
|
+ /* only 2.4 GHz band */
|
|
+ dev->phy->channels_supported[0] = 0x7fff800;
|
|
+
|
|
+ dev->flags = IEEE802154_HW_OMIT_CKSUM;
|
|
+
|
|
+ dev->parent = tty->dev;
|
|
+
|
|
+ zbdev->tty = tty_kref_get(tty);
|
|
+
|
|
+ cleanup(zbdev);
|
|
+
|
|
+ tty->disc_data = zbdev;
|
|
+ tty->receive_room = MAX_DATA_SIZE;
|
|
+
|
|
+ /* FIXME: why is this needed. Note don't use ldisc_ref here as the
|
|
+ open path is before the ldisc is referencable */
|
|
+
|
|
+ if (tty->ldisc->ops->flush_buffer)
|
|
+ tty->ldisc->ops->flush_buffer(tty);
|
|
+ tty_driver_flush_buffer(tty);
|
|
+
|
|
+ err = ieee802154_register_device(dev);
|
|
+ if (err) {
|
|
+ printk(KERN_ERR "%s: device register failed\n", __func__);
|
|
+ goto out_free;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ ieee802154_unregister_device(zbdev->dev);
|
|
+
|
|
+out_free:
|
|
+ tty->disc_data = NULL;
|
|
+ tty_kref_put(tty);
|
|
+ zbdev->tty = NULL;
|
|
+
|
|
+ ieee802154_free_device(zbdev->dev);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Called when the tty is put into another line discipline or it hangs up. We
|
|
+ * have to wait for any cpu currently executing in any of the other zb_tty_*
|
|
+ * routines to finish before we can call zb_tty_close and free the
|
|
+ * zb_serial_dev struct. This routine must be called from process context, not
|
|
+ * interrupt or softirq context.
|
|
+ */
|
|
+static void
|
|
+ieee802154_tty_close(struct tty_struct *tty)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+
|
|
+ zbdev = tty->disc_data;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_WARNING "%s: match is not found\n", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ tty->disc_data = NULL;
|
|
+ tty_kref_put(tty);
|
|
+ zbdev->tty = NULL;
|
|
+
|
|
+ ieee802154_unregister_device(zbdev->dev);
|
|
+
|
|
+ tty_ldisc_flush(tty);
|
|
+ tty_driver_flush_buffer(tty);
|
|
+
|
|
+ ieee802154_free_device(zbdev->dev);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Called on tty hangup in process context.
|
|
+ */
|
|
+static int
|
|
+ieee802154_tty_hangup(struct tty_struct *tty)
|
|
+{
|
|
+ ieee802154_tty_close(tty);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Called in process context only. May be re-entered
|
|
+ * by multiple ioctl calling threads.
|
|
+ */
|
|
+static int
|
|
+ieee802154_tty_ioctl(struct tty_struct *tty, struct file *file,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+
|
|
+ pr_debug("cmd = 0x%x\n", cmd);
|
|
+
|
|
+ zbdev = tty->disc_data;
|
|
+ if (NULL == zbdev) {
|
|
+ pr_debug("match is not found\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ switch (cmd) {
|
|
+ case TCFLSH:
|
|
+ return tty_perform_flush(tty, arg);
|
|
+ default:
|
|
+ /* Try the mode commands */
|
|
+ return tty_mode_ioctl(tty, file, cmd, arg);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * This can now be called from hard interrupt level as well
|
|
+ * as soft interrupt level or mainline.
|
|
+ */
|
|
+static void
|
|
+ieee802154_tty_receive(struct tty_struct *tty, const unsigned char *buf,
|
|
+ char *cflags, int count)
|
|
+{
|
|
+ struct zb_device *zbdev;
|
|
+ int i;
|
|
+
|
|
+ /* Debug info */
|
|
+ printk(KERN_INFO "%s, received %d bytes\n", __func__,
|
|
+ count);
|
|
+#ifdef DEBUG
|
|
+ print_hex_dump_bytes("ieee802154_tty_receive ", DUMP_PREFIX_NONE,
|
|
+ buf, count);
|
|
+#endif
|
|
+
|
|
+ /* Actual processing */
|
|
+ zbdev = tty->disc_data;
|
|
+ if (NULL == zbdev) {
|
|
+ printk(KERN_ERR "%s(): record for tty is not found\n",
|
|
+ __func__);
|
|
+ return;
|
|
+ }
|
|
+ for (i = 0; i < count; ++i)
|
|
+ process_char(zbdev, buf[i]);
|
|
+#if 0
|
|
+ if (tty->driver->flush_chars)
|
|
+ tty->driver->flush_chars(tty);
|
|
+#endif
|
|
+ tty_unthrottle(tty);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Line discipline device structure
|
|
+ */
|
|
+static struct tty_ldisc_ops ieee802154_ldisc = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .magic = TTY_LDISC_MAGIC,
|
|
+ .name = "ieee802154-ldisc",
|
|
+ .open = ieee802154_tty_open,
|
|
+ .close = ieee802154_tty_close,
|
|
+ .hangup = ieee802154_tty_hangup,
|
|
+ .receive_buf = ieee802154_tty_receive,
|
|
+ .ioctl = ieee802154_tty_ioctl,
|
|
+};
|
|
+
|
|
+/*****************************************************************************
|
|
+ * Module service routinues
|
|
+ *****************************************************************************/
|
|
+
|
|
+static int __init ieee802154_serial_init(void)
|
|
+{
|
|
+ printk(KERN_INFO "Initializing ZigBee TTY interface\n");
|
|
+
|
|
+ if (tty_register_ldisc(N_IEEE802154, &ieee802154_ldisc) != 0) {
|
|
+ printk(KERN_ERR "%s: line discipline register failed\n",
|
|
+ __func__);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit ieee802154_serial_cleanup(void)
|
|
+{
|
|
+ if (tty_unregister_ldisc(N_IEEE802154) != 0)
|
|
+ printk(KERN_CRIT
|
|
+ "failed to unregister ZigBee line discipline.\n");
|
|
+}
|
|
+
|
|
+module_init(ieee802154_serial_init);
|
|
+module_exit(ieee802154_serial_cleanup);
|
|
+
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS_LDISC(N_IEEE802154);
|
|
+
|
|
diff --git a/drivers/ieee802154/spi_atben.c b/drivers/ieee802154/spi_atben.c
|
|
new file mode 100644
|
|
index 0000000..431bfe0
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/spi_atben.c
|
|
@@ -0,0 +1,421 @@
|
|
+/*
|
|
+ * spi_atben.c - SPI host look-alike for ATBEN
|
|
+ *
|
|
+ * Written 2011 by Werner Almesberger
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/at86rf230.h>
|
|
+#include <asm/mach-jz4740/base.h>
|
|
+
|
|
+#include "at86rf230.h"
|
|
+
|
|
+
|
|
+enum {
|
|
+ VDD_OFF = 1 << 2, /* VDD disable, PD02 */
|
|
+ MOSI = 1 << 8, /* CMD, PD08 */
|
|
+ SLP_TR = 1 << 9, /* CLK, PD09 */
|
|
+ MISO = 1 << 10, /* DAT0, PD10 */
|
|
+ SCLK = 1 << 11, /* DAT1, PD11 */
|
|
+ IRQ = 1 << 12, /* DAT2, PD12 */
|
|
+ nSEL = 1 << 13, /* DAT3/CD, PD13 */
|
|
+};
|
|
+
|
|
+#define PDPIN (prv->regs)
|
|
+#define PDDATS (prv->regs+0x14)
|
|
+#define PDDATC (prv->regs+0x18)
|
|
+
|
|
+
|
|
+struct atben_prv {
|
|
+ struct device *dev;
|
|
+ void __iomem *regs;
|
|
+ struct resource *ioarea;
|
|
+ struct at86rf230_platform_data
|
|
+ platform_data;
|
|
+ /* copy platform_data so that we can adapt .reset_data */
|
|
+};
|
|
+
|
|
+
|
|
+/* ----- ATBEN reset ------------------------------------------------------- */
|
|
+
|
|
+
|
|
+static void atben_reset(void *reset_data)
|
|
+{
|
|
+ struct atben_prv *prv = reset_data;
|
|
+ const int charge = nSEL | MOSI | SLP_TR | SCLK;
|
|
+ const int discharge = charge | IRQ | MISO;
|
|
+
|
|
+ dev_info(prv->dev, "atben_reset\n");
|
|
+ jz_gpio_port_set_value(JZ_GPIO_PORTD(0), 1 << 2, 1 << 2);
|
|
+ jz_gpio_port_direction_output(JZ_GPIO_PORTD(0), discharge);
|
|
+ jz_gpio_port_set_value(JZ_GPIO_PORTD(0), 0, discharge);
|
|
+ msleep(100); /* let power drop */
|
|
+
|
|
+ /*
|
|
+ * Hack: PD12/DAT2/IRQ is an active-high interrupt input, which is
|
|
+ * indicated by setting its direction bit to 1. We thus must not
|
|
+ * configure it as an "input".
|
|
+ */
|
|
+ jz_gpio_port_direction_input(JZ_GPIO_PORTD(0), MISO);
|
|
+ jz_gpio_port_set_value(JZ_GPIO_PORTD(0), charge, charge);
|
|
+ msleep(10); /* precharge caps */
|
|
+
|
|
+ jz_gpio_port_set_value(JZ_GPIO_PORTD(0), 0, VDD_OFF | SLP_TR | SCLK);
|
|
+ msleep(10);
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- SPI transfers ----------------------------------------------------- */
|
|
+
|
|
+
|
|
+static void rx_only(const struct atben_prv *prv, uint8_t *buf, int len)
|
|
+{
|
|
+ uint8_t v;
|
|
+
|
|
+ while (len--) {
|
|
+ writel(SCLK, PDDATS);
|
|
+ v = readl(PDPIN) & MISO ? 0x80 : 0;
|
|
+ writel(SCLK, PDDATC);
|
|
+
|
|
+ #define DO_BIT(m) \
|
|
+ writel(SCLK, PDDATS); \
|
|
+ if (readl(PDPIN) & MISO) \
|
|
+ v |= (m); \
|
|
+ writel(SCLK, PDDATC)
|
|
+
|
|
+ DO_BIT(0x40);
|
|
+ DO_BIT(0x20);
|
|
+ DO_BIT(0x10);
|
|
+ DO_BIT(0x08);
|
|
+ DO_BIT(0x04);
|
|
+ DO_BIT(0x02);
|
|
+ DO_BIT(0x01);
|
|
+
|
|
+ #undef DO_BIT
|
|
+
|
|
+ *buf++ = v;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void tx_only(const struct atben_prv *prv, const uint8_t *buf, int len)
|
|
+{
|
|
+ uint8_t tv;
|
|
+
|
|
+ while (len--) {
|
|
+ tv = *buf++;
|
|
+
|
|
+ if (tv & 0x80) {
|
|
+ writel(MOSI, PDDATS);
|
|
+ goto b6_1;
|
|
+ } else {
|
|
+ writel(MOSI, PDDATC);
|
|
+ goto b6_0;
|
|
+ }
|
|
+
|
|
+ #define DO_BIT(m, this, next) \
|
|
+ this##_1: \
|
|
+ writel(SCLK, PDDATS); \
|
|
+ if (tv & (m)) { \
|
|
+ writel(SCLK, PDDATC); \
|
|
+ goto next##_1; \
|
|
+ } else { \
|
|
+ writel(MOSI | SCLK, PDDATC); \
|
|
+ goto next##_0; \
|
|
+ } \
|
|
+ this##_0: \
|
|
+ writel(SCLK, PDDATS); \
|
|
+ writel(SCLK, PDDATC); \
|
|
+ if (tv & (m)) { \
|
|
+ writel(MOSI, PDDATS); \
|
|
+ goto next##_1; \
|
|
+ } else { \
|
|
+ goto next##_0; \
|
|
+ }
|
|
+
|
|
+ DO_BIT(0x40, b6, b5);
|
|
+ DO_BIT(0x20, b5, b4);
|
|
+ DO_BIT(0x10, b4, b3);
|
|
+ DO_BIT(0x08, b3, b2);
|
|
+ DO_BIT(0x04, b2, b1);
|
|
+ DO_BIT(0x02, b1, b0);
|
|
+ DO_BIT(0x01, b0, done);
|
|
+
|
|
+ #undef DO_BIT
|
|
+
|
|
+done_1:
|
|
+done_0:
|
|
+ writel(SCLK, PDDATS);
|
|
+ writel(SCLK, PDDATC);
|
|
+ writel(SCLK, PDDATC); /* delay to meet t5 timing */
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void bidir(const struct atben_prv *prv, const uint8_t *tx, uint8_t *rx,
|
|
+ int len)
|
|
+{
|
|
+ uint8_t mask, tv, rv;
|
|
+
|
|
+ while (len--) {
|
|
+ tv = *tx++;
|
|
+ for (mask = 0x80; mask; mask >>= 1) {
|
|
+ if (tv & mask)
|
|
+ writel(MOSI, PDDATS);
|
|
+ else
|
|
+ writel(MOSI, PDDATC);
|
|
+ writel(SCLK, PDDATS);
|
|
+ if (readl(PDPIN) & MISO)
|
|
+ rv |= mask;
|
|
+ writel(SCLK, PDDATC);
|
|
+ }
|
|
+ *rx++ = rv;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static int atben_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
+{
|
|
+ struct atben_prv *prv = spi_master_get_devdata(spi->master);
|
|
+ struct spi_transfer *xfer;
|
|
+ const uint8_t *tx;
|
|
+ uint8_t *rx;
|
|
+
|
|
+ if (unlikely(list_empty(&msg->transfers))) {
|
|
+ dev_err(prv->dev, "transfer is empty\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ msg->actual_length = 0;
|
|
+
|
|
+ writel(nSEL, PDDATC);
|
|
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
+ tx = xfer->tx_buf;
|
|
+ rx = xfer->rx_buf;
|
|
+ msg->actual_length += xfer->len;
|
|
+
|
|
+ if (!tx)
|
|
+ rx_only(prv, rx, xfer->len);
|
|
+ else if (!rx)
|
|
+ tx_only(prv, tx, xfer->len);
|
|
+ else
|
|
+ bidir(prv, tx, rx, xfer->len);
|
|
+ }
|
|
+ writel(nSEL, PDDATS);
|
|
+
|
|
+ msg->status = 0;
|
|
+ msg->complete(msg->context);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int atben_setup(struct spi_device *spi)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- SPI master creation/removal --------------------------------------- */
|
|
+
|
|
+
|
|
+const static struct at86rf230_platform_data at86rf230_platform_data = {
|
|
+ .rstn = -1,
|
|
+ .slp_tr = JZ_GPIO_PORTD(9),
|
|
+ .dig2 = -1,
|
|
+ .reset = atben_reset,
|
|
+ /* set .reset_data later */
|
|
+};
|
|
+
|
|
+static int __devinit atben_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct spi_board_info board_info = {
|
|
+ .modalias = "at86rf230",
|
|
+ /* set .irq later */
|
|
+ .chip_select = 0,
|
|
+ .bus_num = -1,
|
|
+ .max_speed_hz = 8 * 1000 * 1000,
|
|
+ };
|
|
+
|
|
+ struct spi_master *master;
|
|
+ struct atben_prv *prv;
|
|
+ struct resource *regs;
|
|
+ struct spi_device *spi;
|
|
+ int err = -ENXIO;
|
|
+
|
|
+ master = spi_alloc_master(&pdev->dev, sizeof(*prv));
|
|
+ if (!master)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ prv = spi_master_get_devdata(master);
|
|
+ prv->dev = &pdev->dev;
|
|
+ platform_set_drvdata(pdev, spi_master_get(master));
|
|
+
|
|
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
|
+ master->bus_num = pdev->id;
|
|
+ master->num_chipselect = 1;
|
|
+ master->setup = atben_setup;
|
|
+ master->transfer = atben_transfer;
|
|
+
|
|
+ dev_dbg(prv->dev, "Setting up ATBEN SPI\n");
|
|
+
|
|
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!regs) {
|
|
+ dev_err(prv->dev, "no IORESOURCE_MEM\n");
|
|
+ err = -ENOENT;
|
|
+ goto out_master;
|
|
+ }
|
|
+ prv->ioarea = request_mem_region(regs->start, resource_size(regs),
|
|
+ pdev->name);
|
|
+ if (!prv->ioarea) {
|
|
+ dev_err(prv->dev, "can't request ioarea\n");
|
|
+ goto out_master;
|
|
+ }
|
|
+
|
|
+ prv->regs = ioremap(regs->start, resource_size(regs));
|
|
+ if (!prv->regs) {
|
|
+ dev_err(prv->dev, "can't ioremap\n");
|
|
+ goto out_ioarea;
|
|
+ }
|
|
+
|
|
+ board_info.irq = platform_get_irq(pdev, 0);
|
|
+ if (board_info.irq < 0) {
|
|
+ dev_err(prv->dev, "can't get GPIO irq\n");
|
|
+ err = -ENOENT;
|
|
+ goto out_regs;
|
|
+ }
|
|
+
|
|
+ err = spi_register_master(master);
|
|
+ if (err) {
|
|
+ dev_err(prv->dev, "can't register master\n");
|
|
+ goto out_regs;
|
|
+ }
|
|
+
|
|
+ prv->platform_data = at86rf230_platform_data;
|
|
+ prv->platform_data.reset_data = prv;
|
|
+ board_info.platform_data = &prv->platform_data;
|
|
+
|
|
+ spi = spi_new_device(master, &board_info);
|
|
+ if (!spi) {
|
|
+ dev_err(&pdev->dev, "can't create new device for %s\n",
|
|
+ board_info.modalias);
|
|
+ err = -ENXIO;
|
|
+ goto out_registered;
|
|
+ }
|
|
+
|
|
+ dev_info(&spi->dev, "ATBEN ready for mischief (IRQ %d)\n",
|
|
+ board_info.irq);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+out_registered:
|
|
+ spi_unregister_master(master);
|
|
+
|
|
+out_regs:
|
|
+ iounmap(prv->regs);
|
|
+
|
|
+out_ioarea:
|
|
+ release_resource(prv->ioarea);
|
|
+ kfree(prv->ioarea);
|
|
+
|
|
+out_master:
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ spi_master_put(master);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int __devexit atben_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct spi_master *master = platform_get_drvdata(pdev);
|
|
+ struct atben_prv *prv = spi_master_get_devdata(master);
|
|
+
|
|
+// restore GPIOs
|
|
+
|
|
+ spi_unregister_master(master);
|
|
+
|
|
+ iounmap(prv->regs);
|
|
+
|
|
+ release_resource(prv->ioarea);
|
|
+ kfree(prv->ioarea);
|
|
+
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ spi_master_put(master);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver atben_driver = {
|
|
+ .driver = {
|
|
+ .name = "spi_atben",
|
|
+ .owner = THIS_MODULE,
|
|
+ },
|
|
+ .remove = __devexit_p(atben_remove),
|
|
+};
|
|
+
|
|
+static struct resource atben_resources[] = {
|
|
+ {
|
|
+ .start = JZ4740_GPIO_BASE_ADDR+0x300,
|
|
+ .end = JZ4740_GPIO_BASE_ADDR+0x3ff,
|
|
+ .flags = IORESOURCE_MEM,
|
|
+ },
|
|
+ {
|
|
+ /* set start and end later */
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct platform_device atben_device = {
|
|
+ .name = "spi_atben",
|
|
+ .id = -1,
|
|
+ .num_resources = ARRAY_SIZE(atben_resources),
|
|
+ .resource = atben_resources,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Registering the platform device just to probe it immediately afterwards
|
|
+ * seems a little circuitous. Need to see if there's a better way.
|
|
+ *
|
|
+ * What we actually should do is this:
|
|
+ * - in module init, register the device
|
|
+ * - maybe probe as well, but keep the device also if the probe fails
|
|
+ * (due to a conflicting driver already occupying the 8:10 slot)
|
|
+ * - have a means for user space to kick off driver probing, e.g., when
|
|
+ * anything about the 8:10 slot changes
|
|
+ */
|
|
+
|
|
+static int __init atben_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = platform_device_register(&atben_device);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ atben_resources[1].start = atben_resources[1].end =
|
|
+ gpio_to_irq(JZ_GPIO_PORTD(12));
|
|
+
|
|
+ return platform_driver_probe(&atben_driver, atben_probe);
|
|
+}
|
|
+
|
|
+static void __exit atben_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&atben_driver);
|
|
+ platform_device_unregister(&atben_device);
|
|
+}
|
|
+
|
|
+module_init(atben_init);
|
|
+module_exit(atben_exit);
|
|
+
|
|
+
|
|
+MODULE_DESCRIPTION("ATBEN SPI Controller Driver");
|
|
+MODULE_AUTHOR("Werner Almesberger <werner@almesberger.net>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/ieee802154/spi_atusb.c b/drivers/ieee802154/spi_atusb.c
|
|
new file mode 100644
|
|
index 0000000..b16f5be
|
|
--- /dev/null
|
|
+++ b/drivers/ieee802154/spi_atusb.c
|
|
@@ -0,0 +1,751 @@
|
|
+/*
|
|
+ * spi_atusb - SPI host look-alike for ATUSB
|
|
+ *
|
|
+ * Copyright (c) 2011 Richard Sharpe <realrichardsharpe@gmail.com>
|
|
+ * Copyright (c) 2011 Stefan Schmidt <stefan@datenfreihafen.org>
|
|
+ * Copyright (c) 2011 Werner Almesberger <werner@almesberger.net>
|
|
+ *
|
|
+ * 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, version 2
|
|
+ *
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * - implement more robust interrupt synchronization
|
|
+ * - check URB killing in atusb_disconnect for races
|
|
+ * - switch from bulk to interrupt endpoint
|
|
+ * - implement buffer read without extra copy
|
|
+ * - harmonize indentation style
|
|
+ * - mv atusb.c ../ieee802.15.4/spi_atusb.c, or maybe atrf_atusb.c or such
|
|
+ * - check module load/unload
|
|
+ * - review dev_* severity levels
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/jiffies.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/spi/spi.h>
|
|
+#include <linux/spi/at86rf230.h>
|
|
+
|
|
+#include "at86rf230.h"
|
|
+
|
|
+
|
|
+#define SYNC_TIMEOUT_MS 50 /* assume interrupt has been synced after
|
|
+ waiting this long */
|
|
+
|
|
+#define VENDOR_ID 0x20b7
|
|
+#define PRODUCT_ID 0x1540
|
|
+
|
|
+/* The devices we work with */
|
|
+static const struct usb_device_id atusb_device_table[] = {
|
|
+ { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(usb, atusb_device_table);
|
|
+
|
|
+#define ATUSB_BUILD_SIZE 256
|
|
+struct atusb_local {
|
|
+ struct usb_device * udev;
|
|
+ /* The interface to the RF part info, if applicable */
|
|
+ uint8_t ep0_atusb_major;
|
|
+ uint8_t ep0_atusb_minor;
|
|
+ uint8_t atusb_hw_type;
|
|
+ struct spi_master *master;
|
|
+ int slave_irq;
|
|
+ struct urb *irq_urb;
|
|
+ uint8_t irq_buf; /* receive irq serial here*/
|
|
+ uint8_t irq_seen; /* last irq serial from bulk */
|
|
+ uint8_t irq_sync; /* last irq serial from WRITE2_SYNC */
|
|
+ struct tasklet_struct task; /* interrupt delivery tasklet */
|
|
+ struct timer_list timer; /* delay, for interrupt synch */
|
|
+ struct at86rf230_platform_data platform_data;
|
|
+ /* copy platform_data so that we can adapt .reset_data */
|
|
+ struct spi_device *spi;
|
|
+// unsigned char buffer[3];
|
|
+ unsigned char buffer[260]; /* XXL, just in case */
|
|
+ struct spi_message *msg;
|
|
+};
|
|
+
|
|
+/* Commands to our device. Make sure this is synced with the firmware */
|
|
+enum atspi_requests {
|
|
+ ATUSB_ID = 0x00, /* system status/control grp */
|
|
+ ATUSB_BUILD,
|
|
+ ATUSB_RESET,
|
|
+ ATUSB_RF_RESET = 0x10, /* debug/test group */
|
|
+ ATUSB_POLL_INT,
|
|
+ ATUSB_TEST, /* atusb-sil only */
|
|
+ ATUSB_TIMER,
|
|
+ ATUSB_GPIO,
|
|
+ ATUSB_SLP_TR,
|
|
+ ATUSB_GPIO_CLEANUP,
|
|
+ ATUSB_REG_WRITE = 0x20, /* transceiver group */
|
|
+ ATUSB_REG_READ,
|
|
+ ATUSB_BUF_WRITE,
|
|
+ ATUSB_BUF_READ,
|
|
+ ATUSB_SRAM_WRITE,
|
|
+ ATUSB_SRAM_READ,
|
|
+ ATUSB_SPI_WRITE = 0x30, /* SPI group */
|
|
+ ATUSB_SPI_READ1,
|
|
+ ATUSB_SPI_READ2,
|
|
+ ATUSB_SPI_WRITE2_SYNC,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Direction bRequest wValue wIndex wLength
|
|
+ *
|
|
+ * ->host ATUSB_ID - - 3
|
|
+ * ->host ATUSB_BUILD - - #bytes
|
|
+ * host-> ATUSB_RESET - - 0
|
|
+ *
|
|
+ * host-> ATUSB_RF_RESET - - 0
|
|
+ * ->host ATUSB_POLL_INT - - 1
|
|
+ * host-> ATUSB_TEST - - 0
|
|
+ * ->host ATUSB_TIMER - - #bytes (6)
|
|
+ * ->host ATUSB_GPIO dir+data mask+p# 3
|
|
+ * host-> ATUSB_SLP_TR - - 0
|
|
+ * host-> ATUSB_GPIO_CLEANUP - - 0
|
|
+ *
|
|
+ * host-> ATUSB_REG_WRITE value addr 0
|
|
+ * ->host ATUSB_REG_READ - addr 1
|
|
+ * host-> ATUSB_BUF_WRITE - - #bytes
|
|
+ * ->host ATUSB_BUF_READ - - #bytes
|
|
+ * host-> ATUSB_SRAM_WRITE - addr #bytes
|
|
+ * ->host ATUSB_SRAM_READ - addr #bytes
|
|
+ *
|
|
+ * host-> ATUSB_SPI_WRITE byte0 byte1 #bytes
|
|
+ * ->host ATUSB_SPI_READ1 byte0 - #bytes
|
|
+ * ->host ATUSB_SPI_READ2 byte0 byte1 #bytes
|
|
+ * ->host ATUSB_SPI_WRITE2_SYNC byte0 byte1 0/1
|
|
+ */
|
|
+
|
|
+#define ATUSB_FROM_DEV (USB_TYPE_VENDOR | USB_DIR_IN)
|
|
+#define ATUSB_TO_DEV (USB_TYPE_VENDOR | USB_DIR_OUT)
|
|
+
|
|
+
|
|
+/* ----- Control transfers ------------------------------------------------- */
|
|
+
|
|
+
|
|
+static int atusb_async_errchk(struct urb *urb)
|
|
+{
|
|
+ struct atusb_local *atusb = urb->context;
|
|
+ struct spi_message *msg = atusb->msg;
|
|
+ struct usb_device *dev = atusb->udev;
|
|
+
|
|
+ if (!urb->status) {
|
|
+ dev_dbg(&dev->dev, "atusb_async_errchk OK len %d\n",
|
|
+ urb->actual_length);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
|
|
+ urb->status != -ESHUTDOWN)
|
|
+ dev_info(&dev->dev, "atusb_async_errchk FAIL error %d\n",
|
|
+ urb->status);
|
|
+
|
|
+ msg->actual_length = 0;
|
|
+
|
|
+ return urb->status;
|
|
+}
|
|
+
|
|
+static void atusb_async_finish(struct urb *urb)
|
|
+{
|
|
+ struct atusb_local *atusb = urb->context;
|
|
+ struct spi_message *msg = atusb->msg;
|
|
+
|
|
+ msg->status = urb->status;
|
|
+ msg->complete(msg->context);
|
|
+
|
|
+ kfree(urb->setup_packet);
|
|
+ usb_free_urb(urb);
|
|
+}
|
|
+
|
|
+static void atusb_ctrl_cb(struct urb *urb)
|
|
+{
|
|
+ atusb_async_errchk(urb);
|
|
+ atusb_async_finish(urb);
|
|
+}
|
|
+
|
|
+static void atusb_timer(unsigned long data)
|
|
+{
|
|
+ struct urb *urb = (void *) data;
|
|
+
|
|
+ dev_warn(&urb->dev->dev, "atusb_timer\n");
|
|
+ atusb_async_finish(urb);
|
|
+}
|
|
+
|
|
+static void atusb_ctrl_cb_sync(struct urb *urb)
|
|
+{
|
|
+ struct atusb_local *atusb = urb->context;
|
|
+
|
|
+ /* @@@ needs locking/atomic */
|
|
+ if (atusb_async_errchk(urb) || atusb->irq_sync == atusb->irq_seen) {
|
|
+ atusb_async_finish(urb);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ BUG_ON(timer_pending(&atusb->timer));
|
|
+ atusb->timer.expires = jiffies+msecs_to_jiffies(SYNC_TIMEOUT_MS);
|
|
+ atusb->timer.data = (unsigned long) urb;
|
|
+ add_timer(&atusb->timer);
|
|
+}
|
|
+
|
|
+static void atusb_read_fb_cb(struct urb *urb)
|
|
+{
|
|
+ struct atusb_local *atusb = urb->context;
|
|
+ struct spi_message *msg = atusb->msg;
|
|
+ const struct spi_transfer *xfer;
|
|
+ uint8_t *rx;
|
|
+
|
|
+ if (!atusb_async_errchk(urb)) {
|
|
+ BUG_ON(!urb->actual_length);
|
|
+
|
|
+ xfer = list_first_entry(&msg->transfers, struct spi_transfer,
|
|
+ transfer_list);
|
|
+ rx = xfer->rx_buf;
|
|
+ rx[1] = atusb->buffer[0];
|
|
+
|
|
+ xfer = list_entry(xfer->transfer_list.next,
|
|
+ struct spi_transfer, transfer_list);
|
|
+ memcpy(xfer->rx_buf, atusb->buffer+1, urb->actual_length-1);
|
|
+ }
|
|
+
|
|
+ atusb_async_finish(urb);
|
|
+}
|
|
+
|
|
+static int submit_control_msg(struct atusb_local *atusb,
|
|
+ __u8 request, __u8 requesttype, __u16 value, __u16 index,
|
|
+ void *data, __u16 size, usb_complete_t complete_fn, void *context)
|
|
+{
|
|
+ struct usb_device *dev = atusb->udev;
|
|
+ struct usb_ctrlrequest *req;
|
|
+ struct urb *urb;
|
|
+ int retval = -ENOMEM;
|
|
+
|
|
+ req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
|
|
+ if (!req)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ req->bRequest = request;
|
|
+ req->bRequestType = requesttype;
|
|
+ req->wValue = cpu_to_le16(value);
|
|
+ req->wIndex = cpu_to_le16(index);
|
|
+ req->wLength = cpu_to_le16(size);
|
|
+
|
|
+ urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!urb)
|
|
+ goto out_nourb;
|
|
+
|
|
+ usb_fill_control_urb(urb, dev,
|
|
+ requesttype == ATUSB_FROM_DEV ?
|
|
+ usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0),
|
|
+ (unsigned char *) req, data, size, complete_fn, context);
|
|
+
|
|
+ retval = usb_submit_urb(urb, GFP_KERNEL);
|
|
+ if (!retval)
|
|
+ return 0;
|
|
+ dev_warn(&dev->dev, "failed submitting read urb, error %d",
|
|
+ retval);
|
|
+ retval = retval == -ENOMEM ? retval : -EIO;
|
|
+
|
|
+ usb_free_urb(urb);
|
|
+out_nourb:
|
|
+ kfree(req);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- SPI transfers ----------------------------------------------------- */
|
|
+
|
|
+
|
|
+static int atusb_read1(struct atusb_local *atusb,
|
|
+ uint8_t tx, uint8_t *rx, int len)
|
|
+{
|
|
+ dev_dbg(&atusb->udev->dev, "atusb_read1: tx = 0x%x\n", tx);
|
|
+ return submit_control_msg(atusb,
|
|
+ ATUSB_SPI_READ1, ATUSB_FROM_DEV, tx, 0,
|
|
+ rx, 1, atusb_ctrl_cb, atusb);
|
|
+}
|
|
+
|
|
+static int atusb_read_fb(struct atusb_local *atusb,
|
|
+ uint8_t tx, uint8_t *rx0, uint8_t *rx, int len)
|
|
+{
|
|
+ dev_dbg(&atusb->udev->dev, "atusb_read_fb: tx = 0x%x\n", tx);
|
|
+ return submit_control_msg(atusb,
|
|
+ ATUSB_SPI_READ1, ATUSB_FROM_DEV, tx, 0,
|
|
+ atusb->buffer, len+1, atusb_read_fb_cb, atusb);
|
|
+}
|
|
+
|
|
+static int atusb_write(struct atusb_local *atusb,
|
|
+ uint8_t tx0, uint8_t tx1, const uint8_t *tx, int len)
|
|
+{
|
|
+ dev_dbg(&atusb->udev->dev,
|
|
+ "atusb_write: tx0 = 0x%x tx1 = 0x%x\n", tx0, tx1);
|
|
+
|
|
+ /*
|
|
+ * The AT86RF230 driver sometimes requires a transceiver state
|
|
+ * transition to be an interrupt barrier. This is the case after
|
|
+ * writing FORCE_TX_ON to the TRX_CMD field in the TRX_STATE register.
|
|
+ *
|
|
+ * Since there is no other means of notification, we just decode the
|
|
+ * transfer and do a bit of pattern matching.
|
|
+ */
|
|
+ if (tx0 == (CMD_REG | CMD_WRITE | RG_TRX_STATE) &&
|
|
+ (tx1 & 0x1f) == STATE_FORCE_TX_ON)
|
|
+ return submit_control_msg(atusb,
|
|
+ ATUSB_SPI_WRITE2_SYNC, ATUSB_FROM_DEV, tx0, tx1,
|
|
+ &atusb->irq_sync, 1, atusb_ctrl_cb_sync, atusb);
|
|
+ else
|
|
+ return submit_control_msg(atusb,
|
|
+ ATUSB_SPI_WRITE, ATUSB_TO_DEV, tx0, tx1,
|
|
+ (uint8_t *) tx, len, atusb_ctrl_cb, atusb);
|
|
+}
|
|
+
|
|
+static int atusb_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
+{
|
|
+ struct atusb_local *atusb = spi_master_get_devdata(spi->master);
|
|
+ struct spi_transfer *xfer;
|
|
+ struct spi_transfer *x[2];
|
|
+ int n;
|
|
+ const uint8_t *tx;
|
|
+ uint8_t *rx;
|
|
+ int len;
|
|
+ int retval = 0;
|
|
+
|
|
+ if (unlikely(list_empty(&msg->transfers))) {
|
|
+ dev_err(&atusb->udev->dev, "transfer is empty\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ atusb->msg = msg;
|
|
+
|
|
+ /* Classify the request */
|
|
+ n = 0;
|
|
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
+ if (n == ARRAY_SIZE(x)) {
|
|
+ dev_err(&atusb->udev->dev, "too many transfers\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ x[n] = xfer;
|
|
+ n++;
|
|
+ }
|
|
+
|
|
+ tx = x[0]->tx_buf;
|
|
+ rx = x[0]->rx_buf;
|
|
+ len = x[0]->len;
|
|
+
|
|
+ msg->actual_length = len;
|
|
+
|
|
+ if (!tx || len != 2)
|
|
+ goto bad_req;
|
|
+ if (n == 1) {
|
|
+ if (rx) {
|
|
+ dev_dbg(&atusb->udev->dev, "read 1\n");
|
|
+ retval = atusb_read1(atusb, tx[0], rx+1, len-1);
|
|
+ } else {
|
|
+ dev_dbg(&atusb->udev->dev, "write 2\n");
|
|
+ /*
|
|
+ * Don't take our clock away !! ;-)
|
|
+ */
|
|
+ if (tx[0] == (CMD_REG | CMD_WRITE | RG_TRX_CTRL_0)) {
|
|
+ msg->status = 0;
|
|
+ msg->complete(msg->context);
|
|
+ } else {
|
|
+ retval = atusb_write(atusb,
|
|
+ tx[0], tx[1], NULL, 0);
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ if (x[0]->rx_buf) {
|
|
+ if (x[1]->tx_buf || !x[1]->rx_buf)
|
|
+ goto bad_req;
|
|
+ dev_dbg(&atusb->udev->dev, "read 1+\n");
|
|
+ retval = atusb_read_fb(atusb, tx[0], rx+1,
|
|
+ x[1]->rx_buf, x[1]->len);
|
|
+ } else {
|
|
+ if (!x[1]->tx_buf ||x[1]->rx_buf)
|
|
+ goto bad_req;
|
|
+ dev_dbg(&atusb->udev->dev, "write 2+n\n");
|
|
+ retval = atusb_write(atusb, tx[0], tx[1],
|
|
+ x[1]->tx_buf, x[1]->len);
|
|
+ }
|
|
+ }
|
|
+ return retval;
|
|
+
|
|
+bad_req:
|
|
+ dev_err(&atusb->udev->dev, "unrecognized request:\n");
|
|
+ list_for_each_entry(xfer, &msg->transfers, transfer_list)
|
|
+ dev_err(&atusb->udev->dev, "%stx %srx len %u\n",
|
|
+ xfer->tx_buf ? "" : "!", xfer->rx_buf ? " " : "!",
|
|
+ xfer->len);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int atusb_setup(struct spi_device *spi)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- Interrupt handling ------------------------------------------------ */
|
|
+
|
|
+
|
|
+static void atusb_tasklet(unsigned long data)
|
|
+{
|
|
+ struct atusb_local *atusb = (void *) data;
|
|
+
|
|
+ generic_handle_irq(atusb->slave_irq);
|
|
+}
|
|
+
|
|
+static void atusb_irq(struct urb *urb)
|
|
+{
|
|
+ struct atusb_local *atusb = urb->context;
|
|
+
|
|
+ dev_dbg(&urb->dev->dev, "atusb_irq (%d), seen %d sync %d\n",
|
|
+ urb->status, atusb->irq_buf, atusb->irq_sync);
|
|
+ if (!urb->status) {
|
|
+ atusb->irq_seen = atusb->irq_buf;
|
|
+ if (atusb->irq_sync == atusb->irq_seen &&
|
|
+ try_to_del_timer_sync(&atusb->timer) == 1)
|
|
+ atusb_async_finish((struct urb *) atusb->timer.data);
|
|
+ }
|
|
+ usb_free_urb(urb);
|
|
+ atusb->irq_urb = NULL;
|
|
+ tasklet_schedule(&atusb->task);
|
|
+}
|
|
+
|
|
+static int atusb_arm_interrupt(struct atusb_local *atusb)
|
|
+{
|
|
+ struct usb_device *dev = atusb->udev;
|
|
+ struct urb *urb;
|
|
+ int retval = -ENOMEM;
|
|
+
|
|
+ BUG_ON(atusb->irq_urb);
|
|
+
|
|
+ dev_vdbg(&dev->dev, "atusb_arm_interrupt\n");
|
|
+ urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!urb) {
|
|
+ dev_err(&dev->dev,
|
|
+ "atusb_arm_interrupt: usb_alloc_urb failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ usb_fill_bulk_urb(urb, dev, usb_rcvbulkpipe(dev, 1),
|
|
+ &atusb->irq_buf, 1, atusb_irq, atusb);
|
|
+ atusb->irq_urb = urb;
|
|
+ retval = usb_submit_urb(urb, GFP_KERNEL);
|
|
+ if (!retval)
|
|
+ return 0;
|
|
+
|
|
+ dev_err(&dev->dev, "failed submitting bulk urb, error %d\n", retval);
|
|
+ retval = retval == -ENOMEM ? retval : -EIO;
|
|
+
|
|
+ usb_free_urb(urb);
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void atusb_irq_mask(struct irq_data *data)
|
|
+{
|
|
+ struct atusb_local *atusb = irq_data_get_irq_chip_data(data);
|
|
+
|
|
+ dev_vdbg(&atusb->udev->dev, "atusb_irq_mask\n");
|
|
+ tasklet_disable_nosync(&atusb->task);
|
|
+}
|
|
+
|
|
+static void atusb_irq_unmask(struct irq_data *data)
|
|
+{
|
|
+ struct atusb_local *atusb = irq_data_get_irq_chip_data(data);
|
|
+
|
|
+ dev_vdbg(&atusb->udev->dev, "atusb_irq_unmask\n");
|
|
+ tasklet_enable(&atusb->task);
|
|
+}
|
|
+
|
|
+static void atusb_irq_ack(struct irq_data *data)
|
|
+{
|
|
+ struct atusb_local *atusb = irq_data_get_irq_chip_data(data);
|
|
+
|
|
+ dev_vdbg(&atusb->udev->dev, "atusb_irq_ack\n");
|
|
+ atusb_arm_interrupt(atusb);
|
|
+}
|
|
+
|
|
+static struct irq_chip atusb_irq_chip = {
|
|
+ .name = "atusb-slave",
|
|
+ .irq_mask = atusb_irq_mask,
|
|
+ .irq_unmask = atusb_irq_unmask,
|
|
+ .irq_ack = atusb_irq_ack,
|
|
+};
|
|
+
|
|
+
|
|
+/* ----- Transceiver reset ------------------------------------------------- */
|
|
+
|
|
+
|
|
+static void atusb_reset(void *reset_data)
|
|
+{
|
|
+ int retval;
|
|
+ struct atusb_local *atusb = reset_data;
|
|
+
|
|
+ retval = usb_control_msg(atusb->udev,
|
|
+ usb_rcvctrlpipe(atusb->udev, 0),
|
|
+ ATUSB_RF_RESET, ATUSB_TO_DEV, 0, 0,
|
|
+ NULL, 0, 1000);
|
|
+ if (retval < 0) {
|
|
+ dev_err(&atusb->udev->dev,
|
|
+ "%s: error doing reset retval = %d\n",
|
|
+ __func__, retval);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- Firmware version information -------------------------------------- */
|
|
+
|
|
+
|
|
+static int atusb_get_and_show_revision(struct atusb_local *atusb)
|
|
+{
|
|
+ struct usb_device *dev = atusb->udev;
|
|
+ int retval;
|
|
+
|
|
+ /* Get a couple of the ATMega Firmware values */
|
|
+ retval = usb_control_msg(dev,
|
|
+ usb_rcvctrlpipe(dev, 0),
|
|
+ ATUSB_ID, ATUSB_FROM_DEV, 0, 0,
|
|
+ atusb->buffer, 3, 1000);
|
|
+ if (retval < 0) {
|
|
+ dev_info(&dev->dev,
|
|
+ "failed submitting urb for ATUSB_ID, error %d\n", retval);
|
|
+ return retval == -ENOMEM ? retval : -EIO;
|
|
+ }
|
|
+
|
|
+ atusb->ep0_atusb_major = atusb->buffer[0];
|
|
+ atusb->ep0_atusb_minor = atusb->buffer[1];
|
|
+ atusb->atusb_hw_type = atusb->buffer[2];
|
|
+ dev_info(&dev->dev,
|
|
+ "Firmware: major: %u, minor: %u, hardware type: %u\n",
|
|
+ atusb->ep0_atusb_major, atusb->ep0_atusb_minor,
|
|
+ atusb->atusb_hw_type);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int atusb_get_and_show_build(struct atusb_local *atusb)
|
|
+{
|
|
+ struct usb_device *dev = atusb->udev;
|
|
+ char build[ATUSB_BUILD_SIZE+1];
|
|
+ int retval;
|
|
+
|
|
+ retval = usb_control_msg(dev,
|
|
+ usb_rcvctrlpipe(atusb->udev, 0),
|
|
+ ATUSB_BUILD, ATUSB_FROM_DEV, 0, 0,
|
|
+ build, ATUSB_BUILD_SIZE, 1000);
|
|
+ if (retval < 0) {
|
|
+ dev_err(&dev->dev,
|
|
+ "failed submitting urb for ATUSB_BUILD, error %d\n",
|
|
+ retval);
|
|
+ return retval == -ENOMEM ? retval : -EIO;
|
|
+ }
|
|
+
|
|
+ build[retval] = 0;
|
|
+ dev_info(&dev->dev, "Firmware: build %s\n", build);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* ----- Setup ------------------------------------------------------------- */
|
|
+
|
|
+
|
|
+struct at86rf230_platform_data at86rf230_platform_data = {
|
|
+ .rstn = -1,
|
|
+ .slp_tr = -1,
|
|
+ .dig2 = -1,
|
|
+ .reset = atusb_reset,
|
|
+ /* set .reset_data later */
|
|
+};
|
|
+
|
|
+static int atusb_probe(struct usb_interface *interface,
|
|
+ const struct usb_device_id *id)
|
|
+{
|
|
+ struct spi_board_info board_info = {
|
|
+ .modalias = "at86rf230",
|
|
+ /* set .irq later */
|
|
+ .chip_select = 0,
|
|
+ .bus_num = -1,
|
|
+ .max_speed_hz = 8 * 1000 * 1000,
|
|
+ };
|
|
+
|
|
+ struct usb_device *udev = interface_to_usbdev(interface);
|
|
+ struct atusb_local *atusb = NULL;
|
|
+ struct spi_master *master;
|
|
+ int retval;
|
|
+
|
|
+ /*
|
|
+ * Ignore all interfaces used for DFU, i.e., everything while in the
|
|
+ * boot loader, and interface #1 when in the application.
|
|
+ */
|
|
+ if (interface->cur_altsetting->desc.bInterfaceClass !=
|
|
+ USB_CLASS_VENDOR_SPEC) {
|
|
+ dev_dbg(&udev->dev,
|
|
+ "Ignoring interface with class 0x%02x\n",
|
|
+ interface->cur_altsetting->desc.bInterfaceClass);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ master = spi_alloc_master(&udev->dev, sizeof(*atusb));
|
|
+ if (!master)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ atusb = spi_master_get_devdata(master);
|
|
+
|
|
+ atusb->udev = usb_get_dev(udev);
|
|
+ usb_set_intfdata(interface, atusb);
|
|
+
|
|
+ atusb->master = spi_master_get(master);
|
|
+
|
|
+ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
|
+ master->bus_num = -1;
|
|
+ master->num_chipselect = 1;
|
|
+ master->setup = atusb_setup;
|
|
+ master->transfer = atusb_transfer;
|
|
+
|
|
+ atusb->slave_irq = irq_alloc_desc(numa_node_id());
|
|
+ if (atusb->slave_irq < 0) {
|
|
+ dev_err(&udev->dev, "can't allocate slave irq\n");
|
|
+ retval = -ENXIO;
|
|
+ goto err_free;
|
|
+ }
|
|
+
|
|
+ irq_set_chip_data(atusb->slave_irq, atusb);
|
|
+ irq_set_chip(atusb->slave_irq, &atusb_irq_chip);
|
|
+ __irq_set_handler(atusb->slave_irq, handle_level_irq, 0, NULL);
|
|
+
|
|
+ /* FIXME prepare USB IRQ */
|
|
+
|
|
+ retval = spi_register_master(master);
|
|
+ if (retval < 0) {
|
|
+ dev_err(&udev->dev, "can't register spi master\n");
|
|
+ goto err_slave_irq;
|
|
+ }
|
|
+
|
|
+ atusb->platform_data = at86rf230_platform_data;
|
|
+ atusb->platform_data.reset_data = atusb;
|
|
+ board_info.platform_data = &atusb->platform_data;
|
|
+ board_info.irq = atusb->slave_irq;
|
|
+
|
|
+ init_timer(&atusb->timer);
|
|
+ atusb->timer.function = atusb_timer;
|
|
+
|
|
+ tasklet_init(&atusb->task, atusb_tasklet, (unsigned long) atusb);
|
|
+ tasklet_disable(&atusb->task);
|
|
+ atusb_arm_interrupt(atusb);
|
|
+
|
|
+ if (atusb_get_and_show_revision(atusb) < 0)
|
|
+ goto err_master;
|
|
+ if (atusb_get_and_show_build(atusb) < 0)
|
|
+ goto err_master;
|
|
+
|
|
+ atusb->spi = spi_new_device(master, &board_info);
|
|
+ if (!atusb->spi) {
|
|
+ dev_err(&udev->dev, "can't create new device for %s\n",
|
|
+ board_info.modalias);
|
|
+ goto err_master;
|
|
+ }
|
|
+
|
|
+ dev_info(&atusb->spi->dev,
|
|
+ "ATUSB ready for mischief (IRQ %d)\n", board_info.irq);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_master:
|
|
+ /*
|
|
+ * If we come here from a partially successful driver initialization,
|
|
+ * we don't really know how much it has done. In particular, it may
|
|
+ * have triggered an interrupt and thus removed the interrupt URB and
|
|
+ * maybe scheduled the tasklet.
|
|
+ */
|
|
+ tasklet_disable(&atusb->task);
|
|
+ if (atusb->irq_urb)
|
|
+ usb_kill_urb(atusb->irq_urb);
|
|
+ spi_master_put(atusb->master);
|
|
+err_slave_irq:
|
|
+ irq_set_chained_handler(atusb->slave_irq, NULL);
|
|
+ irq_set_chip_data(atusb->slave_irq, NULL);
|
|
+ irq_free_desc(atusb->slave_irq);
|
|
+err_free:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void atusb_disconnect(struct usb_interface *interface)
|
|
+{
|
|
+ struct atusb_local *atusb = usb_get_intfdata(interface);
|
|
+ struct spi_master *master = atusb->master;
|
|
+
|
|
+ tasklet_disable(&atusb->task);
|
|
+ /* @@@ this needs some extra protecion - wa */
|
|
+ if (atusb->irq_urb)
|
|
+ usb_kill_urb(atusb->irq_urb);
|
|
+
|
|
+ BUG_ON(timer_pending(&atusb->timer));
|
|
+
|
|
+ usb_set_intfdata(interface, NULL);
|
|
+ usb_put_dev(atusb->udev);
|
|
+
|
|
+ spi_dev_put(atusb->spi);
|
|
+
|
|
+ spi_unregister_master(master);
|
|
+
|
|
+ irq_set_chained_handler(atusb->slave_irq, NULL);
|
|
+ irq_set_chip_data(atusb->slave_irq, NULL);
|
|
+ irq_free_desc(atusb->slave_irq);
|
|
+
|
|
+ spi_master_put(master);
|
|
+}
|
|
+
|
|
+void atusb_release(struct device *dev)
|
|
+{
|
|
+ return;
|
|
+}
|
|
+
|
|
+static struct usb_driver atusb_driver = {
|
|
+ .name = "atusb_ben-wpan",
|
|
+ .probe = atusb_probe,
|
|
+ .disconnect = atusb_disconnect,
|
|
+ .id_table = atusb_device_table,
|
|
+};
|
|
+
|
|
+static struct platform_device atusb_device = {
|
|
+ .name = "spi_atusb",
|
|
+ .id = -1,
|
|
+ .dev.release = atusb_release,
|
|
+};
|
|
+
|
|
+static int __init atusb_init(void)
|
|
+{
|
|
+ int retval;
|
|
+
|
|
+ retval = platform_device_register(&atusb_device);
|
|
+ if (retval)
|
|
+ return retval;
|
|
+
|
|
+ return usb_register(&atusb_driver);
|
|
+}
|
|
+
|
|
+static void __exit atusb_exit(void)
|
|
+{
|
|
+ usb_deregister(&atusb_driver);
|
|
+ platform_device_unregister(&atusb_device);
|
|
+}
|
|
+
|
|
+module_init (atusb_init);
|
|
+module_exit (atusb_exit);
|
|
+
|
|
+MODULE_AUTHOR("Richard Sharpe <realrichardsharpe@gmail.com>");
|
|
+MODULE_AUTHOR("Stefan Schmidt <stefan@datenfreihafen.org>");
|
|
+MODULE_AUTHOR("Werner Almesberger <werner@almesberger.net>");
|
|
+MODULE_DESCRIPTION("ATUSB ben-wpan Driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/include/linux/if_ieee802154.h b/include/linux/if_ieee802154.h
|
|
new file mode 100644
|
|
index 0000000..cce32bb
|
|
--- /dev/null
|
|
+++ b/include/linux/if_ieee802154.h
|
|
@@ -0,0 +1,6 @@
|
|
+#ifndef __LINUX_IF_IEEE802154_H
|
|
+#define __LINUX_IF_IEEE802154_H
|
|
+
|
|
+#define IEEE802154_ALEN 8 /* size of 64-bit hardware address */
|
|
+
|
|
+#endif
|
|
diff --git a/include/linux/spi/at86rf230.h b/include/linux/spi/at86rf230.h
|
|
new file mode 100644
|
|
index 0000000..dff0225
|
|
--- /dev/null
|
|
+++ b/include/linux/spi/at86rf230.h
|
|
@@ -0,0 +1,34 @@
|
|
+/*
|
|
+ * AT86RF230/RF231 driver
|
|
+ *
|
|
+ * Copyright (C) 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
|
|
+ */
|
|
+#ifndef LINUX_SPI_AT86RF230_H
|
|
+#define LINUX_SPI_AT86RF230_H
|
|
+
|
|
+struct at86rf230_platform_data {
|
|
+ int rstn;
|
|
+ int slp_tr;
|
|
+ int dig2;
|
|
+ void (*reset)(void *reset_data);
|
|
+ void *reset_data;
|
|
+};
|
|
+
|
|
+#endif
|
|
+
|
|
diff --git a/include/net/mac802154.h b/include/net/mac802154.h
|
|
new file mode 100644
|
|
index 0000000..df46f6a
|
|
--- /dev/null
|
|
+++ b/include/net/mac802154.h
|
|
@@ -0,0 +1,156 @@
|
|
+/*
|
|
+ * IEEE802.15.4-2003 specification
|
|
+ *
|
|
+ * Copyright (C) 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ */
|
|
+#ifndef NET_MAC802154_H
|
|
+#define NET_MAC802154_H
|
|
+
|
|
+#include <linux/if_ieee802154.h>
|
|
+
|
|
+/**
|
|
+ * enum ieee802154_hw_addr_filt_flags - hardware flags
|
|
+ *
|
|
+ * These flags are used to indicate changed address settings from
|
|
+ * the stack to the hardware.
|
|
+ *
|
|
+ * @IEEE802515_SADDR_CHANGED:
|
|
+ * Indicates that the Short Address changed
|
|
+ *
|
|
+ * @IEEE802515_IEEEADDR_CHANGED:
|
|
+ * Indicates that the IEEE Address changed
|
|
+ *
|
|
+ * @IEEE802515_PANID_CHANGED:
|
|
+ * Indicates that the PAN ID changed
|
|
+ *
|
|
+ * @IEEE802515_PANC_CHANGED:
|
|
+ * Indicates that PAN Coordinator status changed
|
|
+ */
|
|
+enum ieee802154_hw_addr_filt_flags {
|
|
+ IEEE802515_SADDR_CHANGED = 1 << 0,
|
|
+ IEEE802515_IEEEADDR_CHANGED = 1 << 1,
|
|
+ IEEE802515_PANID_CHANGED = 1 << 2,
|
|
+ IEEE802515_PANC_CHANGED = 1 << 3,
|
|
+};
|
|
+
|
|
+struct ieee802154_hw_addr_filt {
|
|
+ u16 pan_id;
|
|
+ u16 short_addr;
|
|
+ u8 ieee_addr[IEEE802154_ALEN];
|
|
+ u8 pan_coord;
|
|
+};
|
|
+
|
|
+struct ieee802154_dev {
|
|
+ /* filled by the driver */
|
|
+ int extra_tx_headroom; /* headroom to reserve for tx skb */
|
|
+ u32 flags; /* Flags for device to set */
|
|
+ struct device *parent;
|
|
+
|
|
+ /* filled by mac802154 core */
|
|
+ struct ieee802154_hw_addr_filt hw_filt;
|
|
+ void *priv; /* driver-specific data */
|
|
+ struct wpan_phy *phy;
|
|
+};
|
|
+
|
|
+/* Checksum is in hardware and is omitted from packet */
|
|
+/**
|
|
+ * enum ieee802154_hw_flags - hardware flags
|
|
+ *
|
|
+ * These flags are used to indicate hardware capabilities to
|
|
+ * the stack. Generally, flags here should have their meaning
|
|
+ * done in a way that the simplest hardware doesn't need setting
|
|
+ * any particular flags. There are some exceptions to this rule,
|
|
+ * however, so you are advised to review these flags carefully.
|
|
+ *
|
|
+ * @IEEE802154_HW_OMIT_CKSUM:
|
|
+ * Indicates that receiver omits FCS and transmitter will add
|
|
+ * FCS on it's own.
|
|
+ *
|
|
+ * @IEEE802154_HW_AACK:
|
|
+ * Indicates that receiver will autorespond with ACK frames.
|
|
+ */
|
|
+enum ieee802154_hw_flags {
|
|
+ IEEE802154_HW_OMIT_CKSUM = 1 << 0,
|
|
+ IEEE802154_HW_AACK = 1 << 1,
|
|
+};
|
|
+
|
|
+struct sk_buff;
|
|
+
|
|
+/**
|
|
+ * struct ieee802154_ops - callbacks from mac802154 to the driver
|
|
+ *
|
|
+ * This structure contains various callbacks that the driver may
|
|
+ * handle or, in some cases, must handle, for example to transmit
|
|
+ * a frame.
|
|
+ *
|
|
+ * @start: Handler that 802.15.4 module calls for device initialisation.
|
|
+ * This function is called before the first interface is attached.
|
|
+ *
|
|
+ * @stop: Handler that 802.15.4 module calls for device cleanup
|
|
+ * This function is called after the last interface is removed.
|
|
+ *
|
|
+ * @xmit: Handler that 802.15.4 module calls for each transmitted frame.
|
|
+ * skb cntains the buffer starting from the IEEE 802.15.4 header.
|
|
+ * The low-level driver should send the frame based on available
|
|
+ * configuration.
|
|
+ * This function should return zero or negative errno.
|
|
+ * Called with pib_lock held.
|
|
+ *
|
|
+ * @ed: Handler that 802.15.4 module calls for Energy Detection.
|
|
+ * This function should place the value for detected energy
|
|
+ * (usually device-dependant) in the level pointer and return
|
|
+ * either zero or negative errno.
|
|
+ * Called with pib_lock held.
|
|
+ *
|
|
+ * @set_channel: Set radio for listening on specific channel.
|
|
+ * Set the device for listening on specified channel.
|
|
+ * Returns either zero, or negative errno.
|
|
+ * Called with pib_lock held.
|
|
+ *
|
|
+ * @set_hw_addr_filt: Set radio for listening on specific address.
|
|
+ * Set the device for listening on specified address.
|
|
+ * Returns either zero, or negative errno.
|
|
+ */
|
|
+struct ieee802154_ops {
|
|
+ struct module *owner;
|
|
+ int (*start)(struct ieee802154_dev *dev);
|
|
+ void (*stop)(struct ieee802154_dev *dev);
|
|
+ int (*xmit)(struct ieee802154_dev *dev,
|
|
+ struct sk_buff *skb);
|
|
+ int (*ed)(struct ieee802154_dev *dev, u8 *level);
|
|
+ int (*set_channel)(struct ieee802154_dev *dev,
|
|
+ int page,
|
|
+ int channel);
|
|
+ int (*set_hw_addr_filt)(struct ieee802154_dev *dev,
|
|
+ struct ieee802154_hw_addr_filt *filt,
|
|
+ unsigned long changed);
|
|
+ int (*ieee_addr)(struct ieee802154_dev *dev,
|
|
+ u8 addr[IEEE802154_ALEN]);
|
|
+};
|
|
+
|
|
+struct ieee802154_dev *ieee802154_alloc_device(size_t priv_size,
|
|
+ struct ieee802154_ops *ops);
|
|
+int ieee802154_register_device(struct ieee802154_dev *dev);
|
|
+void ieee802154_unregister_device(struct ieee802154_dev *dev);
|
|
+void ieee802154_free_device(struct ieee802154_dev *dev);
|
|
+
|
|
+void ieee802154_rx(struct ieee802154_dev *dev, struct sk_buff *skb, u8 lqi);
|
|
+void ieee802154_rx_irqsafe(struct ieee802154_dev *dev, struct sk_buff *skb,
|
|
+ u8 lqi);
|
|
+#endif
|
|
+
|
|
diff --git a/net/mac802154/Kconfig b/net/mac802154/Kconfig
|
|
new file mode 100644
|
|
index 0000000..32e63bc
|
|
--- /dev/null
|
|
+++ b/net/mac802154/Kconfig
|
|
@@ -0,0 +1,24 @@
|
|
+config MAC802154
|
|
+ tristate "Generic IEEE 802.15.4 Soft Networking Stack (mac802154)"
|
|
+ depends on IEEE802154 && EXPERIMENTAL
|
|
+ select CRC_CCITT
|
|
+ ---help---
|
|
+ This option enables the hardware independent IEEE 802.15.4
|
|
+ networking stack for SoftMAC devices (the ones implementing
|
|
+ only PHY level of IEEE 802.15.4 standard).
|
|
+
|
|
+ Note: this implementation is neither certified, nor feature
|
|
+ complete! We do not guarantee that it is compatible w/ other
|
|
+ implementations, etc.
|
|
+
|
|
+ If you plan to use HardMAC IEEE 802.15.4 devices, you can
|
|
+ say N here. Alternatievly you can say M to compile it as
|
|
+ module.
|
|
+
|
|
+config MAC802154_DEBUG
|
|
+ bool "IEEE 802.15.4 SoftMAC debugging messages"
|
|
+ depends on MAC802154
|
|
+ default y
|
|
+ help
|
|
+ Say Y here to make the IEEE 802.15.4 SoftMAC generate extensive
|
|
+ debugging messages.
|
|
diff --git a/net/mac802154/Makefile b/net/mac802154/Makefile
|
|
new file mode 100644
|
|
index 0000000..d76fabb
|
|
--- /dev/null
|
|
+++ b/net/mac802154/Makefile
|
|
@@ -0,0 +1,6 @@
|
|
+obj-$(CONFIG_MAC802154) += mac802154.o
|
|
+mac802154-objs := rx.o tx.o main.o monitor.o wpan.o mac_cmd.o scan.o mib.o \
|
|
+ beacon.o beacon_hash.o smac.o
|
|
+
|
|
+ccflags-$(CONFIG_MAC802154_DEBUG) += -DDEBUG
|
|
+ccflags-y += -Wall
|
|
diff --git a/net/mac802154/beacon.c b/net/mac802154/beacon.c
|
|
new file mode 100644
|
|
index 0000000..fbf67e9
|
|
--- /dev/null
|
|
+++ b/net/mac802154/beacon.c
|
|
@@ -0,0 +1,285 @@
|
|
+/*
|
|
+ * MAC beacon interface
|
|
+ *
|
|
+ * Copyright 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/list.h>
|
|
+
|
|
+#include <net/af_ieee802154.h>
|
|
+#include <net/nl802154.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/ieee802154.h>
|
|
+#include <net/ieee802154_netdev.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+#include "beacon_hash.h"
|
|
+
|
|
+/* Beacon frame format per specification is the followinf:
|
|
+ * Standard MAC frame header:
|
|
+ * FC (2) SEQ (1)
|
|
+ * Addressing (4-20)
|
|
+ * Beacon fields:
|
|
+ * <Superframe specification> (2)
|
|
+ * <GTS> (?)
|
|
+ * <Pending address> (?)
|
|
+ * <Beacon payload> (?)
|
|
+ * FCS (2)
|
|
+ *
|
|
+ * Superframe specification:
|
|
+ * bit Value
|
|
+ * 15 Association permit
|
|
+ * 14 PAN coordinator
|
|
+ * 13 Reserved
|
|
+ * 12 Battery life extension
|
|
+ * 8-11 Final CAP slot
|
|
+ * 4-7 Superframe order
|
|
+ * 0-3 Beacon order
|
|
+ *
|
|
+ * GTS:
|
|
+ * <GTS specification> (1)
|
|
+ * <GTS directions> (0-1)
|
|
+ * <GTS list> (?)
|
|
+ *
|
|
+ * Pending address:
|
|
+ * <Pending address specification> (1)
|
|
+ * <Pending address list (?)
|
|
+ *
|
|
+ * GTS specification:
|
|
+ * bit Value
|
|
+ * 7 GTS permit
|
|
+ * 3-6 Reserved
|
|
+ * 0-2 GTS descriptor count
|
|
+ *
|
|
+ * Pending address specification:
|
|
+ * bit Value
|
|
+ * 7 Reserved
|
|
+ * 4-6 Number of extended addresses pendinf
|
|
+ * 3 Reserved
|
|
+ * 0-2 Number of short addresses pending
|
|
+ * */
|
|
+
|
|
+#define IEEE802154_BEACON_SF_BO_BEACONLESS (15 << 0)
|
|
+#define IEEE802154_BEACON_SF_SO(x) ((x & 0xf) << 4)
|
|
+#define IEEE802154_BEACON_SF_SO_INACTIVE IEEE802154_BEACON_SF_SO(15)
|
|
+#define IEEE802154_BEACON_SF_PANCOORD (1 << 14)
|
|
+#define IEEE802154_BEACON_SF_CANASSOC (1 << 15)
|
|
+#define IEEE802154_BEACON_GTS_COUNT(x) (x << 0)
|
|
+#define IEEE802154_BEACON_GTS_PERMIT (1 << 7)
|
|
+#define IEEE802154_BEACON_PA_SHORT(x) ((x & 7) << 0)
|
|
+#define IEEE802154_BEACON_PA_LONG(x) ((x & 7) << 4)
|
|
+
|
|
+/* Flags parameter */
|
|
+#define IEEE802154_BEACON_FLAG_PANCOORD (1 << 0)
|
|
+#define IEEE802154_BEACON_FLAG_CANASSOC (1 << 1)
|
|
+#define IEEE802154_BEACON_FLAG_GTSPERMIT (1 << 2)
|
|
+
|
|
+struct mac802154_address_list {
|
|
+ struct list_head list;
|
|
+ struct ieee802154_addr addr;
|
|
+};
|
|
+
|
|
+/* Per spec; optimizations are needed */
|
|
+struct mac802154_pandsc {
|
|
+ struct list_head list;
|
|
+ struct ieee802154_addr addr; /* Contains panid */
|
|
+ int channel;
|
|
+ u16 sf;
|
|
+ bool gts_permit;
|
|
+ u8 lqi;
|
|
+/* FIXME: Aging of stored PAN descriptors is not decided yet,
|
|
+ * because no PAN descriptor storage is implemented yet */
|
|
+ u32 timestamp;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * @dev device
|
|
+ * @addr destination address
|
|
+ * @saddr source address
|
|
+ * @buf beacon payload
|
|
+ * @len beacon payload size
|
|
+ * @pan_coord - if we're PAN coordinator while sending this frame
|
|
+ * @gts_permit - wheather we allow GTS requests
|
|
+ * @al address list to be provided in beacon
|
|
+ *
|
|
+ * TODO:
|
|
+ * For a beacon frame, the sequence number field shall specify a BSN.
|
|
+ * Each coordinator shall store its current
|
|
+ * BSN value in the MAC PIB attribute macBSN and initialize it to
|
|
+ * a random value.
|
|
+ * The algorithm for choosing a random number is out of the scope
|
|
+ * of this standard. The coordinator shall copy the value of the macBSN
|
|
+ * attribute into the sequence number field of a beacon frame,
|
|
+ * each time one is generated, and shall then increment macBSN by one.
|
|
+ *
|
|
+*/
|
|
+
|
|
+
|
|
+int mac802154_send_beacon(struct net_device *dev,
|
|
+ struct ieee802154_addr *saddr,
|
|
+ u16 pan_id, const u8 *buf, int len,
|
|
+ int flags, struct list_head *al)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+ int err;
|
|
+ u16 sf;
|
|
+ u8 gts;
|
|
+ u8 pa_spec;
|
|
+ int addr16_cnt;
|
|
+ int addr64_cnt;
|
|
+ int hlen, tlen;
|
|
+ struct ieee802154_addr addr;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ hlen = LL_RESERVED_SPACE(dev);
|
|
+ tlen = dev->needed_tailroom;
|
|
+ skb = alloc_skb(len + hlen + tlen, GFP_ATOMIC);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
|
|
+
|
|
+ skb_reset_network_header(skb);
|
|
+
|
|
+ mac_cb(skb)->flags = IEEE802154_FC_TYPE_BEACON;
|
|
+ mac_cb(skb)->seq = ieee802154_mlme_ops(dev)->get_bsn(dev);
|
|
+
|
|
+ addr.addr_type = IEEE802154_ADDR_NONE;
|
|
+ err = dev_hard_header(skb, dev, ETH_P_IEEE802154, &addr, saddr, len);
|
|
+ if (err < 0) {
|
|
+ kfree_skb(skb);
|
|
+ return err;
|
|
+ }
|
|
+ skb_reset_mac_header(skb);
|
|
+
|
|
+ /* Superframe */
|
|
+ sf = IEEE802154_BEACON_SF_BO_BEACONLESS;
|
|
+ sf |= IEEE802154_BEACON_SF_SO_INACTIVE;
|
|
+ if (flags & IEEE802154_BEACON_FLAG_PANCOORD)
|
|
+ sf |= IEEE802154_BEACON_SF_PANCOORD;
|
|
+
|
|
+ if (flags & IEEE802154_BEACON_FLAG_CANASSOC)
|
|
+ sf |= IEEE802154_BEACON_SF_CANASSOC;
|
|
+ memcpy(skb_put(skb, sizeof(sf)), &sf, sizeof(sf));
|
|
+
|
|
+ /* TODO GTS */
|
|
+ gts = 0;
|
|
+
|
|
+ if (flags & IEEE802154_BEACON_FLAG_GTSPERMIT)
|
|
+ gts |= IEEE802154_BEACON_GTS_PERMIT;
|
|
+ memcpy(skb_put(skb, sizeof(gts)), >s, sizeof(gts));
|
|
+
|
|
+ /* FIXME pending address */
|
|
+ addr16_cnt = 0;
|
|
+ addr64_cnt = 0;
|
|
+
|
|
+ pa_spec = IEEE802154_BEACON_PA_LONG(addr64_cnt) |
|
|
+ IEEE802154_BEACON_PA_SHORT(addr16_cnt);
|
|
+ memcpy(skb_put(skb, sizeof(pa_spec)), &pa_spec, sizeof(pa_spec));
|
|
+
|
|
+ memcpy(skb_put(skb, len), buf, len);
|
|
+
|
|
+ skb->dev = dev;
|
|
+ skb->protocol = htons(ETH_P_IEEE802154);
|
|
+
|
|
+ return dev_queue_xmit(skb);
|
|
+}
|
|
+
|
|
+/* at entry to this function we need skb->data to point to start
|
|
+ * of beacon field and MAC frame already parsed into MAC_CB */
|
|
+
|
|
+static int parse_beacon_frame(struct sk_buff *skb, u8 *buf,
|
|
+ int *flags, struct list_head *al)
|
|
+{
|
|
+ int offt = 0;
|
|
+ u8 gts_spec;
|
|
+ u8 pa_spec;
|
|
+ struct mac802154_pandsc *pd;
|
|
+ u16 sf = skb->data[0] + (skb->data[1] << 8);
|
|
+
|
|
+ pd = kzalloc(sizeof(struct mac802154_pandsc), GFP_KERNEL);
|
|
+
|
|
+ /* Filling-up pre-parsed values */
|
|
+ pd->lqi = mac_cb(skb)->lqi;
|
|
+ pd->sf = sf;
|
|
+ /* FIXME: make sure we do it right */
|
|
+ memcpy(&pd->addr, &mac_cb(skb)->da, sizeof(struct ieee802154_addr));
|
|
+
|
|
+ /* Supplying our nitifiers with data */
|
|
+ ieee802154_nl_beacon_indic(skb->dev, pd->addr.pan_id,
|
|
+ pd->addr.short_addr);
|
|
+ /* FIXME: We don't cache PAN descriptors yet */
|
|
+ kfree(pd);
|
|
+
|
|
+ offt += 2;
|
|
+ gts_spec = skb->data[offt++];
|
|
+ /* FIXME !!! */
|
|
+ if ((gts_spec & 7) != 0) {
|
|
+ pr_debug("We still don't parse GTS part properly");
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+ pa_spec = skb->data[offt++];
|
|
+ /* FIXME !!! */
|
|
+ if (pa_spec != 0) {
|
|
+ pr_debug("We still don't parse PA part properly");
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+
|
|
+ *flags = 0;
|
|
+
|
|
+ if (sf & IEEE802154_BEACON_SF_PANCOORD)
|
|
+ *flags |= IEEE802154_BEACON_FLAG_PANCOORD;
|
|
+
|
|
+ if (sf & IEEE802154_BEACON_SF_CANASSOC)
|
|
+ *flags |= IEEE802154_BEACON_FLAG_CANASSOC;
|
|
+ BUG_ON(skb->len - offt < 0);
|
|
+ /* FIXME */
|
|
+ if (buf && (skb->len - offt > 0))
|
|
+ memcpy(buf, skb->data + offt, skb->len - offt);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int mac802154_process_beacon(struct net_device *dev,
|
|
+ struct sk_buff *skb)
|
|
+{
|
|
+ int flags;
|
|
+ int ret;
|
|
+ ret = parse_beacon_frame(skb, NULL, &flags, NULL);
|
|
+
|
|
+ /* Here we have cb->sa = coordinator address, and PAN address */
|
|
+
|
|
+ if (ret < 0) {
|
|
+ ret = NET_RX_DROP;
|
|
+ goto fail;
|
|
+ }
|
|
+ dev_dbg(&dev->dev, "got beacon from pan %04x\n",
|
|
+ mac_cb(skb)->sa.pan_id);
|
|
+ mac802154_beacon_hash_add(&mac_cb(skb)->sa);
|
|
+ mac802154_beacon_hash_dump();
|
|
+ ret = NET_RX_SUCCESS;
|
|
+fail:
|
|
+ kfree_skb(skb);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
diff --git a/net/mac802154/beacon_hash.c b/net/mac802154/beacon_hash.c
|
|
new file mode 100644
|
|
index 0000000..97fb987
|
|
--- /dev/null
|
|
+++ b/net/mac802154/beacon_hash.c
|
|
@@ -0,0 +1,106 @@
|
|
+/*
|
|
+ * MAC beacon hash storage
|
|
+ *
|
|
+ * Copyright 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/slab.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+#include <net/af_ieee802154.h>
|
|
+
|
|
+#include "beacon_hash.h"
|
|
+
|
|
+static struct hlist_head beacon_hash[IEEE802154_BEACON_HTABLE_SIZE];
|
|
+static DEFINE_SPINLOCK(beacon_hash_lock);
|
|
+
|
|
+static int beacon_hashfn(struct ieee802154_addr *coord_addr, u16 pan_addr)
|
|
+{
|
|
+ return pan_addr % IEEE802154_BEACON_HTABLE_SIZE;
|
|
+}
|
|
+
|
|
+static void __beacon_add_node(struct ieee802154_addr *coord_addr, u16 pan_addr)
|
|
+{
|
|
+ struct beacon_node *node =
|
|
+ kzalloc(sizeof(struct beacon_node), GFP_KERNEL);
|
|
+ struct hlist_head *list =
|
|
+ &beacon_hash[beacon_hashfn(coord_addr, pan_addr)];
|
|
+ memcpy(&node->coord_addr, coord_addr, sizeof(struct ieee802154_addr));
|
|
+ node->pan_addr = pan_addr;
|
|
+ INIT_HLIST_NODE(&node->list);
|
|
+ hlist_add_head(&node->list, list);
|
|
+}
|
|
+
|
|
+struct beacon_node *mac802154_beacon_find_pan(
|
|
+ struct ieee802154_addr *coord_addr, u16 pan_addr)
|
|
+{
|
|
+ struct hlist_head *list;
|
|
+ struct hlist_node *tmp;
|
|
+ list = &beacon_hash[beacon_hashfn(coord_addr, pan_addr)];
|
|
+ if (hlist_empty(list))
|
|
+ return NULL;
|
|
+ hlist_for_each(tmp, list) {
|
|
+ struct beacon_node *entry =
|
|
+ hlist_entry(tmp, struct beacon_node, list);
|
|
+ if (entry->pan_addr == pan_addr)
|
|
+ return entry;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void mac802154_beacon_hash_add(struct ieee802154_addr *coord_addr)
|
|
+{
|
|
+ if (!mac802154_beacon_find_pan(coord_addr, coord_addr->pan_id)) {
|
|
+ spin_lock(&beacon_hash_lock);
|
|
+ __beacon_add_node(coord_addr, coord_addr->pan_id);
|
|
+ spin_unlock(&beacon_hash_lock);
|
|
+ }
|
|
+}
|
|
+
|
|
+void mac802154_beacon_hash_del(struct ieee802154_addr *coord_addr)
|
|
+{
|
|
+ struct beacon_node *entry = mac802154_beacon_find_pan(coord_addr,
|
|
+ coord_addr->pan_id);
|
|
+ if (!entry)
|
|
+ return;
|
|
+ spin_lock(&beacon_hash_lock);
|
|
+ hlist_del(&entry->list);
|
|
+ spin_unlock(&beacon_hash_lock);
|
|
+ kfree(entry);
|
|
+}
|
|
+
|
|
+void mac802154_beacon_hash_dump(void)
|
|
+{
|
|
+ int i;
|
|
+ struct hlist_node *tmp;
|
|
+ pr_debug("beacon hash dump begin\n");
|
|
+ spin_lock(&beacon_hash_lock);
|
|
+ for (i = 0; i < IEEE802154_BEACON_HTABLE_SIZE; i++) {
|
|
+ struct beacon_node *entry;
|
|
+ hlist_for_each(tmp, &beacon_hash[i]) {
|
|
+ entry = hlist_entry(tmp, struct beacon_node, list);
|
|
+ pr_debug("PAN: %04x\n", entry->pan_addr);
|
|
+ }
|
|
+ }
|
|
+ spin_unlock(&beacon_hash_lock);
|
|
+ pr_debug("beacon hash dump end\n");
|
|
+}
|
|
+
|
|
diff --git a/net/mac802154/beacon_hash.h b/net/mac802154/beacon_hash.h
|
|
new file mode 100644
|
|
index 0000000..a732aa5
|
|
--- /dev/null
|
|
+++ b/net/mac802154/beacon_hash.h
|
|
@@ -0,0 +1,41 @@
|
|
+/*
|
|
+ * MAC beacon hash storage
|
|
+ *
|
|
+ * Copyright 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#ifndef IEEE802154_BEACON_HASH_H
|
|
+#define IEEE802154_BEACON_HASH_H
|
|
+
|
|
+#define IEEE802154_BEACON_HTABLE_SIZE 256
|
|
+
|
|
+struct beacon_node {
|
|
+ struct hlist_node list;
|
|
+ struct ieee802154_addr coord_addr;
|
|
+ u16 pan_addr;
|
|
+};
|
|
+struct beacon_node *mac802154_beacon_find_pan(
|
|
+ struct ieee802154_addr *coord_addr,
|
|
+ u16 pan_addr);
|
|
+void mac802154_beacon_hash_add(struct ieee802154_addr *coord_addr);
|
|
+void mac802154_beacon_hash_del(struct ieee802154_addr *coord_addr);
|
|
+void mac802154_beacon_hash_dump(void);
|
|
+#endif
|
|
+
|
|
diff --git a/net/mac802154/mac802154.h b/net/mac802154/mac802154.h
|
|
new file mode 100644
|
|
index 0000000..f35245d
|
|
--- /dev/null
|
|
+++ b/net/mac802154/mac802154.h
|
|
@@ -0,0 +1,126 @@
|
|
+/*
|
|
+ * Copyright (C) 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Pavel Smolenskiy <pavel.smolenskiy@gmail.com>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+#ifndef MAC802154_H
|
|
+#define MAC802154_H
|
|
+
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+struct mac802154_priv {
|
|
+ struct ieee802154_dev hw;
|
|
+ struct ieee802154_ops *ops;
|
|
+
|
|
+ struct wpan_phy *phy;
|
|
+
|
|
+ int open_count;
|
|
+ /* As in mac80211 slaves list is modified:
|
|
+ * 1) under the RTNL
|
|
+ * 2) protected by slaves_mtx;
|
|
+ * 3) in an RCU manner
|
|
+ *
|
|
+ * So atomic readers can use any of this protection methods
|
|
+ */
|
|
+ struct list_head slaves;
|
|
+ struct mutex slaves_mtx;
|
|
+ /* This one is used for scanning and other
|
|
+ * jobs not to be interfered with serial driver */
|
|
+ struct workqueue_struct *dev_workqueue;
|
|
+
|
|
+ /*
|
|
+ * These flags are also modified under slaves_mtx and RTNL,
|
|
+ * so you can read them using any of protection methods.
|
|
+ */
|
|
+ /* SoftMAC device is registered and running. One can add subinterfaces. */
|
|
+ unsigned running: 1;
|
|
+};
|
|
+
|
|
+#define mac802154_to_priv(_hw) container_of(_hw, struct mac802154_priv, hw)
|
|
+
|
|
+struct mac802154_wpan_mib {
|
|
+ spinlock_t mib_lock;
|
|
+
|
|
+ u16 pan_id;
|
|
+ u16 short_addr;
|
|
+
|
|
+ u8 chan;
|
|
+ u8 page;
|
|
+
|
|
+ /* MAC BSN field */
|
|
+ u8 bsn;
|
|
+ /* MAC BSN field */
|
|
+ u8 dsn;
|
|
+};
|
|
+
|
|
+struct mac802154_sub_if_data {
|
|
+ struct list_head list; /* the ieee802154_priv->slaves list */
|
|
+
|
|
+ struct mac802154_priv *hw;
|
|
+ struct net_device *dev;
|
|
+
|
|
+ int type;
|
|
+
|
|
+ spinlock_t mib_lock;
|
|
+
|
|
+ u16 pan_id;
|
|
+ u16 short_addr;
|
|
+
|
|
+ u8 chan;
|
|
+ u8 page;
|
|
+
|
|
+ /* MAC BSN field */
|
|
+ u8 bsn;
|
|
+ /* MAC DSN field */
|
|
+ u8 dsn;
|
|
+};
|
|
+
|
|
+struct ieee802154_addr;
|
|
+
|
|
+extern struct ieee802154_mlme_ops mac802154_mlme_wpan;
|
|
+extern struct simple_mlme_ops mac802154_mlme_simple;
|
|
+
|
|
+int mac802154_mlme_scan_req(struct net_device *dev,
|
|
+ u8 type, u32 channels, u8 page, u8 duration);
|
|
+
|
|
+int mac802154_process_cmd(struct net_device *dev, struct sk_buff *skb);
|
|
+int mac802154_process_beacon(struct net_device *dev, struct sk_buff *skb);
|
|
+int mac802154_send_beacon(struct net_device *dev,
|
|
+ struct ieee802154_addr *saddr,
|
|
+ u16 pan_id, const u8 *buf, int len,
|
|
+ int flags, struct list_head *al);
|
|
+int mac802154_send_beacon_req(struct net_device *dev);
|
|
+
|
|
+struct mac802154_priv *mac802154_slave_get_priv(struct net_device *dev);
|
|
+
|
|
+void mac802154_monitors_rx(struct mac802154_priv *priv, struct sk_buff *skb);
|
|
+void mac802154_monitor_setup(struct net_device *dev);
|
|
+
|
|
+void mac802154_smacs_rx(struct mac802154_priv *priv, struct sk_buff *skb);
|
|
+void mac802154_smac_setup(struct net_device *dev);
|
|
+
|
|
+void mac802154_wpans_rx(struct mac802154_priv *priv, struct sk_buff *skb);
|
|
+void mac802154_wpan_setup(struct net_device *dev);
|
|
+
|
|
+int mac802154_slave_open(struct net_device *dev);
|
|
+int mac802154_slave_close(struct net_device *dev);
|
|
+
|
|
+netdev_tx_t mac802154_tx(struct mac802154_priv *priv, struct sk_buff *skb,
|
|
+ u8 page, u8 chan);
|
|
+#endif
|
|
diff --git a/net/mac802154/mac_cmd.c b/net/mac802154/mac_cmd.c
|
|
new file mode 100644
|
|
index 0000000..e92947a
|
|
--- /dev/null
|
|
+++ b/net/mac802154/mac_cmd.c
|
|
@@ -0,0 +1,365 @@
|
|
+/*
|
|
+ * MAC commands interface
|
|
+ *
|
|
+ * Copyright 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <net/af_ieee802154.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/ieee802154.h>
|
|
+#include <net/ieee802154_netdev.h>
|
|
+#include <net/nl802154.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+#include "mib.h"
|
|
+
|
|
+static int mac802154_cmd_beacon_req(struct sk_buff *skb)
|
|
+{
|
|
+ struct ieee802154_addr saddr; /* jeez */
|
|
+ int flags = 0;
|
|
+ u16 shortaddr;
|
|
+
|
|
+ if (skb->len != 1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (skb->pkt_type != PACKET_BROADCAST)
|
|
+ return 0;
|
|
+
|
|
+ /* Checking if we're really PAN coordinator
|
|
+ * before sending beacons */
|
|
+ if (!(skb->dev->priv_flags & IFF_IEEE802154_COORD))
|
|
+ return 0;
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_NONE ||
|
|
+ mac_cb(skb)->da.addr_type != IEEE802154_ADDR_SHORT ||
|
|
+ mac_cb(skb)->da.pan_id != IEEE802154_PANID_BROADCAST ||
|
|
+ mac_cb(skb)->da.short_addr != IEEE802154_ADDR_BROADCAST)
|
|
+ return -EINVAL;
|
|
+
|
|
+ shortaddr = mac802154_dev_get_short_addr(skb->dev);
|
|
+ if (shortaddr != IEEE802154_ADDR_BROADCAST &&
|
|
+ shortaddr != IEEE802154_ADDR_UNDEF) {
|
|
+ saddr.addr_type = IEEE802154_ADDR_SHORT;
|
|
+ saddr.short_addr = shortaddr;
|
|
+ } else {
|
|
+ saddr.addr_type = IEEE802154_ADDR_LONG;
|
|
+ memcpy(saddr.hwaddr, skb->dev->dev_addr, IEEE802154_ADDR_LEN);
|
|
+ }
|
|
+ saddr.pan_id = mac802154_dev_get_pan_id(skb->dev);
|
|
+
|
|
+
|
|
+ /* 7 bytes of MHR and 1 byte of command frame identifier
|
|
+ * We have no information in this command to proceed with.
|
|
+ * we need to submit beacon as answer to this. */
|
|
+
|
|
+ return mac802154_send_beacon(skb->dev, &saddr,
|
|
+ ieee802154_mlme_ops(skb->dev)->get_pan_id(skb->dev),
|
|
+ NULL, 0, flags, NULL);
|
|
+}
|
|
+
|
|
+static int mac802154_cmd_assoc_req(struct sk_buff *skb)
|
|
+{
|
|
+ u8 cap;
|
|
+
|
|
+ if (skb->len != 2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (skb->pkt_type != PACKET_HOST)
|
|
+ return 0;
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_LONG ||
|
|
+ mac_cb(skb)->sa.pan_id != IEEE802154_PANID_BROADCAST)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /*
|
|
+ * FIXME: check that we allow incoming ASSOC requests
|
|
+ * by consulting MIB
|
|
+ */
|
|
+
|
|
+ cap = skb->data[1];
|
|
+
|
|
+ return ieee802154_nl_assoc_indic(skb->dev, &mac_cb(skb)->sa, cap);
|
|
+}
|
|
+
|
|
+static int mac802154_cmd_assoc_resp(struct sk_buff *skb)
|
|
+{
|
|
+ u8 status;
|
|
+ u16 short_addr;
|
|
+
|
|
+ if (skb->len != 4)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (skb->pkt_type != PACKET_HOST)
|
|
+ return 0;
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_LONG ||
|
|
+ mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_LONG ||
|
|
+ !(mac_cb(skb)->flags & MAC_CB_FLAG_INTRAPAN))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* FIXME: check that we requested association ? */
|
|
+
|
|
+ status = skb->data[3];
|
|
+ short_addr = skb->data[1] | (skb->data[2] << 8);
|
|
+ pr_info("Received ASSOC-RESP status %x, addr %hx\n", status,
|
|
+ short_addr);
|
|
+ if (status) {
|
|
+ mac802154_dev_set_short_addr(skb->dev,
|
|
+ IEEE802154_ADDR_BROADCAST);
|
|
+ mac802154_dev_set_pan_id(skb->dev,
|
|
+ IEEE802154_PANID_BROADCAST);
|
|
+ } else
|
|
+ mac802154_dev_set_short_addr(skb->dev, short_addr);
|
|
+
|
|
+ return ieee802154_nl_assoc_confirm(skb->dev, short_addr, status);
|
|
+}
|
|
+
|
|
+static int mac802154_cmd_disassoc_notify(struct sk_buff *skb)
|
|
+{
|
|
+ u8 reason;
|
|
+
|
|
+ if (skb->len != 2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (skb->pkt_type != PACKET_HOST)
|
|
+ return 0;
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_LONG ||
|
|
+ (mac_cb(skb)->da.addr_type != IEEE802154_ADDR_LONG &&
|
|
+ mac_cb(skb)->da.addr_type != IEEE802154_ADDR_SHORT) ||
|
|
+ mac_cb(skb)->sa.pan_id != mac_cb(skb)->da.pan_id)
|
|
+ return -EINVAL;
|
|
+
|
|
+ reason = skb->data[1];
|
|
+
|
|
+ /* FIXME: checks if this was our coordinator and the disassoc us */
|
|
+ /* FIXME: if we device, one should receive ->da and not ->sa */
|
|
+ /* FIXME: the status should also help */
|
|
+
|
|
+ return ieee802154_nl_disassoc_indic(skb->dev, &mac_cb(skb)->sa,
|
|
+ reason);
|
|
+}
|
|
+
|
|
+int mac802154_process_cmd(struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ u8 cmd;
|
|
+
|
|
+ if (skb->len < 1) {
|
|
+ pr_warning("Uncomplete command frame!\n");
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ cmd = *(skb->data);
|
|
+ pr_debug("Command %02x on device %s\n", cmd, dev->name);
|
|
+
|
|
+ switch (cmd) {
|
|
+ case IEEE802154_CMD_ASSOCIATION_REQ:
|
|
+ mac802154_cmd_assoc_req(skb);
|
|
+ break;
|
|
+ case IEEE802154_CMD_ASSOCIATION_RESP:
|
|
+ mac802154_cmd_assoc_resp(skb);
|
|
+ break;
|
|
+ case IEEE802154_CMD_DISASSOCIATION_NOTIFY:
|
|
+ mac802154_cmd_disassoc_notify(skb);
|
|
+ break;
|
|
+ case IEEE802154_CMD_BEACON_REQ:
|
|
+ mac802154_cmd_beacon_req(skb);
|
|
+ break;
|
|
+ default:
|
|
+ pr_debug("Frame type is not supported yet\n");
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_SUCCESS;
|
|
+
|
|
+drop:
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_DROP;
|
|
+}
|
|
+
|
|
+static int mac802154_send_cmd(struct net_device *dev,
|
|
+ struct ieee802154_addr *addr, struct ieee802154_addr *saddr,
|
|
+ const u8 *buf, int len)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+ int hlen, tlen;
|
|
+ int err;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ hlen = LL_RESERVED_SPACE(dev);
|
|
+ tlen = dev->needed_tailroom;
|
|
+ skb = alloc_skb(len + hlen + tlen, GFP_KERNEL);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
|
|
+
|
|
+ skb_reset_network_header(skb);
|
|
+
|
|
+ mac_cb(skb)->flags = IEEE802154_FC_TYPE_MAC_CMD | MAC_CB_FLAG_ACKREQ;
|
|
+ mac_cb(skb)->seq = ieee802154_mlme_ops(dev)->get_dsn(dev);
|
|
+ err = dev_hard_header(skb, dev, ETH_P_IEEE802154, addr, saddr, len);
|
|
+ if (err < 0) {
|
|
+ kfree_skb(skb);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ skb_reset_mac_header(skb);
|
|
+ memcpy(skb_put(skb, len), buf, len);
|
|
+
|
|
+ skb->dev = dev;
|
|
+ skb->protocol = htons(ETH_P_IEEE802154);
|
|
+
|
|
+ return dev_queue_xmit(skb);
|
|
+}
|
|
+
|
|
+int mac802154_send_beacon_req(struct net_device *dev)
|
|
+{
|
|
+ struct ieee802154_addr addr;
|
|
+ struct ieee802154_addr saddr;
|
|
+ u8 cmd = IEEE802154_CMD_BEACON_REQ;
|
|
+ addr.addr_type = IEEE802154_ADDR_SHORT;
|
|
+ addr.short_addr = IEEE802154_ADDR_BROADCAST;
|
|
+ addr.pan_id = IEEE802154_PANID_BROADCAST;
|
|
+ saddr.addr_type = IEEE802154_ADDR_NONE;
|
|
+ return mac802154_send_cmd(dev, &addr, &saddr, &cmd, 1);
|
|
+}
|
|
+
|
|
+
|
|
+static int mac802154_mlme_assoc_req(struct net_device *dev,
|
|
+ struct ieee802154_addr *addr, u8 channel, u8 page, u8 cap)
|
|
+{
|
|
+ struct ieee802154_addr saddr;
|
|
+ u8 buf[2];
|
|
+ int pos = 0;
|
|
+
|
|
+ saddr.addr_type = IEEE802154_ADDR_LONG;
|
|
+ saddr.pan_id = IEEE802154_PANID_BROADCAST;
|
|
+ memcpy(saddr.hwaddr, dev->dev_addr, IEEE802154_ADDR_LEN);
|
|
+
|
|
+
|
|
+ /* FIXME: set PIB/MIB info */
|
|
+ mac802154_dev_set_pan_id(dev, addr->pan_id);
|
|
+ mac802154_dev_set_page_channel(dev, page, channel);
|
|
+ mac802154_dev_set_ieee_addr(dev);
|
|
+
|
|
+ buf[pos++] = IEEE802154_CMD_ASSOCIATION_REQ;
|
|
+ buf[pos++] = cap;
|
|
+
|
|
+ return mac802154_send_cmd(dev, addr, &saddr, buf, pos);
|
|
+}
|
|
+
|
|
+static int mac802154_mlme_assoc_resp(struct net_device *dev,
|
|
+ struct ieee802154_addr *addr, u16 short_addr, u8 status)
|
|
+{
|
|
+ struct ieee802154_addr saddr;
|
|
+ u8 buf[4];
|
|
+ int pos = 0;
|
|
+
|
|
+ saddr.addr_type = IEEE802154_ADDR_LONG;
|
|
+ saddr.pan_id = addr->pan_id;
|
|
+ memcpy(saddr.hwaddr, dev->dev_addr, IEEE802154_ADDR_LEN);
|
|
+
|
|
+ buf[pos++] = IEEE802154_CMD_ASSOCIATION_RESP;
|
|
+ buf[pos++] = short_addr;
|
|
+ buf[pos++] = short_addr >> 8;
|
|
+ buf[pos++] = status;
|
|
+
|
|
+ return mac802154_send_cmd(dev, addr, &saddr, buf, pos);
|
|
+}
|
|
+
|
|
+static int mac802154_mlme_disassoc_req(struct net_device *dev,
|
|
+ struct ieee802154_addr *addr, u8 reason)
|
|
+{
|
|
+ struct ieee802154_addr saddr;
|
|
+ u8 buf[2];
|
|
+ int pos = 0;
|
|
+ int ret;
|
|
+
|
|
+ saddr.addr_type = IEEE802154_ADDR_LONG;
|
|
+ saddr.pan_id = addr->pan_id;
|
|
+ memcpy(saddr.hwaddr, dev->dev_addr, IEEE802154_ADDR_LEN);
|
|
+
|
|
+ buf[pos++] = IEEE802154_CMD_DISASSOCIATION_NOTIFY;
|
|
+ buf[pos++] = reason;
|
|
+
|
|
+ ret = mac802154_send_cmd(dev, addr, &saddr, buf, pos);
|
|
+
|
|
+ /* FIXME: this should be after the ack receved */
|
|
+ mac802154_dev_set_pan_id(dev, 0xffff);
|
|
+ mac802154_dev_set_short_addr(dev, 0xffff);
|
|
+ ieee802154_nl_disassoc_confirm(dev, 0x00);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mac802154_mlme_start_req(struct net_device *dev,
|
|
+ struct ieee802154_addr *addr,
|
|
+ u8 channel, u8 page,
|
|
+ u8 bcn_ord, u8 sf_ord, u8 pan_coord, u8 blx,
|
|
+ u8 coord_realign)
|
|
+{
|
|
+ BUG_ON(addr->addr_type != IEEE802154_ADDR_SHORT);
|
|
+
|
|
+ mac802154_dev_set_pan_id(dev, addr->pan_id);
|
|
+ mac802154_dev_set_short_addr(dev, addr->short_addr);
|
|
+ mac802154_dev_set_ieee_addr(dev);
|
|
+ mac802154_dev_set_page_channel(dev, page, channel);
|
|
+
|
|
+ /*
|
|
+ * FIXME: add validation for unused parameters to be sane
|
|
+ * for SoftMAC
|
|
+ */
|
|
+
|
|
+ if (pan_coord)
|
|
+ dev->priv_flags |= IFF_IEEE802154_COORD;
|
|
+ else
|
|
+ dev->priv_flags &= ~IFF_IEEE802154_COORD;
|
|
+
|
|
+ mac802154_dev_set_pan_coord(dev);
|
|
+ ieee802154_nl_start_confirm(dev, IEEE802154_SUCCESS);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct ieee802154_mlme_ops mac802154_mlme_wpan = {
|
|
+ .assoc_req = mac802154_mlme_assoc_req,
|
|
+ .assoc_resp = mac802154_mlme_assoc_resp,
|
|
+ .disassoc_req = mac802154_mlme_disassoc_req,
|
|
+ .start_req = mac802154_mlme_start_req,
|
|
+ .scan_req = mac802154_mlme_scan_req,
|
|
+
|
|
+ .wpan_ops.get_phy = mac802154_get_phy,
|
|
+
|
|
+ .get_pan_id = mac802154_dev_get_pan_id,
|
|
+ .get_short_addr = mac802154_dev_get_short_addr,
|
|
+ .get_dsn = mac802154_dev_get_dsn,
|
|
+ .get_bsn = mac802154_dev_get_bsn,
|
|
+};
|
|
+
|
|
+struct simple_mlme_ops mac802154_mlme_simple = {
|
|
+ .get_phy = mac802154_get_phy,
|
|
+};
|
|
diff --git a/net/mac802154/main.c b/net/mac802154/main.c
|
|
new file mode 100644
|
|
index 0000000..f2acbcb
|
|
--- /dev/null
|
|
+++ b/net/mac802154/main.c
|
|
@@ -0,0 +1,283 @@
|
|
+/*
|
|
+ * Copyright (C) 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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/netdevice.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/nl802154.h>
|
|
+#include <linux/module.h>
|
|
+#include <net/route.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+#include "mib.h"
|
|
+
|
|
+int mac802154_slave_open(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ int res = 0;
|
|
+
|
|
+ if (priv->hw->open_count++ == 0) {
|
|
+ res = priv->hw->ops->start(&priv->hw->hw);
|
|
+ WARN_ON(res);
|
|
+ if (res)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ if (priv->hw->ops->ieee_addr) {
|
|
+ res = priv->hw->ops->ieee_addr(&priv->hw->hw, dev->dev_addr);
|
|
+ WARN_ON(res);
|
|
+ if (res)
|
|
+ goto err;
|
|
+ mac802154_dev_set_ieee_addr(dev);
|
|
+ }
|
|
+
|
|
+ netif_start_queue(dev);
|
|
+ return 0;
|
|
+err:
|
|
+ priv->hw->open_count--;
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+int mac802154_slave_close(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+
|
|
+ dev->priv_flags &= ~IFF_IEEE802154_COORD;
|
|
+
|
|
+ netif_stop_queue(dev);
|
|
+
|
|
+ if ((--priv->hw->open_count) == 0)
|
|
+ priv->hw->ops->stop(&priv->hw->hw);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int mac802154_netdev_register(struct wpan_phy *phy,
|
|
+ struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+ struct mac802154_priv *ipriv;
|
|
+ int err;
|
|
+
|
|
+ ipriv = wpan_phy_priv(phy);
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+ priv->dev = dev;
|
|
+ priv->hw = ipriv;
|
|
+
|
|
+ dev->needed_headroom = ipriv->hw.extra_tx_headroom;
|
|
+
|
|
+ SET_NETDEV_DEV(dev, &ipriv->phy->dev);
|
|
+
|
|
+ mutex_lock(&ipriv->slaves_mtx);
|
|
+ if (!ipriv->running) {
|
|
+ mutex_unlock(&ipriv->slaves_mtx);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ mutex_unlock(&ipriv->slaves_mtx);
|
|
+
|
|
+ err = register_netdev(dev);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ rtnl_lock();
|
|
+ mutex_lock(&ipriv->slaves_mtx);
|
|
+ list_add_tail_rcu(&priv->list, &ipriv->slaves);
|
|
+ mutex_unlock(&ipriv->slaves_mtx);
|
|
+ rtnl_unlock();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mac802154_del_iface(struct wpan_phy *phy,
|
|
+ struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *sdata;
|
|
+ ASSERT_RTNL();
|
|
+
|
|
+ sdata = netdev_priv(dev);
|
|
+
|
|
+ BUG_ON(sdata->hw->phy != phy);
|
|
+
|
|
+ mutex_lock(&sdata->hw->slaves_mtx);
|
|
+ list_del_rcu(&sdata->list);
|
|
+ mutex_unlock(&sdata->hw->slaves_mtx);
|
|
+
|
|
+ synchronize_rcu();
|
|
+ unregister_netdevice(sdata->dev);
|
|
+}
|
|
+
|
|
+static struct net_device *mac802154_add_iface(struct wpan_phy *phy,
|
|
+ const char *name, int type)
|
|
+{
|
|
+ struct net_device *dev;
|
|
+ int err = -ENOMEM;
|
|
+
|
|
+ switch (type) {
|
|
+ case IEEE802154_DEV_WPAN:
|
|
+ dev = alloc_netdev(sizeof(struct mac802154_sub_if_data),
|
|
+ name, mac802154_wpan_setup);
|
|
+ break;
|
|
+ case IEEE802154_DEV_MONITOR:
|
|
+ dev = alloc_netdev(sizeof(struct mac802154_sub_if_data),
|
|
+ name, mac802154_monitor_setup);
|
|
+ break;
|
|
+ case IEEE802154_DEV_SMAC:
|
|
+ dev = alloc_netdev(sizeof(struct mac802154_sub_if_data),
|
|
+ name, mac802154_smac_setup);
|
|
+ break;
|
|
+ default:
|
|
+ dev = NULL;
|
|
+ err = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+ if (!dev)
|
|
+ goto err;
|
|
+
|
|
+
|
|
+ err = mac802154_netdev_register(phy, dev);
|
|
+
|
|
+ if (err)
|
|
+ goto err_free;
|
|
+
|
|
+ dev_hold(dev); /* we return a device w/ incremented refcount */
|
|
+ return dev;
|
|
+
|
|
+err_free:
|
|
+ free_netdev(dev);
|
|
+err:
|
|
+ return ERR_PTR(err);
|
|
+}
|
|
+
|
|
+
|
|
+struct ieee802154_dev *ieee802154_alloc_device(size_t priv_size,
|
|
+ struct ieee802154_ops *ops)
|
|
+{
|
|
+ struct wpan_phy *phy;
|
|
+ struct mac802154_priv *priv;
|
|
+
|
|
+ phy = wpan_phy_alloc(ALIGN(sizeof(*priv), NETDEV_ALIGN) + priv_size);
|
|
+ if (!phy) {
|
|
+ printk(KERN_ERR
|
|
+ "Failure to initialize master IEEE802154 device\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ priv = wpan_phy_priv(phy);
|
|
+ priv->hw.phy = priv->phy = phy;
|
|
+
|
|
+ priv->hw.priv = (char *)priv + ALIGN(sizeof(*priv), NETDEV_ALIGN);
|
|
+
|
|
+ BUG_ON(!ops);
|
|
+ BUG_ON(!ops->xmit);
|
|
+ BUG_ON(!ops->ed);
|
|
+ BUG_ON(!ops->start);
|
|
+ BUG_ON(!ops->stop);
|
|
+
|
|
+ priv->ops = ops;
|
|
+
|
|
+ INIT_LIST_HEAD(&priv->slaves);
|
|
+ mutex_init(&priv->slaves_mtx);
|
|
+
|
|
+ return &priv->hw;
|
|
+}
|
|
+EXPORT_SYMBOL(ieee802154_alloc_device);
|
|
+
|
|
+void ieee802154_free_device(struct ieee802154_dev *hw)
|
|
+{
|
|
+ struct mac802154_priv *priv = mac802154_to_priv(hw);
|
|
+
|
|
+ BUG_ON(!list_empty(&priv->slaves));
|
|
+
|
|
+ wpan_phy_free(priv->phy);
|
|
+}
|
|
+EXPORT_SYMBOL(ieee802154_free_device);
|
|
+
|
|
+int ieee802154_register_device(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct mac802154_priv *priv = mac802154_to_priv(dev);
|
|
+ int rc;
|
|
+
|
|
+ priv->dev_workqueue =
|
|
+ create_singlethread_workqueue(wpan_phy_name(priv->phy));
|
|
+ if (!priv->dev_workqueue) {
|
|
+ rc = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ wpan_phy_set_dev(priv->phy, priv->hw.parent);
|
|
+
|
|
+ priv->phy->add_iface = mac802154_add_iface;
|
|
+ priv->phy->del_iface = mac802154_del_iface;
|
|
+
|
|
+ rc = wpan_phy_register(priv->phy);
|
|
+ if (rc < 0)
|
|
+ goto out_wq;
|
|
+
|
|
+ rtnl_lock();
|
|
+ mutex_lock(&priv->slaves_mtx);
|
|
+ priv->running = 1;
|
|
+ mutex_unlock(&priv->slaves_mtx);
|
|
+ rtnl_unlock();
|
|
+
|
|
+ return 0;
|
|
+
|
|
+out_wq:
|
|
+ destroy_workqueue(priv->dev_workqueue);
|
|
+out:
|
|
+ return rc;
|
|
+}
|
|
+EXPORT_SYMBOL(ieee802154_register_device);
|
|
+
|
|
+void ieee802154_unregister_device(struct ieee802154_dev *dev)
|
|
+{
|
|
+ struct mac802154_priv *priv = mac802154_to_priv(dev);
|
|
+ struct mac802154_sub_if_data *sdata, *next;
|
|
+
|
|
+
|
|
+ flush_workqueue(priv->dev_workqueue);
|
|
+ destroy_workqueue(priv->dev_workqueue);
|
|
+
|
|
+ rtnl_lock();
|
|
+
|
|
+ mutex_lock(&priv->slaves_mtx);
|
|
+ priv->running = 0;
|
|
+ mutex_unlock(&priv->slaves_mtx);
|
|
+
|
|
+ list_for_each_entry_safe(sdata, next, &priv->slaves, list) {
|
|
+ mutex_lock(&sdata->hw->slaves_mtx);
|
|
+ list_del(&sdata->list);
|
|
+ mutex_unlock(&sdata->hw->slaves_mtx);
|
|
+
|
|
+ unregister_netdevice(sdata->dev);
|
|
+ }
|
|
+
|
|
+ rtnl_unlock();
|
|
+
|
|
+ wpan_phy_unregister(priv->phy);
|
|
+}
|
|
+EXPORT_SYMBOL(ieee802154_unregister_device);
|
|
+
|
|
+MODULE_DESCRIPTION("IEEE 802.15.4 implementation");
|
|
+MODULE_LICENSE("GPL v2");
|
|
+
|
|
diff --git a/net/mac802154/mib.c b/net/mac802154/mib.c
|
|
new file mode 100644
|
|
index 0000000..23871df
|
|
--- /dev/null
|
|
+++ b/net/mac802154/mib.c
|
|
@@ -0,0 +1,249 @@
|
|
+/*
|
|
+ * Copyright 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ */
|
|
+
|
|
+#include <linux/if_arp.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+#include "mib.h"
|
|
+
|
|
+struct phy_chan_notify_work {
|
|
+ struct work_struct work;
|
|
+ struct net_device *dev;
|
|
+};
|
|
+
|
|
+struct hw_addr_filt_notify_work {
|
|
+ struct work_struct work;
|
|
+ struct net_device *dev;
|
|
+ unsigned long changed;
|
|
+};
|
|
+
|
|
+static void hw_addr_notify(struct work_struct *work)
|
|
+{
|
|
+ struct hw_addr_filt_notify_work *nw = container_of(work,
|
|
+ struct hw_addr_filt_notify_work, work);
|
|
+ struct mac802154_priv *hw = mac802154_slave_get_priv(nw->dev);
|
|
+ int res;
|
|
+
|
|
+ res = hw->ops->set_hw_addr_filt(&hw->hw,
|
|
+ &hw->hw.hw_filt, nw->changed);
|
|
+ if (res)
|
|
+ pr_debug("%s: failed changed mask %lx\n",
|
|
+ __func__, nw->changed);
|
|
+
|
|
+ kfree(nw);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static void set_hw_addr_filt(struct net_device *dev, unsigned long changed)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ struct hw_addr_filt_notify_work *work;
|
|
+
|
|
+ work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
|
+ if (!work)
|
|
+ return;
|
|
+
|
|
+ INIT_WORK(&work->work, hw_addr_notify);
|
|
+ work->dev = dev;
|
|
+ work->changed = changed;
|
|
+ queue_work(priv->hw->dev_workqueue, &work->work);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static void phy_chan_notify(struct work_struct *work)
|
|
+{
|
|
+ struct phy_chan_notify_work *nw = container_of(work,
|
|
+ struct phy_chan_notify_work, work);
|
|
+ struct mac802154_priv *hw = mac802154_slave_get_priv(nw->dev);
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(nw->dev);
|
|
+ int res;
|
|
+
|
|
+ res = hw->ops->set_channel(&hw->hw, priv->page, priv->chan);
|
|
+ if (res)
|
|
+ pr_debug("set_channel failed\n");
|
|
+
|
|
+ kfree(nw);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+u16 mac802154_dev_get_pan_id(const struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ u16 ret;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ ret = priv->pan_id;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+u16 mac802154_dev_get_short_addr(const struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ u16 ret;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ ret = priv->short_addr;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void mac802154_dev_set_pan_id(struct net_device *dev, u16 val)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ priv->pan_id = val;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ if (priv->hw->ops->set_hw_addr_filt &&
|
|
+ (priv->hw->hw.hw_filt.pan_id != priv->pan_id)) {
|
|
+ priv->hw->hw.hw_filt.pan_id = priv->pan_id;
|
|
+ set_hw_addr_filt(dev, IEEE802515_PANID_CHANGED);
|
|
+ }
|
|
+}
|
|
+
|
|
+void mac802154_dev_set_pan_coord(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ int pan_coord = !!(dev->priv_flags & IFF_IEEE802154_COORD);
|
|
+
|
|
+ if (priv->hw->ops->set_hw_addr_filt &&
|
|
+ (priv->hw->hw.hw_filt.pan_coord != pan_coord)) {
|
|
+ priv->hw->hw.hw_filt.pan_coord = pan_coord;
|
|
+ set_hw_addr_filt(dev, IEEE802515_PANC_CHANGED);
|
|
+ }
|
|
+}
|
|
+
|
|
+void mac802154_dev_set_short_addr(struct net_device *dev, u16 val)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ priv->short_addr = val;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ if (priv->hw->ops->set_hw_addr_filt &&
|
|
+ (priv->hw->hw.hw_filt.short_addr != priv->short_addr)) {
|
|
+ priv->hw->hw.hw_filt.short_addr = priv->short_addr;
|
|
+ set_hw_addr_filt(dev, IEEE802515_SADDR_CHANGED);
|
|
+ }
|
|
+}
|
|
+
|
|
+void mac802154_dev_set_ieee_addr(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+
|
|
+ if (priv->hw->ops->set_hw_addr_filt &&
|
|
+ memcmp(priv->hw->hw.hw_filt.ieee_addr,
|
|
+ dev->dev_addr, IEEE802154_ALEN)) {
|
|
+ memcpy(priv->hw->hw.hw_filt.ieee_addr,
|
|
+ dev->dev_addr, IEEE802154_ALEN);
|
|
+ set_hw_addr_filt(dev, IEEE802515_IEEEADDR_CHANGED);
|
|
+ }
|
|
+}
|
|
+
|
|
+void mac802154_dev_set_page_channel(struct net_device *dev, u8 page, u8 chan)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ struct phy_chan_notify_work *work;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ priv->page = page;
|
|
+ priv->chan = chan;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ if (priv->hw->phy->current_channel != priv->chan ||
|
|
+ priv->hw->phy->current_page != priv->page) {
|
|
+ work = kzalloc(sizeof(*work), GFP_ATOMIC);
|
|
+ if (!work)
|
|
+ return;
|
|
+
|
|
+ INIT_WORK(&work->work, phy_chan_notify);
|
|
+ work->dev = dev;
|
|
+ queue_work(priv->hw->dev_workqueue, &work->work);
|
|
+ }
|
|
+}
|
|
+
|
|
+u8 mac802154_dev_get_dsn(const struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ u16 ret;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ ret = priv->dsn++;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+u8 mac802154_dev_get_bsn(const struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ u16 ret;
|
|
+
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ ret = priv->bsn++;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+struct mac802154_priv *mac802154_slave_get_priv(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154);
|
|
+
|
|
+ return priv->hw;
|
|
+}
|
|
+
|
|
+struct wpan_phy *mac802154_get_phy(const struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ BUG_ON(dev->type != ARPHRD_IEEE802154
|
|
+ && dev->type != ARPHRD_IEEE802154_MONITOR
|
|
+ && dev->type != ARPHRD_SMAC);
|
|
+
|
|
+ return to_phy(get_device(&priv->hw->phy->dev));
|
|
+}
|
|
diff --git a/net/mac802154/mib.h b/net/mac802154/mib.h
|
|
new file mode 100644
|
|
index 0000000..228f6d0
|
|
--- /dev/null
|
|
+++ b/net/mac802154/mib.h
|
|
@@ -0,0 +1,35 @@
|
|
+/*
|
|
+ * Copyright 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef MIB802154_H
|
|
+#define MIB802154_H
|
|
+
|
|
+/* FIXME: should be dropped in favour of generic MIB API */
|
|
+u8 mac802154_dev_get_dsn(const struct net_device *dev);
|
|
+u8 mac802154_dev_get_bsn(const struct net_device *dev);
|
|
+u16 mac802154_dev_get_pan_id(const struct net_device *dev);
|
|
+u16 mac802154_dev_get_short_addr(const struct net_device *dev);
|
|
+void mac802154_dev_set_pan_id(struct net_device *dev, u16 val);
|
|
+void mac802154_dev_set_pan_coord(struct net_device *dev);
|
|
+void mac802154_dev_set_short_addr(struct net_device *dev, u16 val);
|
|
+void mac802154_dev_set_ieee_addr(struct net_device *dev);
|
|
+void mac802154_dev_set_page_channel(struct net_device *dev, u8 page, u8 chan);
|
|
+struct wpan_phy *mac802154_get_phy(const struct net_device *dev);
|
|
+
|
|
+
|
|
+#endif
|
|
diff --git a/net/mac802154/monitor.c b/net/mac802154/monitor.c
|
|
new file mode 100644
|
|
index 0000000..24a4fbb
|
|
--- /dev/null
|
|
+++ b/net/mac802154/monitor.c
|
|
@@ -0,0 +1,117 @@
|
|
+/*
|
|
+ * Copyright 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ */
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/nl802154.h>
|
|
+#include <linux/crc-ccitt.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+#include <linux/hardirq.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+
|
|
+static netdev_tx_t mac802154_monitor_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+ u8 chan, page;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+
|
|
+ /* FIXME: locking */
|
|
+ chan = priv->hw->phy->current_channel;
|
|
+ page = priv->hw->phy->current_page;
|
|
+
|
|
+ if (chan == (u8)-1) /* not init */
|
|
+ return NETDEV_TX_OK;
|
|
+
|
|
+ BUG_ON(page >= WPAN_NUM_PAGES);
|
|
+ BUG_ON(chan >= 27);
|
|
+
|
|
+ skb->skb_iif = dev->ifindex;
|
|
+ dev->stats.tx_packets++;
|
|
+ dev->stats.tx_bytes += skb->len;
|
|
+
|
|
+ return mac802154_tx(priv->hw, skb, page, chan);
|
|
+}
|
|
+
|
|
+
|
|
+void mac802154_monitors_rx(struct mac802154_priv *priv, struct sk_buff *skb)
|
|
+{
|
|
+ struct sk_buff *skb2;
|
|
+ struct mac802154_sub_if_data *sdata;
|
|
+ u16 crc = crc_ccitt(0, skb->data, skb->len);
|
|
+ u8 *data;
|
|
+
|
|
+ rcu_read_lock();
|
|
+ list_for_each_entry_rcu(sdata, &priv->slaves, list) {
|
|
+ if (sdata->type != IEEE802154_DEV_MONITOR)
|
|
+ continue;
|
|
+
|
|
+ skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
+ skb2->dev = sdata->dev;
|
|
+ skb2->pkt_type = PACKET_HOST;
|
|
+ data = skb_put(skb2, 2);
|
|
+ data[0] = crc & 0xff;
|
|
+ data[1] = crc >> 8;
|
|
+
|
|
+ if (in_interrupt())
|
|
+ netif_rx(skb2);
|
|
+ else
|
|
+ netif_rx_ni(skb2);
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
+}
|
|
+
|
|
+static const struct net_device_ops mac802154_monitor_ops = {
|
|
+ .ndo_open = mac802154_slave_open,
|
|
+ .ndo_stop = mac802154_slave_close,
|
|
+ .ndo_start_xmit = mac802154_monitor_xmit,
|
|
+};
|
|
+
|
|
+void mac802154_monitor_setup(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+
|
|
+ dev->addr_len = 0;
|
|
+ dev->features = NETIF_F_HW_CSUM;
|
|
+ dev->hard_header_len = 0;
|
|
+ dev->needed_tailroom = 2; /* FCS */
|
|
+ dev->mtu = 127;
|
|
+ dev->tx_queue_len = 10;
|
|
+ dev->type = ARPHRD_IEEE802154_MONITOR;
|
|
+ dev->flags = IFF_NOARP | IFF_BROADCAST;
|
|
+ dev->watchdog_timeo = 0;
|
|
+
|
|
+ dev->destructor = free_netdev;
|
|
+ dev->netdev_ops = &mac802154_monitor_ops;
|
|
+ dev->ml_priv = &mac802154_mlme_simple;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+ priv->type = IEEE802154_DEV_MONITOR;
|
|
+
|
|
+ priv->chan = -1; /* not initialized */
|
|
+ priv->page = 0; /* for compat */
|
|
+}
|
|
+
|
|
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
|
|
new file mode 100644
|
|
index 0000000..0e9d5d4
|
|
--- /dev/null
|
|
+++ b/net/mac802154/rx.c
|
|
@@ -0,0 +1,117 @@
|
|
+/*
|
|
+ * Copyright (C) 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Pavel Smolenskiy <pavel.smolenskiy@gmail.com>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/crc-ccitt.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/ieee802154_netdev.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+
|
|
+static void mac802154_subif_rx(struct ieee802154_dev *hw, struct sk_buff *skb)
|
|
+{
|
|
+ struct mac802154_priv *priv = mac802154_to_priv(hw);
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(struct ieee802154_mac_cb) > sizeof(skb->cb));
|
|
+ pr_debug("%s()\n", __func__);
|
|
+
|
|
+ if (!(priv->hw.flags & IEEE802154_HW_OMIT_CKSUM)) {
|
|
+ u16 crc;
|
|
+
|
|
+ if (skb->len < 2) {
|
|
+ pr_debug("%s(): Got invalid frame\n", __func__);
|
|
+ goto out;
|
|
+ }
|
|
+ crc = crc_ccitt(0, skb->data, skb->len);
|
|
+ if (crc) {
|
|
+ pr_debug("%s(): CRC mismatch\n", __func__);
|
|
+ goto out;
|
|
+ }
|
|
+ skb_trim(skb, skb->len - 2); /* CRC */
|
|
+ }
|
|
+
|
|
+ mac802154_monitors_rx(priv, skb);
|
|
+ mac802154_smacs_rx(priv, skb);
|
|
+ mac802154_wpans_rx(priv, skb);
|
|
+
|
|
+out:
|
|
+ dev_kfree_skb(skb);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static void __mac802154_rx_prepare(struct ieee802154_dev *dev,
|
|
+ struct sk_buff *skb, u8 lqi)
|
|
+{
|
|
+ BUG_ON(!skb);
|
|
+
|
|
+ mac_cb(skb)->lqi = lqi;
|
|
+
|
|
+ skb->protocol = htons(ETH_P_IEEE802154);
|
|
+
|
|
+ skb_reset_mac_header(skb);
|
|
+}
|
|
+
|
|
+void mac802154_rx(struct ieee802154_dev *dev, struct sk_buff *skb, u8 lqi)
|
|
+{
|
|
+ __mac802154_rx_prepare(dev, skb, lqi);
|
|
+
|
|
+ mac802154_subif_rx(dev, skb);
|
|
+}
|
|
+EXPORT_SYMBOL(mac802154_rx);
|
|
+
|
|
+struct rx_work {
|
|
+ struct sk_buff *skb;
|
|
+ struct work_struct work;
|
|
+ struct ieee802154_dev *dev;
|
|
+};
|
|
+
|
|
+static void mac802154_rx_worker(struct work_struct *work)
|
|
+{
|
|
+ struct rx_work *rw = container_of(work, struct rx_work, work);
|
|
+ struct sk_buff *skb = rw->skb;
|
|
+
|
|
+ mac802154_subif_rx(rw->dev, skb);
|
|
+ kfree(rw);
|
|
+}
|
|
+
|
|
+void ieee802154_rx_irqsafe(struct ieee802154_dev *dev,
|
|
+ struct sk_buff *skb, u8 lqi)
|
|
+{
|
|
+ struct mac802154_priv *priv = mac802154_to_priv(dev);
|
|
+ struct rx_work *work = kzalloc(sizeof(struct rx_work), GFP_ATOMIC);
|
|
+
|
|
+ if (!work)
|
|
+ return;
|
|
+
|
|
+ __mac802154_rx_prepare(dev, skb, lqi);
|
|
+
|
|
+ INIT_WORK(&work->work, mac802154_rx_worker);
|
|
+ work->skb = skb;
|
|
+ work->dev = dev;
|
|
+
|
|
+ queue_work(priv->dev_workqueue, &work->work);
|
|
+}
|
|
+EXPORT_SYMBOL(ieee802154_rx_irqsafe);
|
|
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
|
|
new file mode 100644
|
|
index 0000000..7c6f313
|
|
--- /dev/null
|
|
+++ b/net/mac802154/scan.c
|
|
@@ -0,0 +1,203 @@
|
|
+/*
|
|
+ * scan.c
|
|
+ *
|
|
+ * Description: MAC scan helper functions.
|
|
+ *
|
|
+ * Copyright (C) 2007, 2008 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Pavel Smolenskiy <pavel.smolenskiy@gmail.com>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ */
|
|
+#include <linux/net.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/netdevice.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/nl802154.h>
|
|
+#include <net/ieee802154.h>
|
|
+#include <net/ieee802154_netdev.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+
|
|
+/*
|
|
+ * ED scan is periodic issuing of ed device function
|
|
+ * on evry permitted channel, so it is virtually PHY-only scan */
|
|
+
|
|
+struct scan_work {
|
|
+ struct work_struct work;
|
|
+
|
|
+ int (*scan_ch)(struct scan_work *work, int channel, u8 duration);
|
|
+ struct net_device *dev;
|
|
+
|
|
+ u8 edl[27];
|
|
+
|
|
+ u8 type;
|
|
+ u32 channels;
|
|
+ u8 page;
|
|
+ u8 duration;
|
|
+};
|
|
+
|
|
+static int scan_ed(struct scan_work *work, int channel, u8 duration)
|
|
+{
|
|
+ int ret;
|
|
+ struct mac802154_priv *hw = mac802154_slave_get_priv(work->dev);
|
|
+ pr_debug("ed scan channel %d duration %d\n", channel, duration);
|
|
+ mutex_lock(&hw->phy->pib_lock);
|
|
+ ret = hw->ops->ed(&hw->hw, &work->edl[channel]);
|
|
+ mutex_unlock(&hw->phy->pib_lock);
|
|
+ pr_debug("ed scan channel %d value %d\n", channel, work->edl[channel]);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int scan_passive(struct scan_work *work, int channel, u8 duration)
|
|
+{
|
|
+ unsigned long j;
|
|
+ pr_debug("passive scan channel %d duration %d\n", channel, duration);
|
|
+
|
|
+ /* Hope 2 msecs will be enough for scan */
|
|
+ j = msecs_to_jiffies(2);
|
|
+ while (j > 0)
|
|
+ j = schedule_timeout(j);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Active scan is periodic submission of beacon request
|
|
+ * and waiting for beacons which is useful for collecting LWPAN information */
|
|
+static int scan_active(struct scan_work *work, int channel, u8 duration)
|
|
+{
|
|
+ int ret;
|
|
+ pr_debug("active scan channel %d duration %d\n", channel, duration);
|
|
+ ret = mac802154_send_beacon_req(work->dev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ return scan_passive(work, channel, duration);
|
|
+}
|
|
+
|
|
+static int scan_orphan(struct scan_work *work, int channel, u8 duration)
|
|
+{
|
|
+ pr_debug("orphan scan channel %d duration %d\n", channel, duration);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void scanner(struct work_struct *work)
|
|
+{
|
|
+ struct scan_work *sw = container_of(work, struct scan_work, work);
|
|
+ struct mac802154_priv *hw = mac802154_slave_get_priv(sw->dev);
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(sw->dev);
|
|
+ int i;
|
|
+ int ret;
|
|
+
|
|
+ for (i = 0; i < 27; i++) {
|
|
+ if (!(sw->channels & (1 << i)))
|
|
+ continue;
|
|
+
|
|
+ mutex_lock(&hw->phy->pib_lock);
|
|
+ ret = hw->ops->set_channel(&hw->hw, sw->page, i);
|
|
+ mutex_unlock(&hw->phy->pib_lock);
|
|
+ if (ret)
|
|
+ goto exit_error;
|
|
+
|
|
+ priv->chan = i;
|
|
+ priv->page = sw->page;
|
|
+
|
|
+ ret = sw->scan_ch(sw, i, sw->duration);
|
|
+ if (ret)
|
|
+ goto exit_error;
|
|
+
|
|
+ sw->channels &= ~(1 << i);
|
|
+ }
|
|
+
|
|
+ ieee802154_nl_scan_confirm(sw->dev, IEEE802154_SUCCESS, sw->type,
|
|
+ sw->channels, sw->page, sw->edl/*, NULL */);
|
|
+
|
|
+ kfree(sw);
|
|
+
|
|
+ return;
|
|
+
|
|
+exit_error:
|
|
+ ieee802154_nl_scan_confirm(sw->dev, IEEE802154_INVALID_PARAMETER,
|
|
+ sw->type, sw->channels, sw->page, NULL/*, NULL */);
|
|
+ kfree(sw);
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Alloc ed_detect list for ED scan.
|
|
+ *
|
|
+ * @param mac current mac pointer
|
|
+ * @param type type of the scan to be performed
|
|
+ * @param channels 32-bit mask of requested to scan channels
|
|
+ * @param duration scan duration, see ieee802.15.4-2003.pdf, page 145.
|
|
+ * @return 0 if request is ok, errno otherwise.
|
|
+ */
|
|
+int mac802154_mlme_scan_req(struct net_device *dev,
|
|
+ u8 type, u32 channels, u8 page, u8 duration)
|
|
+{
|
|
+ struct mac802154_priv *hw = mac802154_slave_get_priv(dev);
|
|
+ struct scan_work *work;
|
|
+
|
|
+ pr_debug("%s()\n", __func__);
|
|
+
|
|
+ if (page > WPAN_NUM_PAGES)
|
|
+ goto inval;
|
|
+ if (duration > 14)
|
|
+ goto inval;
|
|
+ if (channels & ~hw->phy->channels_supported[page])
|
|
+ goto inval;
|
|
+
|
|
+ work = kzalloc(sizeof(struct scan_work), GFP_KERNEL);
|
|
+ if (!work)
|
|
+ goto inval;
|
|
+
|
|
+ work->dev = dev;
|
|
+ work->channels = channels;
|
|
+ work->page = page;
|
|
+ work->duration = duration;
|
|
+ work->type = type;
|
|
+
|
|
+ switch (type) {
|
|
+ case IEEE802154_MAC_SCAN_ED:
|
|
+ work->scan_ch = scan_ed;
|
|
+ break;
|
|
+ case IEEE802154_MAC_SCAN_ACTIVE:
|
|
+ work->scan_ch = scan_active;
|
|
+ break;
|
|
+ case IEEE802154_MAC_SCAN_PASSIVE:
|
|
+ work->scan_ch = scan_passive;
|
|
+ break;
|
|
+ case IEEE802154_MAC_SCAN_ORPHAN:
|
|
+ work->scan_ch = scan_orphan;
|
|
+ break;
|
|
+ default:
|
|
+ pr_debug("%s(): invalid type %d\n", __func__, type);
|
|
+ goto inval;
|
|
+ }
|
|
+
|
|
+ INIT_WORK(&work->work, scanner);
|
|
+ queue_work(hw->dev_workqueue, &work->work);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+inval:
|
|
+ ieee802154_nl_scan_confirm(dev, IEEE802154_INVALID_PARAMETER, type,
|
|
+ channels, page, NULL/*, NULL */);
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
diff --git a/net/mac802154/smac.c b/net/mac802154/smac.c
|
|
new file mode 100644
|
|
index 0000000..88bf1e1
|
|
--- /dev/null
|
|
+++ b/net/mac802154/smac.c
|
|
@@ -0,0 +1,128 @@
|
|
+/*
|
|
+ * Copyright 2010 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/nl802154.h>
|
|
+#include <linux/hardirq.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+
|
|
+static const u8 smac_header[] = {0x7E, 0xFF};
|
|
+
|
|
+static netdev_tx_t mac802154_smac_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+ u8 chan, page;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+
|
|
+ /* FIXME: locking */
|
|
+ chan = priv->hw->phy->current_channel;
|
|
+ page = priv->hw->phy->current_page;
|
|
+
|
|
+ if (chan == (u8)-1) /* not init */
|
|
+ return NETDEV_TX_OK;
|
|
+
|
|
+ BUG_ON(page >= WPAN_NUM_PAGES);
|
|
+ BUG_ON(chan >= 27);
|
|
+
|
|
+ memcpy(skb_push(skb, sizeof(smac_header)), smac_header, sizeof(smac_header));
|
|
+
|
|
+ skb->skb_iif = dev->ifindex;
|
|
+ dev->stats.tx_packets++;
|
|
+ dev->stats.tx_bytes += skb->len;
|
|
+
|
|
+ return mac802154_tx(priv->hw, skb, page, chan);
|
|
+}
|
|
+
|
|
+void mac802154_smacs_rx(struct mac802154_priv *priv, struct sk_buff *skb)
|
|
+{
|
|
+ struct mac802154_sub_if_data *sdata;
|
|
+
|
|
+ if (skb->len < sizeof(smac_header))
|
|
+ return;
|
|
+
|
|
+ if (memcmp(skb->data, smac_header, sizeof(smac_header)))
|
|
+ return;
|
|
+
|
|
+ /*
|
|
+ * Currently we are the owner of the skb.
|
|
+ * We can change it's data pointers, provided we change them
|
|
+ * back at the end.
|
|
+ */
|
|
+
|
|
+ skb_pull(skb, sizeof(smac_header));
|
|
+
|
|
+ list_for_each_entry_rcu(sdata, &priv->slaves, list) {
|
|
+ struct sk_buff *skb2;
|
|
+
|
|
+ if (sdata->type != IEEE802154_DEV_SMAC)
|
|
+ continue;
|
|
+
|
|
+ skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
+ skb2->dev = sdata->dev;
|
|
+ skb2->pkt_type = PACKET_HOST;
|
|
+
|
|
+ if (in_interrupt())
|
|
+ netif_rx(skb2);
|
|
+ else
|
|
+ netif_rx_ni(skb2);
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
+
|
|
+ skb_push(skb, sizeof(smac_header));
|
|
+}
|
|
+
|
|
+static const struct net_device_ops mac802154_smac_ops = {
|
|
+ .ndo_open = mac802154_slave_open,
|
|
+ .ndo_stop = mac802154_slave_close,
|
|
+ .ndo_start_xmit = mac802154_smac_xmit,
|
|
+};
|
|
+
|
|
+void mac802154_smac_setup(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+
|
|
+ dev->addr_len = 0;
|
|
+ dev->features = NETIF_F_HW_CSUM;
|
|
+ dev->hard_header_len = 2;
|
|
+ dev->needed_tailroom = 2; /* FCS */
|
|
+ dev->mtu = 123; /* 127 - 2 (FCS) - 2 (header) */
|
|
+ dev->tx_queue_len = 10;
|
|
+ dev->type = ARPHRD_SMAC;
|
|
+ dev->flags = IFF_NOARP | IFF_BROADCAST;
|
|
+ dev->watchdog_timeo = 0;
|
|
+
|
|
+ dev->destructor = free_netdev;
|
|
+ dev->netdev_ops = &mac802154_smac_ops;
|
|
+ dev->ml_priv = &mac802154_mlme_simple;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+ priv->type = IEEE802154_DEV_SMAC;
|
|
+
|
|
+ priv->chan = -1; /* not initialized */
|
|
+ priv->page = 0; /* for compat */
|
|
+}
|
|
+
|
|
diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c
|
|
new file mode 100644
|
|
index 0000000..0703195
|
|
--- /dev/null
|
|
+++ b/net/mac802154/tx.c
|
|
@@ -0,0 +1,106 @@
|
|
+/*
|
|
+ * Copyright 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ */
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/crc-ccitt.h>
|
|
+
|
|
+#include <net/mac802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+
|
|
+struct xmit_work {
|
|
+ struct sk_buff *skb;
|
|
+ struct work_struct work;
|
|
+ struct mac802154_priv *priv;
|
|
+ u8 page;
|
|
+ u8 chan;
|
|
+};
|
|
+
|
|
+static void mac802154_xmit_worker(struct work_struct *work)
|
|
+{
|
|
+ struct xmit_work *xw = container_of(work, struct xmit_work, work);
|
|
+ int res;
|
|
+
|
|
+ BUG_ON(xw->chan == (u8)-1);
|
|
+
|
|
+ mutex_lock(&xw->priv->phy->pib_lock);
|
|
+ if (xw->priv->phy->current_channel != xw->chan ||
|
|
+ xw->priv->phy->current_page != xw->page) {
|
|
+ res = xw->priv->ops->set_channel(&xw->priv->hw,
|
|
+ xw->page,
|
|
+ xw->chan);
|
|
+ if (res) {
|
|
+ pr_debug("set_channel failed\n");
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ res = xw->priv->ops->xmit(&xw->priv->hw, xw->skb);
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&xw->priv->phy->pib_lock);
|
|
+
|
|
+ /* FIXME: result processing and/or requeue!!! */
|
|
+ dev_kfree_skb(xw->skb);
|
|
+
|
|
+ kfree(xw);
|
|
+}
|
|
+
|
|
+netdev_tx_t mac802154_tx(struct mac802154_priv *priv, struct sk_buff *skb,
|
|
+ u8 page, u8 chan)
|
|
+{
|
|
+ struct xmit_work *work;
|
|
+
|
|
+ if (WARN_ON(!(priv->phy->channels_supported[page] &
|
|
+ (1 << chan))))
|
|
+ return NETDEV_TX_OK;
|
|
+
|
|
+ mac802154_monitors_rx(mac802154_to_priv(&priv->hw), skb);
|
|
+
|
|
+ if (!(priv->hw.flags & IEEE802154_HW_OMIT_CKSUM)) {
|
|
+ u16 crc = crc_ccitt(0, skb->data, skb->len);
|
|
+ u8 *data = skb_put(skb, 2);
|
|
+ data[0] = crc & 0xff;
|
|
+ data[1] = crc >> 8;
|
|
+ }
|
|
+
|
|
+ if (skb_cow_head(skb, priv->hw.extra_tx_headroom)) {
|
|
+ dev_kfree_skb(skb);
|
|
+ return NETDEV_TX_OK;
|
|
+ }
|
|
+
|
|
+ work = kzalloc(sizeof(struct xmit_work), GFP_ATOMIC);
|
|
+ if (!work)
|
|
+ return NETDEV_TX_BUSY;
|
|
+
|
|
+ INIT_WORK(&work->work, mac802154_xmit_worker);
|
|
+ work->skb = skb;
|
|
+ work->priv = priv;
|
|
+ work->page = page;
|
|
+ work->chan = chan;
|
|
+
|
|
+ queue_work(priv->dev_workqueue, &work->work);
|
|
+
|
|
+ return NETDEV_TX_OK;
|
|
+}
|
|
diff --git a/net/mac802154/wpan.c b/net/mac802154/wpan.c
|
|
new file mode 100644
|
|
index 0000000..d5369a8
|
|
--- /dev/null
|
|
+++ b/net/mac802154/wpan.c
|
|
@@ -0,0 +1,631 @@
|
|
+/*
|
|
+ * Copyright 2007, 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
|
+ */
|
|
+
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/capability.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/rculist.h>
|
|
+#include <linux/random.h>
|
|
+#include <linux/crc-ccitt.h>
|
|
+#include <linux/nl802154.h>
|
|
+#include <linux/hardirq.h>
|
|
+
|
|
+#include <net/rtnetlink.h>
|
|
+#include <net/af_ieee802154.h>
|
|
+#include <net/mac802154.h>
|
|
+#include <net/ieee802154_netdev.h>
|
|
+#include <net/ieee802154.h>
|
|
+#include <net/wpan-phy.h>
|
|
+
|
|
+#include "mac802154.h"
|
|
+#include "mib.h"
|
|
+
|
|
+static netdev_tx_t mac802154_wpan_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+ u8 chan, page;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ chan = priv->chan;
|
|
+ page = priv->page;
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+
|
|
+ if (chan == (u8)-1) /* not init */
|
|
+ return NETDEV_TX_OK;
|
|
+
|
|
+ BUG_ON(page >= WPAN_NUM_PAGES);
|
|
+ BUG_ON(chan >= 27);
|
|
+
|
|
+ skb->skb_iif = dev->ifindex;
|
|
+ dev->stats.tx_packets++;
|
|
+ dev->stats.tx_bytes += skb->len;
|
|
+
|
|
+ return mac802154_tx(priv->hw, skb, page, chan);
|
|
+}
|
|
+
|
|
+static int mac802154_wpan_ioctl(struct net_device *dev, struct ifreq *ifr,
|
|
+ int cmd)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+ struct sockaddr_ieee802154 *sa =
|
|
+ (struct sockaddr_ieee802154 *)&ifr->ifr_addr;
|
|
+ int err = -ENOIOCTLCMD;
|
|
+
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+
|
|
+ switch (cmd) {
|
|
+ case SIOCGIFADDR:
|
|
+ if (priv->pan_id == IEEE802154_PANID_BROADCAST ||
|
|
+ priv->short_addr == IEEE802154_ADDR_BROADCAST) {
|
|
+ err = -EADDRNOTAVAIL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ sa->family = AF_IEEE802154;
|
|
+ sa->addr.addr_type = IEEE802154_ADDR_SHORT;
|
|
+ sa->addr.pan_id = priv->pan_id;
|
|
+ sa->addr.short_addr = priv->short_addr;
|
|
+
|
|
+ err = 0;
|
|
+ break;
|
|
+ case SIOCSIFADDR:
|
|
+ dev_warn(&dev->dev,
|
|
+ "Using DEBUGing ioctl SIOCSIFADDR isn't recommened!\n");
|
|
+ if (sa->family != AF_IEEE802154 ||
|
|
+ sa->addr.addr_type != IEEE802154_ADDR_SHORT ||
|
|
+ sa->addr.pan_id == IEEE802154_PANID_BROADCAST ||
|
|
+ sa->addr.short_addr == IEEE802154_ADDR_BROADCAST ||
|
|
+ sa->addr.short_addr == IEEE802154_ADDR_UNDEF) {
|
|
+ err = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ priv->pan_id = sa->addr.pan_id;
|
|
+ priv->short_addr = sa->addr.short_addr;
|
|
+ err = 0;
|
|
+ break;
|
|
+ }
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int mac802154_wpan_mac_addr(struct net_device *dev, void *p)
|
|
+{
|
|
+ struct sockaddr *addr = p;
|
|
+
|
|
+ if (netif_running(dev))
|
|
+ return -EBUSY;
|
|
+ /* FIXME: validate addr */
|
|
+ memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
|
|
+ mac802154_dev_set_ieee_addr(dev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mac802154_haddr_copy_swap(u8 *dest, const u8 *src)
|
|
+{
|
|
+ int i;
|
|
+ for (i = 0; i < IEEE802154_ADDR_LEN; i++)
|
|
+ dest[IEEE802154_ADDR_LEN - i - 1] = src[i];
|
|
+}
|
|
+
|
|
+static int mac802154_header_create(struct sk_buff *skb,
|
|
+ struct net_device *dev,
|
|
+ unsigned short type, const void *_daddr,
|
|
+ const void *_saddr, unsigned len)
|
|
+{
|
|
+ u8 head[24] = {};
|
|
+ int pos = 0;
|
|
+
|
|
+ u16 fc;
|
|
+ const struct ieee802154_addr *saddr = _saddr;
|
|
+ const struct ieee802154_addr *daddr = _daddr;
|
|
+ struct ieee802154_addr dev_addr;
|
|
+ struct mac802154_sub_if_data *priv = netdev_priv(dev);
|
|
+
|
|
+ fc = mac_cb_type(skb);
|
|
+ if (mac_cb_is_ackreq(skb))
|
|
+ fc |= IEEE802154_FC_ACK_REQ;
|
|
+
|
|
+ pos = 2;
|
|
+
|
|
+ head[pos++] = mac_cb(skb)->seq; /* DSN/BSN */
|
|
+
|
|
+ if (!daddr)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!saddr) {
|
|
+ spin_lock_bh(&priv->mib_lock);
|
|
+ if (priv->short_addr == IEEE802154_ADDR_BROADCAST ||
|
|
+ priv->short_addr == IEEE802154_ADDR_UNDEF ||
|
|
+ priv->pan_id == IEEE802154_PANID_BROADCAST) {
|
|
+ dev_addr.addr_type = IEEE802154_ADDR_LONG;
|
|
+ memcpy(dev_addr.hwaddr, dev->dev_addr,
|
|
+ IEEE802154_ADDR_LEN);
|
|
+ } else {
|
|
+ dev_addr.addr_type = IEEE802154_ADDR_SHORT;
|
|
+ dev_addr.short_addr = priv->short_addr;
|
|
+ }
|
|
+
|
|
+ dev_addr.pan_id = priv->pan_id;
|
|
+ saddr = &dev_addr;
|
|
+
|
|
+ spin_unlock_bh(&priv->mib_lock);
|
|
+ }
|
|
+
|
|
+ if (daddr->addr_type != IEEE802154_ADDR_NONE) {
|
|
+ fc |= (daddr->addr_type << IEEE802154_FC_DAMODE_SHIFT);
|
|
+
|
|
+ head[pos++] = daddr->pan_id & 0xff;
|
|
+ head[pos++] = daddr->pan_id >> 8;
|
|
+
|
|
+ if (daddr->addr_type == IEEE802154_ADDR_SHORT) {
|
|
+ head[pos++] = daddr->short_addr & 0xff;
|
|
+ head[pos++] = daddr->short_addr >> 8;
|
|
+ } else {
|
|
+ mac802154_haddr_copy_swap(head + pos, daddr->hwaddr);
|
|
+ pos += IEEE802154_ADDR_LEN;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (saddr->addr_type != IEEE802154_ADDR_NONE) {
|
|
+ fc |= (saddr->addr_type << IEEE802154_FC_SAMODE_SHIFT);
|
|
+
|
|
+ if ((saddr->pan_id == daddr->pan_id) &&
|
|
+ (saddr->pan_id != IEEE802154_PANID_BROADCAST))
|
|
+ /* PANID compression/ intra PAN */
|
|
+ fc |= IEEE802154_FC_INTRA_PAN;
|
|
+ else {
|
|
+ head[pos++] = saddr->pan_id & 0xff;
|
|
+ head[pos++] = saddr->pan_id >> 8;
|
|
+ }
|
|
+
|
|
+ if (saddr->addr_type == IEEE802154_ADDR_SHORT) {
|
|
+ head[pos++] = saddr->short_addr & 0xff;
|
|
+ head[pos++] = saddr->short_addr >> 8;
|
|
+ } else {
|
|
+ mac802154_haddr_copy_swap(head + pos, saddr->hwaddr);
|
|
+ pos += IEEE802154_ADDR_LEN;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ head[0] = fc;
|
|
+ head[1] = fc >> 8;
|
|
+
|
|
+ memcpy(skb_push(skb, pos), head, pos);
|
|
+
|
|
+ return pos;
|
|
+}
|
|
+
|
|
+static int mac802154_header_parse(const struct sk_buff *skb,
|
|
+ unsigned char *haddr)
|
|
+{
|
|
+ const u8 *hdr = skb_mac_header(skb), *tail = skb_tail_pointer(skb);
|
|
+ struct ieee802154_addr *addr = (struct ieee802154_addr *)haddr;
|
|
+ u16 fc;
|
|
+ int da_type;
|
|
+
|
|
+ if (hdr + 3 > tail)
|
|
+ goto malformed;
|
|
+
|
|
+ fc = hdr[0] | (hdr[1] << 8);
|
|
+
|
|
+ hdr += 3;
|
|
+
|
|
+ da_type = IEEE802154_FC_DAMODE(fc);
|
|
+ addr->addr_type = IEEE802154_FC_SAMODE(fc);
|
|
+
|
|
+ switch (da_type) {
|
|
+ case IEEE802154_ADDR_NONE:
|
|
+ if (fc & IEEE802154_FC_INTRA_PAN)
|
|
+ goto malformed;
|
|
+ break;
|
|
+
|
|
+ case IEEE802154_ADDR_LONG:
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ if (fc & IEEE802154_FC_INTRA_PAN) {
|
|
+ addr->pan_id = hdr[0] | (hdr[1] << 8);
|
|
+ hdr += 2;
|
|
+ }
|
|
+
|
|
+ if (hdr + IEEE802154_ADDR_LEN > tail)
|
|
+ goto malformed;
|
|
+ hdr += IEEE802154_ADDR_LEN;
|
|
+ break;
|
|
+
|
|
+ case IEEE802154_ADDR_SHORT:
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ if (fc & IEEE802154_FC_INTRA_PAN) {
|
|
+ addr->pan_id = hdr[0] | (hdr[1] << 8);
|
|
+ hdr += 2;
|
|
+ }
|
|
+
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ hdr += 2;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ goto malformed;
|
|
+
|
|
+ }
|
|
+
|
|
+ switch (addr->addr_type) {
|
|
+ case IEEE802154_ADDR_NONE:
|
|
+ break;
|
|
+
|
|
+ case IEEE802154_ADDR_LONG:
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ if (!(fc & IEEE802154_FC_INTRA_PAN)) {
|
|
+ addr->pan_id = hdr[0] | (hdr[1] << 8);
|
|
+ hdr += 2;
|
|
+ }
|
|
+
|
|
+ if (hdr + IEEE802154_ADDR_LEN > tail)
|
|
+ goto malformed;
|
|
+ mac802154_haddr_copy_swap(addr->hwaddr, hdr);
|
|
+ hdr += IEEE802154_ADDR_LEN;
|
|
+ break;
|
|
+
|
|
+ case IEEE802154_ADDR_SHORT:
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ if (!(fc & IEEE802154_FC_INTRA_PAN)) {
|
|
+ addr->pan_id = hdr[0] | (hdr[1] << 8);
|
|
+ hdr += 2;
|
|
+ }
|
|
+
|
|
+ if (hdr + 2 > tail)
|
|
+ goto malformed;
|
|
+ addr->short_addr = hdr[0] | (hdr[1] << 8);
|
|
+ hdr += 2;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ goto malformed;
|
|
+
|
|
+ }
|
|
+
|
|
+ return sizeof(struct ieee802154_addr);
|
|
+
|
|
+malformed:
|
|
+ pr_debug("malformed packet\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct header_ops mac802154_header_ops = {
|
|
+ .create = mac802154_header_create,
|
|
+ .parse = mac802154_header_parse,
|
|
+};
|
|
+
|
|
+static const struct net_device_ops mac802154_wpan_ops = {
|
|
+ .ndo_open = mac802154_slave_open,
|
|
+ .ndo_stop = mac802154_slave_close,
|
|
+ .ndo_start_xmit = mac802154_wpan_xmit,
|
|
+ .ndo_do_ioctl = mac802154_wpan_ioctl,
|
|
+ .ndo_set_mac_address = mac802154_wpan_mac_addr,
|
|
+};
|
|
+
|
|
+void mac802154_wpan_setup(struct net_device *dev)
|
|
+{
|
|
+ struct mac802154_sub_if_data *priv;
|
|
+
|
|
+ dev->addr_len = IEEE802154_ADDR_LEN;
|
|
+ memset(dev->broadcast, 0xff, IEEE802154_ADDR_LEN);
|
|
+ dev->features = NETIF_F_HW_CSUM;
|
|
+ dev->hard_header_len = 2 + 1 + 20 + 14;
|
|
+ dev->header_ops = &mac802154_header_ops;
|
|
+ dev->needed_tailroom = 2; /* FCS */
|
|
+ dev->mtu = 127;
|
|
+ dev->tx_queue_len = 10;
|
|
+ dev->type = ARPHRD_IEEE802154;
|
|
+ dev->flags = IFF_NOARP | IFF_BROADCAST;
|
|
+ dev->watchdog_timeo = 0;
|
|
+
|
|
+ dev->destructor = free_netdev;
|
|
+ dev->netdev_ops = &mac802154_wpan_ops;
|
|
+ dev->ml_priv = &mac802154_mlme_wpan.wpan_ops;
|
|
+
|
|
+ priv = netdev_priv(dev);
|
|
+ priv->type = IEEE802154_DEV_WPAN;
|
|
+
|
|
+ priv->chan = -1; /* not initialized */
|
|
+ priv->page = 0; /* for compat */
|
|
+
|
|
+ spin_lock_init(&priv->mib_lock);
|
|
+
|
|
+ get_random_bytes(&priv->bsn, 1);
|
|
+ get_random_bytes(&priv->dsn, 1);
|
|
+
|
|
+ priv->pan_id = IEEE802154_PANID_BROADCAST;
|
|
+ priv->short_addr = IEEE802154_ADDR_BROADCAST;
|
|
+}
|
|
+
|
|
+static int mac802154_process_ack(struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ pr_debug("got ACK for SEQ=%d\n", mac_cb(skb)->seq);
|
|
+
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_SUCCESS;
|
|
+}
|
|
+
|
|
+static int mac802154_process_data(struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ if (in_interrupt())
|
|
+ return netif_rx(skb);
|
|
+ else
|
|
+ return netif_rx_ni(skb);
|
|
+}
|
|
+
|
|
+static int mac802154_subif_frame(struct mac802154_sub_if_data *sdata,
|
|
+ struct sk_buff *skb)
|
|
+{
|
|
+ pr_debug("%s Getting packet via slave interface %s\n",
|
|
+ __func__, sdata->dev->name);
|
|
+
|
|
+ spin_lock_bh(&sdata->mib_lock);
|
|
+
|
|
+ switch (mac_cb(skb)->da.addr_type) {
|
|
+ case IEEE802154_ADDR_NONE:
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_NONE)
|
|
+ /* FIXME: check if we are PAN coordinator :) */
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ else
|
|
+ /* ACK comes with both addresses empty */
|
|
+ skb->pkt_type = PACKET_HOST;
|
|
+ break;
|
|
+ case IEEE802154_ADDR_LONG:
|
|
+ if (mac_cb(skb)->da.pan_id != sdata->pan_id &&
|
|
+ mac_cb(skb)->da.pan_id != IEEE802154_PANID_BROADCAST)
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ else if (!memcmp(mac_cb(skb)->da.hwaddr, sdata->dev->dev_addr,
|
|
+ IEEE802154_ADDR_LEN))
|
|
+ skb->pkt_type = PACKET_HOST;
|
|
+ else
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ break;
|
|
+ case IEEE802154_ADDR_SHORT:
|
|
+ if (mac_cb(skb)->da.pan_id != sdata->pan_id &&
|
|
+ mac_cb(skb)->da.pan_id != IEEE802154_PANID_BROADCAST)
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ else if (mac_cb(skb)->da.short_addr == sdata->short_addr)
|
|
+ skb->pkt_type = PACKET_HOST;
|
|
+ else if (mac_cb(skb)->da.short_addr ==
|
|
+ IEEE802154_ADDR_BROADCAST)
|
|
+ skb->pkt_type = PACKET_BROADCAST;
|
|
+ else
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ spin_unlock_bh(&sdata->mib_lock);
|
|
+
|
|
+ skb->dev = sdata->dev;
|
|
+
|
|
+ if (skb->pkt_type == PACKET_HOST && mac_cb_is_ackreq(skb) &&
|
|
+ !(sdata->hw->hw.flags & IEEE802154_HW_AACK))
|
|
+ dev_warn(&sdata->dev->dev,
|
|
+ "ACK requested, however AACK not supported.\n");
|
|
+
|
|
+ switch (mac_cb_type(skb)) {
|
|
+ case IEEE802154_FC_TYPE_BEACON:
|
|
+ return mac802154_process_beacon(sdata->dev, skb);
|
|
+ case IEEE802154_FC_TYPE_ACK:
|
|
+ return mac802154_process_ack(sdata->dev, skb);
|
|
+ case IEEE802154_FC_TYPE_MAC_CMD:
|
|
+ return mac802154_process_cmd(sdata->dev, skb);
|
|
+ case IEEE802154_FC_TYPE_DATA:
|
|
+ return mac802154_process_data(sdata->dev, skb);
|
|
+ default:
|
|
+ pr_warning("ieee802154: Bad frame received (type = %d)\n",
|
|
+ mac_cb_type(skb));
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_DROP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static u8 fetch_skb_u8(struct sk_buff *skb)
|
|
+{
|
|
+ u8 ret;
|
|
+
|
|
+ BUG_ON(skb->len < 1);
|
|
+
|
|
+ ret = skb->data[0];
|
|
+ skb_pull(skb, 1);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static u16 fetch_skb_u16(struct sk_buff *skb)
|
|
+{
|
|
+ u16 ret;
|
|
+
|
|
+ BUG_ON(skb->len < 2);
|
|
+
|
|
+ ret = skb->data[0] + (skb->data[1] * 256);
|
|
+ skb_pull(skb, 2);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void fetch_skb_u64(struct sk_buff *skb, u8 *dest)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ BUG_ON(skb->len < IEEE802154_ADDR_LEN);
|
|
+
|
|
+ for (i = 0; i < IEEE802154_ADDR_LEN; i++)
|
|
+ dest[IEEE802154_ADDR_LEN - i - 1] = skb->data[i];
|
|
+ skb_pull(skb, IEEE802154_ADDR_LEN);
|
|
+}
|
|
+
|
|
+#define IEEE802154_FETCH_U8(skb, var) \
|
|
+ do { \
|
|
+ if (skb->len < 1) \
|
|
+ goto exit_error; \
|
|
+ var = fetch_skb_u8(skb); \
|
|
+ } while (0)
|
|
+
|
|
+#define IEEE802154_FETCH_U16(skb, var) \
|
|
+ do { \
|
|
+ if (skb->len < 2) \
|
|
+ goto exit_error; \
|
|
+ var = fetch_skb_u16(skb); \
|
|
+ } while (0)
|
|
+
|
|
+#define IEEE802154_FETCH_U64(skb, var) \
|
|
+ do { \
|
|
+ if (skb->len < IEEE802154_ADDR_LEN) \
|
|
+ goto exit_error; \
|
|
+ fetch_skb_u64(skb, var); \
|
|
+ } while (0)
|
|
+
|
|
+static int parse_frame_start(struct sk_buff *skb)
|
|
+{
|
|
+ u8 *head = skb->data;
|
|
+ u16 fc;
|
|
+
|
|
+ if (skb->len < 3) {
|
|
+ pr_debug("frame size %d bytes is too short\n", skb->len);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ IEEE802154_FETCH_U16(skb, fc);
|
|
+ IEEE802154_FETCH_U8(skb, mac_cb(skb)->seq);
|
|
+
|
|
+ pr_debug("%s: %04x dsn%02x\n", __func__, fc, head[2]);
|
|
+
|
|
+ mac_cb(skb)->flags = IEEE802154_FC_TYPE(fc);
|
|
+
|
|
+ if (fc & IEEE802154_FC_ACK_REQ) {
|
|
+ pr_debug("%s(): ACKNOWLEDGE required\n", __func__);
|
|
+ mac_cb(skb)->flags |= MAC_CB_FLAG_ACKREQ;
|
|
+ }
|
|
+
|
|
+ if (fc & IEEE802154_FC_SECEN)
|
|
+ mac_cb(skb)->flags |= MAC_CB_FLAG_SECEN;
|
|
+
|
|
+ if (fc & IEEE802154_FC_INTRA_PAN)
|
|
+ mac_cb(skb)->flags |= MAC_CB_FLAG_INTRAPAN;
|
|
+
|
|
+ /* TODO */
|
|
+ if (mac_cb_is_secen(skb)) {
|
|
+ pr_info("security support is not implemented\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ mac_cb(skb)->sa.addr_type = IEEE802154_FC_SAMODE(fc);
|
|
+ if (mac_cb(skb)->sa.addr_type == IEEE802154_ADDR_NONE)
|
|
+ pr_debug("%s(): src addr_type is NONE\n", __func__);
|
|
+
|
|
+ mac_cb(skb)->da.addr_type = IEEE802154_FC_DAMODE(fc);
|
|
+ if (mac_cb(skb)->da.addr_type == IEEE802154_ADDR_NONE)
|
|
+ pr_debug("%s(): dst addr_type is NONE\n", __func__);
|
|
+
|
|
+ if (IEEE802154_FC_TYPE(fc) == IEEE802154_FC_TYPE_ACK) {
|
|
+ /* ACK can only have NONE-type addresses */
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_NONE ||
|
|
+ mac_cb(skb)->da.addr_type != IEEE802154_ADDR_NONE)
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (mac_cb(skb)->da.addr_type != IEEE802154_ADDR_NONE) {
|
|
+ IEEE802154_FETCH_U16(skb, mac_cb(skb)->da.pan_id);
|
|
+
|
|
+ if (mac_cb_is_intrapan(skb)) { /* ! panid compress */
|
|
+ pr_debug("%s(): src IEEE802154_FC_INTRA_PAN\n",
|
|
+ __func__);
|
|
+ mac_cb(skb)->sa.pan_id = mac_cb(skb)->da.pan_id;
|
|
+ pr_debug("%s(): src PAN address %04x\n",
|
|
+ __func__, mac_cb(skb)->sa.pan_id);
|
|
+ }
|
|
+
|
|
+ pr_debug("%s(): dst PAN address %04x\n",
|
|
+ __func__, mac_cb(skb)->da.pan_id);
|
|
+
|
|
+ if (mac_cb(skb)->da.addr_type == IEEE802154_ADDR_SHORT) {
|
|
+ IEEE802154_FETCH_U16(skb, mac_cb(skb)->da.short_addr);
|
|
+ pr_debug("%s(): dst SHORT address %04x\n",
|
|
+ __func__, mac_cb(skb)->da.short_addr);
|
|
+
|
|
+ } else {
|
|
+ IEEE802154_FETCH_U64(skb, mac_cb(skb)->da.hwaddr);
|
|
+ pr_debug("%s(): dst hardware addr\n", __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type != IEEE802154_ADDR_NONE) {
|
|
+ pr_debug("%s(): got src non-NONE address\n", __func__);
|
|
+ if (!(mac_cb_is_intrapan(skb))) { /* ! panid compress */
|
|
+ IEEE802154_FETCH_U16(skb, mac_cb(skb)->sa.pan_id);
|
|
+ pr_debug("%s(): src IEEE802154_FC_INTRA_PAN\n",
|
|
+ __func__);
|
|
+ }
|
|
+
|
|
+ if (mac_cb(skb)->sa.addr_type == IEEE802154_ADDR_SHORT) {
|
|
+ IEEE802154_FETCH_U16(skb, mac_cb(skb)->sa.short_addr);
|
|
+ pr_debug("%s(): src IEEE802154_ADDR_SHORT\n",
|
|
+ __func__);
|
|
+ } else {
|
|
+ IEEE802154_FETCH_U64(skb, mac_cb(skb)->sa.hwaddr);
|
|
+ pr_debug("%s(): src hardware addr\n", __func__);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+exit_error:
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+void mac802154_wpans_rx(struct mac802154_priv *priv, struct sk_buff *skb)
|
|
+{
|
|
+ int ret;
|
|
+ struct mac802154_sub_if_data *sdata;
|
|
+ struct sk_buff *skb2;
|
|
+
|
|
+ ret = parse_frame_start(skb); /* 3 bytes pulled after this */
|
|
+ if (ret) {
|
|
+ pr_debug("%s(): Got invalid frame\n", __func__);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ pr_debug("%s() frame %d\n", __func__, mac_cb_type(skb));
|
|
+
|
|
+ rcu_read_lock();
|
|
+ list_for_each_entry_rcu(sdata, &priv->slaves, list)
|
|
+ {
|
|
+ if (sdata->type != IEEE802154_DEV_WPAN)
|
|
+ continue;
|
|
+
|
|
+ skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
+ if (skb2)
|
|
+ mac802154_subif_frame(sdata, skb2);
|
|
+ }
|
|
+
|
|
+ rcu_read_unlock();
|
|
+}
|
|
+
|
|
diff --git a/net/zigbee/Kconfig b/net/zigbee/Kconfig
|
|
new file mode 100644
|
|
index 0000000..85134a9
|
|
--- /dev/null
|
|
+++ b/net/zigbee/Kconfig
|
|
@@ -0,0 +1,7 @@
|
|
+config ZIGBEE
|
|
+ tristate "ZigBee Low-Rate Wireless Personal Area Networks support (EXPERIMENTAL)"
|
|
+ depends on EXPERIMENTAL && BROKEN
|
|
+ ---help---
|
|
+
|
|
+ Say Y here to compile ZigBee support into the kernel or say M to
|
|
+ compile it as modules.
|
|
diff --git a/net/zigbee/Makefile b/net/zigbee/Makefile
|
|
new file mode 100644
|
|
index 0000000..8c2eee5
|
|
--- /dev/null
|
|
+++ b/net/zigbee/Makefile
|
|
@@ -0,0 +1,5 @@
|
|
+obj-$(CONFIG_ZIGBEE) += af_zb.o
|
|
+
|
|
+af_zb-objs := af_zigbee.o dgram.o
|
|
+
|
|
+EXTRA_CFLAGS += -Wall -DEXPORT_SYMTAB -DCONFIG_FFD -DCONFIG_ZIGBEE_DEBUG -DIEEE80215_DEBUG -DDEBUG
|
|
diff --git a/net/zigbee/af_zigbee.c b/net/zigbee/af_zigbee.c
|
|
new file mode 100644
|
|
index 0000000..820d668
|
|
--- /dev/null
|
|
+++ b/net/zigbee/af_zigbee.c
|
|
@@ -0,0 +1,285 @@
|
|
+/*
|
|
+ * ZigBee socket interface
|
|
+ *
|
|
+ * Copyright 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Maxim Yu. Osipov <Maksim.Osipov@siemens.com>
|
|
+ */
|
|
+#include <linux/net.h>
|
|
+#include <linux/capability.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/if.h>
|
|
+#include <linux/termios.h> /* For TIOCOUTQ/INQ */
|
|
+#include <linux/list.h>
|
|
+#include <net/datalink.h>
|
|
+#include <net/psnap.h>
|
|
+#include <net/sock.h>
|
|
+#include <net/tcp_states.h>
|
|
+#include <net/route.h>
|
|
+
|
|
+//#include <net/ieee80215/af_ieee80215.h>
|
|
+#include <net/zigbee/af_zigbee.h>
|
|
+#include <net/zigbee/nwk.h>
|
|
+
|
|
+#define DBG_DUMP(data, len) { \
|
|
+ int i; \
|
|
+ pr_debug("file %s: function: %s: data: len %d:\n", __FILE__, __func__, len); \
|
|
+ for (i = 0; i < len; i++) {\
|
|
+ pr_debug("%02x: %02x\n", i, (data)[i]); \
|
|
+ } \
|
|
+}
|
|
+
|
|
+static int zb_sock_release(struct socket *sock)
|
|
+{
|
|
+ struct sock *sk = sock->sk;
|
|
+
|
|
+ if (sk) {
|
|
+ sock->sk = NULL;
|
|
+ sk->sk_prot->close(sk, 0);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+static int zb_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len)
|
|
+{
|
|
+ struct sock *sk = sock->sk;
|
|
+
|
|
+ return sk->sk_prot->sendmsg(iocb, sk, msg, len);
|
|
+}
|
|
+
|
|
+static int zb_sock_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
|
+{
|
|
+ struct sock *sk = sock->sk;
|
|
+
|
|
+ if (sk->sk_prot->bind)
|
|
+ return sk->sk_prot->bind(sk, uaddr, addr_len);
|
|
+
|
|
+ return sock_no_bind(sock, uaddr, addr_len);
|
|
+}
|
|
+
|
|
+static int zb_sock_connect(struct socket *sock, struct sockaddr *uaddr,
|
|
+ int addr_len, int flags)
|
|
+{
|
|
+ struct sock *sk = sock->sk;
|
|
+
|
|
+ if (uaddr->sa_family == AF_UNSPEC)
|
|
+ return sk->sk_prot->disconnect(sk, flags);
|
|
+
|
|
+ return sk->sk_prot->connect(sk, uaddr, addr_len);
|
|
+}
|
|
+
|
|
+#if 0
|
|
+static int zb_dev_ioctl(struct sock *sk, struct ifreq __user *arg, unsigned int cmd)
|
|
+{
|
|
+ struct ifreq ifr;
|
|
+ int ret = -EINVAL;
|
|
+ struct net_device *dev;
|
|
+
|
|
+ if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ ifr.ifr_name[IFNAMSIZ-1] = 0;
|
|
+
|
|
+ dev_load(sock_net(sk), ifr.ifr_name);
|
|
+ dev = dev_get_by_name(sock_net(sk), ifr.ifr_name);
|
|
+ if (dev->type == ARPHRD_ZIGBEE || dev->type == ARPHRD_ZIGBEE_PHY)
|
|
+ ret = dev->do_ioctl(dev, &ifr, cmd);
|
|
+
|
|
+ if (!ret && copy_to_user(arg, &ifr, sizeof(struct ifreq)))
|
|
+ ret = -EFAULT;
|
|
+ dev_put(dev);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int zb_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct sock *sk = sock->sk;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case SIOCGSTAMP:
|
|
+ return sock_get_timestamp(sk, (struct timeval __user *)arg);
|
|
+ case SIOCGSTAMPNS:
|
|
+ return sock_get_timestampns(sk, (struct timespec __user *)arg);
|
|
+#if 0
|
|
+ case SIOCGIFADDR:
|
|
+ case SIOCSIFADDR:
|
|
+ return zb_dev_ioctl(sk, (struct ifreq __user *)arg, cmd);
|
|
+#endif
|
|
+ default:
|
|
+ if (!sk->sk_prot->ioctl)
|
|
+ return -ENOIOCTLCMD;
|
|
+ return sk->sk_prot->ioctl(sk, cmd, arg);
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct proto_ops zb_dgram_ops = {
|
|
+ .family = PF_ZIGBEE,
|
|
+ .owner = THIS_MODULE,
|
|
+ .release = zb_sock_release,
|
|
+ .bind = zb_sock_bind,
|
|
+ .connect = zb_sock_connect,
|
|
+ .socketpair = sock_no_socketpair,
|
|
+ .accept = sock_no_accept,
|
|
+ .getname = sock_no_getname,
|
|
+ .poll = datagram_poll,
|
|
+ .ioctl = zb_sock_ioctl,
|
|
+ .listen = sock_no_listen,
|
|
+ .shutdown = sock_no_shutdown,
|
|
+ .setsockopt = sock_common_setsockopt,
|
|
+ .getsockopt = sock_common_getsockopt,
|
|
+ .sendmsg = zb_sock_sendmsg,
|
|
+ .recvmsg = sock_common_recvmsg,
|
|
+ .mmap = sock_no_mmap,
|
|
+ .sendpage = sock_no_sendpage,
|
|
+#ifdef CONFIG_COMPAT
|
|
+ .compat_setsockopt = compat_sock_common_setsockopt,
|
|
+ .compat_getsockopt = compat_sock_common_getsockopt,
|
|
+#endif
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * Create a socket. Initialise the socket, blank the addresses
|
|
+ * set the state.
|
|
+ */
|
|
+static int zb_create(struct net *net, struct socket *sock, int protocol)
|
|
+{
|
|
+ struct sock *sk;
|
|
+ int rc;
|
|
+ struct proto *proto;
|
|
+ const struct proto_ops *ops;
|
|
+
|
|
+ // FIXME: init_net
|
|
+ if (net != &init_net)
|
|
+ return -EAFNOSUPPORT;
|
|
+
|
|
+ if (sock->type == SOCK_DGRAM) {
|
|
+ proto = &zb_dgram_prot;
|
|
+ ops = &zb_dgram_ops;
|
|
+ }
|
|
+ else {
|
|
+ rc = -ESOCKTNOSUPPORT;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ rc = -ENOMEM;
|
|
+ sk = sk_alloc(net, PF_ZIGBEE, GFP_KERNEL, proto);
|
|
+ if (!sk)
|
|
+ goto out;
|
|
+ rc = 0;
|
|
+
|
|
+ sock->ops = ops;
|
|
+
|
|
+ sock_init_data(sock, sk);
|
|
+ // FIXME: sk->sk_destruct
|
|
+ sk->sk_family = PF_ZIGBEE;
|
|
+
|
|
+#if 0
|
|
+ /* Checksums on by default */
|
|
+ // FIXME:
|
|
+ sock_set_flag(sk, SOCK_ZAPPED);
|
|
+
|
|
+ // FIXME:
|
|
+ if (sk->sk_prot->hash)
|
|
+ sk->sk_prot->hash(sk);
|
|
+#endif
|
|
+
|
|
+ if (sk->sk_prot->init) {
|
|
+ rc = sk->sk_prot->init(sk);
|
|
+ if (rc)
|
|
+ sk_common_release(sk);
|
|
+ }
|
|
+out:
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static struct net_proto_family zb_family_ops = {
|
|
+ .family = PF_ZIGBEE,
|
|
+ .create = zb_create,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Main ZigBEE NWK receive routine.
|
|
+ */
|
|
+static int zb_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
|
|
+{
|
|
+ struct nwkhdr *nwkh;
|
|
+ u32 len;
|
|
+
|
|
+ DBG_DUMP(skb->data, skb->len);
|
|
+ pr_debug("got frame, type %d, dev %p\n", dev->type, dev);
|
|
+ // FIXME: init_net
|
|
+ if (!net_eq(dev_net(dev), &init_net))
|
|
+ goto drop;
|
|
+
|
|
+ zb_raw_deliver(dev, skb);
|
|
+
|
|
+ if (skb->pkt_type != PACKET_OTHERHOST)
|
|
+ return zb_dgram_deliver(dev, skb);
|
|
+
|
|
+drop:
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_DROP;
|
|
+}
|
|
+
|
|
+
|
|
+static struct packet_type zb_packet_type = {
|
|
+ .type = __constant_htons(ETH_P_ZIGBEE),
|
|
+ .func = zb_rcv,
|
|
+};
|
|
+
|
|
+static int __init af_zb_init(void)
|
|
+{
|
|
+ int rc = -EINVAL;
|
|
+
|
|
+ rc = proto_register(&zb_dgram_prot, 1);
|
|
+ if (rc)
|
|
+ goto err;
|
|
+
|
|
+ /* Tell SOCKET that we are alive */
|
|
+ rc = sock_register(&zb_family_ops);
|
|
+
|
|
+ if (rc)
|
|
+ goto err;
|
|
+
|
|
+ dev_add_pack(&zb_packet_type);
|
|
+
|
|
+ rc = 0;
|
|
+ goto out;
|
|
+
|
|
+err:
|
|
+ proto_unregister(&zb_dgram_prot);
|
|
+out:
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void af_zb_remove(void)
|
|
+{
|
|
+ dev_remove_pack(&zb_packet_type);
|
|
+ sock_unregister(PF_ZIGBEE);
|
|
+ proto_unregister(&zb_dgram_prot);
|
|
+}
|
|
+
|
|
+module_init(af_zb_init);
|
|
+module_exit(af_zb_remove);
|
|
+
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_ALIAS_NETPROTO(PF_ZIGBEE);
|
|
diff --git a/net/zigbee/dgram.c b/net/zigbee/dgram.c
|
|
new file mode 100644
|
|
index 0000000..4d9b682
|
|
--- /dev/null
|
|
+++ b/net/zigbee/dgram.c
|
|
@@ -0,0 +1,401 @@
|
|
+/*
|
|
+ * ZigBee socket interface
|
|
+ *
|
|
+ * Copyright 2008, 2009 Siemens AG
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ * Written by:
|
|
+ * Sergey Lapin <slapin@ossfans.org>
|
|
+ * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
|
+ * Maxim Yu. Osipov <Maksim.Osipov@siemens.com>
|
|
+ */
|
|
+
|
|
+#include <linux/net.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/list.h>
|
|
+#include <net/sock.h>
|
|
+//#include <net/ieee80215/netdev.h>
|
|
+//#include <net/ieee80215/af_ieee80215.h>
|
|
+//#include <net/ieee80215/mac_def.h>
|
|
+#include <net/zigbee/nwk.h>
|
|
+#include <asm/ioctls.h>
|
|
+
|
|
+static HLIST_HEAD(dgram_head);
|
|
+static DEFINE_RWLOCK(dgram_lock);
|
|
+
|
|
+struct dgram_sock {
|
|
+ struct sock sk;
|
|
+
|
|
+ int bound;
|
|
+ struct ieee80215_addr src_addr;
|
|
+ struct ieee80215_addr dst_addr;
|
|
+};
|
|
+
|
|
+static void dgram_hash(struct sock *sk)
|
|
+{
|
|
+ write_lock_bh(&dgram_lock);
|
|
+ sk_add_node(sk, &dgram_head);
|
|
+ sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
|
|
+ write_unlock_bh(&dgram_lock);
|
|
+}
|
|
+
|
|
+static void dgram_unhash(struct sock *sk)
|
|
+{
|
|
+ write_lock_bh(&dgram_lock);
|
|
+ if (sk_del_node_init(sk))
|
|
+ sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
|
|
+ write_unlock_bh(&dgram_lock);
|
|
+}
|
|
+
|
|
+static int dgram_init(struct sock *sk)
|
|
+{
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+
|
|
+ ro->dst_addr.addr_type = IEEE80215_ADDR_SHORT;
|
|
+// ro->dst_addr.pan_id = 0xffff;
|
|
+ ro->dst_addr.short_addr = 0xffff;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void dgram_close(struct sock *sk, long timeout)
|
|
+{
|
|
+ sk_common_release(sk);
|
|
+}
|
|
+
|
|
+static int dgram_bind(struct sock *sk, struct sockaddr *uaddr, int len)
|
|
+{
|
|
+ struct sockaddr_ieee80215 *addr = (struct sockaddr_ieee80215 *)uaddr;
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+ int err = 0;
|
|
+
|
|
+ if (ro->bound)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (len < sizeof(*addr))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if ((addr->family != AF_ZIGBEE) ||
|
|
+ (addr->addr.addr_type != IEEE80215_ADDR_SHORT))
|
|
+ return -EINVAL;
|
|
+
|
|
+ lock_sock(sk);
|
|
+ /*
|
|
+ * FIXME: should check here that address is not already in use
|
|
+ * Problem that this address is not independent - this is the
|
|
+ * address of the lower layer
|
|
+ */
|
|
+#if 0
|
|
+ dev = ieee80215_get_dev(sock_net(sk), &addr->addr);
|
|
+ if (!dev) {
|
|
+ err = -ENODEV;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (dev->type != ARPHRD_IEEE80215) {
|
|
+ err = -ENODEV;
|
|
+ goto out_put;
|
|
+ }
|
|
+#endif
|
|
+ memcpy(&ro->src_addr, &addr->addr, sizeof(struct ieee80215_addr));
|
|
+
|
|
+ ro->bound = 1;
|
|
+#if 0
|
|
+out_put:
|
|
+ dev_put(dev);
|
|
+out:
|
|
+#endif
|
|
+ release_sock(sk);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int dgram_ioctl(struct sock *sk, int cmd, unsigned long arg)
|
|
+{
|
|
+ switch (cmd) {
|
|
+ case SIOCOUTQ:
|
|
+ {
|
|
+ int amount = atomic_read(&sk->sk_wmem_alloc);
|
|
+ return put_user(amount, (int __user *)arg);
|
|
+ }
|
|
+
|
|
+ case SIOCINQ:
|
|
+ {
|
|
+ struct sk_buff *skb;
|
|
+ unsigned long amount = 0;
|
|
+
|
|
+ spin_lock_bh(&sk->sk_receive_queue.lock);
|
|
+ skb = skb_peek(&sk->sk_receive_queue);
|
|
+ if (skb != NULL) {
|
|
+ /*
|
|
+ * We will only return the amount
|
|
+ * of this packet since that is all
|
|
+ * that will be read.
|
|
+ */
|
|
+ amount = skb->len - sizeof(struct nwkhdr);
|
|
+ }
|
|
+ spin_unlock_bh(&sk->sk_receive_queue.lock);
|
|
+ return put_user(amount, (int __user *)arg);
|
|
+
|
|
+ }
|
|
+#if 0
|
|
+ /* May be implement here the commands */
|
|
+ case IEEE80215_SIOC_NETWORK_DISCOVERY:
|
|
+ return ioctl_network_discovery(sk, (struct ieee80215_user_data __user *) arg);
|
|
+ break;
|
|
+ case IEEE80215_SIOC_NETWORK_FORMATION:
|
|
+ return ioctl_network_formation(sk, (struct ieee80215_user_data __user *) arg);
|
|
+ break;
|
|
+ case IEEE80215_SIOC_PERMIT_JOINING:
|
|
+ return ioctl_permit_joining(sk, (struct ieee80215_user_data __user *) arg);
|
|
+ break;
|
|
+ case IEEE80215_SIOC_START_ROUTER:
|
|
+ return ioctl_start_router(sk, (struct ieee80215_user_data __user *) arg);
|
|
+ break;
|
|
+ case IEEE80215_SIOC_JOIN:
|
|
+ return ioctl_mac_join(sk, (struct ieee80215_user_data __user *) arg);
|
|
+ break;
|
|
+ case IEEE80215_SIOC_MAC_CMD:
|
|
+ return ioctl_mac_cmd(sk, (struct ieee80215_user_data __user *) arg);
|
|
+
|
|
+ break;
|
|
+#endif
|
|
+ default:
|
|
+ return -ENOIOCTLCMD;
|
|
+ }
|
|
+}
|
|
+
|
|
+// FIXME: autobind
|
|
+static int dgram_connect(struct sock *sk, struct sockaddr *uaddr,
|
|
+ int len)
|
|
+{
|
|
+ struct sockaddr_ieee80215 *addr = (struct sockaddr_ieee80215 *)uaddr;
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+
|
|
+ int err = 0;
|
|
+
|
|
+ if (len < sizeof(*addr))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if ((addr->family != AF_ZIGBEE) ||
|
|
+ (addr->addr.addr_type != IEEE80215_ADDR_SHORT))
|
|
+ return -EINVAL;
|
|
+
|
|
+ lock_sock(sk);
|
|
+
|
|
+ if (!ro->bound) {
|
|
+ err = -ENETUNREACH;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ memcpy(&ro->dst_addr, &addr->addr, sizeof(struct ieee80215_addr));
|
|
+
|
|
+out:
|
|
+ release_sock(sk);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int dgram_disconnect(struct sock *sk, int flags)
|
|
+{
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+
|
|
+ lock_sock(sk);
|
|
+
|
|
+ ro->dst_addr.addr_type = IEEE80215_ADDR_SHORT;
|
|
+// ro->dst_addr.pan_id = 0xffff;
|
|
+ ro->dst_addr.short_addr = 0xffff;
|
|
+
|
|
+ release_sock(sk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dgram_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
|
|
+ size_t size)
|
|
+{
|
|
+ struct net_device *dev;
|
|
+ unsigned mtu;
|
|
+ struct sk_buff *skb;
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+
|
|
+ int err;
|
|
+ struct ieee80215_priv *hw;
|
|
+
|
|
+ if (msg->msg_flags & MSG_OOB) {
|
|
+ pr_debug("msg->msg_flags = 0x%x\n", msg->msg_flags);
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ if (!ro->bound)
|
|
+ dev = dev_getfirstbyhwtype(sock_net(sk), ARPHRD_IEEE80215);
|
|
+ else
|
|
+ dev = ieee80215_get_dev(sock_net(sk), &ro->src_addr);
|
|
+
|
|
+ if (!dev) {
|
|
+ pr_debug("no dev\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+ hw = ieee80215_slave_get_hw(dev);
|
|
+ mtu = dev->mtu;
|
|
+ pr_debug("name = %s, mtu = %u\n", dev->name, mtu);
|
|
+
|
|
+ skb = sock_alloc_send_skb(sk, LL_ALLOCATED_SPACE(dev) + size, msg->msg_flags & MSG_DONTWAIT,
|
|
+ &err);
|
|
+ if (!skb) {
|
|
+ dev_put(dev);
|
|
+ return err;
|
|
+ }
|
|
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
|
|
+
|
|
+ skb_reset_network_header(skb);
|
|
+
|
|
+ MAC_CB(skb)->flags = IEEE80215_FC_TYPE_DATA | MAC_CB_FLAG_ACKREQ;
|
|
+ MAC_CB(skb)->seq = hw->dsn;
|
|
+ err = dev_hard_header(skb, dev, ETH_P_IEEE80215, &ro->dst_addr, ro->bound ? &ro->src_addr : NULL, size);
|
|
+ if (err < 0) {
|
|
+ kfree_skb(skb);
|
|
+ dev_put(dev);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ skb_reset_mac_header(skb);
|
|
+
|
|
+ err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
|
|
+ if (err < 0) {
|
|
+ kfree_skb(skb);
|
|
+ dev_put(dev);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ if (size > mtu) {
|
|
+ pr_debug("size = %u, mtu = %u\n", size, mtu);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ skb->dev = dev;
|
|
+ skb->sk = sk;
|
|
+ skb->protocol = htons(ETH_P_IEEE80215);
|
|
+
|
|
+ err = dev_queue_xmit(skb);
|
|
+ hw->dsn++;
|
|
+
|
|
+ dev_put(dev);
|
|
+
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ return size;
|
|
+}
|
|
+
|
|
+static int dgram_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
|
|
+ size_t len, int noblock, int flags, int *addr_len)
|
|
+{
|
|
+ size_t copied = 0;
|
|
+ int err = -EOPNOTSUPP;
|
|
+ struct sk_buff *skb;
|
|
+
|
|
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
|
|
+ if (!skb)
|
|
+ goto out;
|
|
+
|
|
+ copied = skb->len;
|
|
+ if (len < copied) {
|
|
+ msg->msg_flags |= MSG_TRUNC;
|
|
+ copied = len;
|
|
+ }
|
|
+
|
|
+ // FIXME: skip headers if necessary ?!
|
|
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
|
+ if (err)
|
|
+ goto done;
|
|
+
|
|
+ sock_recv_timestamp(msg, sk, skb);
|
|
+
|
|
+ if (flags & MSG_TRUNC)
|
|
+ copied = skb->len;
|
|
+done:
|
|
+ skb_free_datagram(sk, skb);
|
|
+out:
|
|
+ if (err)
|
|
+ return err;
|
|
+ return copied;
|
|
+}
|
|
+
|
|
+static int dgram_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
|
+{
|
|
+ if (sock_queue_rcv_skb(sk, skb) < 0) {
|
|
+ atomic_inc(&sk->sk_drops);
|
|
+ kfree_skb(skb);
|
|
+ return NET_RX_DROP;
|
|
+ }
|
|
+
|
|
+ return NET_RX_SUCCESS;
|
|
+}
|
|
+
|
|
+int ieee80215_dgram_deliver(struct net_device *dev, struct sk_buff *skb)
|
|
+{
|
|
+ struct sock *sk, *prev = NULL;
|
|
+ struct hlist_node*node;
|
|
+ int ret = NET_RX_SUCCESS;
|
|
+
|
|
+ /* Data frame processing */
|
|
+
|
|
+ read_lock(&dgram_lock);
|
|
+ sk_for_each(sk, node, &dgram_head) {
|
|
+ struct dgram_sock *ro = container_of(sk, struct dgram_sock, sk);
|
|
+ if (!ro->bound ||
|
|
+ (ro->src_addr.addr_type == IEEE80215_ADDR_LONG &&
|
|
+ !memcmp(ro->src_addr.hwaddr, dev->dev_addr, IEEE80215_ADDR_LEN)) ||
|
|
+ (ro->src_addr.addr_type == IEEE80215_ADDR_SHORT &&
|
|
+ ieee80215_dev_get_pan_id(dev) == ro->src_addr.pan_id &&
|
|
+ ieee80215_dev_get_short_addr(dev) == ro->src_addr.short_addr)) {
|
|
+ if (prev) {
|
|
+ struct sk_buff *clone;
|
|
+ clone = skb_clone(skb, GFP_ATOMIC);
|
|
+ if (clone)
|
|
+ dgram_rcv_skb(prev, clone);
|
|
+ }
|
|
+
|
|
+ prev = sk;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (prev)
|
|
+ dgram_rcv_skb(prev, skb);
|
|
+ else {
|
|
+ kfree_skb(skb);
|
|
+ ret = NET_RX_DROP;
|
|
+ }
|
|
+ read_unlock(&dgram_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+struct proto ieee80215_dgram_prot = {
|
|
+ .name = "ZigBEE",
|
|
+ .owner = THIS_MODULE,
|
|
+ .obj_size = sizeof(struct dgram_sock),
|
|
+ .init = dgram_init,
|
|
+ .close = dgram_close,
|
|
+ .bind = dgram_bind,
|
|
+ .sendmsg = dgram_sendmsg,
|
|
+ .recvmsg = dgram_recvmsg,
|
|
+ .hash = dgram_hash,
|
|
+ .unhash = dgram_unhash,
|
|
+ .connect = dgram_connect,
|
|
+ .disconnect = dgram_disconnect,
|
|
+ .ioctl = dgram_ioctl,
|
|
+};
|
|
+
|
|
--
|
|
1.7.9.5
|
|
|