1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2024-11-18 13:56:15 +02:00
openwrt-xburst/target/linux/xburst/patches-3.3/800-WPAN-new-files.patch

9482 lines
246 KiB
Diff
Raw Normal View History

From 942f9ce3dd8dde01c501f7d7840700637eb2d285 Mon Sep 17 00:00:00 2001
2012-06-08 05:49:34 +03:00
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 ++++++++++++++++++++++++++++
2012-06-08 05:49:34 +03:00
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(+)
2012-06-08 05:49:34 +03:00
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, &reg);
+ 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, &reg);
+ 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
2012-06-08 05:49:34 +03:00
--- /dev/null
+++ b/drivers/ieee802154/spi_atusb.c
@@ -0,0 +1,751 @@
2012-06-08 05:49:34 +03:00
+/*
+ * 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>
2012-06-08 05:49:34 +03:00
+#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);
2012-06-08 05:49:34 +03:00
+
+ /* 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)), &gts, 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
2012-06-08 05:49:34 +03:00