Commit 876a762f authored by Marcel Holtmann's avatar Marcel Holtmann

Merge bk://linux.bkbits.net/linux-2.5

into hostme.bitkeeper.com:/ua/repos/l/linux-bt/bt-2.5
parents bda0e956 dcfa61d5
menu "Bluetooth device drivers" menu "Bluetooth device drivers"
depends on BT!=n depends on BT
config BT_HCIUSB config BT_HCIUSB
tristate "HCI USB driver" tristate "HCI USB driver"
...@@ -57,6 +57,14 @@ config BT_HCIUART_BCSP ...@@ -57,6 +57,14 @@ config BT_HCIUART_BCSP
Say Y here to compile support for HCI BCSP protocol. Say Y here to compile support for HCI BCSP protocol.
config BT_HCIUART_BCSP_TXCRC
bool "Transmit CRC with every BCSP packet"
depends on BT_HCIUART_BCSP
help
If you say Y here, a 16-bit CRC checksum will be transmitted along with
every BCSP (BlueCore Serial Protocol) packet sent to the Bluetooth chip.
This increases reliability, but slightly reduces efficiency.
config BT_HCIDTL1 config BT_HCIDTL1
tristate "HCI DTL1 (PC Card) driver" tristate "HCI DTL1 (PC Card) driver"
depends on PCMCIA && BT depends on PCMCIA && BT
...@@ -73,7 +81,7 @@ config BT_HCIDTL1 ...@@ -73,7 +81,7 @@ config BT_HCIDTL1
config BT_HCIBT3C config BT_HCIBT3C
tristate "HCI BT3C (PC Card) driver" tristate "HCI BT3C (PC Card) driver"
depends on PCMCIA && BT depends on PCMCIA && BT
---help--- help
Bluetooth HCI BT3C (PC Card) driver. Bluetooth HCI BT3C (PC Card) driver.
This driver provides support for Bluetooth PCMCIA devices with This driver provides support for Bluetooth PCMCIA devices with
3Com BT3C interface: 3Com BT3C interface:
...@@ -99,6 +107,22 @@ config BT_HCIBLUECARD ...@@ -99,6 +107,22 @@ config BT_HCIBLUECARD
Say Y here to compile support for HCI BlueCard devices into the Say Y here to compile support for HCI BlueCard devices into the
kernel or say M to compile it as module (bluecard_cs.o). kernel or say M to compile it as module (bluecard_cs.o).
config BT_HCIBTUART
tristate "HCI UART (PC Card) device driver"
depends on PCMCIA && BT
help
Bluetooth HCI UART (PC Card) driver.
This driver provides support for Bluetooth PCMCIA devices with
an UART interface:
Xircom CreditCard Bluetooth Adapter
Xircom RealPort2 Bluetooth Adapter
Sphinx PICO Card
H-Soft blue+Card
Cyber-blue Compact Flash Card
Say Y here to compile support for HCI UART devices into the
kernel or say M to compile it as module (btuart_cs.o).
config BT_HCIVHCI config BT_HCIVHCI
tristate "HCI VHCI (Virtual HCI device) driver" tristate "HCI VHCI (Virtual HCI device) driver"
depends on BT depends on BT
......
...@@ -8,6 +8,7 @@ obj-$(CONFIG_BT_HCIUART) += hci_uart.o ...@@ -8,6 +8,7 @@ obj-$(CONFIG_BT_HCIUART) += hci_uart.o
obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o
obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o
obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o
obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o
hci_uart-y := hci_ldisc.o hci_uart-y := hci_ldisc.o
hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o
......
/*
*
* Driver for Bluetooth PCMCIA cards with HCI UART interface
*
* Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org>
*
*
* 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;
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The initial developer of the original code is David A. Hinds
* <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
* are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/serial.h>
#include <linux/serial_reg.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ciscode.h>
#include <pcmcia/ds.h>
#include <pcmcia/cisreg.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
/* ======================== Module parameters ======================== */
/* Bit map of interrupts to choose from */
static u_int irq_mask = 0xffff;
static int irq_list[4] = { -1 };
MODULE_PARM(irq_mask, "i");
MODULE_PARM(irq_list, "1-4i");
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth driver for Bluetooth PCMCIA cards with HCI UART interface");
MODULE_LICENSE("GPL");
/* ======================== Local structures ======================== */
typedef struct btuart_info_t {
dev_link_t link;
dev_node_t node;
struct hci_dev hdev;
spinlock_t lock; /* For serializing operations */
struct sk_buff_head txq;
unsigned long tx_state;
unsigned long rx_state;
unsigned long rx_count;
struct sk_buff *rx_skb;
} btuart_info_t;
void btuart_config(dev_link_t *link);
void btuart_release(u_long arg);
int btuart_event(event_t event, int priority, event_callback_args_t *args);
static dev_info_t dev_info = "btuart_cs";
dev_link_t *btuart_attach(void);
void btuart_detach(dev_link_t *);
static dev_link_t *dev_list = NULL;
/* Maximum baud rate */
#define SPEED_MAX 115200
/* Default baud rate: 57600, 115200, 230400 or 460800 */
#define DEFAULT_BAUD_RATE 115200
/* Transmit states */
#define XMIT_SENDING 1
#define XMIT_WAKEUP 2
#define XMIT_WAITING 8
/* Receiver states */
#define RECV_WAIT_PACKET_TYPE 0
#define RECV_WAIT_EVENT_HEADER 1
#define RECV_WAIT_ACL_HEADER 2
#define RECV_WAIT_SCO_HEADER 3
#define RECV_WAIT_DATA 4
/* ======================== Interrupt handling ======================== */
static int btuart_write(unsigned int iobase, int fifo_size, __u8 *buf, int len)
{
int actual = 0;
/* Tx FIFO should be empty */
if (!(inb(iobase + UART_LSR) & UART_LSR_THRE))
return 0;
/* Fill FIFO with current frame */
while ((fifo_size-- > 0) && (actual < len)) {
/* Transmit next byte */
outb(buf[actual], iobase + UART_TX);
actual++;
}
return actual;
}
static void btuart_write_wakeup(btuart_info_t *info)
{
if (!info) {
printk(KERN_WARNING "btuart_cs: Call of write_wakeup for unknown device.\n");
return;
}
if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) {
set_bit(XMIT_WAKEUP, &(info->tx_state));
return;
}
do {
register unsigned int iobase = info->link.io.BasePort1;
register struct sk_buff *skb;
register int len;
clear_bit(XMIT_WAKEUP, &(info->tx_state));
if (!(info->link.state & DEV_PRESENT))
return;
if (!(skb = skb_dequeue(&(info->txq))))
break;
/* Send frame */
len = btuart_write(iobase, 16, skb->data, skb->len);
set_bit(XMIT_WAKEUP, &(info->tx_state));
if (len == skb->len) {
kfree_skb(skb);
} else {
skb_pull(skb, len);
skb_queue_head(&(info->txq), skb);
}
info->hdev.stat.byte_tx += len;
} while (test_bit(XMIT_WAKEUP, &(info->tx_state)));
clear_bit(XMIT_SENDING, &(info->tx_state));
}
static void btuart_receive(btuart_info_t *info)
{
unsigned int iobase;
int boguscount = 0;
if (!info) {
printk(KERN_WARNING "btuart_cs: Call of receive for unknown device.\n");
return;
}
iobase = info->link.io.BasePort1;
do {
info->hdev.stat.byte_rx++;
/* Allocate packet */
if (info->rx_skb == NULL) {
info->rx_state = RECV_WAIT_PACKET_TYPE;
info->rx_count = 0;
if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) {
printk(KERN_WARNING "btuart_cs: Can't allocate mem for new packet.\n");
return;
}
}
if (info->rx_state == RECV_WAIT_PACKET_TYPE) {
info->rx_skb->dev = (void *)&(info->hdev);
info->rx_skb->pkt_type = inb(iobase + UART_RX);
switch (info->rx_skb->pkt_type) {
case HCI_EVENT_PKT:
info->rx_state = RECV_WAIT_EVENT_HEADER;
info->rx_count = HCI_EVENT_HDR_SIZE;
break;
case HCI_ACLDATA_PKT:
info->rx_state = RECV_WAIT_ACL_HEADER;
info->rx_count = HCI_ACL_HDR_SIZE;
break;
case HCI_SCODATA_PKT:
info->rx_state = RECV_WAIT_SCO_HEADER;
info->rx_count = HCI_SCO_HDR_SIZE;
break;
default:
/* Unknown packet */
printk(KERN_WARNING "btuart_cs: Unknown HCI packet with type 0x%02x received.\n", info->rx_skb->pkt_type);
info->hdev.stat.err_rx++;
clear_bit(HCI_RUNNING, &(info->hdev.flags));
kfree_skb(info->rx_skb);
info->rx_skb = NULL;
break;
}
} else {
*skb_put(info->rx_skb, 1) = inb(iobase + UART_RX);
info->rx_count--;
if (info->rx_count == 0) {
int dlen;
struct hci_event_hdr *eh;
struct hci_acl_hdr *ah;
struct hci_sco_hdr *sh;
switch (info->rx_state) {
case RECV_WAIT_EVENT_HEADER:
eh = (struct hci_event_hdr *)(info->rx_skb->data);
info->rx_state = RECV_WAIT_DATA;
info->rx_count = eh->plen;
break;
case RECV_WAIT_ACL_HEADER:
ah = (struct hci_acl_hdr *)(info->rx_skb->data);
dlen = __le16_to_cpu(ah->dlen);
info->rx_state = RECV_WAIT_DATA;
info->rx_count = dlen;
break;
case RECV_WAIT_SCO_HEADER:
sh = (struct hci_sco_hdr *)(info->rx_skb->data);
info->rx_state = RECV_WAIT_DATA;
info->rx_count = sh->dlen;
break;
case RECV_WAIT_DATA:
hci_recv_frame(info->rx_skb);
info->rx_skb = NULL;
break;
}
}
}
/* Make sure we don't stay here to long */
if (boguscount++ > 16)
break;
} while (inb(iobase + UART_LSR) & UART_LSR_DR);
}
void btuart_interrupt(int irq, void *dev_inst, struct pt_regs *regs)
{
btuart_info_t *info = dev_inst;
unsigned int iobase;
int boguscount = 0;
int iir, lsr;
if (!info) {
printk(KERN_WARNING "btuart_cs: Call of irq %d for unknown device.\n", irq);
return;
}
iobase = info->link.io.BasePort1;
spin_lock(&(info->lock));
iir = inb(iobase + UART_IIR) & UART_IIR_ID;
while (iir) {
/* Clear interrupt */
lsr = inb(iobase + UART_LSR);
switch (iir) {
case UART_IIR_RLSI:
printk(KERN_NOTICE "btuart_cs: RLSI\n");
break;
case UART_IIR_RDI:
/* Receive interrupt */
btuart_receive(info);
break;
case UART_IIR_THRI:
if (lsr & UART_LSR_THRE) {
/* Transmitter ready for data */
btuart_write_wakeup(info);
}
break;
default:
printk(KERN_NOTICE "btuart_cs: Unhandled IIR=%#x\n", iir);
break;
}
/* Make sure we don't stay here to long */
if (boguscount++ > 100)
break;
iir = inb(iobase + UART_IIR) & UART_IIR_ID;
}
spin_unlock(&(info->lock));
}
static void btuart_change_speed(btuart_info_t *info, unsigned int speed)
{
unsigned long flags;
unsigned int iobase;
int fcr; /* FIFO control reg */
int lcr; /* Line control reg */
int divisor;
if (!info) {
printk(KERN_WARNING "btuart_cs: Call of change speed for unknown device.\n");
return;
}
iobase = info->link.io.BasePort1;
spin_lock_irqsave(&(info->lock), flags);
/* Turn off interrupts */
outb(0, iobase + UART_IER);
divisor = SPEED_MAX / speed;
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT;
/*
* Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and
* almost 1,7 ms at 19200 bps. At speeds above that we can just forget
* about this timeout since it will always be fast enough.
*/
if (speed < 38400)
fcr |= UART_FCR_TRIGGER_1;
else
fcr |= UART_FCR_TRIGGER_14;
/* Bluetooth cards use 8N1 */
lcr = UART_LCR_WLEN8;
outb(UART_LCR_DLAB | lcr, iobase + UART_LCR); /* Set DLAB */
outb(divisor & 0xff, iobase + UART_DLL); /* Set speed */
outb(divisor >> 8, iobase + UART_DLM);
outb(lcr, iobase + UART_LCR); /* Set 8N1 */
outb(fcr, iobase + UART_FCR); /* Enable FIFO's */
/* Turn on interrups */
outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER);
spin_unlock_irqrestore(&(info->lock), flags);
}
/* ======================== HCI interface ======================== */
static int btuart_hci_flush(struct hci_dev *hdev)
{
btuart_info_t *info = (btuart_info_t *)(hdev->driver_data);
/* Drop TX queue */
skb_queue_purge(&(info->txq));
return 0;
}
static int btuart_hci_open(struct hci_dev *hdev)
{
set_bit(HCI_RUNNING, &(hdev->flags));
return 0;
}
static int btuart_hci_close(struct hci_dev *hdev)
{
if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags)))
return 0;
btuart_hci_flush(hdev);
return 0;
}
static int btuart_hci_send_frame(struct sk_buff *skb)
{
btuart_info_t *info;
struct hci_dev *hdev = (struct hci_dev *)(skb->dev);
if (!hdev) {
printk(KERN_WARNING "btuart_cs: Frame for unknown HCI device (hdev=NULL).");
return -ENODEV;
}
info = (btuart_info_t *)(hdev->driver_data);
switch (skb->pkt_type) {
case HCI_COMMAND_PKT:
hdev->stat.cmd_tx++;
break;
case HCI_ACLDATA_PKT:
hdev->stat.acl_tx++;
break;
case HCI_SCODATA_PKT:
hdev->stat.sco_tx++;
break;
};
/* Prepend skb with frame type */
memcpy(skb_push(skb, 1), &(skb->pkt_type), 1);
skb_queue_tail(&(info->txq), skb);
btuart_write_wakeup(info);
return 0;
}
static void btuart_hci_destruct(struct hci_dev *hdev)
{
}
static int btuart_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg)
{
return -ENOIOCTLCMD;
}
/* ======================== Card services HCI interaction ======================== */
int btuart_open(btuart_info_t *info)
{
unsigned long flags;
unsigned int iobase = info->link.io.BasePort1;
struct hci_dev *hdev;
spin_lock_init(&(info->lock));
skb_queue_head_init(&(info->txq));
info->rx_state = RECV_WAIT_PACKET_TYPE;
info->rx_count = 0;
info->rx_skb = NULL;
spin_lock_irqsave(&(info->lock), flags);
/* Reset UART */
outb(0, iobase + UART_MCR);
/* Turn off interrupts */
outb(0, iobase + UART_IER);
/* Initialize UART */
outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */
outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR);
/* Turn on interrupts */
// outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER);
spin_unlock_irqrestore(&(info->lock), flags);
btuart_change_speed(info, DEFAULT_BAUD_RATE);
/* Timeout before it is safe to send the first HCI packet */
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ);
/* Initialize and register HCI device */
hdev = &(info->hdev);
hdev->type = HCI_PCCARD;
hdev->driver_data = info;
hdev->open = btuart_hci_open;
hdev->close = btuart_hci_close;
hdev->flush = btuart_hci_flush;
hdev->send = btuart_hci_send_frame;
hdev->destruct = btuart_hci_destruct;
hdev->ioctl = btuart_hci_ioctl;
if (hci_register_dev(hdev) < 0) {
printk(KERN_WARNING "btuart_cs: Can't register HCI device %s.\n", hdev->name);
return -ENODEV;
}
return 0;
}
int btuart_close(btuart_info_t *info)
{
unsigned long flags;
unsigned int iobase = info->link.io.BasePort1;
struct hci_dev *hdev = &(info->hdev);
btuart_hci_close(hdev);
spin_lock_irqsave(&(info->lock), flags);
/* Reset UART */
outb(0, iobase + UART_MCR);
/* Turn off interrupts */
outb(0, iobase + UART_IER);
spin_unlock_irqrestore(&(info->lock), flags);
if (hci_unregister_dev(hdev) < 0)
printk(KERN_WARNING "btuart_cs: Can't unregister HCI device %s.\n", hdev->name);
return 0;
}
/* ======================== Card services ======================== */
static void cs_error(client_handle_t handle, int func, int ret)
{
error_info_t err = { func, ret };
CardServices(ReportError, handle, &err);
}
dev_link_t *btuart_attach(void)
{
btuart_info_t *info;
client_reg_t client_reg;
dev_link_t *link;
int i, ret;
/* Create new info device */
info = kmalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return NULL;
memset(info, 0, sizeof(*info));
link = &info->link;
link->priv = info;
init_timer(&link->release);
link->release.function = &btuart_release;
link->release.data = (u_long)link;
link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
link->io.NumPorts1 = 8;
link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
if (irq_list[0] == -1)
link->irq.IRQInfo2 = irq_mask;
else
for (i = 0; i < 4; i++)
link->irq.IRQInfo2 |= 1 << irq_list[i];
link->irq.Handler = btuart_interrupt;
link->irq.Instance = info;
link->conf.Attributes = CONF_ENABLE_IRQ;
link->conf.Vcc = 50;
link->conf.IntType = INT_MEMORY_AND_IO;
/* Register with Card Services */
link->next = dev_list;
dev_list = link;
client_reg.dev_info = &dev_info;
client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE;
client_reg.EventMask =
CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
client_reg.event_handler = &btuart_event;
client_reg.Version = 0x0210;
client_reg.event_callback_args.client_data = link;
ret = CardServices(RegisterClient, &link->handle, &client_reg);
if (ret != CS_SUCCESS) {
cs_error(link->handle, RegisterClient, ret);
btuart_detach(link);
return NULL;
}
return link;
}
void btuart_detach(dev_link_t *link)
{
btuart_info_t *info = link->priv;
dev_link_t **linkp;
int ret;
/* Locate device structure */
for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
if (*linkp == link)
break;
if (*linkp == NULL)
return;
del_timer(&link->release);
if (link->state & DEV_CONFIG)
btuart_release((u_long)link);
if (link->handle) {
ret = CardServices(DeregisterClient, link->handle);
if (ret != CS_SUCCESS)
cs_error(link->handle, DeregisterClient, ret);
}
/* Unlink device structure, free bits */
*linkp = link->next;
kfree(info);
}
static int get_tuple(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse)
{
int i;
i = CardServices(fn, handle, tuple);
if (i != CS_SUCCESS)
return CS_NO_MORE_ITEMS;
i = CardServices(GetTupleData, handle, tuple);
if (i != CS_SUCCESS)
return i;
return CardServices(ParseTuple, handle, tuple, parse);
}
#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c)
#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c)
void btuart_config(dev_link_t *link)
{
static ioaddr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 };
client_handle_t handle = link->handle;
btuart_info_t *info = link->priv;
tuple_t tuple;
u_short buf[256];
cisparse_t parse;
cistpl_cftable_entry_t *cf = &parse.cftable_entry;
config_info_t config;
int i, j, try, last_ret, last_fn;
tuple.TupleData = (cisdata_t *)buf;
tuple.TupleOffset = 0;
tuple.TupleDataMax = 255;
tuple.Attributes = 0;
/* Get configuration register information */
tuple.DesiredTuple = CISTPL_CONFIG;
last_ret = first_tuple(handle, &tuple, &parse);
if (last_ret != CS_SUCCESS) {
last_fn = ParseTuple;
goto cs_failed;
}
link->conf.ConfigBase = parse.config.base;
link->conf.Present = parse.config.rmask[0];
/* Configure card */
link->state |= DEV_CONFIG;
i = CardServices(GetConfigurationInfo, handle, &config);
link->conf.Vcc = config.Vcc;
/* First pass: look for a config entry that looks normal. */
tuple.TupleData = (cisdata_t *) buf;
tuple.TupleOffset = 0;
tuple.TupleDataMax = 255;
tuple.Attributes = 0;
tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
/* Two tries: without IO aliases, then with aliases */
for (try = 0; try < 2; try++) {
i = first_tuple(handle, &tuple, &parse);
while (i != CS_NO_MORE_ITEMS) {
if (i != CS_SUCCESS)
goto next_entry;
if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM))
link->conf.Vpp1 = link->conf.Vpp2 = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000;
if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && (cf->io.win[0].base != 0)) {
link->conf.ConfigIndex = cf->index;
link->io.BasePort1 = cf->io.win[0].base;
link->io.IOAddrLines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK;
i = CardServices(RequestIO, link->handle, &link->io);
if (i == CS_SUCCESS)
goto found_port;
}
next_entry:
i = next_tuple(handle, &tuple, &parse);
}
}
/* Second pass: try to find an entry that isn't picky about
its base address, then try to grab any standard serial port
address, and finally try to get any free port. */
i = first_tuple(handle, &tuple, &parse);
while (i != CS_NO_MORE_ITEMS) {
if ((i == CS_SUCCESS) && (cf->io.nwin > 0)
&& ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) {
link->conf.ConfigIndex = cf->index;
for (j = 0; j < 5; j++) {
link->io.BasePort1 = base[j];
link->io.IOAddrLines = base[j] ? 16 : 3;
i = CardServices(RequestIO, link->handle, &link->io);
if (i == CS_SUCCESS)
goto found_port;
}
}
i = next_tuple(handle, &tuple, &parse);
}
found_port:
if (i != CS_SUCCESS) {
printk(KERN_NOTICE "btuart_cs: No usable port range found. Giving up.\n");
cs_error(link->handle, RequestIO, i);
goto failed;
}
i = CardServices(RequestIRQ, link->handle, &link->irq);
if (i != CS_SUCCESS) {
cs_error(link->handle, RequestIRQ, i);
link->irq.AssignedIRQ = 0;
}
i = CardServices(RequestConfiguration, link->handle, &link->conf);
if (i != CS_SUCCESS) {
cs_error(link->handle, RequestConfiguration, i);
goto failed;
}
MOD_INC_USE_COUNT;
if (btuart_open(info) != 0)
goto failed;
strcpy(info->node.dev_name, info->hdev.name);
link->dev = &info->node;
link->state &= ~DEV_CONFIG_PENDING;
return;
cs_failed:
cs_error(link->handle, last_fn, last_ret);
failed:
btuart_release((u_long) link);
}
void btuart_release(u_long arg)
{
dev_link_t *link = (dev_link_t *)arg;
btuart_info_t *info = link->priv;
if (link->state & DEV_PRESENT)
btuart_close(info);
MOD_DEC_USE_COUNT;
link->dev = NULL;
CardServices(ReleaseConfiguration, link->handle);
CardServices(ReleaseIO, link->handle, &link->io);
CardServices(ReleaseIRQ, link->handle, &link->irq);
link->state &= ~DEV_CONFIG;
}
int btuart_event(event_t event, int priority, event_callback_args_t *args)
{
dev_link_t *link = args->client_data;
btuart_info_t *info = link->priv;
switch (event) {
case CS_EVENT_CARD_REMOVAL:
link->state &= ~DEV_PRESENT;
if (link->state & DEV_CONFIG) {
btuart_close(info);
mod_timer(&link->release, jiffies + HZ / 20);
}
break;
case CS_EVENT_CARD_INSERTION:
link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
btuart_config(link);
break;
case CS_EVENT_PM_SUSPEND:
link->state |= DEV_SUSPEND;
/* Fall through... */
case CS_EVENT_RESET_PHYSICAL:
if (link->state & DEV_CONFIG)
CardServices(ReleaseConfiguration, link->handle);
break;
case CS_EVENT_PM_RESUME:
link->state &= ~DEV_SUSPEND;
/* Fall through... */
case CS_EVENT_CARD_RESET:
if (DEV_OK(link))
CardServices(RequestConfiguration, link->handle, &link->conf);
break;
}
return 0;
}
/* ======================== Module initialization ======================== */
int __init init_btuart_cs(void)
{
servinfo_t serv;
int err;
CardServices(GetCardServicesInfo, &serv);
if (serv.Revision != CS_RELEASE_CODE) {
printk(KERN_NOTICE "btuart_cs: Card Services release does not match!\n");
return -1;
}
err = register_pccard_driver(&dev_info, &btuart_attach, &btuart_detach);
return err;
}
void __exit exit_btuart_cs(void)
{
unregister_pccard_driver(&dev_info);
while (dev_list != NULL)
btuart_detach(dev_list);
}
module_init(init_btuart_cs);
module_exit(exit_btuart_cs);
EXPORT_NO_SYMBOLS;
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
#define HCI_PCCARD 2 #define HCI_PCCARD 2
#define HCI_UART 3 #define HCI_UART 3
#define HCI_RS232 4 #define HCI_RS232 4
#define HCI_PCI 5
/* HCI device flags */ /* HCI device flags */
enum { enum {
......
...@@ -224,6 +224,8 @@ struct l2cap_pinfo { ...@@ -224,6 +224,8 @@ struct l2cap_pinfo {
__u8 ident; __u8 ident;
__u16 sport;
struct l2cap_conn *conn; struct l2cap_conn *conn;
struct sock *next_c; struct sock *next_c;
struct sock *prev_c; struct sock *prev_c;
......
...@@ -7,7 +7,7 @@ menu "Bluetooth support" ...@@ -7,7 +7,7 @@ menu "Bluetooth support"
config BT config BT
tristate "Bluetooth subsystem support" tristate "Bluetooth subsystem support"
---help--- help
Bluetooth is low-cost, low-power, short-range wireless technology. Bluetooth is low-cost, low-power, short-range wireless technology.
It was designed as a replacement for cables and other short-range It was designed as a replacement for cables and other short-range
technologies like IrDA. Bluetooth operates in personal area range technologies like IrDA. Bluetooth operates in personal area range
......
config BT_BNEP config BT_BNEP
tristate "BNEP protocol support" tristate "BNEP protocol support"
depends on BT_L2CAP depends on BT && BT_L2CAP
---help--- help
BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet
emulation layer on top of Bluetooth. BNEP is required for Bluetooth emulation layer on top of Bluetooth. BNEP is required for Bluetooth
PAN (Personal Area Network). PAN (Personal Area Network).
......
...@@ -128,18 +128,17 @@ static int bnep_send_rsp(struct bnep_session *s, u8 ctrl, u16 resp) ...@@ -128,18 +128,17 @@ static int bnep_send_rsp(struct bnep_session *s, u8 ctrl, u16 resp)
return bnep_send(s, &rsp, sizeof(rsp)); return bnep_send(s, &rsp, sizeof(rsp));
} }
static int bnep_ctrl_set_netfilter(struct bnep_session *s, struct sk_buff *skb) static int bnep_ctrl_set_netfilter(struct bnep_session *s, u16 *data, int len)
{ {
u16 *data;
int n; int n;
data = (void *) skb->data; if (len < 2)
if (!skb_pull(skb, 2))
return -EILSEQ; return -EILSEQ;
n = ntohs(get_unaligned(data)); n = ntohs(get_unaligned(data));
data++; len -= 2;
data = (void *) skb->data; if (len < n)
if (!skb_pull(skb, n))
return -EILSEQ; return -EILSEQ;
BT_DBG("filter len %d", n); BT_DBG("filter len %d", n);
...@@ -170,18 +169,17 @@ static int bnep_ctrl_set_netfilter(struct bnep_session *s, struct sk_buff *skb) ...@@ -170,18 +169,17 @@ static int bnep_ctrl_set_netfilter(struct bnep_session *s, struct sk_buff *skb)
return 0; return 0;
} }
static int bnep_ctrl_set_mcfilter(struct bnep_session *s, struct sk_buff *skb) static int bnep_ctrl_set_mcfilter(struct bnep_session *s, u8 *data, int len)
{ {
u8 *data;
int n; int n;
data = (void *) skb->data; if (len < 2)
if (!skb_pull(skb, 2))
return -EILSEQ; return -EILSEQ;
n = ntohs(get_unaligned((u16 *) data)); n = ntohs(get_unaligned((u16 *) data));
data += 2; len -= 2;
data = (void *) skb->data; if (len < n)
if (!skb_pull(skb, n))
return -EILSEQ; return -EILSEQ;
BT_DBG("filter len %d", n); BT_DBG("filter len %d", n);
...@@ -225,11 +223,12 @@ static int bnep_ctrl_set_mcfilter(struct bnep_session *s, struct sk_buff *skb) ...@@ -225,11 +223,12 @@ static int bnep_ctrl_set_mcfilter(struct bnep_session *s, struct sk_buff *skb)
return 0; return 0;
} }
static int bnep_rx_control(struct bnep_session *s, struct sk_buff *skb) static int bnep_rx_control(struct bnep_session *s, void *data, int len)
{ {
u8 cmd = *(u8 *)data;
int err = 0; int err = 0;
u8 cmd = *(u8 *) skb->data;
skb_pull(skb, 1); data++; len--;
switch (cmd) { switch (cmd) {
case BNEP_CMD_NOT_UNDERSTOOD: case BNEP_CMD_NOT_UNDERSTOOD:
...@@ -241,11 +240,11 @@ static int bnep_rx_control(struct bnep_session *s, struct sk_buff *skb) ...@@ -241,11 +240,11 @@ static int bnep_rx_control(struct bnep_session *s, struct sk_buff *skb)
break; break;
case BNEP_FILTER_NET_TYPE_SET: case BNEP_FILTER_NET_TYPE_SET:
err = bnep_ctrl_set_netfilter(s, skb); err = bnep_ctrl_set_netfilter(s, data, len);
break; break;
case BNEP_FILTER_MULTI_ADDR_SET: case BNEP_FILTER_MULTI_ADDR_SET:
err = bnep_ctrl_set_mcfilter(s, skb); err = bnep_ctrl_set_mcfilter(s, data, len);
break; break;
default: { default: {
...@@ -277,12 +276,15 @@ static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) ...@@ -277,12 +276,15 @@ static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb)
switch (h->type & BNEP_TYPE_MASK) { switch (h->type & BNEP_TYPE_MASK) {
case BNEP_EXT_CONTROL: case BNEP_EXT_CONTROL:
err = bnep_rx_control(s, skb); bnep_rx_control(s, skb->data, skb->len);
break; break;
default: default:
/* Unknown extension */ /* Unknown extension, skip it. */
if (!skb_pull(skb, h->len)) break;
}
if (!skb_pull(skb, h->len)) {
err = -EILSEQ; err = -EILSEQ;
break; break;
} }
...@@ -315,7 +317,7 @@ static inline int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) ...@@ -315,7 +317,7 @@ static inline int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
goto badframe; goto badframe;
if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) {
bnep_rx_control(s, skb); bnep_rx_control(s, skb->data, skb->len);
kfree_skb(skb); kfree_skb(skb);
return 0; return 0;
} }
......
...@@ -718,7 +718,7 @@ int hci_get_dev_list(unsigned long arg) ...@@ -718,7 +718,7 @@ int hci_get_dev_list(unsigned long arg)
if (!dev_num) if (!dev_num)
return -EINVAL; return -EINVAL;
size = dev_num * sizeof(struct hci_dev_req) + sizeof(__u16); size = dev_num * sizeof(*dr) + sizeof(*dl);
if (verify_area(VERIFY_WRITE, (void *) arg, size)) if (verify_area(VERIFY_WRITE, (void *) arg, size))
return -EFAULT; return -EFAULT;
...@@ -739,7 +739,7 @@ int hci_get_dev_list(unsigned long arg) ...@@ -739,7 +739,7 @@ int hci_get_dev_list(unsigned long arg)
read_unlock_bh(&hci_dev_list_lock); read_unlock_bh(&hci_dev_list_lock);
dl->dev_num = n; dl->dev_num = n;
size = n * sizeof(struct hci_dev_req) + sizeof(__u16); size = n * sizeof(*dr) + sizeof(*dl);
copy_to_user((void *) arg, dl, size); copy_to_user((void *) arg, dl, size);
kfree(dl); kfree(dl);
......
...@@ -186,69 +186,12 @@ static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, stru ...@@ -186,69 +186,12 @@ static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, stru
write_unlock(&l->lock); write_unlock(&l->lock);
} }
int l2cap_connect(struct sock *sk)
{
bdaddr_t *src = &bt_sk(sk)->src;
bdaddr_t *dst = &bt_sk(sk)->dst;
struct l2cap_conn *conn;
struct hci_conn *hcon;
struct hci_dev *hdev;
int err = 0;
BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm);
if (!(hdev = hci_get_route(dst, src)))
return -EHOSTUNREACH;
hci_dev_lock_bh(hdev);
err = -ENOMEM;
hcon = hci_connect(hdev, ACL_LINK, dst);
if (!hcon)
goto done;
conn = l2cap_conn_add(hcon, 0);
if (!conn) {
hci_conn_put(hcon);
goto done;
}
err = 0;
/* Update source addr of the socket */
bacpy(src, conn->src);
l2cap_chan_add(conn, sk, NULL);
sk->state = BT_CONNECT;
l2cap_sock_set_timer(sk, sk->sndtimeo);
if (hcon->state == BT_CONNECTED) {
if (sk->type == SOCK_SEQPACKET) {
struct l2cap_conn_req req;
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
req.psm = l2cap_pi(sk)->psm;
l2cap_send_req(conn, L2CAP_CONN_REQ, sizeof(req), &req);
} else {
l2cap_sock_clear_timer(sk);
sk->state = BT_CONNECTED;
}
}
done:
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
return err;
}
/* ---- Socket interface ---- */ /* ---- Socket interface ---- */
static struct sock *__l2cap_get_sock_by_addr(u16 psm, bdaddr_t *src) static struct sock *__l2cap_get_sock_by_addr(u16 psm, bdaddr_t *src)
{ {
struct sock *sk; struct sock *sk;
for (sk = l2cap_sk_list.head; sk; sk = sk->next) { for (sk = l2cap_sk_list.head; sk; sk = sk->next) {
if (l2cap_pi(sk)->psm == psm && if (l2cap_pi(sk)->sport == psm && !bacmp(&bt_sk(sk)->src, src))
!bacmp(&bt_sk(sk)->src, src))
break; break;
} }
return sk; return sk;
...@@ -438,6 +381,9 @@ static int l2cap_sock_create(struct socket *sock, int protocol) ...@@ -438,6 +381,9 @@ static int l2cap_sock_create(struct socket *sock, int protocol)
if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_DGRAM && sock->type != SOCK_RAW) if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_DGRAM && sock->type != SOCK_RAW)
return -ESOCKTNOSUPPORT; return -ESOCKTNOSUPPORT;
if (sock->type == SOCK_RAW && !capable(CAP_NET_RAW))
return -EPERM;
sock->ops = &l2cap_sock_ops; sock->ops = &l2cap_sock_ops;
sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL); sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL);
...@@ -473,6 +419,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_ ...@@ -473,6 +419,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_
/* Save source address */ /* Save source address */
bacpy(&bt_sk(sk)->src, &la->l2_bdaddr); bacpy(&bt_sk(sk)->src, &la->l2_bdaddr);
l2cap_pi(sk)->psm = la->l2_psm; l2cap_pi(sk)->psm = la->l2_psm;
l2cap_pi(sk)->sport = la->l2_psm;
sk->state = BT_BOUND; sk->state = BT_BOUND;
} }
write_unlock_bh(&l2cap_sk_list.lock); write_unlock_bh(&l2cap_sk_list.lock);
...@@ -482,6 +429,62 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_ ...@@ -482,6 +429,62 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_
return err; return err;
} }
static int l2cap_do_connect(struct sock *sk)
{
bdaddr_t *src = &bt_sk(sk)->src;
bdaddr_t *dst = &bt_sk(sk)->dst;
struct l2cap_conn *conn;
struct hci_conn *hcon;
struct hci_dev *hdev;
int err = 0;
BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm);
if (!(hdev = hci_get_route(dst, src)))
return -EHOSTUNREACH;
hci_dev_lock_bh(hdev);
err = -ENOMEM;
hcon = hci_connect(hdev, ACL_LINK, dst);
if (!hcon)
goto done;
conn = l2cap_conn_add(hcon, 0);
if (!conn) {
hci_conn_put(hcon);
goto done;
}
err = 0;
/* Update source addr of the socket */
bacpy(src, conn->src);
l2cap_chan_add(conn, sk, NULL);
sk->state = BT_CONNECT;
l2cap_sock_set_timer(sk, sk->sndtimeo);
if (hcon->state == BT_CONNECTED) {
if (sk->type == SOCK_SEQPACKET) {
struct l2cap_conn_req req;
req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
req.psm = l2cap_pi(sk)->psm;
l2cap_send_req(conn, L2CAP_CONN_REQ, sizeof(req), &req);
} else {
l2cap_sock_clear_timer(sk);
sk->state = BT_CONNECTED;
}
}
done:
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
return err;
}
static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags)
{ {
struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr;
...@@ -527,7 +530,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al ...@@ -527,7 +530,7 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al
bacpy(&bt_sk(sk)->dst, &la->l2_bdaddr); bacpy(&bt_sk(sk)->dst, &la->l2_bdaddr);
l2cap_pi(sk)->psm = la->l2_psm; l2cap_pi(sk)->psm = la->l2_psm;
if ((err = l2cap_connect(sk))) if ((err = l2cap_do_connect(sk)))
goto done; goto done;
wait: wait:
...@@ -2074,7 +2077,7 @@ static int __init l2cap_proc_init(void) ...@@ -2074,7 +2077,7 @@ static int __init l2cap_proc_init(void)
static void __init l2cap_proc_cleanup(void) static void __init l2cap_proc_cleanup(void)
{ {
return 0; return;
} }
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
......
config BT_RFCOMM config BT_RFCOMM
tristate "RFCOMM protocol support" tristate "RFCOMM protocol support"
depends on BT_L2CAP depends on BT && BT_L2CAP
help help
RFCOMM provides connection oriented stream transport. RFCOMM RFCOMM provides connection oriented stream transport. RFCOMM
support is required for Dialup Networking, OBEX and other Bluetooth support is required for Dialup Networking, OBEX and other Bluetooth
......
...@@ -1312,6 +1312,9 @@ static int rfcomm_recv_mcc(struct rfcomm_session *s, struct sk_buff *skb) ...@@ -1312,6 +1312,9 @@ static int rfcomm_recv_mcc(struct rfcomm_session *s, struct sk_buff *skb)
rfcomm_send_test(s, 0, skb->data, skb->len); rfcomm_send_test(s, 0, skb->data, skb->len);
break; break;
case RFCOMM_NSC:
break;
default: default:
BT_ERR("Unknown control type 0x%02x", type); BT_ERR("Unknown control type 0x%02x", type);
rfcomm_send_nsc(s, cr, type); rfcomm_send_nsc(s, cr, type);
...@@ -1442,7 +1445,7 @@ static inline int rfcomm_process_tx(struct rfcomm_dlc *d) ...@@ -1442,7 +1445,7 @@ static inline int rfcomm_process_tx(struct rfcomm_dlc *d)
/* Send pending MSC */ /* Send pending MSC */
if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags)) if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags))
rfcomm_send_msc(d->session, d->dlci, 1, d->v24_sig); rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig);
if (d->credits) { if (d->credits) {
/* CFC enabled. /* CFC enabled.
...@@ -1831,7 +1834,7 @@ static int __init rfcomm_proc_init(void) ...@@ -1831,7 +1834,7 @@ static int __init rfcomm_proc_init(void)
static void __init rfcomm_proc_cleanup(void) static void __init rfcomm_proc_cleanup(void)
{ {
return 0; return;
} }
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
......
...@@ -851,7 +851,7 @@ static int __init rfcomm_sock_proc_init(void) ...@@ -851,7 +851,7 @@ static int __init rfcomm_sock_proc_init(void)
static void __init rfcomm_sock_proc_cleanup(void) static void __init rfcomm_sock_proc_cleanup(void)
{ {
return 0; return;
} }
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
......
...@@ -442,7 +442,7 @@ static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) ...@@ -442,7 +442,7 @@ static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb)
struct tty_struct *tty; struct tty_struct *tty;
if (!dev || !(tty = dev->tty)) { if (!dev || !(tty = dev->tty)) {
kfree(skb); kfree_skb(skb);
return; return;
} }
...@@ -669,12 +669,12 @@ static int rfcomm_tty_set_modem_status(uint cmd, struct rfcomm_dlc *dlc, uint st ...@@ -669,12 +669,12 @@ static int rfcomm_tty_set_modem_status(uint cmd, struct rfcomm_dlc *dlc, uint st
else else
rfcomm_dlc_get_modem_status(dlc, &v24_sig); rfcomm_dlc_get_modem_status(dlc, &v24_sig);
mask = (status & TIOCM_DSR) ? RFCOMM_V24_RTC : 0 | mask = ((status & TIOCM_DSR) ? RFCOMM_V24_RTC : 0) |
(status & TIOCM_DTR) ? RFCOMM_V24_RTC : 0 | ((status & TIOCM_DTR) ? RFCOMM_V24_RTC : 0) |
(status & TIOCM_RTS) ? RFCOMM_V24_RTR : 0 | ((status & TIOCM_RTS) ? RFCOMM_V24_RTR : 0) |
(status & TIOCM_CTS) ? RFCOMM_V24_RTR : 0 | ((status & TIOCM_CTS) ? RFCOMM_V24_RTR : 0) |
(status & TIOCM_RI) ? RFCOMM_V24_IC : 0 | ((status & TIOCM_RI) ? RFCOMM_V24_IC : 0) |
(status & TIOCM_CD) ? RFCOMM_V24_DV : 0; ((status & TIOCM_CD) ? RFCOMM_V24_DV : 0);
if (cmd == TIOCMBIC) if (cmd == TIOCMBIC)
v24_sig &= ~mask; v24_sig &= ~mask;
......
...@@ -962,7 +962,7 @@ static int __init sco_proc_init(void) ...@@ -962,7 +962,7 @@ static int __init sco_proc_init(void)
static void __init sco_proc_cleanup(void) static void __init sco_proc_cleanup(void)
{ {
return 0; return;
} }
#endif /* CONFIG_PROC_FS */ #endif /* CONFIG_PROC_FS */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment