From 942f9ce3dd8dde01c501f7d7840700637eb2d285 Mon Sep 17 00:00:00 2001 From: Xiangfu Date: Tue, 5 Jun 2012 11:32:52 +0800 Subject: [PATCH 2/3] 001 --- drivers/ieee802154/adf7242.c | 1034 +++++++++++++++++++++++++++++++++++++++ drivers/ieee802154/at86rf230.c | 872 +++++++++++++++++++++++++++++++++ drivers/ieee802154/at86rf230.h | 211 ++++++++ drivers/ieee802154/cc2420.c | 859 ++++++++++++++++++++++++++++++++ drivers/ieee802154/fakelb.c | 311 ++++++++++++ drivers/ieee802154/serial.c | 1047 ++++++++++++++++++++++++++++++++++++++++ drivers/ieee802154/spi_atben.c | 421 ++++++++++++++++ drivers/ieee802154/spi_atusb.c | 751 ++++++++++++++++++++++++++++ include/linux/if_ieee802154.h | 6 + include/linux/spi/at86rf230.h | 34 ++ include/net/mac802154.h | 156 ++++++ net/mac802154/Kconfig | 24 + net/mac802154/Makefile | 6 + net/mac802154/beacon.c | 285 +++++++++++ net/mac802154/beacon_hash.c | 106 ++++ net/mac802154/beacon_hash.h | 41 ++ net/mac802154/mac802154.h | 126 +++++ net/mac802154/mac_cmd.c | 365 ++++++++++++++ net/mac802154/main.c | 283 +++++++++++ net/mac802154/mib.c | 249 ++++++++++ net/mac802154/mib.h | 35 ++ net/mac802154/monitor.c | 117 +++++ net/mac802154/rx.c | 117 +++++ net/mac802154/scan.c | 203 ++++++++ net/mac802154/smac.c | 128 +++++ net/mac802154/tx.c | 106 ++++ net/mac802154/wpan.c | 631 ++++++++++++++++++++++++ net/zigbee/Kconfig | 7 + net/zigbee/Makefile | 5 + net/zigbee/af_zigbee.c | 285 +++++++++++ net/zigbee/dgram.c | 401 +++++++++++++++ 31 files changed, 9222 insertions(+) create mode 100644 drivers/ieee802154/adf7242.c create mode 100644 drivers/ieee802154/at86rf230.c create mode 100644 drivers/ieee802154/at86rf230.h create mode 100644 drivers/ieee802154/cc2420.c create mode 100644 drivers/ieee802154/fakelb.c create mode 100644 drivers/ieee802154/serial.c create mode 100644 drivers/ieee802154/spi_atben.c create mode 100644 drivers/ieee802154/spi_atusb.c create mode 100644 include/linux/if_ieee802154.h create mode 100644 include/linux/spi/at86rf230.h create mode 100644 include/net/mac802154.h create mode 100644 net/mac802154/Kconfig create mode 100644 net/mac802154/Makefile create mode 100644 net/mac802154/beacon.c create mode 100644 net/mac802154/beacon_hash.c create mode 100644 net/mac802154/beacon_hash.h create mode 100644 net/mac802154/mac802154.h create mode 100644 net/mac802154/mac_cmd.c create mode 100644 net/mac802154/main.c create mode 100644 net/mac802154/mib.c create mode 100644 net/mac802154/mib.h create mode 100644 net/mac802154/monitor.c create mode 100644 net/mac802154/rx.c create mode 100644 net/mac802154/scan.c create mode 100644 net/mac802154/smac.c create mode 100644 net/mac802154/tx.c create mode 100644 net/mac802154/wpan.c create mode 100644 net/zigbee/Kconfig create mode 100644 net/zigbee/Makefile create mode 100644 net/zigbee/af_zigbee.c create mode 100644 net/zigbee/dgram.c diff --git a/drivers/ieee802154/adf7242.c b/drivers/ieee802154/adf7242.c new file mode 100644 index 0000000..b578a55 --- /dev/null +++ b/drivers/ieee802154/adf7242.c @@ -0,0 +1,1034 @@ +/* + * Analog Devices ADF7242 Low-Power IEEE 802.15.4 Transceiver + * + * Copyright 2009-2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * DEBUG LEVEL + * 0 OFF + * 1 INFO + * 2 INFO + TRACE + */ + +#define ADF_DEBUG 0 +#define DBG(n, args...) do { if (ADF_DEBUG >= (n)) pr_debug(args); } while (0) + +#define FIRMWARE "adf7242_firmware.bin" +#define MAX_POLL_LOOPS 10 + +/* All Registers */ + +#define REG_EXT_CTRL 0x100 /* RW External LNA/PA and internal PA control configuration bits */ +#define REG_TX_FSK_TEST 0x101 /* RW TX FSK test mode configuration */ +#define REG_CCA1 0x105 /* RW RSSI threshold for CCA */ +#define REG_CCA2 0x106 /* RW CCA mode configuration */ +#define REG_BUFFERCFG 0x107 /* RW RX_BUFFER overwrite control */ +#define REG_PKT_CFG 0x108 /* RW FCS evaluation configuration */ +#define REG_DELAYCFG0 0x109 /* RW RC_RX command to SFD or sync word search delay */ +#define REG_DELAYCFG1 0x10A /* RW RC_TX command to TX state */ +#define REG_DELAYCFG2 0x10B /* RW Mac delay extention */ +#define REG_SYNC_WORD0 0x10C /* RW sync word bits [7:0] of [23:0] */ +#define REG_SYNC_WORD1 0x10D /* RW sync word bits [15:8] of [23:0] */ +#define REG_SYNC_WORD2 0x10E /* RW sync word bits [23:16] of [23:0] */ +#define REG_SYNC_CONFIG 0x10F /* RW sync word configuration */ +#define REG_RC_CFG 0x13E /* RW RX / TX packet configuration */ +#define REG_RC_VAR44 0x13F /* RW RESERVED */ +#define REG_CH_FREQ0 0x300 /* RW Channel Frequency Settings - Low Byte */ +#define REG_CH_FREQ1 0x301 /* RW Channel Frequency Settings - Middle Byte */ +#define REG_CH_FREQ2 0x302 /* RW Channel Frequency Settings - 2 MSBs */ +#define REG_TX_FD 0x304 /* RW TX Frequency Deviation Register */ +#define REG_DM_CFG0 0x305 /* RW RX Discriminator BW Register */ +#define REG_TX_M 0x306 /* RW TX Mode Register */ +#define REG_RX_M 0x307 /* RW RX Mode Register */ +#define REG_RRB 0x30C /* R RSSI Readback Register */ +#define REG_LRB 0x30D /* R Link Quality Readback Register */ +#define REG_DR0 0x30E /* RW bits [15:8] of [15:0] for data rate setting */ +#define REG_DR1 0x30F /* RW bits [7:0] of [15:0] for data rate setting */ +#define REG_PRAMPG 0x313 /* RW RESERVED */ +#define REG_TXPB 0x314 /* RW TX Packet Storage Base Address */ +#define REG_RXPB 0x315 /* RW RX Packet Storage Base Address */ +#define REG_TMR_CFG0 0x316 /* RW Wake up Timer Configuration Register - High Byte */ +#define REG_TMR_CFG1 0x317 /* RW Wake up Timer Configuration Register - Low Byte */ +#define REG_TMR_RLD0 0x318 /* RW Wake up Timer Value Register - High Byte */ +#define REG_TMR_RLD1 0x319 /* RW Wake up Timer Value Register - Low Byte */ +#define REG_TMR_CTRL 0x31A /* RW Wake up Timer Timeout flag */ +#define REG_PD_AUX 0x31E /* RW Battmon enable */ +#define REG_GP_CFG 0x32C /* RW GPIO Configuration */ +#define REG_GP_OUT 0x32D /* RW GPIO Configuration */ +#define REG_GP_IN 0x32E /* R GPIO Configuration */ +#define REG_SYNT 0x335 /* RW bandwidth calibration timers */ +#define REG_CAL_CFG 0x33D /* RW Calibration Settings */ +#define REG_SYNT_CAL 0x371 /* RW Oscillator and Doubler Configuration */ +#define REG_IIRF_CFG 0x389 /* RW BB Filter Decimation Rate */ +#define REG_CDR_CFG 0x38A /* RW CDR kVCO */ +#define REG_DM_CFG1 0x38B /* RW Postdemodulator Filter */ +#define REG_AGCSTAT 0x38E /* R RXBB Ref Osc Calibration Engine Readback */ +#define REG_RXCAL0 0x395 /* RW RX BB filter tuning, LSB */ +#define REG_RXCAL1 0x396 /* RW RX BB filter tuning, MSB */ +#define REG_RXFE_CFG 0x39B /* RW RXBB Ref Osc & RXFE Calibration */ +#define REG_PA_RR 0x3A7 /* RW Set PA ramp rate */ +#define REG_PA_CFG 0x3A8 /* RW PA enable */ +#define REG_EXTPA_CFG 0x3A9 /* RW External PA BIAS DAC */ +#define REG_EXTPA_MSC 0x3AA /* RW PA Bias Mode */ +#define REG_ADC_RBK 0x3AE /* R Readback temp */ +#define REG_AGC_CFG1 0x3B2 /* RW GC Parameters */ +#define REG_AGC_MAX 0x3B4 /* RW Slew rate */ +#define REG_AGC_CFG2 0x3B6 /* RW RSSI Parameters */ +#define REG_AGC_CFG3 0x3B7 /* RW RSSI Parameters */ +#define REG_AGC_CFG4 0x3B8 /* RW RSSI Parameters */ +#define REG_AGC_CFG5 0x3B9 /* RW RSSI & NDEC Parameters */ +#define REG_AGC_CFG6 0x3BA /* RW NDEC Parameters */ +#define REG_OCL_CFG1 0x3C4 /* RW OCL System Parameters */ +#define REG_IRQ1_EN0 0x3C7 /* RW Interrupt Mask set bits [7:0] of [15:0] for IRQ1 */ +#define REG_IRQ1_EN1 0x3C8 /* RW Interrupt Mask set bits [15:8] of [15:0] for IRQ1 */ +#define REG_IRQ2_EN0 0x3C9 /* RW Interrupt Mask set bits [7:0] of [15:0] for IRQ2 */ +#define REG_IRQ2_EN1 0x3CA /* RW Interrupt Mask set bits [15:8] of [15:0] for IRQ2 */ +#define REG_IRQ1_SRC0 0x3CB /* RW Interrupt Source bits [7:0] of [15:0] for IRQ */ +#define REG_IRQ1_SRC1 0x3CC /* RW Interrupt Source bits [15:8] of [15:0] for IRQ */ +#define REG_OCL_BW0 0x3D2 /* RW OCL System Parameters */ +#define REG_OCL_BW1 0x3D3 /* RW OCL System Parameters */ +#define REG_OCL_BW2 0x3D4 /* RW OCL System Parameters */ +#define REG_OCL_BW3 0x3D5 /* RW OCL System Parameters */ +#define REG_OCL_BW4 0x3D6 /* RW OCL System Parameters */ +#define REG_OCL_BWS 0x3D7 /* RW OCL System Parameters */ +#define REG_OCL_CFG13 0x3E0 /* RW OCL System Parameters */ +#define REG_GP_DRV 0x3E3 /* RW I/O pads Configuration and bg trim */ +#define REG_BM_CFG 0x3E6 /* RW Battery Monitor Threshold Voltage setting */ +#define REG_SFD_15_4 0x3F4 /* RW Option to set non standard SFD */ +#define REG_AFC_CFG 0x3F7 /* RW AFC mode and polarity */ +#define REG_AFC_KI_KP 0x3F8 /* RW AFC ki and kp */ +#define REG_AFC_RANGE 0x3F9 /* RW AFC range */ +#define REG_AFC_READ 0x3FA /* RW Readback frequency error */ + +#define REG_PAN_ID0 0x112 +#define REG_PAN_ID1 0x113 +#define REG_SHORT_ADDR_0 0x114 +#define REG_SHORT_ADDR_1 0x115 +#define REG_IEEE_ADDR_0 0x116 +#define REG_IEEE_ADDR_1 0x117 +#define REG_IEEE_ADDR_2 0x118 +#define REG_IEEE_ADDR_3 0x119 +#define REG_IEEE_ADDR_4 0x11A +#define REG_IEEE_ADDR_5 0x11B +#define REG_IEEE_ADDR_6 0x11C +#define REG_IEEE_ADDR_7 0x11D +#define REG_FFILT_CFG 0x11E +#define REG_AUTO_CFG 0x11F +#define REG_AUTO_TX1 0x120 +#define REG_AUTO_TX2 0x121 +#define REG_AUTO_STATUS 0x122 + +/* REG_FFILT_CFG */ +#define ACCEPT_BEACON_FRAMES (1 << 0) +#define ACCEPT_DATA_FRAMES (1 << 1) +#define ACCEPT_ACK_FRAMES (1 << 2) +#define ACCEPT_MACCMD_FRAMES (1 << 3) +#define ACCEPT_RESERVED_FRAMES (1 << 4) +#define ACCEPT_ALL_ADDRESS (1 << 5) + +/* REG_AUTO_CFG */ +#define AUTO_ACK_FRAMEPEND (1 << 0) +#define IS_PANCOORD (1 << 1) +#define RX_AUTO_ACK_EN (1 << 3) +#define CSMA_CA_RX_TURNAROUND (1 << 4) + +/* REG_AUTO_TX1 */ +#define MAX_FRAME_RETRIES(x) ((x) & 0xF) +#define MAX_CCA_RETRIES(x) (((x) & 0x7) << 4) + +/* REG_AUTO_TX2 */ +#define CSMA_MAX_BE(x) ((x) & 0xF) +#define CSMA_MIN_BE(x) (((x) & 0xF) << 4) + +#define CMD_SPI_NOP 0xFF /* No operation. Use for dummy writes */ +#define CMD_SPI_PKT_WR 0x10 /* Write telegram to the Packet RAM starting from the TX packet base address pointer tx_packet_base */ +#define CMD_SPI_PKT_RD 0x30 /* Read telegram from the Packet RAM starting from RX packet base address pointer rxpb.rx_packet_base */ +#define CMD_SPI_MEM_WR(x) (0x18 + (x >> 8)) /* Write data to MCR or Packet RAM sequentially */ +#define CMD_SPI_MEM_RD(x) (0x38 + (x >> 8)) /* Read data from MCR or Packet RAM sequentially */ +#define CMD_SPI_MEMR_WR(x) (0x08 + (x >> 8)) /* Write data to MCR or Packet RAM as random block */ +#define CMD_SPI_MEMR_RD(x) (0x28 + (x >> 8)) /* Read data from MCR or Packet RAM as random block */ +#define CMD_SPI_PRAM_WR 0x1E /* Write data sequentially to current PRAM page selected */ +#define CMD_RC_SLEEP 0xB1 /* Invoke transition of radio controller into SLEEP state */ +#define CMD_RC_IDLE 0xB2 /* Invoke transition of radio controller into IDLE state */ +#define CMD_RC_PHY_RDY 0xB3 /* Invoke transition of radio controller into PHY_RDY state */ +#define CMD_RC_RX 0xB4 /* Invoke transition of radio controller into RX state */ +#define CMD_RC_TX 0xB5 /* Invoke transition of radio controller into TX state */ +#define CMD_RC_MEAS 0xB6 /* Invoke transition of radio controller into MEAS state */ +#define CMD_RC_CCA 0xB7 /* Invoke Clear channel assessment */ +#define CMD_RC_CSMACA 0xC1 /* initiates CSMA-CA channel access sequence and frame transmission */ + +/* STATUS */ + +#define STAT_SPI_READY (1 << 7) +#define STAT_IRQ_STATUS (1 << 6) +#define STAT_RC_READY (1 << 5) +#define STAT_CCA_RESULT (1 << 4) +#define RC_STATUS_IDLE 1 +#define RC_STATUS_MEAS 2 +#define RC_STATUS_PHY_RDY 3 +#define RC_STATUS_RX 4 +#define RC_STATUS_TX 5 +#define RC_STATUS_MASK 0xF + +/* AUTO_STATUS */ + +#define SUCCESS 0 +#define SUCCESS_DATPEND 1 +#define FAILURE_CSMACA 2 +#define FAILURE_NOACK 3 +#define AUTO_STATUS_MASK 0x3 + +#define PRAM_PAGESIZE 256 + +/* IRQ1 */ + +#define IRQ_CCA_COMPLETE (1 << 0) +#define IRQ_SFD_RX (1 << 1) +#define IRQ_SFD_TX (1 << 2) +#define IRQ_RX_PKT_RCVD (1 << 3) +#define IRQ_TX_PKT_SENT (1 << 4) +#define IRQ_FRAME_VALID (1 << 5) +#define IRQ_ADDRESS_VALID (1 << 6) +#define IRQ_CSMA_CA (1 << 7) + +#define AUTO_TX_TURNAROUND (1 << 3) +#define ADDON_EN (1 << 4) + +struct adf7242_local { + struct spi_device *spi; + struct adf7242_platform_data *pdata; + struct work_struct irqwork; + struct completion tx_complete; + struct ieee802154_dev *dev; + struct mutex bmux; + spinlock_t lock; + unsigned irq_disabled:1; /* P: lock */ + unsigned is_tx:1; /* P: lock */ + unsigned mode; + unsigned tx_irq; + int tx_stat; + u8 buf[3]; +}; + +static int adf7242_status(struct adf7242_local *lp, u8 *stat) +{ + int status; + struct spi_message msg; + u8 buf_tx[1], buf_rx[1]; + + struct spi_transfer xfer = { + .len = 1, + .tx_buf = buf_tx, + .rx_buf = buf_rx, + }; + + buf_tx[0] = CMD_SPI_NOP; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + + *stat = buf_rx[0]; + + return status; +} + +static int adf7242_wait_ready(struct adf7242_local *lp) +{ + u8 stat; + int cnt = 0; + + DBG(2, "%s :Enter\n", __func__); + + do { + adf7242_status(lp, &stat); + cnt++; + } while (!(stat & STAT_RC_READY) && (cnt < MAX_POLL_LOOPS)); + + DBG(2, "%s :Exit loops=%d\n", __func__, cnt); + + return 0; +} + +static int adf7242_wait_status(struct adf7242_local *lp, int status) +{ + u8 stat; + int cnt = 0; + + DBG(2, "%s :Enter\n", __func__); + + do { + adf7242_status(lp, &stat); + stat &= RC_STATUS_MASK; + cnt++; + } while ((stat != status) && (cnt < MAX_POLL_LOOPS)); + + DBG(2, "%s :Exit loops=%d\n", __func__, cnt); + + return 0; +} + +static int adf7242_write_fbuf(struct adf7242_local *lp, u8 *data, u8 len) +{ + u8 *buf = lp->buf; + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = buf, + + }; + struct spi_transfer xfer_buf = { + .len = len, + .tx_buf = data, + }; + + DBG(2, "%s :Enter\n", __func__); + adf7242_wait_ready(lp); + + buf[0] = CMD_SPI_PKT_WR; + buf[1] = len + 2; + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + + DBG(2, "%s :Exit\n", __func__); + return status; +} + +static int adf7242_read_fbuf(struct adf7242_local *lp, + u8 *data, u8 *len, u8 *lqi) +{ + u8 *buf = lp->buf; + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 3, + .tx_buf = buf, + .rx_buf = buf, + + }; + struct spi_transfer xfer_buf = { + .len = *len, + .rx_buf = data, + }; + + DBG(2, "%s :Enter\n", __func__); + adf7242_wait_ready(lp); + + mutex_lock(&lp->bmux); + buf[0] = CMD_SPI_PKT_RD; + buf[1] = CMD_SPI_NOP; + buf[2] = 0; /* PHR */ + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + status = spi_sync(lp->spi, &msg); + + if (!status) { + *lqi = data[buf[2] - 1]; + *len = buf[2]; /* PHR */ + } + + mutex_unlock(&lp->bmux); + DBG(2, "%s :Exit\n", __func__); + return status; +} + +static int adf7242_read_reg(struct adf7242_local *lp, + u16 addr, u8 *data) +{ + int status; + struct spi_message msg; + u8 buf_tx[4], buf_rx[4]; + + struct spi_transfer xfer = { + .len = 4, + .tx_buf = buf_tx, + .rx_buf = buf_rx, + }; + + DBG(2, "%s :Enter\n", __func__); + adf7242_wait_ready(lp); + + mutex_lock(&lp->bmux); + buf_tx[0] = CMD_SPI_MEM_RD(addr); + buf_tx[1] = addr; + buf_tx[2] = CMD_SPI_NOP; + buf_tx[3] = CMD_SPI_NOP; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + status = spi_sync(lp->spi, &msg); + if (msg.status) + status = msg.status; + + if (!status) + *data = buf_rx[3]; + + mutex_unlock(&lp->bmux); + DBG(2, "%s :Exit\n", __func__); + + return status; +} + +static int adf7242_write_reg(struct adf7242_local *lp, + u16 addr, u8 data) +{ + int status; + struct spi_message msg; + u8 buf_tx[4]; + + struct spi_transfer xfer = { + .len = 3, + .tx_buf = buf_tx, + }; + DBG(2, "%s :Enter\n", __func__); + adf7242_wait_ready(lp); + + buf_tx[0] = CMD_SPI_MEM_WR(addr); + buf_tx[1] = addr; + buf_tx[2] = data; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + DBG(2, "%s :Exit\n", __func__); + + return status; +} + +static int adf7242_cmd(struct adf7242_local *lp, u8 cmd) +{ + int status; + struct spi_message msg; + u8 buf_tx[1]; + + struct spi_transfer xfer = { + .len = 1, + .tx_buf = buf_tx, + }; + + DBG(2, "%s :Enter CMD=0x%X\n", __func__, cmd); + adf7242_wait_ready(lp); + + buf_tx[0] = cmd; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + DBG(2, "%s :Exit\n", __func__); + + return status; +} + +static int adf7242_upload_firmware(struct adf7242_local *lp, + u8 *data, u16 len) +{ + int status, i, page = 0; + struct spi_message msg; + struct spi_transfer xfer_buf = {}; + u8 buf[2]; + + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = buf, + }; + + buf[0] = CMD_SPI_PRAM_WR; + buf[1] = 0; + + for (i = len; i >= 0 ; i -= PRAM_PAGESIZE) { + adf7242_write_reg(lp, REG_PRAMPG, page); + + xfer_buf.len = i >= PRAM_PAGESIZE ? PRAM_PAGESIZE : i, + xfer_buf.tx_buf = &data[page * PRAM_PAGESIZE], + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + mutex_lock(&lp->bmux); + status = spi_sync(lp->spi, &msg); + mutex_unlock(&lp->bmux); + page++; + } + + return status; +} + +static int adf7242_ed(struct ieee802154_dev *dev, u8 *level) +{ + struct adf7242_local *lp = dev->priv; + int ret; + + DBG(2, "%s :Enter\n", __func__); +#if 0 + adf7242_cmd(lp, CMD_RC_PHY_RDY); + adf7242_cmd(lp, CMD_RC_CCA); + adf7242_wait_status(lp, RC_STATUS_PHY_RDY); +#else + udelay(128); +#endif + ret = adf7242_read_reg(lp, REG_RRB, level); + adf7242_cmd(lp, CMD_RC_RX); + DBG(2, "%s :Exit\n", __func__); + + return ret; +} + +static int adf7242_start(struct ieee802154_dev *dev) +{ + struct adf7242_local *lp = dev->priv; + int ret; + + DBG(2, "%s :Enter\n", __func__); + ret = adf7242_cmd(lp, CMD_RC_RX); + DBG(2, "%s :Exit\n", __func__); + + return ret; +} + +static void adf7242_stop(struct ieee802154_dev *dev) +{ + struct adf7242_local *lp = dev->priv; + + DBG(2, "%s :Enter\n", __func__); + adf7242_cmd(lp, CMD_RC_PHY_RDY); + DBG(2, "%s :Exit\n", __func__); +} + +static int adf7242_channel(struct ieee802154_dev *dev, int page, int channel) +{ + struct adf7242_local *lp = dev->priv; + unsigned long freq; + + DBG(2, "%s :Enter\n", __func__); + DBG(1, "%s :Channel=%d\n", __func__, channel); + + might_sleep(); + + BUG_ON(page != 0); + BUG_ON(channel < 11); + BUG_ON(channel > 26); + + freq = (2405 + 5 * (channel - 11)) * 100; + + adf7242_cmd(lp, CMD_RC_PHY_RDY); + + adf7242_write_reg(lp, REG_CH_FREQ0, freq); + adf7242_write_reg(lp, REG_CH_FREQ1, freq >> 8); + adf7242_write_reg(lp, REG_CH_FREQ2, freq >> 16); + + adf7242_cmd(lp, CMD_RC_RX); + + dev->phy->current_channel = channel; + DBG(2, "%s :Exit\n", __func__); + + return 0; +} + +static int adf7242_set_hw_addr_filt(struct ieee802154_dev *dev, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + struct adf7242_local *lp = dev->priv; + u8 reg; + + DBG(2, "%s :Enter\n", __func__); + DBG(1, "%s :Changed=0x%lX\n", __func__, changed); + + might_sleep(); + + if (changed & IEEE802515_IEEEADDR_CHANGED) { + adf7242_write_reg(lp, REG_IEEE_ADDR_0, filt->ieee_addr[7]); + adf7242_write_reg(lp, REG_IEEE_ADDR_1, filt->ieee_addr[6]); + adf7242_write_reg(lp, REG_IEEE_ADDR_2, filt->ieee_addr[5]); + adf7242_write_reg(lp, REG_IEEE_ADDR_3, filt->ieee_addr[4]); + adf7242_write_reg(lp, REG_IEEE_ADDR_4, filt->ieee_addr[3]); + adf7242_write_reg(lp, REG_IEEE_ADDR_5, filt->ieee_addr[2]); + adf7242_write_reg(lp, REG_IEEE_ADDR_6, filt->ieee_addr[1]); + adf7242_write_reg(lp, REG_IEEE_ADDR_7, filt->ieee_addr[0]); + } + + if (changed & IEEE802515_SADDR_CHANGED) { + adf7242_write_reg(lp, REG_SHORT_ADDR_0, filt->short_addr); + adf7242_write_reg(lp, REG_SHORT_ADDR_1, filt->short_addr >> 8); + } + + if (changed & IEEE802515_PANID_CHANGED) { + adf7242_write_reg(lp, REG_PAN_ID0, filt->pan_id); + adf7242_write_reg(lp, REG_PAN_ID1, filt->pan_id >> 8); + } + + if (changed & IEEE802515_PANC_CHANGED) { + adf7242_read_reg(lp, REG_AUTO_CFG, ®); + if (filt->pan_coord) + reg |= IS_PANCOORD; + else + reg &= ~IS_PANCOORD; + adf7242_write_reg(lp, REG_AUTO_CFG, reg); + } + + DBG(2, "%s :Exit\n", __func__); + return 0; +} + +static int adf7242_xmit(struct ieee802154_dev *dev, struct sk_buff *skb) +{ + struct adf7242_local *lp = dev->priv; + int ret; + unsigned long flags; + + DBG(2, "%s :Enter\n", __func__); + + spin_lock_irqsave(&lp->lock, flags); + BUG_ON(lp->is_tx); + lp->is_tx = 1; + spin_unlock_irqrestore(&lp->lock, flags); + + ret = adf7242_write_fbuf(lp, skb->data, skb->len); + if (ret) + goto err_rx; + + if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA) { + ret = adf7242_cmd(lp, CMD_RC_PHY_RDY); + ret |= adf7242_cmd(lp, CMD_RC_CSMACA); + } else { + ret = adf7242_cmd(lp, CMD_RC_TX); + } + + if (ret) + goto err_rx; + + ret = wait_for_completion_interruptible(&lp->tx_complete); + + if (ret < 0) + goto err_rx; + + DBG(2, "%s :Exit\n", __func__); + return ret; + +err_rx: + spin_lock_irqsave(&lp->lock, flags); + lp->is_tx = 0; + spin_unlock_irqrestore(&lp->lock, flags); + return ret; +} + +static int adf7242_rx(struct adf7242_local *lp) +{ + u8 len = 128; + u8 lqi = 0; + int ret; + struct sk_buff *skb; + + DBG(2, "%s :Enter\n", __func__); + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + ret = adf7242_read_fbuf(lp, skb_put(skb, len), &len, &lqi); + + adf7242_cmd(lp, CMD_RC_RX); + + skb_trim(skb, len - 2); /* We do not put RSSI/LQI or CRC into the frame */ + + if (len < 2) { + kfree_skb(skb); + return -EINVAL; + } + + ieee802154_rx_irqsafe(lp->dev, skb, lqi); + + DBG(1, "%s: %d %d %x\n", __func__, ret, len, lqi); + DBG(2, "%s :Exit\n", __func__); + + return 0; +} + +static struct ieee802154_ops adf7242_ops = { + .owner = THIS_MODULE, + .xmit = adf7242_xmit, + .ed = adf7242_ed, + .set_channel = adf7242_channel, + .set_hw_addr_filt = adf7242_set_hw_addr_filt, + .start = adf7242_start, + .stop = adf7242_stop, +}; + +static void adf7242_irqwork(struct work_struct *work) +{ + struct adf7242_local *lp = + container_of(work, struct adf7242_local, irqwork); + u8 irq1, auto_stat = 0, stat = 0; + int ret; + unsigned long flags; + + DBG(2, "%s :Enter\n", __func__); + + ret = adf7242_read_reg(lp, REG_IRQ1_SRC1, &irq1); + + DBG(1, "%s IRQ1 = %X:\n%s%s%s%s%s%s%s%s\n", __func__, irq1, + irq1 & IRQ_CCA_COMPLETE ? "IRQ_CCA_COMPLETE\n" : "", + irq1 & IRQ_SFD_RX ? "IRQ_SFD_RX\n" : "", + irq1 & IRQ_SFD_TX ? "IRQ_SFD_TX\n" : "", + irq1 & IRQ_RX_PKT_RCVD ? "IRQ_RX_PKT_RCVD\n" : "", + irq1 & IRQ_TX_PKT_SENT ? "IRQ_TX_PKT_SENT\n" : "", + irq1 & IRQ_CSMA_CA ? "IRQ_CSMA_CA\n" : "", + irq1 & IRQ_FRAME_VALID ? "IRQ_FRAME_VALID\n" : "", + irq1 & IRQ_ADDRESS_VALID ? "IRQ_ADDRESS_VALID\n" : ""); + + adf7242_status(lp, &stat); + + DBG(1, "%s STATUS = %X:\n%s\n%s%s%s%s%s\n", __func__, stat, + stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY", + (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "", + (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "", + (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "", + (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "", + (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : ""); + + adf7242_write_reg(lp, REG_IRQ1_SRC1, irq1); + + if (irq1 & IRQ_RX_PKT_RCVD) { + + /* Wait until ACK is processed */ + if ((lp->mode & ADF_IEEE802154_HW_AACK) && + ((stat & RC_STATUS_MASK) != RC_STATUS_PHY_RDY)) + adf7242_wait_status(lp, RC_STATUS_PHY_RDY); + + adf7242_rx(lp); + } + + if (irq1 & lp->tx_irq) { + + if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA) { + adf7242_read_reg(lp, REG_AUTO_STATUS, &auto_stat); + auto_stat &= AUTO_STATUS_MASK; + + DBG(1, "%s AUTO_STATUS = %X:\n%s%s%s%s\n", + __func__, auto_stat, + auto_stat == SUCCESS ? "SUCCESS" : "", + auto_stat == SUCCESS_DATPEND ? "SUCCESS_DATPEND" : "", + auto_stat == FAILURE_CSMACA ? "FAILURE_CSMACA" : "", + auto_stat == FAILURE_NOACK ? "FAILURE_NOACK" : ""); + + /* save CSMA-CA completion status */ + lp->tx_stat = auto_stat; + } + spin_lock_irqsave(&lp->lock, flags); + if (lp->is_tx) { + lp->is_tx = 0; + complete(&lp->tx_complete); + } + spin_unlock_irqrestore(&lp->lock, flags); + + /* in case we just received a frame we are already in PHY_RX */ + + if (!(irq1 & IRQ_RX_PKT_RCVD)) + adf7242_cmd(lp, CMD_RC_RX); + } + + spin_lock_irqsave(&lp->lock, flags); + if (lp->irq_disabled) { + lp->irq_disabled = 0; + enable_irq(lp->spi->irq); + } + spin_unlock_irqrestore(&lp->lock, flags); + + DBG(2, "%s :Exit\n", __func__); +} + +static irqreturn_t adf7242_isr(int irq, void *data) +{ + struct adf7242_local *lp = data; + + DBG(2, "%s :Enter\n", __func__); + + spin_lock(&lp->lock); + if (!lp->irq_disabled) { + disable_irq_nosync(irq); + lp->irq_disabled = 1; + } + spin_unlock(&lp->lock); + + schedule_work(&lp->irqwork); + + DBG(2, "%s :Exit\n", __func__); + + return IRQ_HANDLED; +} + + +static int adf7242_hw_init(struct adf7242_local *lp) +{ + int ret; + const struct firmware *fw; + + DBG(2, "%s :Enter\n", __func__); + + adf7242_cmd(lp, CMD_RC_IDLE); + + if (lp->mode) { + /* get ADF7242 addon firmware + * build this driver as module + * and place under /lib/firmware/adf7242_firmware.bin + */ + ret = request_firmware(&fw, FIRMWARE, &lp->spi->dev); + if (ret) { + dev_err(&lp->spi->dev, + "request_firmware() failed with %i\n", ret); + return ret; + } + + adf7242_upload_firmware(lp, (u8 *) fw->data, fw->size); + release_firmware(fw); + + adf7242_write_reg(lp, REG_FFILT_CFG, + ACCEPT_BEACON_FRAMES | + ACCEPT_DATA_FRAMES | + ACCEPT_ACK_FRAMES | + ACCEPT_MACCMD_FRAMES | + (lp->mode & ADF_IEEE802154_PROMISCUOUS_MODE ? + ACCEPT_ALL_ADDRESS : 0) | + ACCEPT_RESERVED_FRAMES); + + adf7242_write_reg(lp, REG_AUTO_TX1, + MAX_FRAME_RETRIES(lp->pdata->max_frame_retries) | + MAX_CCA_RETRIES(lp->pdata->max_cca_retries)); + + adf7242_write_reg(lp, REG_AUTO_TX2, + CSMA_MAX_BE(lp->pdata->max_csma_be) | + CSMA_MIN_BE(lp->pdata->min_csma_be)); + + adf7242_write_reg(lp, REG_AUTO_CFG, + (lp->mode & ADF_IEEE802154_HW_AACK ? + RX_AUTO_ACK_EN : 0)); + } + + adf7242_write_reg(lp, REG_PKT_CFG, lp->mode ? ADDON_EN : 0); + + adf7242_write_reg(lp, REG_EXTPA_MSC, 0xF1); + adf7242_write_reg(lp, REG_RXFE_CFG, 0x1D); + adf7242_write_reg(lp, REG_IRQ1_EN0, 0); + + adf7242_write_reg(lp, REG_IRQ1_EN1, IRQ_RX_PKT_RCVD | lp->tx_irq); + + adf7242_write_reg(lp, REG_IRQ1_SRC1, 0xFF); + adf7242_write_reg(lp, REG_IRQ1_SRC0, 0xFF); + + adf7242_cmd(lp, CMD_RC_PHY_RDY); + + DBG(2, "%s :Exit\n", __func__); + + return 0; +} + +static int adf7242_suspend(struct spi_device *spi, pm_message_t message) +{ + return 0; +} + +static int adf7242_resume(struct spi_device *spi) +{ + return 0; +} + +static ssize_t adf7242_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct adf7242_local *lp = dev_get_drvdata(dev); + u8 stat; + + adf7242_status(lp, &stat); + + return sprintf(buf, "STATUS = %X:\n%s\n%s%s%s%s%s\n", stat, + stat & STAT_RC_READY ? "RC_READY" : "RC_BUSY", + (stat & 0xf) == RC_STATUS_IDLE ? "RC_STATUS_IDLE" : "", + (stat & 0xf) == RC_STATUS_MEAS ? "RC_STATUS_MEAS" : "", + (stat & 0xf) == RC_STATUS_PHY_RDY ? "RC_STATUS_PHY_RDY" : "", + (stat & 0xf) == RC_STATUS_RX ? "RC_STATUS_RX" : "", + (stat & 0xf) == RC_STATUS_TX ? "RC_STATUS_TX" : ""); + +} +static DEVICE_ATTR(status, 0664, adf7242_show, NULL); + +static struct attribute *adf7242_attributes[] = { + &dev_attr_status.attr, + NULL +}; + +static const struct attribute_group adf7242_attr_group = { + .attrs = adf7242_attributes, +}; + +static int __devinit adf7242_probe(struct spi_device *spi) +{ + struct adf7242_platform_data *pdata = spi->dev.platform_data; + struct ieee802154_dev *dev; + struct adf7242_local *lp; + int ret; + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ specified\n"); + return -EINVAL; + } + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + dev = ieee802154_alloc_device(sizeof(*lp), &adf7242_ops); + if (!dev) + return -ENOMEM; + + lp = dev->priv; + lp->dev = dev; + lp->spi = spi; + lp->pdata = pdata; + lp->mode = pdata->mode; + + dev->priv = lp; + dev->parent = &spi->dev; + dev->extra_tx_headroom = 0; + /* We do support only 2.4 Ghz */ + + dev->phy->channels_supported[0] = 0x7FFF800; + + if (!lp->mode) { + adf7242_ops.set_hw_addr_filt = NULL; + lp->tx_irq = IRQ_TX_PKT_SENT; + } else { + if ((lp->mode & ADF_IEEE802154_PROMISCUOUS_MODE) && + (lp->mode & ADF_IEEE802154_HW_AACK)) + lp->mode &= ~ADF_IEEE802154_HW_AACK; + + if (lp->mode & ADF_IEEE802154_AUTO_CSMA_CA) + lp->tx_irq = IRQ_CSMA_CA; + else + lp->tx_irq = IRQ_TX_PKT_SENT; + } + + dev->flags = IEEE802154_HW_OMIT_CKSUM | + (lp->mode & ADF_IEEE802154_HW_AACK ? + IEEE802154_HW_AACK : 0); + + mutex_init(&lp->bmux); + INIT_WORK(&lp->irqwork, adf7242_irqwork); + spin_lock_init(&lp->lock); + init_completion(&lp->tx_complete); + + spi_set_drvdata(spi, lp); + + ret = adf7242_hw_init(lp); + if (ret) + goto err_hw_init; + + ret = request_irq(spi->irq, adf7242_isr, IRQF_TRIGGER_HIGH, + dev_name(&spi->dev), lp); + if (ret) + goto err_hw_init; + + ret = ieee802154_register_device(lp->dev); + if (ret) + goto err_irq; + + dev_set_drvdata(&spi->dev, lp); + + ret = sysfs_create_group(&spi->dev.kobj, &adf7242_attr_group); + if (ret) + goto out; + + dev_info(&spi->dev, "mac802154 IRQ-%d registered\n", spi->irq); + + return ret; + +out: + ieee802154_unregister_device(lp->dev); +err_irq: + free_irq(spi->irq, lp); + flush_work(&lp->irqwork); +err_hw_init: + spi_set_drvdata(spi, NULL); + mutex_destroy(&lp->bmux); + ieee802154_free_device(lp->dev); + return ret; +} + +static int __devexit adf7242_remove(struct spi_device *spi) +{ + struct adf7242_local *lp = spi_get_drvdata(spi); + + ieee802154_unregister_device(lp->dev); + free_irq(spi->irq, lp); + flush_work(&lp->irqwork); + spi_set_drvdata(spi, NULL); + mutex_destroy(&lp->bmux); + ieee802154_free_device(lp->dev); + + return 0; +} + +static struct spi_driver adf7242_driver = { + .driver = { + .name = "adf7242", + .owner = THIS_MODULE, + }, + .probe = adf7242_probe, + .remove = __devexit_p(adf7242_remove), + .suspend = adf7242_suspend, + .resume = adf7242_resume, +}; + +static int __init adf7242_init(void) +{ + return spi_register_driver(&adf7242_driver); +} +module_init(adf7242_init); + +static void __exit adf7242_exit(void) +{ + spi_unregister_driver(&adf7242_driver); +} +module_exit(adf7242_exit); + +MODULE_AUTHOR("Michael Hennerich "); +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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_OF +#include +#include +#endif + +#include +#include + +#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 + */ + +#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 + * + * Modified 2010: xue liu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CC2420_WRITEREG(x) (x) +#define CC2420_READREG(x) (0x40 | x) +#define CC2420_RAMADDR(x) ((x & 0x7F) | 0x80) +#define CC2420_RAMBANK(x) ((x >> 1) & 0xc0) +#define CC2420_WRITERAM(x) (x) +#define CC2420_READRAM(x) (0x20 | x) + +#define CC2420_FREQ_MASK 0x3FF +#define CC2420_ADR_DECODE_MASK 0x0B00 +#define CC2420_FIFOP_THR_MASK 0x003F +#define CC2420_CRC_MASK 0x80 +#define CC2420_RSSI_MASK 0x7F +#define CC2420_FSMSTATE_MASK 0x2F + +#define CC2420_MANFIDLOW 0x233D +#define CC2420_MANFIDHIGH 0x3000 /* my chip appears to version 3 - broaden this with testing */ + +#define RSSI_OFFSET 45 + +#define STATE_PDOWN 0 +#define STATE_IDLE 1 +#define STATE_RX_CALIBRATE 2 +#define STATE_RX_CALIBRATE2 40 + +#define STATE_RX_SFD_SEARCH_MIN 3 +#define STATE_RX_SFD_SEARCH_MAX 6 +#define STATE_RX_FRAME 16 +#define STATE_RX_FRAME2 40 +#define STATE_RX_WAIT 14 +#define STATE_RX_OVERFLOW 17 +#define STATE_TX_ACK_CALIBRATE 48 +#define STATE_TX_ACK_PREAMBLE_MIN 49 +#define STATE_TX_ACK_PREAMBLE_MAX 51 +#define STATE_TX_ACK_MIN 52 +#define STATE_TX_ACK_MAX 54 +#define STATE_TX_CALIBRATE 32 +#define STATE_TX_PREAMBLE_MIN 34 +#define STATE_TX_PREAMBLE_MAX 36 +#define STATE_TX_FRAME_MIN 37 +#define STATE_TX_FRAME_MAX 39 +#define STATE_TX_UNDERFLOW 56 + +struct cc2420_local { + struct cc2420_platform_data *pdata; + struct spi_device *spi; + struct ieee802154_dev *dev; + u8 *buf; + struct mutex bmux; + int fifop_irq; + int sfd_irq; + struct work_struct fifop_irqwork; + struct work_struct sfd_irqwork; + spinlock_t lock; + unsigned irq_disabled:1;/* P:lock */ + unsigned is_tx:1; /* P:lock */ + + struct completion tx_complete; +}; +static int cc2420_get_status(struct cc2420_local *lp, u8 *status) +{ + int ret; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 1, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_WRITEREG(CC2420_SNOP); + dev_vdbg(&lp->spi->dev, "get status command buf[0] = %02x\n", lp->buf[0]); + ret = spi_sync(lp->spi, &msg); + if (!ret) + *status = lp->buf[0]; + dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]); + mutex_unlock(&lp->bmux); + return ret; + +} +static int cc2420_cmd_strobe(struct cc2420_local *lp, + u8 addr) +{ + int ret; + u8 status = 0xf; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 1, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_WRITEREG(addr); + dev_vdbg(&lp->spi->dev, "cmd strobe buf[0] = %02x\n", lp->buf[0]); + ret = spi_sync(lp->spi, &msg); + if (!ret) + status = lp->buf[0]; + dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]); + + mutex_unlock(&lp->bmux); + + return ret; +} + +static int cc2420_read_16_bit_reg(struct cc2420_local *lp, + u8 addr, u16 *data) +{ + int ret; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 3, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_READREG(addr); + dev_vdbg(&lp->spi->dev, "readreg addr buf[0] = %02x\n", lp->buf[0]); + ret = spi_sync(lp->spi, &msg); + dev_dbg(&lp->spi->dev, "status = %d\n", ret); + mutex_unlock(&lp->bmux); + dev_dbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]); + if (!ret) + *data = ((u16) (lp->buf[1]) << 8) | lp->buf[2]; + return ret; +} + +static int cc2420_write_16_bit_reg_partial(struct cc2420_local *lp, + u8 addr, u16 data, u16 mask) +{ + int ret; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 3, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + dev_dbg(&lp->spi->dev, "data = %x\n", data); + dev_dbg(&lp->spi->dev, "mask = %x\n", mask); + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_READREG(addr); + dev_vdbg(&lp->spi->dev, "read addr buf[0] = %02x\n", lp->buf[0]); + ret = spi_sync(lp->spi, &msg); + if (ret) + goto err_ret; + dev_dbg(&lp->spi->dev, "read buf[0] = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]); + + lp->buf[0] = CC2420_WRITEREG(addr); + + lp->buf[1] &= ~(mask >> 8); + lp->buf[2] &= ~(mask & 0xFF); + lp->buf[1] |= (mask >> 8) & (data >> 8); + lp->buf[2] |= (mask & 0xFF) & (data & 0xFF); + dev_vdbg(&lp->spi->dev, "writereg addr buf[0] = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]); + ret = spi_sync(lp->spi, &msg); + if (ret) + goto err_ret; + dev_dbg(&lp->spi->dev, "return status buf[0] = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + dev_dbg(&lp->spi->dev, "buf[2] = %02x\n", lp->buf[2]); + +err_ret: + mutex_unlock(&lp->bmux); + return ret; +} + +static int cc2420_channel(struct ieee802154_dev *dev, int page, int channel) +{ + struct cc2420_local *lp = dev->priv; + int ret; + + might_sleep(); + dev_dbg(&lp->spi->dev, "trying to set channel\n"); + + BUG_ON(page != 0); + BUG_ON(channel < CC2420_MIN_CHANNEL); + BUG_ON(channel > CC2420_MAX_CHANNEL); + + ret = cc2420_write_16_bit_reg_partial(lp, CC2420_FSCTRL, 357 + 5*(channel - 11), CC2420_FREQ_MASK); + + dev->phy->current_channel = channel; + return ret; +} + +static int cc2420_write_ram(struct cc2420_local *lp, u16 addr, u8 len, u8 *data) +{ + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + struct spi_transfer xfer_buf = { + .len = len, + .tx_buf = data, + }; + + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_RAMADDR(addr); + lp->buf[1] = CC2420_WRITERAM(CC2420_RAMBANK(addr)); + dev_dbg(&lp->spi->dev, "write ram addr buf[0] = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "ram bank buf[1] = %02x\n", lp->buf[1]); + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + status = spi_sync(lp->spi, &msg); + dev_dbg(&lp->spi->dev, "spi status = %d\n", status); + if (msg.status) + status = msg.status; + dev_dbg(&lp->spi->dev, "cc2420 status = %02x\n", lp->buf[0]); + dev_dbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + + mutex_unlock(&lp->bmux); + return status; +} + +static int cc2420_write_txfifo(struct cc2420_local *lp, u8 *data, u8 len) +{ + int status; + /* Length byte must include FCS even if calculated in hardware */ + int len_byte = len + 2; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 1, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + struct spi_transfer xfer_len = { + .len = 1, + .tx_buf = &len_byte, + }; + struct spi_transfer xfer_buf = { + .len = len, + .tx_buf = data, + }; + + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_WRITEREG(CC2420_TXFIFO); + dev_vdbg(&lp->spi->dev, "TX_FIFO addr buf[0] = %02x\n", lp->buf[0]); + + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_len, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + status = spi_sync(lp->spi, &msg); + dev_vdbg(&lp->spi->dev, "status = %d\n", status); + if (msg.status) + status = msg.status; + dev_vdbg(&lp->spi->dev, "status = %d\n", status); + dev_vdbg(&lp->spi->dev, "buf[0] = %02x\n", lp->buf[0]); + + mutex_unlock(&lp->bmux); + return status; +} + +static int +cc2420_read_rxfifo(struct cc2420_local *lp, u8 *data, u8 *len, u8 *lqi) +{ + int status; + struct spi_message msg; + struct spi_transfer xfer_head = { + .len = 2, + .tx_buf = lp->buf, + .rx_buf = lp->buf, + }; + struct spi_transfer xfer_buf = { + .len = *len, + .rx_buf = data, + }; + + mutex_lock(&lp->bmux); + lp->buf[0] = CC2420_READREG(CC2420_RXFIFO); + lp->buf[1] = 0x00; + dev_vdbg(&lp->spi->dev, "read rxfifo buf[0] = %02x\n", lp->buf[0]); + dev_vdbg(&lp->spi->dev, "buf[1] = %02x\n", lp->buf[1]); + spi_message_init(&msg); + spi_message_add_tail(&xfer_head, &msg); + spi_message_add_tail(&xfer_buf, &msg); + + status = spi_sync(lp->spi, &msg); + dev_vdbg(&lp->spi->dev, "status = %d\n", status); + if (msg.status) + status = msg.status; + dev_vdbg(&lp->spi->dev, "status = %d\n", status); + dev_vdbg(&lp->spi->dev, "return status buf[0] = %02x\n", lp->buf[0]); + dev_vdbg(&lp->spi->dev, "length buf[1] = %02x\n", lp->buf[1]); + + *lqi = data[lp->buf[1] - 1] & 0x7f; + *len = lp->buf[1]; /* it should be less than 130 */ + + mutex_unlock(&lp->bmux); + + return status; +} + + +static int cc2420_tx(struct ieee802154_dev *dev, struct sk_buff *skb) +{ + struct cc2420_local *lp = dev->priv; + int rc; + unsigned long flags; + u8 status = 0; + + pr_debug("%s\n", __func__); + + might_sleep(); + + rc = cc2420_cmd_strobe(lp, CC2420_SFLUSHTX); + if (rc) + goto err_rx; + rc = cc2420_write_txfifo(lp, skb->data, skb->len); + if (rc) + goto err_rx; + + /* TODO: test CCA pin */ + + rc = cc2420_get_status(lp, &status); + if (rc) + goto err_rx; + + if (status & CC2420_STATUS_TX_UNDERFLOW) { + dev_err(&lp->spi->dev, "cc2420 tx underflow!\n"); + goto err_rx; + } + + spin_lock_irqsave(&lp->lock, flags); + BUG_ON(lp->is_tx); + lp->is_tx = 1; + INIT_COMPLETION(lp->tx_complete); + spin_unlock_irqrestore(&lp->lock, flags); + + rc = cc2420_cmd_strobe(lp, CC2420_STXONCCA); + if (rc) + goto err; + + rc = wait_for_completion_interruptible(&lp->tx_complete); + if (rc < 0) + goto err; + + cc2420_cmd_strobe(lp, CC2420_SFLUSHTX); + cc2420_cmd_strobe(lp, CC2420_SRXON); + + return rc; + +err: + spin_lock_irqsave(&lp->lock, flags); + lp->is_tx = 0; + spin_unlock_irqrestore(&lp->lock, flags); +err_rx: + cc2420_cmd_strobe(lp, CC2420_SFLUSHTX); + cc2420_cmd_strobe(lp, CC2420_SRXON); + return rc; +} + +static int cc2420_rx(struct cc2420_local *lp) +{ + u8 len = 128; + u8 lqi = 0; /* link quality */ + int rc; + struct sk_buff *skb; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + rc = cc2420_read_rxfifo(lp, skb_put(skb, len), &len, &lqi); + if (len < 2) { + kfree_skb(skb); + return -EINVAL; + } + + /* Clip last two bytes. When using hardware FCS they get replaced with + * correlation value, FCS flag and RSSI value */ + skb_trim(skb, len-2); + + ieee802154_rx_irqsafe(lp->dev, skb, lqi); + + dev_dbg(&lp->spi->dev, "RXFIFO: %d %d %x\n", rc, len, lqi); + + return 0; +} + +static int +cc2420_set_hw_addr_filt(struct ieee802154_dev *dev, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + struct cc2420_local *lp = dev->priv; + u16 reg; + + might_sleep(); + + if (changed & IEEE802515_IEEEADDR_CHANGED) + cc2420_write_ram(lp, CC2420_RAM_IEEEADR, + IEEE802154_ALEN, + filt->ieee_addr); + + if (changed & IEEE802515_SADDR_CHANGED) { + u8 short_addr[2]; + short_addr[0] = filt->short_addr & 0xff;/* LSB */ + short_addr[1] = filt->short_addr >> 8; /* MSB */ + cc2420_write_ram(lp, CC2420_RAM_SHORTADR, + sizeof(short_addr), + short_addr); + } + + if (changed & IEEE802515_PANID_CHANGED) { + u8 panid[2]; + panid[0] = filt->pan_id & 0xff; /* LSB */ + panid[1] = filt->pan_id >> 8; /* MSB */ + cc2420_write_ram(lp, CC2420_RAM_PANID, + sizeof(panid), + panid); + } + + if (changed & IEEE802515_PANC_CHANGED) { + cc2420_read_16_bit_reg(lp, CC2420_MDMCTRL0, ®); + if (filt->pan_coord) + reg |= 1 << CC2420_MDMCTRL0_PANCRD; + else + reg &= ~(1 << CC2420_MDMCTRL0_PANCRD); + cc2420_write_16_bit_reg_partial(lp, CC2420_MDMCTRL0, + reg, 1 << CC2420_MDMCTRL0_PANCRD); + } + + return 0; +} + +static int cc2420_ed(struct ieee802154_dev *dev, u8 *level) +{ + struct cc2420_local *lp = dev->priv; + u16 rssi; + int ret; + dev_dbg(&lp->spi->dev, "ed called\n"); + + ret = cc2420_read_16_bit_reg(lp, CC2420_RSSI, &rssi); + if (ret) + return ret; + + /* P = RSSI_VAL + RSSI_OFFSET[dBm] */ + *level = (rssi & CC2420_RSSI_MASK) + RSSI_OFFSET; + return ret; +} + +static int cc2420_start(struct ieee802154_dev *dev) +{ + return cc2420_cmd_strobe(dev->priv, CC2420_SRXON); +} + +static void cc2420_stop(struct ieee802154_dev *dev) +{ + cc2420_cmd_strobe(dev->priv, CC2420_SRFOFF); +} + +static struct ieee802154_ops cc2420_ops = { + .owner = THIS_MODULE, + .xmit = cc2420_tx, + .ed = cc2420_ed, + .start = cc2420_start, + .stop = cc2420_stop, + .set_channel = cc2420_channel, + .set_hw_addr_filt = cc2420_set_hw_addr_filt, +}; + +static int cc2420_register(struct cc2420_local *lp) +{ + int ret = -ENOMEM; + lp->dev = ieee802154_alloc_device(sizeof(*lp), &cc2420_ops); + if (!lp->dev) + goto err_ret; + + lp->dev->priv = lp; + lp->dev->parent = &lp->spi->dev; + //look this up. + lp->dev->extra_tx_headroom = 0; + //and this + //lp->dev->channel_mask = 0x7ff; + //and more. + + /* We do support only 2.4 Ghz */ + lp->dev->phy->channels_supported[0] = 0x7FFF800; + lp->dev->flags = IEEE802154_HW_OMIT_CKSUM; + + dev_dbg(&lp->spi->dev, "registered cc2420\n"); + ret = ieee802154_register_device(lp->dev); + if (ret) + goto err_free_device; + + return 0; +err_free_device: + ieee802154_free_device(lp->dev); +err_ret: + return ret; +} + +static void cc2420_unregister(struct cc2420_local *lp) +{ + ieee802154_unregister_device(lp->dev); + //check this is needed + ieee802154_free_device(lp->dev); +} + +static irqreturn_t cc2420_isr(int irq, void *data) +{ + struct cc2420_local *lp = data; + + spin_lock(&lp->lock); + if (!lp->irq_disabled) { + disable_irq_nosync(irq); + lp->irq_disabled = 1; + } + spin_unlock(&lp->lock); + + if (irq == lp->sfd_irq) + schedule_work(&lp->sfd_irqwork); + + if (irq == lp->fifop_irq) + schedule_work(&lp->fifop_irqwork); + + return IRQ_HANDLED; +} + +static void cc2420_fifop_irqwork(struct work_struct *work) +{ + struct cc2420_local *lp + = container_of(work, struct cc2420_local, fifop_irqwork); + unsigned long flags; + + dev_dbg(&lp->spi->dev, "fifop interrupt received\n"); + + if (gpio_get_value(lp->pdata->fifo)) + cc2420_rx(lp); + else + dev_err(&lp->spi->dev, "rxfifo overflow\n"); + + cc2420_cmd_strobe(lp, CC2420_SFLUSHRX); + cc2420_cmd_strobe(lp, CC2420_SFLUSHRX); + + spin_lock_irqsave(&lp->lock, flags); + if (lp->irq_disabled) { + lp->irq_disabled = 0; + enable_irq(lp->fifop_irq); + } + spin_unlock_irqrestore(&lp->lock, flags); +} + +static void cc2420_sfd_irqwork(struct work_struct *work) +{ + struct cc2420_local *lp + = container_of(work, struct cc2420_local, sfd_irqwork); + unsigned long flags; + + dev_dbg(&lp->spi->dev, "sfd interrupt received\n"); + + spin_lock_irqsave(&lp->lock, flags); + if (lp->is_tx) { + lp->is_tx = 0; + spin_unlock_irqrestore(&lp->lock, flags); + complete(&lp->tx_complete); + } else { + spin_unlock_irqrestore(&lp->lock, flags); + } + + spin_lock_irqsave(&lp->lock, flags); + if (lp->irq_disabled) { + lp->irq_disabled = 0; + enable_irq(lp->sfd_irq); + } + spin_unlock_irqrestore(&lp->lock, flags); +} + +static int cc2420_hw_init(struct cc2420_local *lp) +{ + int ret; + u16 state; + u8 status = 0xff; + int timeout = 500; /* 500us delay */ + ret = cc2420_read_16_bit_reg(lp, CC2420_FSMSTATE, &state); + if (ret) + goto error_ret; + /* reset has occured prior to this, so there should be no other option */ + if (state != STATE_PDOWN) { + ret = -EINVAL; + goto error_ret; + } + ret = cc2420_cmd_strobe(lp, CC2420_SXOSCON); + if (ret) + goto error_ret; + + do { + ret = cc2420_get_status(lp, &status); + if (ret) + goto error_ret; + if (timeout-- <= 0) { + dev_err(&lp->spi->dev, "oscillator start failed!\n"); + return ret; + } + udelay(1); + } while (!(status & CC2420_STATUS_XOSC16M_STABLE)); + + dev_info(&lp->spi->dev, "oscillator succesfully brought up\n"); + + return 0; +error_ret: + return ret; +} + +static int __devinit cc2420_probe(struct spi_device *spi) +{ + int ret; + u16 manidl, manidh; + struct cc2420_local *lp = kzalloc(sizeof *lp, GFP_KERNEL); + if (!lp) { + ret = -ENOMEM; + goto error_ret; + } + + lp->pdata = spi->dev.platform_data; + if (!lp->pdata) { + dev_err(&spi->dev, "no platform data\n"); + ret = -EINVAL; + goto err_free_local; + } + spi_set_drvdata(spi, lp); + mutex_init(&lp->bmux); + INIT_WORK(&lp->fifop_irqwork, cc2420_fifop_irqwork); + INIT_WORK(&lp->sfd_irqwork, cc2420_sfd_irqwork); + spin_lock_init(&lp->lock); + init_completion(&lp->tx_complete); + + lp->spi = spi; + lp->buf = kmalloc(3*sizeof *lp->buf, GFP_KERNEL); + if (!lp->buf) { + ret = -ENOMEM; + goto err_free_local; + } + + /* Request all the gpio's */ + ret = gpio_request(lp->pdata->fifo, "fifo"); + if (ret) + goto err_free_buf; + ret = gpio_request(lp->pdata->cca, "cca"); + if (ret) + goto err_free_gpio_fifo; +#if 0 + /* This is causing problems as fifop is gpio 0 ? */ + ret = gpio_request(lp->pdata->fifop, "fifop"); + if (ret) + goto err_free_gpio_cca; +#endif + ret = gpio_request(lp->pdata->sfd, "sfd"); + if (ret) + goto err_free_gpio_fifop; + ret = gpio_request(lp->pdata->reset, "reset"); + if (ret) + goto err_free_gpio_sfd; + ret = gpio_request(lp->pdata->vreg, "vreg"); + if (ret) + goto err_free_gpio_reset; + /* Configure the gpios appropriately */ + + /* Enable the voltage regulator */ + ret = gpio_direction_output(lp->pdata->vreg, 1); + if (ret) + goto err_free_gpio_reset; + udelay(600); /* Time for regulator to power up */ + /* Toggle the reset */ + ret = gpio_direction_output(lp->pdata->reset, 0); + if (ret) + goto err_disable_vreg; + udelay(10); /* no idea how long this should be? */ + ret = gpio_direction_output(lp->pdata->reset, 1); + if (ret) + goto err_disable_vreg; + udelay(10); + + ret = gpio_direction_input(lp->pdata->cca); + if (ret) + goto err_disable_vreg; + ret = gpio_direction_input(lp->pdata->fifo); + if (ret) + goto err_disable_vreg; + ret = gpio_direction_input(lp->pdata->fifop); + if (ret) + goto err_disable_vreg; + ret = gpio_direction_input(lp->pdata->sfd); + if (ret) + goto err_disable_vreg; + + + /* Check this is actually a cc2420 */ + ret = cc2420_read_16_bit_reg(lp, CC2420_MANFIDL, &manidl); + if (ret) + goto err_free_gpio_vreg; + ret = cc2420_read_16_bit_reg(lp, CC2420_MANFIDH, &manidh); + if (ret) + goto err_free_gpio_vreg; + if (manidh != CC2420_MANFIDHIGH || manidl != CC2420_MANFIDLOW) { + dev_err(&spi->dev, "Incorrect manufacturer id %x%x\n", manidh, manidl); + ret = -ENODEV; + goto err_free_gpio_vreg; + } + /* TODO: make it more readable */ + dev_info(&lp->spi->dev, "Found Chipcon CC2420\n"); + dev_info(&lp->spi->dev, "Manufacturer ID:%x Version:%x Partnum:%x\n", + manidl & 0x0FFF, manidh >> 12, manidl >> 12); + + ret = cc2420_hw_init(lp); + if (ret) + goto err_disable_vreg; + + lp->fifop_irq = gpio_to_irq(lp->pdata->fifop); + lp->sfd_irq = gpio_to_irq(lp->pdata->sfd); + + ret = request_irq(lp->fifop_irq, + cc2420_isr, + IRQF_TRIGGER_RISING | IRQF_SHARED, + dev_name(&spi->dev), + lp); + if (ret) { + dev_err(&spi->dev, "could not get fifop irq?\n"); + goto err_free_fifop_irq; + } + + ret = request_irq(lp->sfd_irq, + cc2420_isr, + IRQF_TRIGGER_FALLING, + dev_name(&spi->dev), + lp); + if (ret) { + dev_err(&spi->dev, "could not get sfd irq?\n"); + goto err_free_sfd_irq; + } + + dev_info(&lp->spi->dev, "Set fifo threshold to 127\n"); + cc2420_write_16_bit_reg_partial(lp, CC2420_IOCFG0, 127, CC2420_FIFOP_THR_MASK); + ret = cc2420_register(lp); + if (ret) + goto err_free_sfd_irq; + + return 0; +err_free_sfd_irq: + free_irq(lp->sfd_irq, lp); +err_free_fifop_irq: + free_irq(lp->fifop_irq, lp); +err_disable_vreg: + gpio_set_value(lp->pdata->vreg, 0); +err_free_gpio_vreg: + gpio_free(lp->pdata->vreg); +err_free_gpio_reset: + gpio_free(lp->pdata->reset); +err_free_gpio_sfd: + gpio_free(lp->pdata->sfd); +err_free_gpio_fifop: + gpio_free(lp->pdata->fifop); +//err_free_gpio_cca: +// gpio_free(lp->pdata->cca); +err_free_gpio_fifo: + gpio_free(lp->pdata->fifo); +err_free_buf: + kfree(lp->buf); +err_free_local: + kfree(lp); +error_ret: + return ret; +} + +static int __devexit cc2420_remove(struct spi_device *spi) +{ + struct cc2420_local *lp = spi_get_drvdata(spi); + + cc2420_unregister(lp); + free_irq(lp->fifop_irq, lp); + free_irq(lp->sfd_irq, lp); + flush_work(&lp->fifop_irqwork); + flush_work(&lp->sfd_irqwork); + gpio_free(lp->pdata->vreg); + gpio_free(lp->pdata->reset); + gpio_free(lp->pdata->sfd); + gpio_free(lp->pdata->fifop); + gpio_free(lp->pdata->cca); + gpio_free(lp->pdata->fifo); + kfree(lp->buf); + kfree(lp); + + return 0; +} + +static struct spi_driver cc2420_driver = { + .driver = { + .name = "cc2420", + .owner = THIS_MODULE, + }, + .probe = cc2420_probe, + .remove = __devexit_p(cc2420_remove), +}; + +static int __init cc2420_init(void) +{ + return spi_register_driver(&cc2420_driver); +} +module_init(cc2420_init); + +static void __exit cc2420_exit(void) +{ + spi_unregister_driver(&cc2420_driver); +} +module_exit(cc2420_exit); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/ieee802154/fakelb.c b/drivers/ieee802154/fakelb.c new file mode 100644 index 0000000..ae6e53f --- /dev/null +++ b/drivers/ieee802154/fakelb.c @@ -0,0 +1,311 @@ +/* + * Loopback IEEE 802.15.4 interface + * + * Copyright 2007, 2008 Siemens AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: + * Sergey Lapin + * Dmitry Eremin-Solenikov + */ + +#include +#include +#include +#include +#include +#include +#include + +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 Osipov + * Sergey Lapin + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* 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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); diff --git a/drivers/ieee802154/spi_atusb.c b/drivers/ieee802154/spi_atusb.c new file mode 100644 index 0000000..b16f5be --- /dev/null +++ b/drivers/ieee802154/spi_atusb.c @@ -0,0 +1,751 @@ +/* + * spi_atusb - SPI host look-alike for ATUSB + * + * Copyright (c) 2011 Richard Sharpe + * Copyright (c) 2011 Stefan Schmidt + * Copyright (c) 2011 Werner Almesberger + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "at86rf230.h" + + +#define SYNC_TIMEOUT_MS 50 /* assume interrupt has been synced after + waiting this long */ + +#define VENDOR_ID 0x20b7 +#define PRODUCT_ID 0x1540 + +/* The devices we work with */ +static const struct usb_device_id atusb_device_table[] = { + { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, atusb_device_table); + +#define ATUSB_BUILD_SIZE 256 +struct atusb_local { + struct usb_device * udev; + /* The interface to the RF part info, if applicable */ + uint8_t ep0_atusb_major; + uint8_t ep0_atusb_minor; + uint8_t atusb_hw_type; + struct spi_master *master; + int slave_irq; + struct urb *irq_urb; + uint8_t irq_buf; /* receive irq serial here*/ + uint8_t irq_seen; /* last irq serial from bulk */ + uint8_t irq_sync; /* last irq serial from WRITE2_SYNC */ + struct tasklet_struct task; /* interrupt delivery tasklet */ + struct timer_list timer; /* delay, for interrupt synch */ + struct at86rf230_platform_data platform_data; + /* copy platform_data so that we can adapt .reset_data */ + struct spi_device *spi; +// unsigned char buffer[3]; + unsigned char buffer[260]; /* XXL, just in case */ + struct spi_message *msg; +}; + +/* Commands to our device. Make sure this is synced with the firmware */ +enum atspi_requests { + ATUSB_ID = 0x00, /* system status/control grp */ + ATUSB_BUILD, + ATUSB_RESET, + ATUSB_RF_RESET = 0x10, /* debug/test group */ + ATUSB_POLL_INT, + ATUSB_TEST, /* atusb-sil only */ + ATUSB_TIMER, + ATUSB_GPIO, + ATUSB_SLP_TR, + ATUSB_GPIO_CLEANUP, + ATUSB_REG_WRITE = 0x20, /* transceiver group */ + ATUSB_REG_READ, + ATUSB_BUF_WRITE, + ATUSB_BUF_READ, + ATUSB_SRAM_WRITE, + ATUSB_SRAM_READ, + ATUSB_SPI_WRITE = 0x30, /* SPI group */ + ATUSB_SPI_READ1, + ATUSB_SPI_READ2, + ATUSB_SPI_WRITE2_SYNC, +}; + +/* + * Direction bRequest wValue wIndex wLength + * + * ->host ATUSB_ID - - 3 + * ->host ATUSB_BUILD - - #bytes + * host-> ATUSB_RESET - - 0 + * + * host-> ATUSB_RF_RESET - - 0 + * ->host ATUSB_POLL_INT - - 1 + * host-> ATUSB_TEST - - 0 + * ->host ATUSB_TIMER - - #bytes (6) + * ->host ATUSB_GPIO dir+data mask+p# 3 + * host-> ATUSB_SLP_TR - - 0 + * host-> ATUSB_GPIO_CLEANUP - - 0 + * + * host-> ATUSB_REG_WRITE value addr 0 + * ->host ATUSB_REG_READ - addr 1 + * host-> ATUSB_BUF_WRITE - - #bytes + * ->host ATUSB_BUF_READ - - #bytes + * host-> ATUSB_SRAM_WRITE - addr #bytes + * ->host ATUSB_SRAM_READ - addr #bytes + * + * host-> ATUSB_SPI_WRITE byte0 byte1 #bytes + * ->host ATUSB_SPI_READ1 byte0 - #bytes + * ->host ATUSB_SPI_READ2 byte0 byte1 #bytes + * ->host ATUSB_SPI_WRITE2_SYNC byte0 byte1 0/1 + */ + +#define ATUSB_FROM_DEV (USB_TYPE_VENDOR | USB_DIR_IN) +#define ATUSB_TO_DEV (USB_TYPE_VENDOR | USB_DIR_OUT) + + +/* ----- Control transfers ------------------------------------------------- */ + + +static int atusb_async_errchk(struct urb *urb) +{ + struct atusb_local *atusb = urb->context; + struct spi_message *msg = atusb->msg; + struct usb_device *dev = atusb->udev; + + if (!urb->status) { + dev_dbg(&dev->dev, "atusb_async_errchk OK len %d\n", + urb->actual_length); + return 0; + } + + if (urb->status != -ENOENT && urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN) + dev_info(&dev->dev, "atusb_async_errchk FAIL error %d\n", + urb->status); + + msg->actual_length = 0; + + return urb->status; +} + +static void atusb_async_finish(struct urb *urb) +{ + struct atusb_local *atusb = urb->context; + struct spi_message *msg = atusb->msg; + + msg->status = urb->status; + msg->complete(msg->context); + + kfree(urb->setup_packet); + usb_free_urb(urb); +} + +static void atusb_ctrl_cb(struct urb *urb) +{ + atusb_async_errchk(urb); + atusb_async_finish(urb); +} + +static void atusb_timer(unsigned long data) +{ + struct urb *urb = (void *) data; + + dev_warn(&urb->dev->dev, "atusb_timer\n"); + atusb_async_finish(urb); +} + +static void atusb_ctrl_cb_sync(struct urb *urb) +{ + struct atusb_local *atusb = urb->context; + + /* @@@ needs locking/atomic */ + if (atusb_async_errchk(urb) || atusb->irq_sync == atusb->irq_seen) { + atusb_async_finish(urb); + return; + } + + BUG_ON(timer_pending(&atusb->timer)); + atusb->timer.expires = jiffies+msecs_to_jiffies(SYNC_TIMEOUT_MS); + atusb->timer.data = (unsigned long) urb; + add_timer(&atusb->timer); +} + +static void atusb_read_fb_cb(struct urb *urb) +{ + struct atusb_local *atusb = urb->context; + struct spi_message *msg = atusb->msg; + const struct spi_transfer *xfer; + uint8_t *rx; + + if (!atusb_async_errchk(urb)) { + BUG_ON(!urb->actual_length); + + xfer = list_first_entry(&msg->transfers, struct spi_transfer, + transfer_list); + rx = xfer->rx_buf; + rx[1] = atusb->buffer[0]; + + xfer = list_entry(xfer->transfer_list.next, + struct spi_transfer, transfer_list); + memcpy(xfer->rx_buf, atusb->buffer+1, urb->actual_length-1); + } + + atusb_async_finish(urb); +} + +static int submit_control_msg(struct atusb_local *atusb, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size, usb_complete_t complete_fn, void *context) +{ + struct usb_device *dev = atusb->udev; + struct usb_ctrlrequest *req; + struct urb *urb; + int retval = -ENOMEM; + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->bRequest = request; + req->bRequestType = requesttype; + req->wValue = cpu_to_le16(value); + req->wIndex = cpu_to_le16(index); + req->wLength = cpu_to_le16(size); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto out_nourb; + + usb_fill_control_urb(urb, dev, + requesttype == ATUSB_FROM_DEV ? + usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0), + (unsigned char *) req, data, size, complete_fn, context); + + retval = usb_submit_urb(urb, GFP_KERNEL); + if (!retval) + return 0; + dev_warn(&dev->dev, "failed submitting read urb, error %d", + retval); + retval = retval == -ENOMEM ? retval : -EIO; + + usb_free_urb(urb); +out_nourb: + kfree(req); + + return retval; +} + + +/* ----- SPI transfers ----------------------------------------------------- */ + + +static int atusb_read1(struct atusb_local *atusb, + uint8_t tx, uint8_t *rx, int len) +{ + dev_dbg(&atusb->udev->dev, "atusb_read1: tx = 0x%x\n", tx); + return submit_control_msg(atusb, + ATUSB_SPI_READ1, ATUSB_FROM_DEV, tx, 0, + rx, 1, atusb_ctrl_cb, atusb); +} + +static int atusb_read_fb(struct atusb_local *atusb, + uint8_t tx, uint8_t *rx0, uint8_t *rx, int len) +{ + dev_dbg(&atusb->udev->dev, "atusb_read_fb: tx = 0x%x\n", tx); + return submit_control_msg(atusb, + ATUSB_SPI_READ1, ATUSB_FROM_DEV, tx, 0, + atusb->buffer, len+1, atusb_read_fb_cb, atusb); +} + +static int atusb_write(struct atusb_local *atusb, + uint8_t tx0, uint8_t tx1, const uint8_t *tx, int len) +{ + dev_dbg(&atusb->udev->dev, + "atusb_write: tx0 = 0x%x tx1 = 0x%x\n", tx0, tx1); + + /* + * The AT86RF230 driver sometimes requires a transceiver state + * transition to be an interrupt barrier. This is the case after + * writing FORCE_TX_ON to the TRX_CMD field in the TRX_STATE register. + * + * Since there is no other means of notification, we just decode the + * transfer and do a bit of pattern matching. + */ + if (tx0 == (CMD_REG | CMD_WRITE | RG_TRX_STATE) && + (tx1 & 0x1f) == STATE_FORCE_TX_ON) + return submit_control_msg(atusb, + ATUSB_SPI_WRITE2_SYNC, ATUSB_FROM_DEV, tx0, tx1, + &atusb->irq_sync, 1, atusb_ctrl_cb_sync, atusb); + else + return submit_control_msg(atusb, + ATUSB_SPI_WRITE, ATUSB_TO_DEV, tx0, tx1, + (uint8_t *) tx, len, atusb_ctrl_cb, atusb); +} + +static int atusb_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct atusb_local *atusb = spi_master_get_devdata(spi->master); + struct spi_transfer *xfer; + struct spi_transfer *x[2]; + int n; + const uint8_t *tx; + uint8_t *rx; + int len; + int retval = 0; + + if (unlikely(list_empty(&msg->transfers))) { + dev_err(&atusb->udev->dev, "transfer is empty\n"); + return -EINVAL; + } + + atusb->msg = msg; + + /* Classify the request */ + n = 0; + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if (n == ARRAY_SIZE(x)) { + dev_err(&atusb->udev->dev, "too many transfers\n"); + return -EINVAL; + } + x[n] = xfer; + n++; + } + + tx = x[0]->tx_buf; + rx = x[0]->rx_buf; + len = x[0]->len; + + msg->actual_length = len; + + if (!tx || len != 2) + goto bad_req; + if (n == 1) { + if (rx) { + dev_dbg(&atusb->udev->dev, "read 1\n"); + retval = atusb_read1(atusb, tx[0], rx+1, len-1); + } else { + dev_dbg(&atusb->udev->dev, "write 2\n"); + /* + * Don't take our clock away !! ;-) + */ + if (tx[0] == (CMD_REG | CMD_WRITE | RG_TRX_CTRL_0)) { + msg->status = 0; + msg->complete(msg->context); + } else { + retval = atusb_write(atusb, + tx[0], tx[1], NULL, 0); + } + } + } else { + if (x[0]->rx_buf) { + if (x[1]->tx_buf || !x[1]->rx_buf) + goto bad_req; + dev_dbg(&atusb->udev->dev, "read 1+\n"); + retval = atusb_read_fb(atusb, tx[0], rx+1, + x[1]->rx_buf, x[1]->len); + } else { + if (!x[1]->tx_buf ||x[1]->rx_buf) + goto bad_req; + dev_dbg(&atusb->udev->dev, "write 2+n\n"); + retval = atusb_write(atusb, tx[0], tx[1], + x[1]->tx_buf, x[1]->len); + } + } + return retval; + +bad_req: + dev_err(&atusb->udev->dev, "unrecognized request:\n"); + list_for_each_entry(xfer, &msg->transfers, transfer_list) + dev_err(&atusb->udev->dev, "%stx %srx len %u\n", + xfer->tx_buf ? "" : "!", xfer->rx_buf ? " " : "!", + xfer->len); + return -EINVAL; +} + +static int atusb_setup(struct spi_device *spi) +{ + return 0; +} + + +/* ----- Interrupt handling ------------------------------------------------ */ + + +static void atusb_tasklet(unsigned long data) +{ + struct atusb_local *atusb = (void *) data; + + generic_handle_irq(atusb->slave_irq); +} + +static void atusb_irq(struct urb *urb) +{ + struct atusb_local *atusb = urb->context; + + dev_dbg(&urb->dev->dev, "atusb_irq (%d), seen %d sync %d\n", + urb->status, atusb->irq_buf, atusb->irq_sync); + if (!urb->status) { + atusb->irq_seen = atusb->irq_buf; + if (atusb->irq_sync == atusb->irq_seen && + try_to_del_timer_sync(&atusb->timer) == 1) + atusb_async_finish((struct urb *) atusb->timer.data); + } + usb_free_urb(urb); + atusb->irq_urb = NULL; + tasklet_schedule(&atusb->task); +} + +static int atusb_arm_interrupt(struct atusb_local *atusb) +{ + struct usb_device *dev = atusb->udev; + struct urb *urb; + int retval = -ENOMEM; + + BUG_ON(atusb->irq_urb); + + dev_vdbg(&dev->dev, "atusb_arm_interrupt\n"); + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->dev, + "atusb_arm_interrupt: usb_alloc_urb failed\n"); + return -ENOMEM; + } + + usb_fill_bulk_urb(urb, dev, usb_rcvbulkpipe(dev, 1), + &atusb->irq_buf, 1, atusb_irq, atusb); + atusb->irq_urb = urb; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (!retval) + return 0; + + dev_err(&dev->dev, "failed submitting bulk urb, error %d\n", retval); + retval = retval == -ENOMEM ? retval : -EIO; + + usb_free_urb(urb); + + return retval; +} + +static void atusb_irq_mask(struct irq_data *data) +{ + struct atusb_local *atusb = irq_data_get_irq_chip_data(data); + + dev_vdbg(&atusb->udev->dev, "atusb_irq_mask\n"); + tasklet_disable_nosync(&atusb->task); +} + +static void atusb_irq_unmask(struct irq_data *data) +{ + struct atusb_local *atusb = irq_data_get_irq_chip_data(data); + + dev_vdbg(&atusb->udev->dev, "atusb_irq_unmask\n"); + tasklet_enable(&atusb->task); +} + +static void atusb_irq_ack(struct irq_data *data) +{ + struct atusb_local *atusb = irq_data_get_irq_chip_data(data); + + dev_vdbg(&atusb->udev->dev, "atusb_irq_ack\n"); + atusb_arm_interrupt(atusb); +} + +static struct irq_chip atusb_irq_chip = { + .name = "atusb-slave", + .irq_mask = atusb_irq_mask, + .irq_unmask = atusb_irq_unmask, + .irq_ack = atusb_irq_ack, +}; + + +/* ----- Transceiver reset ------------------------------------------------- */ + + +static void atusb_reset(void *reset_data) +{ + int retval; + struct atusb_local *atusb = reset_data; + + retval = usb_control_msg(atusb->udev, + usb_rcvctrlpipe(atusb->udev, 0), + ATUSB_RF_RESET, ATUSB_TO_DEV, 0, 0, + NULL, 0, 1000); + if (retval < 0) { + dev_err(&atusb->udev->dev, + "%s: error doing reset retval = %d\n", + __func__, retval); + } +} + + +/* ----- Firmware version information -------------------------------------- */ + + +static int atusb_get_and_show_revision(struct atusb_local *atusb) +{ + struct usb_device *dev = atusb->udev; + int retval; + + /* Get a couple of the ATMega Firmware values */ + retval = usb_control_msg(dev, + usb_rcvctrlpipe(dev, 0), + ATUSB_ID, ATUSB_FROM_DEV, 0, 0, + atusb->buffer, 3, 1000); + if (retval < 0) { + dev_info(&dev->dev, + "failed submitting urb for ATUSB_ID, error %d\n", retval); + return retval == -ENOMEM ? retval : -EIO; + } + + atusb->ep0_atusb_major = atusb->buffer[0]; + atusb->ep0_atusb_minor = atusb->buffer[1]; + atusb->atusb_hw_type = atusb->buffer[2]; + dev_info(&dev->dev, + "Firmware: major: %u, minor: %u, hardware type: %u\n", + atusb->ep0_atusb_major, atusb->ep0_atusb_minor, + atusb->atusb_hw_type); + + return 0; +} + +static int atusb_get_and_show_build(struct atusb_local *atusb) +{ + struct usb_device *dev = atusb->udev; + char build[ATUSB_BUILD_SIZE+1]; + int retval; + + retval = usb_control_msg(dev, + usb_rcvctrlpipe(atusb->udev, 0), + ATUSB_BUILD, ATUSB_FROM_DEV, 0, 0, + build, ATUSB_BUILD_SIZE, 1000); + if (retval < 0) { + dev_err(&dev->dev, + "failed submitting urb for ATUSB_BUILD, error %d\n", + retval); + return retval == -ENOMEM ? retval : -EIO; + } + + build[retval] = 0; + dev_info(&dev->dev, "Firmware: build %s\n", build); + + return 0; +} + + +/* ----- Setup ------------------------------------------------------------- */ + + +struct at86rf230_platform_data at86rf230_platform_data = { + .rstn = -1, + .slp_tr = -1, + .dig2 = -1, + .reset = atusb_reset, + /* set .reset_data later */ +}; + +static int atusb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct spi_board_info board_info = { + .modalias = "at86rf230", + /* set .irq later */ + .chip_select = 0, + .bus_num = -1, + .max_speed_hz = 8 * 1000 * 1000, + }; + + struct usb_device *udev = interface_to_usbdev(interface); + struct atusb_local *atusb = NULL; + struct spi_master *master; + int retval; + + /* + * Ignore all interfaces used for DFU, i.e., everything while in the + * boot loader, and interface #1 when in the application. + */ + if (interface->cur_altsetting->desc.bInterfaceClass != + USB_CLASS_VENDOR_SPEC) { + dev_dbg(&udev->dev, + "Ignoring interface with class 0x%02x\n", + interface->cur_altsetting->desc.bInterfaceClass); + return -ENODEV; + } + + master = spi_alloc_master(&udev->dev, sizeof(*atusb)); + if (!master) + return -ENOMEM; + + atusb = spi_master_get_devdata(master); + + atusb->udev = usb_get_dev(udev); + usb_set_intfdata(interface, atusb); + + atusb->master = spi_master_get(master); + + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->bus_num = -1; + master->num_chipselect = 1; + master->setup = atusb_setup; + master->transfer = atusb_transfer; + + atusb->slave_irq = irq_alloc_desc(numa_node_id()); + if (atusb->slave_irq < 0) { + dev_err(&udev->dev, "can't allocate slave irq\n"); + retval = -ENXIO; + goto err_free; + } + + irq_set_chip_data(atusb->slave_irq, atusb); + irq_set_chip(atusb->slave_irq, &atusb_irq_chip); + __irq_set_handler(atusb->slave_irq, handle_level_irq, 0, NULL); + + /* FIXME prepare USB IRQ */ + + retval = spi_register_master(master); + if (retval < 0) { + dev_err(&udev->dev, "can't register spi master\n"); + goto err_slave_irq; + } + + atusb->platform_data = at86rf230_platform_data; + atusb->platform_data.reset_data = atusb; + board_info.platform_data = &atusb->platform_data; + board_info.irq = atusb->slave_irq; + + init_timer(&atusb->timer); + atusb->timer.function = atusb_timer; + + tasklet_init(&atusb->task, atusb_tasklet, (unsigned long) atusb); + tasklet_disable(&atusb->task); + atusb_arm_interrupt(atusb); + + if (atusb_get_and_show_revision(atusb) < 0) + goto err_master; + if (atusb_get_and_show_build(atusb) < 0) + goto err_master; + + atusb->spi = spi_new_device(master, &board_info); + if (!atusb->spi) { + dev_err(&udev->dev, "can't create new device for %s\n", + board_info.modalias); + goto err_master; + } + + dev_info(&atusb->spi->dev, + "ATUSB ready for mischief (IRQ %d)\n", board_info.irq); + + return 0; + +err_master: + /* + * If we come here from a partially successful driver initialization, + * we don't really know how much it has done. In particular, it may + * have triggered an interrupt and thus removed the interrupt URB and + * maybe scheduled the tasklet. + */ + tasklet_disable(&atusb->task); + if (atusb->irq_urb) + usb_kill_urb(atusb->irq_urb); + spi_master_put(atusb->master); +err_slave_irq: + irq_set_chained_handler(atusb->slave_irq, NULL); + irq_set_chip_data(atusb->slave_irq, NULL); + irq_free_desc(atusb->slave_irq); +err_free: + return retval; +} + +static void atusb_disconnect(struct usb_interface *interface) +{ + struct atusb_local *atusb = usb_get_intfdata(interface); + struct spi_master *master = atusb->master; + + tasklet_disable(&atusb->task); + /* @@@ this needs some extra protecion - wa */ + if (atusb->irq_urb) + usb_kill_urb(atusb->irq_urb); + + BUG_ON(timer_pending(&atusb->timer)); + + usb_set_intfdata(interface, NULL); + usb_put_dev(atusb->udev); + + spi_dev_put(atusb->spi); + + spi_unregister_master(master); + + irq_set_chained_handler(atusb->slave_irq, NULL); + irq_set_chip_data(atusb->slave_irq, NULL); + irq_free_desc(atusb->slave_irq); + + spi_master_put(master); +} + +void atusb_release(struct device *dev) +{ + return; +} + +static struct usb_driver atusb_driver = { + .name = "atusb_ben-wpan", + .probe = atusb_probe, + .disconnect = atusb_disconnect, + .id_table = atusb_device_table, +}; + +static struct platform_device atusb_device = { + .name = "spi_atusb", + .id = -1, + .dev.release = atusb_release, +}; + +static int __init atusb_init(void) +{ + int retval; + + retval = platform_device_register(&atusb_device); + if (retval) + return retval; + + return usb_register(&atusb_driver); +} + +static void __exit atusb_exit(void) +{ + usb_deregister(&atusb_driver); + platform_device_unregister(&atusb_device); +} + +module_init (atusb_init); +module_exit (atusb_exit); + +MODULE_AUTHOR("Richard Sharpe "); +MODULE_AUTHOR("Stefan Schmidt "); +MODULE_AUTHOR("Werner Almesberger "); +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 + */ +#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 + +/** + * 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 + * Dmitry Eremin-Solenikov + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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: + * (2) + * (?) + * (?) + * (?) + * 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: + * (1) + * (0-1) + * (?) + * + * Pending address: + * (1) + * type != ARPHRD_IEEE802154); + + hlen = LL_RESERVED_SPACE(dev); + tlen = dev->needed_tailroom; + skb = alloc_skb(len + hlen + tlen, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + skb_reserve(skb, LL_RESERVED_SPACE(dev)); + + skb_reset_network_header(skb); + + mac_cb(skb)->flags = IEEE802154_FC_TYPE_BEACON; + mac_cb(skb)->seq = ieee802154_mlme_ops(dev)->get_bsn(dev); + + addr.addr_type = IEEE802154_ADDR_NONE; + err = dev_hard_header(skb, dev, ETH_P_IEEE802154, &addr, saddr, len); + if (err < 0) { + kfree_skb(skb); + return err; + } + skb_reset_mac_header(skb); + + /* Superframe */ + sf = IEEE802154_BEACON_SF_BO_BEACONLESS; + sf |= IEEE802154_BEACON_SF_SO_INACTIVE; + if (flags & IEEE802154_BEACON_FLAG_PANCOORD) + sf |= IEEE802154_BEACON_SF_PANCOORD; + + if (flags & IEEE802154_BEACON_FLAG_CANASSOC) + sf |= IEEE802154_BEACON_SF_CANASSOC; + memcpy(skb_put(skb, sizeof(sf)), &sf, sizeof(sf)); + + /* TODO GTS */ + gts = 0; + + if (flags & IEEE802154_BEACON_FLAG_GTSPERMIT) + gts |= IEEE802154_BEACON_GTS_PERMIT; + memcpy(skb_put(skb, sizeof(gts)), >s, sizeof(gts)); + + /* FIXME pending address */ + addr16_cnt = 0; + addr64_cnt = 0; + + pa_spec = IEEE802154_BEACON_PA_LONG(addr64_cnt) | + IEEE802154_BEACON_PA_SHORT(addr16_cnt); + memcpy(skb_put(skb, sizeof(pa_spec)), &pa_spec, sizeof(pa_spec)); + + memcpy(skb_put(skb, len), buf, len); + + skb->dev = dev; + skb->protocol = htons(ETH_P_IEEE802154); + + return dev_queue_xmit(skb); +} + +/* at entry to this function we need skb->data to point to start + * of beacon field and MAC frame already parsed into MAC_CB */ + +static int parse_beacon_frame(struct sk_buff *skb, u8 *buf, + int *flags, struct list_head *al) +{ + int offt = 0; + u8 gts_spec; + u8 pa_spec; + struct mac802154_pandsc *pd; + u16 sf = skb->data[0] + (skb->data[1] << 8); + + pd = kzalloc(sizeof(struct mac802154_pandsc), GFP_KERNEL); + + /* Filling-up pre-parsed values */ + pd->lqi = mac_cb(skb)->lqi; + pd->sf = sf; + /* FIXME: make sure we do it right */ + memcpy(&pd->addr, &mac_cb(skb)->da, sizeof(struct ieee802154_addr)); + + /* Supplying our nitifiers with data */ + ieee802154_nl_beacon_indic(skb->dev, pd->addr.pan_id, + pd->addr.short_addr); + /* FIXME: We don't cache PAN descriptors yet */ + kfree(pd); + + offt += 2; + gts_spec = skb->data[offt++]; + /* FIXME !!! */ + if ((gts_spec & 7) != 0) { + pr_debug("We still don't parse GTS part properly"); + return -ENOTSUPP; + } + pa_spec = skb->data[offt++]; + /* FIXME !!! */ + if (pa_spec != 0) { + pr_debug("We still don't parse PA part properly"); + return -ENOTSUPP; + } + + *flags = 0; + + if (sf & IEEE802154_BEACON_SF_PANCOORD) + *flags |= IEEE802154_BEACON_FLAG_PANCOORD; + + if (sf & IEEE802154_BEACON_SF_CANASSOC) + *flags |= IEEE802154_BEACON_FLAG_CANASSOC; + BUG_ON(skb->len - offt < 0); + /* FIXME */ + if (buf && (skb->len - offt > 0)) + memcpy(buf, skb->data + offt, skb->len - offt); + return 0; +} + +int mac802154_process_beacon(struct net_device *dev, + struct sk_buff *skb) +{ + int flags; + int ret; + ret = parse_beacon_frame(skb, NULL, &flags, NULL); + + /* Here we have cb->sa = coordinator address, and PAN address */ + + if (ret < 0) { + ret = NET_RX_DROP; + goto fail; + } + dev_dbg(&dev->dev, "got beacon from pan %04x\n", + mac_cb(skb)->sa.pan_id); + mac802154_beacon_hash_add(&mac_cb(skb)->sa); + mac802154_beacon_hash_dump(); + ret = NET_RX_SUCCESS; +fail: + kfree_skb(skb); + return ret; +} + diff --git a/net/mac802154/beacon_hash.c b/net/mac802154/beacon_hash.c new file mode 100644 index 0000000..97fb987 --- /dev/null +++ b/net/mac802154/beacon_hash.c @@ -0,0 +1,106 @@ +/* + * MAC beacon hash storage + * + * Copyright 2007, 2008 Siemens AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Written by: + * Sergey Lapin + * Dmitry Eremin-Solenikov + */ + +#include +#include +#include + +#include + +#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 + * Dmitry Eremin-Solenikov + */ + +#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 + * Maxim Gorbachyov + * Dmitry Eremin-Solenikov + */ +#ifndef MAC802154_H +#define MAC802154_H + +#include + +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 + * Dmitry Eremin-Solenikov + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include +#include + +#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 + * Sergey Lapin + * Maxim Gorbachyov + */ + +#include + +#include +#include + +#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 + * Sergey Lapin + * Maxim Gorbachyov + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 + * Maxim Gorbachyov + * Dmitry Eremin-Solenikov + */ + +#include +#include +#include +#include +#include + +#include +#include + +#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 + * Maxim Gorbachyov + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 + */ + +#include +#include +#include +#include +#include + +#include +#include + +#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 + * Sergey Lapin + * Maxim Gorbachyov + */ + +#include +#include +#include + +#include +#include + +#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 + * Sergey Lapin + * Maxim Gorbachyov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + * Maxim Yu. Osipov + */ +#include +#include +#include +#include +#include +#include /* For TIOCOUTQ/INQ */ +#include +#include +#include +#include +#include +#include + +//#include +#include +#include + +#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 + * Dmitry Eremin-Solenikov + * Maxim Yu. Osipov + */ + +#include +#include +#include +#include +#include +//#include +//#include +//#include +#include +#include + +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