mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
caif: remove CAIF NETWORK LAYER
Remove CAIF (Communication CPU to Application CPU Interface), the ST-Ericsson modem protocol. The subsystem has been orphaned since 2013. The last meaningful changes from the maintainers were in March 2013:a8c7687bf2("caif_virtio: Check that vringh_config is not null")b2273be8d2("caif_virtio: Use vringh_notify_enable correctly")0d2e1a2926("caif_virtio: Introduce caif over virtio") Not-so-coincidentally, according to "the Internet" ST-Ericsson officially shut down its modem joint venture in Aug 2013. If anyone is using this code please yell! In the 13 years since, the code has accumulated 200 non-merge commits, of which 71 were cross-tree API changes, 21 carried Fixes: tags, and the remaining ~110 were cleanups, doc conversions, treewide refactors, and one partial removal (caif_hsi,ca75bcf0a8). We are still getting fixes to this code, in the last 10 days there were 3 reports on security@ about CAIF that I have been CCed on. UAPI constants (AF_CAIF, ARPHRD_CAIF, N_CAIF, VIRTIO_ID_CAIF) and the SELinux classmap entry are intentionally kept for ABI stability. Acked-by: Michael S. Tsirkin <mst@redhat.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: Linus Walleij <linusw@kernel.org> Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20260416182829.1440262-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
1f5ffc6721
commit
6d5431555d
|
|
@ -1,138 +0,0 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
.. include:: <isonum.txt>
|
||||
|
||||
|
||||
================
|
||||
Using Linux CAIF
|
||||
================
|
||||
|
||||
|
||||
:Copyright: |copy| ST-Ericsson AB 2010
|
||||
|
||||
:Author: Sjur Brendeland/ sjur.brandeland@stericsson.com
|
||||
|
||||
Start
|
||||
=====
|
||||
|
||||
If you have compiled CAIF for modules do::
|
||||
|
||||
$modprobe crc_ccitt
|
||||
$modprobe caif
|
||||
$modprobe caif_socket
|
||||
$modprobe chnl_net
|
||||
|
||||
|
||||
Preparing the setup with a STE modem
|
||||
====================================
|
||||
|
||||
If you are working on integration of CAIF you should make sure
|
||||
that the kernel is built with module support.
|
||||
|
||||
There are some things that need to be tweaked to get the host TTY correctly
|
||||
set up to talk to the modem.
|
||||
Since the CAIF stack is running in the kernel and we want to use the existing
|
||||
TTY, we are installing our physical serial driver as a line discipline above
|
||||
the TTY device.
|
||||
|
||||
To achieve this we need to install the N_CAIF ldisc from user space.
|
||||
The benefit is that we can hook up to any TTY.
|
||||
|
||||
The use of Start-of-frame-extension (STX) must also be set as
|
||||
module parameter "ser_use_stx".
|
||||
|
||||
Normally Frame Checksum is always used on UART, but this is also provided as a
|
||||
module parameter "ser_use_fcs".
|
||||
|
||||
::
|
||||
|
||||
$ modprobe caif_serial ser_ttyname=/dev/ttyS0 ser_use_stx=yes
|
||||
$ ifconfig caif_ttyS0 up
|
||||
|
||||
PLEASE NOTE:
|
||||
There is a limitation in Android shell.
|
||||
It only accepts one argument to insmod/modprobe!
|
||||
|
||||
Trouble shooting
|
||||
================
|
||||
|
||||
There are debugfs parameters provided for serial communication.
|
||||
/sys/kernel/debug/caif_serial/<tty-name>/
|
||||
|
||||
* ser_state: Prints the bit-mask status where
|
||||
|
||||
- 0x02 means SENDING, this is a transient state.
|
||||
- 0x10 means FLOW_OFF_SENT, i.e. the previous frame has not been sent
|
||||
and is blocking further send operation. Flow OFF has been propagated
|
||||
to all CAIF Channels using this TTY.
|
||||
|
||||
* tty_status: Prints the bit-mask tty status information
|
||||
|
||||
- 0x01 - tty->warned is on.
|
||||
- 0x04 - tty->packed is on.
|
||||
- 0x08 - tty->flow.tco_stopped is on.
|
||||
- 0x10 - tty->hw_stopped is on.
|
||||
- 0x20 - tty->flow.stopped is on.
|
||||
|
||||
* last_tx_msg: Binary blob Prints the last transmitted frame.
|
||||
|
||||
This can be printed with::
|
||||
|
||||
$od --format=x1 /sys/kernel/debug/caif_serial/<tty>/last_rx_msg.
|
||||
|
||||
The first two tx messages sent look like this. Note: The initial
|
||||
byte 02 is start of frame extension (STX) used for re-syncing
|
||||
upon errors.
|
||||
|
||||
- Enumeration::
|
||||
|
||||
0000000 02 05 00 00 03 01 d2 02
|
||||
| | | | | |
|
||||
STX(1) | | | |
|
||||
Length(2)| | |
|
||||
Control Channel(1)
|
||||
Command:Enumeration(1)
|
||||
Link-ID(1)
|
||||
Checksum(2)
|
||||
|
||||
- Channel Setup::
|
||||
|
||||
0000000 02 07 00 00 00 21 a1 00 48 df
|
||||
| | | | | | | |
|
||||
STX(1) | | | | | |
|
||||
Length(2)| | | | |
|
||||
Control Channel(1)
|
||||
Command:Channel Setup(1)
|
||||
Channel Type(1)
|
||||
Priority and Link-ID(1)
|
||||
Endpoint(1)
|
||||
Checksum(2)
|
||||
|
||||
* last_rx_msg: Prints the last transmitted frame.
|
||||
|
||||
The RX messages for LinkSetup look almost identical but they have the
|
||||
bit 0x20 set in the command bit, and Channel Setup has added one byte
|
||||
before Checksum containing Channel ID.
|
||||
|
||||
NOTE:
|
||||
Several CAIF Messages might be concatenated. The maximum debug
|
||||
buffer size is 128 bytes.
|
||||
|
||||
Error Scenarios
|
||||
===============
|
||||
|
||||
- last_tx_msg contains channel setup message and last_rx_msg is empty ->
|
||||
The host seems to be able to send over the UART, at least the CAIF ldisc get
|
||||
notified that sending is completed.
|
||||
|
||||
- last_tx_msg contains enumeration message and last_rx_msg is empty ->
|
||||
The host is not able to send the message from UART, the tty has not been
|
||||
able to complete the transmit operation.
|
||||
|
||||
- if /sys/kernel/debug/caif_serial/<tty>/tty_status is non-zero there
|
||||
might be problems transmitting over UART.
|
||||
|
||||
E.g. host and modem wiring is not correct you will typically see
|
||||
tty_status = 0x10 (hw_stopped) and ser_state = 0x10 (FLOW_OFF_SENT).
|
||||
|
||||
You will probably see the enumeration message in last_tx_message
|
||||
and empty last_rx_message.
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CAIF
|
||||
====
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
linux_caif
|
||||
caif
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
.. include:: <isonum.txt>
|
||||
|
||||
==========
|
||||
Linux CAIF
|
||||
==========
|
||||
|
||||
Copyright |copy| ST-Ericsson AB 2010
|
||||
|
||||
:Author: Sjur Brendeland/ sjur.brandeland@stericsson.com
|
||||
:License terms: GNU General Public License (GPL) version 2
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
CAIF is a MUX protocol used by ST-Ericsson cellular modems for
|
||||
communication between Modem and host. The host processes can open virtual AT
|
||||
channels, initiate GPRS Data connections, Video channels and Utility Channels.
|
||||
The Utility Channels are general purpose pipes between modem and host.
|
||||
|
||||
ST-Ericsson modems support a number of transports between modem
|
||||
and host. Currently, UART and Loopback are available for Linux.
|
||||
|
||||
|
||||
Architecture
|
||||
============
|
||||
|
||||
The implementation of CAIF is divided into:
|
||||
|
||||
* CAIF Socket Layer and GPRS IP Interface.
|
||||
* CAIF Core Protocol Implementation
|
||||
* CAIF Link Layer, implemented as NET devices.
|
||||
|
||||
::
|
||||
|
||||
RTNL
|
||||
!
|
||||
! +------+ +------+
|
||||
! +------+! +------+!
|
||||
! ! IP !! !Socket!!
|
||||
+-------> !interf!+ ! API !+ <- CAIF Client APIs
|
||||
! +------+ +------!
|
||||
! ! !
|
||||
! +-----------+
|
||||
! !
|
||||
! +------+ <- CAIF Core Protocol
|
||||
! ! CAIF !
|
||||
! ! Core !
|
||||
! +------+
|
||||
! +----------!---------+
|
||||
! ! ! !
|
||||
! +------+ +-----+ +------+
|
||||
+--> ! HSI ! ! TTY ! ! USB ! <- Link Layer (Net Devices)
|
||||
+------+ +-----+ +------+
|
||||
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
|
||||
CAIF Core Protocol Layer
|
||||
------------------------
|
||||
|
||||
CAIF Core layer implements the CAIF protocol as defined by ST-Ericsson.
|
||||
It implements the CAIF protocol stack in a layered approach, where
|
||||
each layer described in the specification is implemented as a separate layer.
|
||||
The architecture is inspired by the design patterns "Protocol Layer" and
|
||||
"Protocol Packet".
|
||||
|
||||
CAIF structure
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The Core CAIF implementation contains:
|
||||
|
||||
- Simple implementation of CAIF.
|
||||
- Layered architecture (a la Streams), each layer in the CAIF
|
||||
specification is implemented in a separate c-file.
|
||||
- Clients must call configuration function to add PHY layer.
|
||||
- Clients must implement CAIF layer to consume/produce
|
||||
CAIF payload with receive and transmit functions.
|
||||
- Clients must call configuration function to add and connect the
|
||||
Client layer.
|
||||
- When receiving / transmitting CAIF Packets (cfpkt), ownership is passed
|
||||
to the called function (except for framing layers' receive function)
|
||||
|
||||
Layered Architecture
|
||||
====================
|
||||
|
||||
The CAIF protocol can be divided into two parts: Support functions and Protocol
|
||||
Implementation. The support functions include:
|
||||
|
||||
- CFPKT CAIF Packet. Implementation of CAIF Protocol Packet. The
|
||||
CAIF Packet has functions for creating, destroying and adding content
|
||||
and for adding/extracting header and trailers to protocol packets.
|
||||
|
||||
The CAIF Protocol implementation contains:
|
||||
|
||||
- CFCNFG CAIF Configuration layer. Configures the CAIF Protocol
|
||||
Stack and provides a Client interface for adding Link-Layer and
|
||||
Driver interfaces on top of the CAIF Stack.
|
||||
|
||||
- CFCTRL CAIF Control layer. Encodes and Decodes control messages
|
||||
such as enumeration and channel setup. Also matches request and
|
||||
response messages.
|
||||
|
||||
- CFSERVL General CAIF Service Layer functionality; handles flow
|
||||
control and remote shutdown requests.
|
||||
|
||||
- CFVEI CAIF VEI layer. Handles CAIF AT Channels on VEI (Virtual
|
||||
External Interface). This layer encodes/decodes VEI frames.
|
||||
|
||||
- CFDGML CAIF Datagram layer. Handles CAIF Datagram layer (IP
|
||||
traffic), encodes/decodes Datagram frames.
|
||||
|
||||
- CFMUX CAIF Mux layer. Handles multiplexing between multiple
|
||||
physical bearers and multiple channels such as VEI, Datagram, etc.
|
||||
The MUX keeps track of the existing CAIF Channels and
|
||||
Physical Instances and selects the appropriate instance based
|
||||
on Channel-Id and Physical-ID.
|
||||
|
||||
- CFFRML CAIF Framing layer. Handles Framing i.e. Frame length
|
||||
and frame checksum.
|
||||
|
||||
- CFSERL CAIF Serial layer. Handles concatenation/split of frames
|
||||
into CAIF Frames with correct length.
|
||||
|
||||
::
|
||||
|
||||
+---------+
|
||||
| Config |
|
||||
| CFCNFG |
|
||||
+---------+
|
||||
!
|
||||
+---------+ +---------+ +---------+
|
||||
| AT | | Control | | Datagram|
|
||||
| CFVEIL | | CFCTRL | | CFDGML |
|
||||
+---------+ +---------+ +---------+
|
||||
\_____________!______________/
|
||||
!
|
||||
+---------+
|
||||
| MUX |
|
||||
| |
|
||||
+---------+
|
||||
_____!_____
|
||||
/ \
|
||||
+---------+ +---------+
|
||||
| CFFRML | | CFFRML |
|
||||
| Framing | | Framing |
|
||||
+---------+ +---------+
|
||||
! !
|
||||
+---------+ +---------+
|
||||
| | | Serial |
|
||||
| | | CFSERL |
|
||||
+---------+ +---------+
|
||||
|
||||
|
||||
In this layered approach the following "rules" apply.
|
||||
|
||||
- All layers embed the same structure "struct cflayer"
|
||||
- A layer does not depend on any other layer's private data.
|
||||
- Layers are stacked by setting the pointers::
|
||||
|
||||
layer->up , layer->dn
|
||||
|
||||
- In order to send data upwards, each layer should do::
|
||||
|
||||
layer->up->receive(layer->up, packet);
|
||||
|
||||
- In order to send data downwards, each layer should do::
|
||||
|
||||
layer->dn->transmit(layer->dn, packet);
|
||||
|
||||
|
||||
CAIF Socket and IP interface
|
||||
============================
|
||||
|
||||
The IP interface and CAIF socket API are implemented on top of the
|
||||
CAIF Core protocol. The IP Interface and CAIF socket have an instance of
|
||||
'struct cflayer', just like the CAIF Core protocol stack.
|
||||
Net device and Socket implement the 'receive()' function defined by
|
||||
'struct cflayer', just like the rest of the CAIF stack. In this way, transmit and
|
||||
receive of packets is handled as by the rest of the layers: the 'dn->transmit()'
|
||||
function is called in order to transmit data.
|
||||
|
||||
Configuration of Link Layer
|
||||
---------------------------
|
||||
The Link Layer is implemented as Linux network devices (struct net_device).
|
||||
Payload handling and registration is done using standard Linux mechanisms.
|
||||
|
||||
The CAIF Protocol relies on a loss-less link layer without implementing
|
||||
retransmission. This implies that packet drops must not happen.
|
||||
Therefore a flow-control mechanism is implemented where the physical
|
||||
interface can initiate flow stop for all CAIF Channels.
|
||||
|
|
@ -17,7 +17,6 @@ Contents:
|
|||
diagnostic/index
|
||||
dsa/index
|
||||
devlink/index
|
||||
caif/index
|
||||
ethtool-netlink
|
||||
ieee802154
|
||||
iso15765-2
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ Todolist:
|
|||
* diagnostic/index
|
||||
* dsa/index
|
||||
* devlink/index
|
||||
* caif/index
|
||||
* ethtool-netlink
|
||||
* ieee802154
|
||||
* iso15765-2
|
||||
|
|
|
|||
|
|
@ -5674,15 +5674,6 @@ T: git git://linuxtv.org/media.git
|
|||
F: Documentation/admin-guide/media/cafe_ccic*
|
||||
F: drivers/media/platform/marvell/
|
||||
|
||||
CAIF NETWORK LAYER
|
||||
L: netdev@vger.kernel.org
|
||||
S: Orphan
|
||||
F: Documentation/networking/caif/
|
||||
F: drivers/net/caif/
|
||||
F: include/net/caif/
|
||||
F: include/uapi/linux/caif/
|
||||
F: net/caif/
|
||||
|
||||
CAKE QDISC
|
||||
M: Toke Høiland-Jørgensen <toke@toke.dk>
|
||||
L: cake@lists.bufferbloat.net (moderated for non-subscribers)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ CONFIG_CFG80211=y
|
|||
CONFIG_CFG80211_DEBUGFS=y
|
||||
CONFIG_MAC80211=y
|
||||
CONFIG_MAC80211_LEDS=y
|
||||
CONFIG_CAIF=y
|
||||
CONFIG_NFC=m
|
||||
CONFIG_NFC_HCI=m
|
||||
CONFIG_NFC_SHDLC=y
|
||||
|
|
|
|||
|
|
@ -503,8 +503,6 @@ source "drivers/net/arcnet/Kconfig"
|
|||
|
||||
source "drivers/atm/Kconfig"
|
||||
|
||||
source "drivers/net/caif/Kconfig"
|
||||
|
||||
source "drivers/net/dsa/Kconfig"
|
||||
|
||||
source "drivers/net/ethernet/Kconfig"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ obj-$(CONFIG_MHI_NET) += mhi_net.o
|
|||
# Networking Drivers
|
||||
#
|
||||
obj-$(CONFIG_ARCNET) += arcnet/
|
||||
obj-$(CONFIG_CAIF) += caif/
|
||||
obj-$(CONFIG_CAN) += can/
|
||||
ifdef CONFIG_NET_DSA
|
||||
obj-y += dsa/
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# CAIF physical drivers
|
||||
#
|
||||
|
||||
menuconfig CAIF_DRIVERS
|
||||
bool "CAIF transport drivers"
|
||||
depends on CAIF
|
||||
help
|
||||
Enable this to see CAIF physical drivers.
|
||||
|
||||
if CAIF_DRIVERS
|
||||
|
||||
config CAIF_TTY
|
||||
tristate "CAIF TTY transport driver"
|
||||
depends on CAIF && TTY
|
||||
default n
|
||||
help
|
||||
The CAIF TTY transport driver is a Line Discipline (ldisc)
|
||||
identified as N_CAIF. When this ldisc is opened from user space
|
||||
it will redirect the TTY's traffic into the CAIF stack.
|
||||
|
||||
config CAIF_VIRTIO
|
||||
tristate "CAIF virtio transport driver"
|
||||
depends on CAIF && HAS_DMA
|
||||
select VHOST_RING
|
||||
select VIRTIO
|
||||
select GENERIC_ALLOCATOR
|
||||
default n
|
||||
help
|
||||
The CAIF driver for CAIF over Virtio.
|
||||
|
||||
endif # CAIF_DRIVERS
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
ccflags-$(CONFIG_CAIF_DEBUG) := -DDEBUG
|
||||
|
||||
# Serial interface
|
||||
obj-$(CONFIG_CAIF_TTY) += caif_serial.o
|
||||
|
||||
# Virtio interface
|
||||
obj-$(CONFIG_CAIF_VIRTIO) += caif_virtio.o
|
||||
|
|
@ -1,443 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <net/caif/caif_device.h>
|
||||
#include <net/caif/cfcnfg.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Sjur Brendeland");
|
||||
MODULE_DESCRIPTION("CAIF serial device TTY line discipline");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_LDISC(N_CAIF);
|
||||
|
||||
#define SEND_QUEUE_LOW 10
|
||||
#define SEND_QUEUE_HIGH 100
|
||||
#define CAIF_SENDING 1 /* Bit 1 = 0x02*/
|
||||
#define CAIF_FLOW_OFF_SENT 4 /* Bit 4 = 0x10 */
|
||||
#define MAX_WRITE_CHUNK 4096
|
||||
#define ON 1
|
||||
#define OFF 0
|
||||
#define CAIF_MAX_MTU 4096
|
||||
|
||||
static DEFINE_SPINLOCK(ser_lock);
|
||||
static LIST_HEAD(ser_list);
|
||||
static LIST_HEAD(ser_release_list);
|
||||
|
||||
static bool ser_loop;
|
||||
module_param(ser_loop, bool, 0444);
|
||||
MODULE_PARM_DESC(ser_loop, "Run in simulated loopback mode.");
|
||||
|
||||
static bool ser_use_stx = true;
|
||||
module_param(ser_use_stx, bool, 0444);
|
||||
MODULE_PARM_DESC(ser_use_stx, "STX enabled or not.");
|
||||
|
||||
static bool ser_use_fcs = true;
|
||||
|
||||
module_param(ser_use_fcs, bool, 0444);
|
||||
MODULE_PARM_DESC(ser_use_fcs, "FCS enabled or not.");
|
||||
|
||||
static int ser_write_chunk = MAX_WRITE_CHUNK;
|
||||
module_param(ser_write_chunk, int, 0444);
|
||||
|
||||
MODULE_PARM_DESC(ser_write_chunk, "Maximum size of data written to UART.");
|
||||
|
||||
static struct dentry *debugfsdir;
|
||||
|
||||
static int caif_net_open(struct net_device *dev);
|
||||
static int caif_net_close(struct net_device *dev);
|
||||
|
||||
struct ser_device {
|
||||
struct caif_dev_common common;
|
||||
struct list_head node;
|
||||
struct net_device *dev;
|
||||
struct sk_buff_head head;
|
||||
struct tty_struct *tty;
|
||||
bool tx_started;
|
||||
unsigned long state;
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs_tty_dir;
|
||||
struct debugfs_blob_wrapper tx_blob;
|
||||
struct debugfs_blob_wrapper rx_blob;
|
||||
u8 rx_data[128];
|
||||
u8 tx_data[128];
|
||||
u8 tty_status;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
static void caifdev_setup(struct net_device *dev);
|
||||
static void ldisc_tx_wakeup(struct tty_struct *tty);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static inline void update_tty_status(struct ser_device *ser)
|
||||
{
|
||||
ser->tty_status =
|
||||
ser->tty->flow.stopped << 5 |
|
||||
ser->tty->flow.tco_stopped << 3 |
|
||||
ser->tty->ctrl.packet << 2;
|
||||
}
|
||||
static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty)
|
||||
{
|
||||
ser->debugfs_tty_dir = debugfs_create_dir(tty->name, debugfsdir);
|
||||
|
||||
debugfs_create_blob("last_tx_msg", 0400, ser->debugfs_tty_dir,
|
||||
&ser->tx_blob);
|
||||
|
||||
debugfs_create_blob("last_rx_msg", 0400, ser->debugfs_tty_dir,
|
||||
&ser->rx_blob);
|
||||
|
||||
debugfs_create_xul("ser_state", 0400, ser->debugfs_tty_dir,
|
||||
&ser->state);
|
||||
|
||||
debugfs_create_x8("tty_status", 0400, ser->debugfs_tty_dir,
|
||||
&ser->tty_status);
|
||||
|
||||
ser->tx_blob.data = ser->tx_data;
|
||||
ser->tx_blob.size = 0;
|
||||
ser->rx_blob.data = ser->rx_data;
|
||||
ser->rx_blob.size = 0;
|
||||
}
|
||||
|
||||
static inline void debugfs_deinit(struct ser_device *ser)
|
||||
{
|
||||
debugfs_remove_recursive(ser->debugfs_tty_dir);
|
||||
}
|
||||
|
||||
static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size)
|
||||
{
|
||||
if (size > sizeof(ser->rx_data))
|
||||
size = sizeof(ser->rx_data);
|
||||
memcpy(ser->rx_data, data, size);
|
||||
ser->rx_blob.data = ser->rx_data;
|
||||
ser->rx_blob.size = size;
|
||||
}
|
||||
#else
|
||||
static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void debugfs_deinit(struct ser_device *ser)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void update_tty_status(struct ser_device *ser)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ldisc_receive(struct tty_struct *tty, const u8 *data,
|
||||
const u8 *flags, size_t count)
|
||||
{
|
||||
struct sk_buff *skb = NULL;
|
||||
struct ser_device *ser;
|
||||
int ret;
|
||||
|
||||
ser = tty->disc_data;
|
||||
|
||||
/*
|
||||
* NOTE: flags may contain information about break or overrun.
|
||||
* This is not yet handled.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Workaround for garbage at start of transmission,
|
||||
* only enable if STX handling is not enabled.
|
||||
*/
|
||||
if (!ser->common.use_stx && !ser->tx_started) {
|
||||
dev_info(&ser->dev->dev,
|
||||
"Bytes received before initial transmission -"
|
||||
"bytes discarded.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
BUG_ON(ser->dev == NULL);
|
||||
|
||||
/* Get a suitable caif packet and copy in data. */
|
||||
skb = netdev_alloc_skb(ser->dev, count+1);
|
||||
if (skb == NULL)
|
||||
return;
|
||||
skb_put_data(skb, data, count);
|
||||
|
||||
skb->protocol = htons(ETH_P_CAIF);
|
||||
skb_reset_mac_header(skb);
|
||||
debugfs_rx(ser, data, count);
|
||||
/* Push received packet up the stack. */
|
||||
ret = netif_rx(skb);
|
||||
if (!ret) {
|
||||
ser->dev->stats.rx_packets++;
|
||||
ser->dev->stats.rx_bytes += count;
|
||||
} else
|
||||
++ser->dev->stats.rx_dropped;
|
||||
update_tty_status(ser);
|
||||
}
|
||||
|
||||
static int handle_tx(struct ser_device *ser)
|
||||
{
|
||||
struct tty_struct *tty;
|
||||
struct sk_buff *skb;
|
||||
int tty_wr, len, room;
|
||||
|
||||
tty = ser->tty;
|
||||
ser->tx_started = true;
|
||||
|
||||
/* Enter critical section */
|
||||
if (test_and_set_bit(CAIF_SENDING, &ser->state))
|
||||
return 0;
|
||||
|
||||
/* skb_peek is safe because handle_tx is called after skb_queue_tail */
|
||||
while ((skb = skb_peek(&ser->head)) != NULL) {
|
||||
|
||||
/* Make sure you don't write too much */
|
||||
len = skb->len;
|
||||
room = tty_write_room(tty);
|
||||
if (!room)
|
||||
break;
|
||||
if (room > ser_write_chunk)
|
||||
room = ser_write_chunk;
|
||||
if (len > room)
|
||||
len = room;
|
||||
|
||||
/* Write to tty or loopback */
|
||||
if (!ser_loop) {
|
||||
tty_wr = tty->ops->write(tty, skb->data, len);
|
||||
update_tty_status(ser);
|
||||
} else {
|
||||
tty_wr = len;
|
||||
ldisc_receive(tty, skb->data, NULL, len);
|
||||
}
|
||||
ser->dev->stats.tx_packets++;
|
||||
ser->dev->stats.tx_bytes += tty_wr;
|
||||
|
||||
/* Error on TTY ?! */
|
||||
if (tty_wr < 0)
|
||||
goto error;
|
||||
/* Reduce buffer written, and discard if empty */
|
||||
skb_pull(skb, tty_wr);
|
||||
if (skb->len == 0) {
|
||||
struct sk_buff *tmp = skb_dequeue(&ser->head);
|
||||
WARN_ON(tmp != skb);
|
||||
dev_consume_skb_any(skb);
|
||||
}
|
||||
}
|
||||
/* Send flow off if queue is empty */
|
||||
if (ser->head.qlen <= SEND_QUEUE_LOW &&
|
||||
test_and_clear_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
|
||||
ser->common.flowctrl != NULL)
|
||||
ser->common.flowctrl(ser->dev, ON);
|
||||
clear_bit(CAIF_SENDING, &ser->state);
|
||||
return 0;
|
||||
error:
|
||||
clear_bit(CAIF_SENDING, &ser->state);
|
||||
return tty_wr;
|
||||
}
|
||||
|
||||
static netdev_tx_t caif_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct ser_device *ser;
|
||||
|
||||
ser = netdev_priv(dev);
|
||||
|
||||
/* Send flow off once, on high water mark */
|
||||
if (ser->head.qlen > SEND_QUEUE_HIGH &&
|
||||
!test_and_set_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
|
||||
ser->common.flowctrl != NULL)
|
||||
|
||||
ser->common.flowctrl(ser->dev, OFF);
|
||||
|
||||
skb_queue_tail(&ser->head, skb);
|
||||
return handle_tx(ser);
|
||||
}
|
||||
|
||||
|
||||
static void ldisc_tx_wakeup(struct tty_struct *tty)
|
||||
{
|
||||
struct ser_device *ser;
|
||||
|
||||
ser = tty->disc_data;
|
||||
BUG_ON(ser == NULL);
|
||||
WARN_ON(ser->tty != tty);
|
||||
handle_tx(ser);
|
||||
}
|
||||
|
||||
|
||||
static void ser_release(struct work_struct *work)
|
||||
{
|
||||
struct list_head list;
|
||||
struct ser_device *ser, *tmp;
|
||||
struct tty_struct *tty;
|
||||
|
||||
spin_lock(&ser_lock);
|
||||
list_replace_init(&ser_release_list, &list);
|
||||
spin_unlock(&ser_lock);
|
||||
|
||||
if (!list_empty(&list)) {
|
||||
rtnl_lock();
|
||||
list_for_each_entry_safe(ser, tmp, &list, node) {
|
||||
tty = ser->tty;
|
||||
dev_close(ser->dev);
|
||||
unregister_netdevice(ser->dev);
|
||||
debugfs_deinit(ser);
|
||||
tty_kref_put(tty->link);
|
||||
tty_kref_put(tty);
|
||||
}
|
||||
rtnl_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static DECLARE_WORK(ser_release_work, ser_release);
|
||||
|
||||
static int ldisc_open(struct tty_struct *tty)
|
||||
{
|
||||
struct ser_device *ser;
|
||||
struct net_device *dev;
|
||||
char name[64];
|
||||
int result;
|
||||
|
||||
/* No write no play */
|
||||
if (tty->ops->write == NULL)
|
||||
return -EOPNOTSUPP;
|
||||
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG))
|
||||
return -EPERM;
|
||||
|
||||
/* release devices to avoid name collision */
|
||||
ser_release(NULL);
|
||||
|
||||
result = snprintf(name, sizeof(name), "cf%s", tty->name);
|
||||
if (result >= IFNAMSIZ)
|
||||
return -EINVAL;
|
||||
dev = alloc_netdev(sizeof(*ser), name, NET_NAME_UNKNOWN,
|
||||
caifdev_setup);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ser = netdev_priv(dev);
|
||||
ser->tty = tty_kref_get(tty);
|
||||
tty_kref_get(tty->link);
|
||||
ser->dev = dev;
|
||||
debugfs_init(ser, tty);
|
||||
tty->receive_room = 4096;
|
||||
tty->disc_data = ser;
|
||||
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
||||
rtnl_lock();
|
||||
result = register_netdevice(dev);
|
||||
if (result) {
|
||||
tty_kref_put(tty->link);
|
||||
tty_kref_put(tty);
|
||||
rtnl_unlock();
|
||||
free_netdev(dev);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
spin_lock(&ser_lock);
|
||||
list_add(&ser->node, &ser_list);
|
||||
spin_unlock(&ser_lock);
|
||||
rtnl_unlock();
|
||||
netif_stop_queue(dev);
|
||||
update_tty_status(ser);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ldisc_close(struct tty_struct *tty)
|
||||
{
|
||||
struct ser_device *ser = tty->disc_data;
|
||||
|
||||
spin_lock(&ser_lock);
|
||||
list_move(&ser->node, &ser_release_list);
|
||||
spin_unlock(&ser_lock);
|
||||
schedule_work(&ser_release_work);
|
||||
}
|
||||
|
||||
/* The line discipline structure. */
|
||||
static struct tty_ldisc_ops caif_ldisc = {
|
||||
.owner = THIS_MODULE,
|
||||
.num = N_CAIF,
|
||||
.name = "n_caif",
|
||||
.open = ldisc_open,
|
||||
.close = ldisc_close,
|
||||
.receive_buf = ldisc_receive,
|
||||
.write_wakeup = ldisc_tx_wakeup
|
||||
};
|
||||
|
||||
static const struct net_device_ops netdev_ops = {
|
||||
.ndo_open = caif_net_open,
|
||||
.ndo_stop = caif_net_close,
|
||||
.ndo_start_xmit = caif_xmit
|
||||
};
|
||||
|
||||
static void caifdev_setup(struct net_device *dev)
|
||||
{
|
||||
struct ser_device *serdev = netdev_priv(dev);
|
||||
|
||||
dev->features = 0;
|
||||
dev->netdev_ops = &netdev_ops;
|
||||
dev->type = ARPHRD_CAIF;
|
||||
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
||||
dev->mtu = CAIF_MAX_MTU;
|
||||
dev->priv_flags |= IFF_NO_QUEUE;
|
||||
dev->needs_free_netdev = true;
|
||||
skb_queue_head_init(&serdev->head);
|
||||
serdev->common.link_select = CAIF_LINK_LOW_LATENCY;
|
||||
serdev->common.use_frag = true;
|
||||
serdev->common.use_stx = ser_use_stx;
|
||||
serdev->common.use_fcs = ser_use_fcs;
|
||||
serdev->dev = dev;
|
||||
}
|
||||
|
||||
|
||||
static int caif_net_open(struct net_device *dev)
|
||||
{
|
||||
netif_wake_queue(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int caif_net_close(struct net_device *dev)
|
||||
{
|
||||
netif_stop_queue(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init caif_ser_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = tty_register_ldisc(&caif_ldisc);
|
||||
if (ret < 0)
|
||||
pr_err("cannot register CAIF ldisc=%d err=%d\n", N_CAIF, ret);
|
||||
|
||||
debugfsdir = debugfs_create_dir("caif_serial", NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit caif_ser_exit(void)
|
||||
{
|
||||
spin_lock(&ser_lock);
|
||||
list_splice(&ser_list, &ser_release_list);
|
||||
spin_unlock(&ser_lock);
|
||||
ser_release(NULL);
|
||||
cancel_work_sync(&ser_release_work);
|
||||
tty_unregister_ldisc(&caif_ldisc);
|
||||
debugfs_remove_recursive(debugfsdir);
|
||||
}
|
||||
|
||||
module_init(caif_ser_init);
|
||||
module_exit(caif_ser_exit);
|
||||
|
|
@ -1,791 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2013
|
||||
* Authors: Vicram Arv
|
||||
* Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
|
||||
* Sjur Brendeland
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/vringh.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <linux/virtio_ids.h>
|
||||
#include <linux/virtio_caif.h>
|
||||
#include <linux/virtio_ring.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
#include <linux/virtio_config.h>
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Vicram Arv");
|
||||
MODULE_AUTHOR("Sjur Brendeland");
|
||||
MODULE_DESCRIPTION("Virtio CAIF Driver");
|
||||
|
||||
/* NAPI schedule quota */
|
||||
#define CFV_DEFAULT_QUOTA 32
|
||||
|
||||
/* Defaults used if virtio config space is unavailable */
|
||||
#define CFV_DEF_MTU_SIZE 4096
|
||||
#define CFV_DEF_HEADROOM 32
|
||||
#define CFV_DEF_TAILROOM 32
|
||||
|
||||
/* Required IP header alignment */
|
||||
#define IP_HDR_ALIGN 4
|
||||
|
||||
/* struct cfv_napi_contxt - NAPI context info
|
||||
* @riov: IOV holding data read from the ring. Note that riov may
|
||||
* still hold data when cfv_rx_poll() returns.
|
||||
* @head: Last descriptor ID we received from vringh_getdesc_kern.
|
||||
* We use this to put descriptor back on the used ring. USHRT_MAX is
|
||||
* used to indicate invalid head-id.
|
||||
*/
|
||||
struct cfv_napi_context {
|
||||
struct vringh_kiov riov;
|
||||
unsigned short head;
|
||||
};
|
||||
|
||||
/* struct cfv_stats - statistics for debugfs
|
||||
* @rx_napi_complete: Number of NAPI completions (RX)
|
||||
* @rx_napi_resched: Number of calls where the full quota was used (RX)
|
||||
* @rx_nomem: Number of SKB alloc failures (RX)
|
||||
* @rx_kicks: Number of RX kicks
|
||||
* @tx_full_ring: Number times TX ring was full
|
||||
* @tx_no_mem: Number of times TX went out of memory
|
||||
* @tx_flow_on: Number of flow on (TX)
|
||||
* @tx_kicks: Number of TX kicks
|
||||
*/
|
||||
struct cfv_stats {
|
||||
u32 rx_napi_complete;
|
||||
u32 rx_napi_resched;
|
||||
u32 rx_nomem;
|
||||
u32 rx_kicks;
|
||||
u32 tx_full_ring;
|
||||
u32 tx_no_mem;
|
||||
u32 tx_flow_on;
|
||||
u32 tx_kicks;
|
||||
};
|
||||
|
||||
/* struct cfv_info - Caif Virtio control structure
|
||||
* @cfdev: caif common header
|
||||
* @vdev: Associated virtio device
|
||||
* @vr_rx: rx/downlink host vring
|
||||
* @vq_tx: tx/uplink virtqueue
|
||||
* @ndev: CAIF link layer device
|
||||
* @watermark_tx: indicates number of free descriptors we need
|
||||
* to reopen the tx-queues after overload.
|
||||
* @tx_lock: protects vq_tx from concurrent use
|
||||
* @tx_release_tasklet: Tasklet for freeing consumed TX buffers
|
||||
* @napi: Napi context used in cfv_rx_poll()
|
||||
* @ctx: Context data used in cfv_rx_poll()
|
||||
* @tx_hr: transmit headroom
|
||||
* @rx_hr: receive headroom
|
||||
* @tx_tr: transmit tail room
|
||||
* @rx_tr: receive tail room
|
||||
* @mtu: transmit max size
|
||||
* @mru: receive max size
|
||||
* @allocsz: size of dma memory reserved for TX buffers
|
||||
* @alloc_addr: virtual address to dma memory for TX buffers
|
||||
* @alloc_dma: dma address to dma memory for TX buffers
|
||||
* @genpool: Gen Pool used for allocating TX buffers
|
||||
* @reserved_mem: Pointer to memory reserve allocated from genpool
|
||||
* @reserved_size: Size of memory reserve allocated from genpool
|
||||
* @stats: Statistics exposed in sysfs
|
||||
* @debugfs: Debugfs dentry for statistic counters
|
||||
*/
|
||||
struct cfv_info {
|
||||
struct caif_dev_common cfdev;
|
||||
struct virtio_device *vdev;
|
||||
struct vringh *vr_rx;
|
||||
struct virtqueue *vq_tx;
|
||||
struct net_device *ndev;
|
||||
unsigned int watermark_tx;
|
||||
/* Protect access to vq_tx */
|
||||
spinlock_t tx_lock;
|
||||
struct tasklet_struct tx_release_tasklet;
|
||||
struct napi_struct napi;
|
||||
struct cfv_napi_context ctx;
|
||||
u16 tx_hr;
|
||||
u16 rx_hr;
|
||||
u16 tx_tr;
|
||||
u16 rx_tr;
|
||||
u32 mtu;
|
||||
u32 mru;
|
||||
size_t allocsz;
|
||||
void *alloc_addr;
|
||||
dma_addr_t alloc_dma;
|
||||
struct gen_pool *genpool;
|
||||
unsigned long reserved_mem;
|
||||
size_t reserved_size;
|
||||
struct cfv_stats stats;
|
||||
struct dentry *debugfs;
|
||||
};
|
||||
|
||||
/* struct buf_info - maintains transmit buffer data handle
|
||||
* @size: size of transmit buffer
|
||||
* @dma_handle: handle to allocated dma device memory area
|
||||
* @vaddr: virtual address mapping to allocated memory area
|
||||
*/
|
||||
struct buf_info {
|
||||
size_t size;
|
||||
u8 *vaddr;
|
||||
};
|
||||
|
||||
/* Called from virtio device, in IRQ context */
|
||||
static void cfv_release_cb(struct virtqueue *vq_tx)
|
||||
{
|
||||
struct cfv_info *cfv = vq_tx->vdev->priv;
|
||||
|
||||
++cfv->stats.tx_kicks;
|
||||
tasklet_schedule(&cfv->tx_release_tasklet);
|
||||
}
|
||||
|
||||
static void free_buf_info(struct cfv_info *cfv, struct buf_info *buf_info)
|
||||
{
|
||||
if (!buf_info)
|
||||
return;
|
||||
gen_pool_free(cfv->genpool, (unsigned long) buf_info->vaddr,
|
||||
buf_info->size);
|
||||
kfree(buf_info);
|
||||
}
|
||||
|
||||
/* This is invoked whenever the remote processor completed processing
|
||||
* a TX msg we just sent, and the buffer is put back to the used ring.
|
||||
*/
|
||||
static void cfv_release_used_buf(struct virtqueue *vq_tx)
|
||||
{
|
||||
struct cfv_info *cfv = vq_tx->vdev->priv;
|
||||
unsigned long flags;
|
||||
|
||||
BUG_ON(vq_tx != cfv->vq_tx);
|
||||
|
||||
for (;;) {
|
||||
unsigned int len;
|
||||
struct buf_info *buf_info;
|
||||
|
||||
/* Get used buffer from used ring to recycle used descriptors */
|
||||
spin_lock_irqsave(&cfv->tx_lock, flags);
|
||||
buf_info = virtqueue_get_buf(vq_tx, &len);
|
||||
spin_unlock_irqrestore(&cfv->tx_lock, flags);
|
||||
|
||||
/* Stop looping if there are no more buffers to free */
|
||||
if (!buf_info)
|
||||
break;
|
||||
|
||||
free_buf_info(cfv, buf_info);
|
||||
|
||||
/* watermark_tx indicates if we previously stopped the tx
|
||||
* queues. If we have enough free stots in the virtio ring,
|
||||
* re-establish memory reserved and open up tx queues.
|
||||
*/
|
||||
if (cfv->vq_tx->num_free <= cfv->watermark_tx)
|
||||
continue;
|
||||
|
||||
/* Re-establish memory reserve */
|
||||
if (cfv->reserved_mem == 0 && cfv->genpool)
|
||||
cfv->reserved_mem =
|
||||
gen_pool_alloc(cfv->genpool,
|
||||
cfv->reserved_size);
|
||||
|
||||
/* Open up the tx queues */
|
||||
if (cfv->reserved_mem) {
|
||||
cfv->watermark_tx =
|
||||
virtqueue_get_vring_size(cfv->vq_tx);
|
||||
netif_tx_wake_all_queues(cfv->ndev);
|
||||
/* Buffers are recycled in cfv_netdev_tx, so
|
||||
* disable notifications when queues are opened.
|
||||
*/
|
||||
virtqueue_disable_cb(cfv->vq_tx);
|
||||
++cfv->stats.tx_flow_on;
|
||||
} else {
|
||||
/* if no memory reserve, wait for more free slots */
|
||||
WARN_ON(cfv->watermark_tx >
|
||||
virtqueue_get_vring_size(cfv->vq_tx));
|
||||
cfv->watermark_tx +=
|
||||
virtqueue_get_vring_size(cfv->vq_tx) / 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate a SKB and copy packet data to it */
|
||||
static struct sk_buff *cfv_alloc_and_copy_skb(int *err,
|
||||
struct cfv_info *cfv,
|
||||
u8 *frm, u32 frm_len)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
u32 cfpkt_len, pad_len;
|
||||
|
||||
*err = 0;
|
||||
/* Verify that packet size with down-link header and mtu size */
|
||||
if (frm_len > cfv->mru || frm_len <= cfv->rx_hr + cfv->rx_tr) {
|
||||
netdev_err(cfv->ndev,
|
||||
"Invalid frmlen:%u mtu:%u hr:%d tr:%d\n",
|
||||
frm_len, cfv->mru, cfv->rx_hr,
|
||||
cfv->rx_tr);
|
||||
*err = -EPROTO;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cfpkt_len = frm_len - (cfv->rx_hr + cfv->rx_tr);
|
||||
pad_len = (unsigned long)(frm + cfv->rx_hr) & (IP_HDR_ALIGN - 1);
|
||||
|
||||
skb = netdev_alloc_skb(cfv->ndev, frm_len + pad_len);
|
||||
if (!skb) {
|
||||
*err = -ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
skb_reserve(skb, cfv->rx_hr + pad_len);
|
||||
|
||||
skb_put_data(skb, frm + cfv->rx_hr, cfpkt_len);
|
||||
return skb;
|
||||
}
|
||||
|
||||
/* Get packets from the host vring */
|
||||
static int cfv_rx_poll(struct napi_struct *napi, int quota)
|
||||
{
|
||||
struct cfv_info *cfv = container_of(napi, struct cfv_info, napi);
|
||||
int rxcnt = 0;
|
||||
int err = 0;
|
||||
void *buf;
|
||||
struct sk_buff *skb;
|
||||
struct vringh_kiov *riov = &cfv->ctx.riov;
|
||||
unsigned int skb_len;
|
||||
|
||||
do {
|
||||
skb = NULL;
|
||||
|
||||
/* Put the previous iovec back on the used ring and
|
||||
* fetch a new iovec if we have processed all elements.
|
||||
*/
|
||||
if (riov->i == riov->used) {
|
||||
if (cfv->ctx.head != USHRT_MAX) {
|
||||
vringh_complete_kern(cfv->vr_rx,
|
||||
cfv->ctx.head,
|
||||
0);
|
||||
cfv->ctx.head = USHRT_MAX;
|
||||
}
|
||||
|
||||
err = vringh_getdesc_kern(
|
||||
cfv->vr_rx,
|
||||
riov,
|
||||
NULL,
|
||||
&cfv->ctx.head,
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (err <= 0)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
buf = phys_to_virt((unsigned long) riov->iov[riov->i].iov_base);
|
||||
/* TODO: Add check on valid buffer address */
|
||||
|
||||
skb = cfv_alloc_and_copy_skb(&err, cfv, buf,
|
||||
riov->iov[riov->i].iov_len);
|
||||
if (unlikely(err))
|
||||
goto exit;
|
||||
|
||||
/* Push received packet up the stack. */
|
||||
skb_len = skb->len;
|
||||
skb->protocol = htons(ETH_P_CAIF);
|
||||
skb_reset_mac_header(skb);
|
||||
skb->dev = cfv->ndev;
|
||||
err = netif_receive_skb(skb);
|
||||
if (unlikely(err)) {
|
||||
++cfv->ndev->stats.rx_dropped;
|
||||
} else {
|
||||
++cfv->ndev->stats.rx_packets;
|
||||
cfv->ndev->stats.rx_bytes += skb_len;
|
||||
}
|
||||
|
||||
++riov->i;
|
||||
++rxcnt;
|
||||
} while (rxcnt < quota);
|
||||
|
||||
++cfv->stats.rx_napi_resched;
|
||||
goto out;
|
||||
|
||||
exit:
|
||||
switch (err) {
|
||||
case 0:
|
||||
++cfv->stats.rx_napi_complete;
|
||||
|
||||
/* Really out of packets? (stolen from virtio_net)*/
|
||||
napi_complete(napi);
|
||||
if (unlikely(!vringh_notify_enable_kern(cfv->vr_rx)) &&
|
||||
napi_schedule_prep(napi)) {
|
||||
vringh_notify_disable_kern(cfv->vr_rx);
|
||||
__napi_schedule(napi);
|
||||
}
|
||||
break;
|
||||
|
||||
case -ENOMEM:
|
||||
++cfv->stats.rx_nomem;
|
||||
dev_kfree_skb(skb);
|
||||
/* Stop NAPI poll on OOM, we hope to be polled later */
|
||||
napi_complete(napi);
|
||||
vringh_notify_enable_kern(cfv->vr_rx);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* We're doomed, any modem fault is fatal */
|
||||
netdev_warn(cfv->ndev, "Bad ring, disable device\n");
|
||||
cfv->ndev->stats.rx_dropped = riov->used - riov->i;
|
||||
napi_complete(napi);
|
||||
vringh_notify_disable_kern(cfv->vr_rx);
|
||||
netif_carrier_off(cfv->ndev);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
if (rxcnt && vringh_need_notify_kern(cfv->vr_rx) > 0)
|
||||
vringh_notify(cfv->vr_rx);
|
||||
return rxcnt;
|
||||
}
|
||||
|
||||
static void cfv_recv(struct virtio_device *vdev, struct vringh *vr_rx)
|
||||
{
|
||||
struct cfv_info *cfv = vdev->priv;
|
||||
|
||||
++cfv->stats.rx_kicks;
|
||||
vringh_notify_disable_kern(cfv->vr_rx);
|
||||
napi_schedule(&cfv->napi);
|
||||
}
|
||||
|
||||
static void cfv_destroy_genpool(struct cfv_info *cfv)
|
||||
{
|
||||
if (cfv->alloc_addr)
|
||||
dma_free_coherent(cfv->vdev->dev.parent->parent,
|
||||
cfv->allocsz, cfv->alloc_addr,
|
||||
cfv->alloc_dma);
|
||||
|
||||
if (!cfv->genpool)
|
||||
return;
|
||||
gen_pool_free(cfv->genpool, cfv->reserved_mem,
|
||||
cfv->reserved_size);
|
||||
gen_pool_destroy(cfv->genpool);
|
||||
cfv->genpool = NULL;
|
||||
}
|
||||
|
||||
static int cfv_create_genpool(struct cfv_info *cfv)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* dma_alloc can only allocate whole pages, and we need a more
|
||||
* fine graned allocation so we use genpool. We ask for space needed
|
||||
* by IP and a full ring. If the dma allcoation fails we retry with a
|
||||
* smaller allocation size.
|
||||
*/
|
||||
err = -ENOMEM;
|
||||
cfv->allocsz = (virtqueue_get_vring_size(cfv->vq_tx) *
|
||||
(ETH_DATA_LEN + cfv->tx_hr + cfv->tx_tr) * 11)/10;
|
||||
if (cfv->allocsz <= (num_possible_cpus() + 1) * cfv->ndev->mtu)
|
||||
return -EINVAL;
|
||||
|
||||
for (;;) {
|
||||
if (cfv->allocsz <= num_possible_cpus() * cfv->ndev->mtu) {
|
||||
netdev_info(cfv->ndev, "Not enough device memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
cfv->alloc_addr = dma_alloc_coherent(
|
||||
cfv->vdev->dev.parent->parent,
|
||||
cfv->allocsz, &cfv->alloc_dma,
|
||||
GFP_ATOMIC);
|
||||
if (cfv->alloc_addr)
|
||||
break;
|
||||
|
||||
cfv->allocsz = (cfv->allocsz * 3) >> 2;
|
||||
}
|
||||
|
||||
netdev_dbg(cfv->ndev, "Allocated %zd bytes from dma-memory\n",
|
||||
cfv->allocsz);
|
||||
|
||||
/* Allocate on 128 bytes boundaries (1 << 7)*/
|
||||
cfv->genpool = gen_pool_create(7, -1);
|
||||
if (!cfv->genpool)
|
||||
goto err;
|
||||
|
||||
err = gen_pool_add_virt(cfv->genpool, (unsigned long)cfv->alloc_addr,
|
||||
(phys_addr_t)virt_to_phys(cfv->alloc_addr),
|
||||
cfv->allocsz, -1);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/* Reserve some memory for low memory situations. If we hit the roof
|
||||
* in the memory pool, we stop TX flow and release the reserve.
|
||||
*/
|
||||
cfv->reserved_size = num_possible_cpus() * cfv->ndev->mtu;
|
||||
cfv->reserved_mem = gen_pool_alloc(cfv->genpool,
|
||||
cfv->reserved_size);
|
||||
if (!cfv->reserved_mem) {
|
||||
err = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
cfv->watermark_tx = virtqueue_get_vring_size(cfv->vq_tx);
|
||||
return 0;
|
||||
err:
|
||||
cfv_destroy_genpool(cfv);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Enable the CAIF interface and allocate the memory-pool */
|
||||
static int cfv_netdev_open(struct net_device *netdev)
|
||||
{
|
||||
struct cfv_info *cfv = netdev_priv(netdev);
|
||||
|
||||
if (cfv_create_genpool(cfv))
|
||||
return -ENOMEM;
|
||||
|
||||
netif_carrier_on(netdev);
|
||||
napi_enable(&cfv->napi);
|
||||
|
||||
/* Schedule NAPI to read any pending packets */
|
||||
napi_schedule(&cfv->napi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Disable the CAIF interface and free the memory-pool */
|
||||
static int cfv_netdev_close(struct net_device *netdev)
|
||||
{
|
||||
struct cfv_info *cfv = netdev_priv(netdev);
|
||||
unsigned long flags;
|
||||
struct buf_info *buf_info;
|
||||
|
||||
/* Disable interrupts, queues and NAPI polling */
|
||||
netif_carrier_off(netdev);
|
||||
virtqueue_disable_cb(cfv->vq_tx);
|
||||
vringh_notify_disable_kern(cfv->vr_rx);
|
||||
napi_disable(&cfv->napi);
|
||||
|
||||
/* Release any TX buffers on both used and available rings */
|
||||
cfv_release_used_buf(cfv->vq_tx);
|
||||
spin_lock_irqsave(&cfv->tx_lock, flags);
|
||||
while ((buf_info = virtqueue_detach_unused_buf(cfv->vq_tx)))
|
||||
free_buf_info(cfv, buf_info);
|
||||
spin_unlock_irqrestore(&cfv->tx_lock, flags);
|
||||
|
||||
/* Release all dma allocated memory and destroy the pool */
|
||||
cfv_destroy_genpool(cfv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate a buffer in dma-memory and copy skb to it */
|
||||
static struct buf_info *cfv_alloc_and_copy_to_shm(struct cfv_info *cfv,
|
||||
struct sk_buff *skb,
|
||||
struct scatterlist *sg)
|
||||
{
|
||||
struct caif_payload_info *info = (void *)&skb->cb;
|
||||
struct buf_info *buf_info = NULL;
|
||||
u8 pad_len, hdr_ofs;
|
||||
|
||||
if (!cfv->genpool)
|
||||
goto err;
|
||||
|
||||
if (unlikely(cfv->tx_hr + skb->len + cfv->tx_tr > cfv->mtu)) {
|
||||
netdev_warn(cfv->ndev, "Invalid packet len (%d > %d)\n",
|
||||
cfv->tx_hr + skb->len + cfv->tx_tr, cfv->mtu);
|
||||
goto err;
|
||||
}
|
||||
|
||||
buf_info = kmalloc_obj(struct buf_info, GFP_ATOMIC);
|
||||
if (unlikely(!buf_info))
|
||||
goto err;
|
||||
|
||||
/* Make the IP header aligned in the buffer */
|
||||
hdr_ofs = cfv->tx_hr + info->hdr_len;
|
||||
pad_len = hdr_ofs & (IP_HDR_ALIGN - 1);
|
||||
buf_info->size = cfv->tx_hr + skb->len + cfv->tx_tr + pad_len;
|
||||
|
||||
/* allocate dma memory buffer */
|
||||
buf_info->vaddr = (void *)gen_pool_alloc(cfv->genpool, buf_info->size);
|
||||
if (unlikely(!buf_info->vaddr))
|
||||
goto err;
|
||||
|
||||
/* copy skbuf contents to send buffer */
|
||||
skb_copy_bits(skb, 0, buf_info->vaddr + cfv->tx_hr + pad_len, skb->len);
|
||||
sg_init_one(sg, buf_info->vaddr + pad_len,
|
||||
skb->len + cfv->tx_hr + cfv->rx_hr);
|
||||
|
||||
return buf_info;
|
||||
err:
|
||||
kfree(buf_info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Put the CAIF packet on the virtio ring and kick the receiver */
|
||||
static netdev_tx_t cfv_netdev_tx(struct sk_buff *skb, struct net_device *netdev)
|
||||
{
|
||||
struct cfv_info *cfv = netdev_priv(netdev);
|
||||
struct buf_info *buf_info;
|
||||
struct scatterlist sg;
|
||||
unsigned long flags;
|
||||
bool flow_off = false;
|
||||
int ret;
|
||||
|
||||
/* garbage collect released buffers */
|
||||
cfv_release_used_buf(cfv->vq_tx);
|
||||
spin_lock_irqsave(&cfv->tx_lock, flags);
|
||||
|
||||
/* Flow-off check takes into account number of cpus to make sure
|
||||
* virtqueue will not be overfilled in any possible smp conditions.
|
||||
*
|
||||
* Flow-on is triggered when sufficient buffers are freed
|
||||
*/
|
||||
if (unlikely(cfv->vq_tx->num_free <= num_present_cpus())) {
|
||||
flow_off = true;
|
||||
cfv->stats.tx_full_ring++;
|
||||
}
|
||||
|
||||
/* If we run out of memory, we release the memory reserve and retry
|
||||
* allocation.
|
||||
*/
|
||||
buf_info = cfv_alloc_and_copy_to_shm(cfv, skb, &sg);
|
||||
if (unlikely(!buf_info)) {
|
||||
cfv->stats.tx_no_mem++;
|
||||
flow_off = true;
|
||||
|
||||
if (cfv->reserved_mem && cfv->genpool) {
|
||||
gen_pool_free(cfv->genpool, cfv->reserved_mem,
|
||||
cfv->reserved_size);
|
||||
cfv->reserved_mem = 0;
|
||||
buf_info = cfv_alloc_and_copy_to_shm(cfv, skb, &sg);
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(flow_off)) {
|
||||
/* Turn flow on when a 1/4 of the descriptors are released */
|
||||
cfv->watermark_tx = virtqueue_get_vring_size(cfv->vq_tx) / 4;
|
||||
/* Enable notifications of recycled TX buffers */
|
||||
virtqueue_enable_cb(cfv->vq_tx);
|
||||
netif_tx_stop_all_queues(netdev);
|
||||
}
|
||||
|
||||
if (unlikely(!buf_info)) {
|
||||
/* If the memory reserve does it's job, this shouldn't happen */
|
||||
netdev_warn(cfv->ndev, "Out of gen_pool memory\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = virtqueue_add_outbuf(cfv->vq_tx, &sg, 1, buf_info, GFP_ATOMIC);
|
||||
if (unlikely((ret < 0))) {
|
||||
/* If flow control works, this shouldn't happen */
|
||||
netdev_warn(cfv->ndev, "Failed adding buffer to TX vring:%d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* update netdev statistics */
|
||||
cfv->ndev->stats.tx_packets++;
|
||||
cfv->ndev->stats.tx_bytes += skb->len;
|
||||
spin_unlock_irqrestore(&cfv->tx_lock, flags);
|
||||
|
||||
/* tell the remote processor it has a pending message to read */
|
||||
virtqueue_kick(cfv->vq_tx);
|
||||
|
||||
dev_kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
err:
|
||||
spin_unlock_irqrestore(&cfv->tx_lock, flags);
|
||||
cfv->ndev->stats.tx_dropped++;
|
||||
free_buf_info(cfv, buf_info);
|
||||
dev_kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static void cfv_tx_release_tasklet(struct tasklet_struct *t)
|
||||
{
|
||||
struct cfv_info *cfv = from_tasklet(cfv, t, tx_release_tasklet);
|
||||
cfv_release_used_buf(cfv->vq_tx);
|
||||
}
|
||||
|
||||
static const struct net_device_ops cfv_netdev_ops = {
|
||||
.ndo_open = cfv_netdev_open,
|
||||
.ndo_stop = cfv_netdev_close,
|
||||
.ndo_start_xmit = cfv_netdev_tx,
|
||||
};
|
||||
|
||||
static void cfv_netdev_setup(struct net_device *netdev)
|
||||
{
|
||||
netdev->netdev_ops = &cfv_netdev_ops;
|
||||
netdev->type = ARPHRD_CAIF;
|
||||
netdev->tx_queue_len = 100;
|
||||
netdev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
||||
netdev->mtu = CFV_DEF_MTU_SIZE;
|
||||
netdev->needs_free_netdev = true;
|
||||
}
|
||||
|
||||
/* Create debugfs counters for the device */
|
||||
static inline void debugfs_init(struct cfv_info *cfv)
|
||||
{
|
||||
cfv->debugfs = debugfs_create_dir(netdev_name(cfv->ndev), NULL);
|
||||
|
||||
debugfs_create_u32("rx-napi-complete", 0400, cfv->debugfs,
|
||||
&cfv->stats.rx_napi_complete);
|
||||
debugfs_create_u32("rx-napi-resched", 0400, cfv->debugfs,
|
||||
&cfv->stats.rx_napi_resched);
|
||||
debugfs_create_u32("rx-nomem", 0400, cfv->debugfs,
|
||||
&cfv->stats.rx_nomem);
|
||||
debugfs_create_u32("rx-kicks", 0400, cfv->debugfs,
|
||||
&cfv->stats.rx_kicks);
|
||||
debugfs_create_u32("tx-full-ring", 0400, cfv->debugfs,
|
||||
&cfv->stats.tx_full_ring);
|
||||
debugfs_create_u32("tx-no-mem", 0400, cfv->debugfs,
|
||||
&cfv->stats.tx_no_mem);
|
||||
debugfs_create_u32("tx-kicks", 0400, cfv->debugfs,
|
||||
&cfv->stats.tx_kicks);
|
||||
debugfs_create_u32("tx-flow-on", 0400, cfv->debugfs,
|
||||
&cfv->stats.tx_flow_on);
|
||||
}
|
||||
|
||||
/* Setup CAIF for the a virtio device */
|
||||
static int cfv_probe(struct virtio_device *vdev)
|
||||
{
|
||||
vrh_callback_t *vrh_cbs = cfv_recv;
|
||||
const char *cfv_netdev_name = "cfvrt";
|
||||
struct net_device *netdev;
|
||||
struct cfv_info *cfv;
|
||||
int err;
|
||||
|
||||
netdev = alloc_netdev(sizeof(struct cfv_info), cfv_netdev_name,
|
||||
NET_NAME_UNKNOWN, cfv_netdev_setup);
|
||||
if (!netdev)
|
||||
return -ENOMEM;
|
||||
|
||||
cfv = netdev_priv(netdev);
|
||||
cfv->vdev = vdev;
|
||||
cfv->ndev = netdev;
|
||||
|
||||
spin_lock_init(&cfv->tx_lock);
|
||||
|
||||
/* Get the RX virtio ring. This is a "host side vring". */
|
||||
err = -ENODEV;
|
||||
if (!vdev->vringh_config || !vdev->vringh_config->find_vrhs)
|
||||
goto err;
|
||||
|
||||
err = vdev->vringh_config->find_vrhs(vdev, 1, &cfv->vr_rx, &vrh_cbs);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
/* Get the TX virtio ring. This is a "guest side vring". */
|
||||
cfv->vq_tx = virtio_find_single_vq(vdev, cfv_release_cb, "output");
|
||||
if (IS_ERR(cfv->vq_tx)) {
|
||||
err = PTR_ERR(cfv->vq_tx);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Get the CAIF configuration from virtio config space, if available */
|
||||
if (vdev->config->get) {
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, headroom,
|
||||
&cfv->tx_hr);
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, headroom,
|
||||
&cfv->rx_hr);
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, tailroom,
|
||||
&cfv->tx_tr);
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, tailroom,
|
||||
&cfv->rx_tr);
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, mtu,
|
||||
&cfv->mtu);
|
||||
virtio_cread(vdev, struct virtio_caif_transf_config, mtu,
|
||||
&cfv->mru);
|
||||
} else {
|
||||
cfv->tx_hr = CFV_DEF_HEADROOM;
|
||||
cfv->rx_hr = CFV_DEF_HEADROOM;
|
||||
cfv->tx_tr = CFV_DEF_TAILROOM;
|
||||
cfv->rx_tr = CFV_DEF_TAILROOM;
|
||||
cfv->mtu = CFV_DEF_MTU_SIZE;
|
||||
cfv->mru = CFV_DEF_MTU_SIZE;
|
||||
}
|
||||
|
||||
netdev->needed_headroom = cfv->tx_hr;
|
||||
netdev->needed_tailroom = cfv->tx_tr;
|
||||
|
||||
/* Disable buffer release interrupts unless we have stopped TX queues */
|
||||
virtqueue_disable_cb(cfv->vq_tx);
|
||||
|
||||
netdev->mtu = cfv->mtu - cfv->tx_tr;
|
||||
vdev->priv = cfv;
|
||||
|
||||
/* Initialize NAPI poll context data */
|
||||
vringh_kiov_init(&cfv->ctx.riov, NULL, 0);
|
||||
cfv->ctx.head = USHRT_MAX;
|
||||
netif_napi_add_weight(netdev, &cfv->napi, cfv_rx_poll,
|
||||
CFV_DEFAULT_QUOTA);
|
||||
|
||||
tasklet_setup(&cfv->tx_release_tasklet, cfv_tx_release_tasklet);
|
||||
|
||||
/* Carrier is off until netdevice is opened */
|
||||
netif_carrier_off(netdev);
|
||||
|
||||
/* serialize netdev register + virtio_device_ready() with ndo_open() */
|
||||
rtnl_lock();
|
||||
|
||||
/* register Netdev */
|
||||
err = register_netdevice(netdev);
|
||||
if (err) {
|
||||
rtnl_unlock();
|
||||
dev_err(&vdev->dev, "Unable to register netdev (%d)\n", err);
|
||||
goto err;
|
||||
}
|
||||
|
||||
virtio_device_ready(vdev);
|
||||
|
||||
rtnl_unlock();
|
||||
|
||||
debugfs_init(cfv);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
netdev_warn(cfv->ndev, "CAIF Virtio probe failed:%d\n", err);
|
||||
|
||||
if (cfv->vr_rx)
|
||||
vdev->vringh_config->del_vrhs(cfv->vdev);
|
||||
if (cfv->vq_tx)
|
||||
vdev->config->del_vqs(cfv->vdev);
|
||||
free_netdev(netdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cfv_remove(struct virtio_device *vdev)
|
||||
{
|
||||
struct cfv_info *cfv = vdev->priv;
|
||||
|
||||
rtnl_lock();
|
||||
dev_close(cfv->ndev);
|
||||
rtnl_unlock();
|
||||
|
||||
tasklet_kill(&cfv->tx_release_tasklet);
|
||||
debugfs_remove_recursive(cfv->debugfs);
|
||||
|
||||
vringh_kiov_cleanup(&cfv->ctx.riov);
|
||||
virtio_reset_device(vdev);
|
||||
vdev->vringh_config->del_vrhs(cfv->vdev);
|
||||
cfv->vr_rx = NULL;
|
||||
vdev->config->del_vqs(cfv->vdev);
|
||||
unregister_netdev(cfv->ndev);
|
||||
}
|
||||
|
||||
static struct virtio_device_id id_table[] = {
|
||||
{ VIRTIO_ID_CAIF, VIRTIO_DEV_ANY_ID },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
static unsigned int features[] = {
|
||||
};
|
||||
|
||||
static struct virtio_driver caif_virtio_driver = {
|
||||
.feature_table = features,
|
||||
.feature_table_size = ARRAY_SIZE(features),
|
||||
.driver.name = KBUILD_MODNAME,
|
||||
.id_table = id_table,
|
||||
.probe = cfv_probe,
|
||||
.remove = cfv_remove,
|
||||
};
|
||||
|
||||
module_virtio_driver(caif_virtio_driver);
|
||||
MODULE_DEVICE_TABLE(virtio, id_table);
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2012
|
||||
* Author: Sjur Brændeland <sjur.brandeland@stericsson.com>
|
||||
*
|
||||
* This header is BSD licensed so
|
||||
* anyone can use the definitions to implement compatible remote processors
|
||||
*/
|
||||
|
||||
#ifndef VIRTIO_CAIF_H
|
||||
#define VIRTIO_CAIF_H
|
||||
|
||||
#include <linux/types.h>
|
||||
struct virtio_caif_transf_config {
|
||||
__virtio16 headroom;
|
||||
__virtio16 tailroom;
|
||||
__virtio32 mtu;
|
||||
u8 reserved[4];
|
||||
};
|
||||
|
||||
struct virtio_caif_config {
|
||||
struct virtio_caif_transf_config uplink, downlink;
|
||||
u8 reserved[8];
|
||||
};
|
||||
#endif
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CAIF_DEV_H_
|
||||
#define CAIF_DEV_H_
|
||||
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfcnfg.h>
|
||||
#include <net/caif/caif_device.h>
|
||||
#include <linux/caif/caif_socket.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/net.h>
|
||||
|
||||
/**
|
||||
* struct caif_param - CAIF parameters.
|
||||
* @size: Length of data
|
||||
* @data: Binary Data Blob
|
||||
*/
|
||||
struct caif_param {
|
||||
u16 size;
|
||||
u8 data[256];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct caif_connect_request - Request data for CAIF channel setup.
|
||||
* @protocol: Type of CAIF protocol to use (at, datagram etc)
|
||||
* @sockaddr: Socket address to connect.
|
||||
* @priority: Priority of the connection.
|
||||
* @link_selector: Link selector (high bandwidth or low latency)
|
||||
* @ifindex: kernel index of the interface.
|
||||
* @param: Connect Request parameters (CAIF_SO_REQ_PARAM).
|
||||
*
|
||||
* This struct is used when connecting a CAIF channel.
|
||||
* It contains all CAIF channel configuration options.
|
||||
*/
|
||||
struct caif_connect_request {
|
||||
enum caif_protocol_type protocol;
|
||||
struct sockaddr_caif sockaddr;
|
||||
enum caif_channel_priority priority;
|
||||
enum caif_link_selector link_selector;
|
||||
int ifindex;
|
||||
struct caif_param param;
|
||||
};
|
||||
|
||||
/**
|
||||
* caif_connect_client - Connect a client to CAIF Core Stack.
|
||||
* @config: Channel setup parameters, specifying what address
|
||||
* to connect on the Modem.
|
||||
* @client_layer: User implementation of client layer. This layer
|
||||
* MUST have receive and control callback functions
|
||||
* implemented.
|
||||
* @ifindex: Link layer interface index used for this connection.
|
||||
* @headroom: Head room needed by CAIF protocol.
|
||||
* @tailroom: Tail room needed by CAIF protocol.
|
||||
*
|
||||
* This function connects a CAIF channel. The Client must implement
|
||||
* the struct cflayer. This layer represents the Client layer and holds
|
||||
* receive functions and control callback functions. Control callback
|
||||
* function will receive information about connect/disconnect responses,
|
||||
* flow control etc (see enum caif_control).
|
||||
* E.g. CAIF Socket will call this function for each socket it connects
|
||||
* and have one client_layer instance for each socket.
|
||||
*/
|
||||
int caif_connect_client(struct net *net,
|
||||
struct caif_connect_request *conn_req,
|
||||
struct cflayer *client_layer, int *ifindex,
|
||||
int *headroom, int *tailroom);
|
||||
|
||||
/**
|
||||
* caif_disconnect_client - Disconnects a client from the CAIF stack.
|
||||
*
|
||||
* @client_layer: Client layer to be disconnected.
|
||||
*/
|
||||
int caif_disconnect_client(struct net *net, struct cflayer *client_layer);
|
||||
|
||||
|
||||
/**
|
||||
* caif_client_register_refcnt - register ref-count functions provided by client.
|
||||
*
|
||||
* @adapt_layer: Client layer using CAIF Stack.
|
||||
* @hold: Function provided by client layer increasing ref-count
|
||||
* @put: Function provided by client layer decreasing ref-count
|
||||
*
|
||||
* Client of the CAIF Stack must register functions for reference counting.
|
||||
* These functions are called by the CAIF Stack for every upstream packet,
|
||||
* and must therefore be implemented efficiently.
|
||||
*
|
||||
* Client should call caif_free_client when reference count degrease to zero.
|
||||
*/
|
||||
|
||||
void caif_client_register_refcnt(struct cflayer *adapt_layer,
|
||||
void (*hold)(struct cflayer *lyr),
|
||||
void (*put)(struct cflayer *lyr));
|
||||
/**
|
||||
* caif_free_client - Free memory used to manage the client in the CAIF Stack.
|
||||
*
|
||||
* @client_layer: Client layer to be removed.
|
||||
*
|
||||
* This function must be called from client layer in order to free memory.
|
||||
* Caller must guarantee that no packets are in flight upstream when calling
|
||||
* this function.
|
||||
*/
|
||||
void caif_free_client(struct cflayer *adap_layer);
|
||||
|
||||
/**
|
||||
* struct caif_enroll_dev - Enroll a net-device as a CAIF Link layer
|
||||
* @dev: Network device to enroll.
|
||||
* @caifdev: Configuration information from CAIF Link Layer
|
||||
* @link_support: Link layer support layer
|
||||
* @head_room: Head room needed by link support layer
|
||||
* @layer: Lowest layer in CAIF stack
|
||||
* @rcv_fun: Receive function for CAIF stack.
|
||||
*
|
||||
* This function enroll a CAIF link layer into CAIF Stack and
|
||||
* expects the interface to be able to handle CAIF payload.
|
||||
* The link_support layer is used to add any Link Layer specific
|
||||
* framing.
|
||||
*/
|
||||
int caif_enroll_dev(struct net_device *dev, struct caif_dev_common *caifdev,
|
||||
struct cflayer *link_support, int head_room,
|
||||
struct cflayer **layer, int (**rcv_func)(
|
||||
struct sk_buff *, struct net_device *,
|
||||
struct packet_type *, struct net_device *));
|
||||
|
||||
#endif /* CAIF_DEV_H_ */
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CAIF_DEVICE_H_
|
||||
#define CAIF_DEVICE_H_
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/caif/caif_socket.h>
|
||||
#include <net/caif/caif_device.h>
|
||||
|
||||
/**
|
||||
* struct caif_dev_common - data shared between CAIF drivers and stack.
|
||||
* @flowctrl: Flow Control callback function. This function is
|
||||
* supplied by CAIF Core Stack and is used by CAIF
|
||||
* Link Layer to send flow-stop to CAIF Core.
|
||||
* The flow information will be distributed to all
|
||||
* clients of CAIF.
|
||||
*
|
||||
* @link_select: Profile of device, either high-bandwidth or
|
||||
* low-latency. This member is set by CAIF Link
|
||||
* Layer Device in order to indicate if this device
|
||||
* is a high bandwidth or low latency device.
|
||||
*
|
||||
* @use_frag: CAIF Frames may be fragmented.
|
||||
* Is set by CAIF Link Layer in order to indicate if the
|
||||
* interface receives fragmented frames that must be
|
||||
* assembled by CAIF Core Layer.
|
||||
*
|
||||
* @use_fcs: Indicate if Frame CheckSum (fcs) is used.
|
||||
* Is set if the physical interface is
|
||||
* using Frame Checksum on the CAIF Frames.
|
||||
*
|
||||
* @use_stx: Indicate STart of frame eXtension (stx) in use.
|
||||
* Is set if the CAIF Link Layer expects
|
||||
* CAIF Frames to start with the STX byte.
|
||||
*
|
||||
* This structure is shared between the CAIF drivers and the CAIF stack.
|
||||
* It is used by the device to register its behavior.
|
||||
* CAIF Core layer must set the member flowctrl in order to supply
|
||||
* CAIF Link Layer with the flow control function.
|
||||
*
|
||||
*/
|
||||
struct caif_dev_common {
|
||||
void (*flowctrl)(struct net_device *net, int on);
|
||||
enum caif_link_selector link_select;
|
||||
int use_frag;
|
||||
int use_fcs;
|
||||
int use_stx;
|
||||
};
|
||||
|
||||
#endif /* CAIF_DEVICE_H_ */
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CAIF_LAYER_H_
|
||||
#define CAIF_LAYER_H_
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
struct cflayer;
|
||||
struct cfpkt;
|
||||
struct caif_payload_info;
|
||||
|
||||
#define CAIF_LAYER_NAME_SZ 16
|
||||
|
||||
/**
|
||||
* caif_assert() - Assert function for CAIF.
|
||||
* @assert: expression to evaluate.
|
||||
*
|
||||
* This function will print a error message and a do WARN_ON if the
|
||||
* assertion fails. Normally this will do a stack up at the current location.
|
||||
*/
|
||||
#define caif_assert(assert) \
|
||||
do { \
|
||||
if (!(assert)) { \
|
||||
pr_err("caif:Assert detected:'%s'\n", #assert); \
|
||||
WARN_ON(!(assert)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* enum caif_ctrlcmd - CAIF Stack Control Signaling sent in layer.ctrlcmd().
|
||||
*
|
||||
* @CAIF_CTRLCMD_FLOW_OFF_IND: Flow Control is OFF, transmit function
|
||||
* should stop sending data
|
||||
*
|
||||
* @CAIF_CTRLCMD_FLOW_ON_IND: Flow Control is ON, transmit function
|
||||
* can start sending data
|
||||
*
|
||||
* @CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND: Remote end modem has decided to close
|
||||
* down channel
|
||||
*
|
||||
* @CAIF_CTRLCMD_INIT_RSP: Called initially when the layer below
|
||||
* has finished initialization
|
||||
*
|
||||
* @CAIF_CTRLCMD_DEINIT_RSP: Called when de-initialization is
|
||||
* complete
|
||||
*
|
||||
* @CAIF_CTRLCMD_INIT_FAIL_RSP: Called if initialization fails
|
||||
*
|
||||
* @_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND: CAIF Link layer temporarily cannot
|
||||
* send more packets.
|
||||
* @_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND: Called if CAIF Link layer is able
|
||||
* to send packets again.
|
||||
* @_CAIF_CTRLCMD_PHYIF_DOWN_IND: Called if CAIF Link layer is going
|
||||
* down.
|
||||
*
|
||||
* These commands are sent upwards in the CAIF stack to the CAIF Client.
|
||||
* They are used for signaling originating from the modem or CAIF Link Layer.
|
||||
* These are either responses (*_RSP) or events (*_IND).
|
||||
*/
|
||||
enum caif_ctrlcmd {
|
||||
CAIF_CTRLCMD_FLOW_OFF_IND,
|
||||
CAIF_CTRLCMD_FLOW_ON_IND,
|
||||
CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND,
|
||||
CAIF_CTRLCMD_INIT_RSP,
|
||||
CAIF_CTRLCMD_DEINIT_RSP,
|
||||
CAIF_CTRLCMD_INIT_FAIL_RSP,
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND,
|
||||
_CAIF_CTRLCMD_PHYIF_DOWN_IND,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_modemcmd - Modem Control Signaling, sent from CAIF Client
|
||||
* to the CAIF Link Layer or modem.
|
||||
*
|
||||
* @CAIF_MODEMCMD_FLOW_ON_REQ: Flow Control is ON, transmit function
|
||||
* can start sending data.
|
||||
*
|
||||
* @CAIF_MODEMCMD_FLOW_OFF_REQ: Flow Control is OFF, transmit function
|
||||
* should stop sending data.
|
||||
*
|
||||
* @_CAIF_MODEMCMD_PHYIF_USEFULL: Notify physical layer that it is in use
|
||||
*
|
||||
* @_CAIF_MODEMCMD_PHYIF_USELESS: Notify physical layer that it is
|
||||
* no longer in use.
|
||||
*
|
||||
* These are requests sent 'downwards' in the stack.
|
||||
* Flow ON, OFF can be indicated to the modem.
|
||||
*/
|
||||
enum caif_modemcmd {
|
||||
CAIF_MODEMCMD_FLOW_ON_REQ = 0,
|
||||
CAIF_MODEMCMD_FLOW_OFF_REQ = 1,
|
||||
_CAIF_MODEMCMD_PHYIF_USEFULL = 3,
|
||||
_CAIF_MODEMCMD_PHYIF_USELESS = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_direction - CAIF Packet Direction.
|
||||
* Indicate if a packet is to be sent out or to be received in.
|
||||
* @CAIF_DIR_IN: Incoming packet received.
|
||||
* @CAIF_DIR_OUT: Outgoing packet to be transmitted.
|
||||
*/
|
||||
enum caif_direction {
|
||||
CAIF_DIR_IN = 0,
|
||||
CAIF_DIR_OUT = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cflayer - CAIF Stack layer.
|
||||
* Defines the framework for the CAIF Core Stack.
|
||||
* @up: Pointer up to the layer above.
|
||||
* @dn: Pointer down to the layer below.
|
||||
* @node: List node used when layer participate in a list.
|
||||
* @receive: Packet receive function.
|
||||
* @transmit: Packet transmit function.
|
||||
* @ctrlcmd: Used for control signalling upwards in the stack.
|
||||
* @modemcmd: Used for control signaling downwards in the stack.
|
||||
* @id: The identity of this layer
|
||||
* @name: Name of the layer.
|
||||
*
|
||||
* This structure defines the layered structure in CAIF.
|
||||
*
|
||||
* It defines CAIF layering structure, used by all CAIF Layers and the
|
||||
* layers interfacing CAIF.
|
||||
*
|
||||
* In order to integrate with CAIF an adaptation layer on top of the CAIF stack
|
||||
* and PHY layer below the CAIF stack
|
||||
* must be implemented. These layer must follow the design principles below.
|
||||
*
|
||||
* Principles for layering of protocol layers:
|
||||
* - All layers must use this structure. If embedding it, then place this
|
||||
* structure first in the layer specific structure.
|
||||
*
|
||||
* - Each layer should not depend on any others layer's private data.
|
||||
*
|
||||
* - In order to send data upwards do
|
||||
* layer->up->receive(layer->up, packet);
|
||||
*
|
||||
* - In order to send data downwards do
|
||||
* layer->dn->transmit(layer->dn, info, packet);
|
||||
*/
|
||||
struct cflayer {
|
||||
struct cflayer *up;
|
||||
struct cflayer *dn;
|
||||
struct list_head node;
|
||||
|
||||
/*
|
||||
* receive() - Receive Function (non-blocking).
|
||||
* Contract: Each layer must implement a receive function passing the
|
||||
* CAIF packets upwards in the stack.
|
||||
* Packet handling rules:
|
||||
* - The CAIF packet (cfpkt) ownership is passed to the
|
||||
* called receive function. This means that the
|
||||
* packet cannot be accessed after passing it to the
|
||||
* above layer using up->receive().
|
||||
*
|
||||
* - If parsing of the packet fails, the packet must be
|
||||
* destroyed and negative error code returned
|
||||
* from the function.
|
||||
* EXCEPTION: If the framing layer (cffrml) returns
|
||||
* -EILSEQ, the packet is not freed.
|
||||
*
|
||||
* - If parsing succeeds (and above layers return OK) then
|
||||
* the function must return a value >= 0.
|
||||
*
|
||||
* Returns result < 0 indicates an error, 0 or positive value
|
||||
* indicates success.
|
||||
*
|
||||
* @layr: Pointer to the current layer the receive function is
|
||||
* implemented for (this pointer).
|
||||
* @cfpkt: Pointer to CaifPacket to be handled.
|
||||
*/
|
||||
int (*receive)(struct cflayer *layr, struct cfpkt *cfpkt);
|
||||
|
||||
/*
|
||||
* transmit() - Transmit Function (non-blocking).
|
||||
* Contract: Each layer must implement a transmit function passing the
|
||||
* CAIF packet downwards in the stack.
|
||||
* Packet handling rules:
|
||||
* - The CAIF packet (cfpkt) ownership is passed to the
|
||||
* transmit function. This means that the packet
|
||||
* cannot be accessed after passing it to the below
|
||||
* layer using dn->transmit().
|
||||
*
|
||||
* - Upon error the packet ownership is still passed on,
|
||||
* so the packet shall be freed where error is detected.
|
||||
* Callers of the transmit function shall not free packets,
|
||||
* but errors shall be returned.
|
||||
*
|
||||
* - Return value less than zero means error, zero or
|
||||
* greater than zero means OK.
|
||||
*
|
||||
* Returns result < 0 indicates an error, 0 or positive value
|
||||
* indicates success.
|
||||
*
|
||||
* @layr: Pointer to the current layer the receive function
|
||||
* isimplemented for (this pointer).
|
||||
* @cfpkt: Pointer to CaifPacket to be handled.
|
||||
*/
|
||||
int (*transmit) (struct cflayer *layr, struct cfpkt *cfpkt);
|
||||
|
||||
/*
|
||||
* cttrlcmd() - Control Function upwards in CAIF Stack (non-blocking).
|
||||
* Used for signaling responses (CAIF_CTRLCMD_*_RSP)
|
||||
* and asynchronous events from the modem (CAIF_CTRLCMD_*_IND)
|
||||
*
|
||||
* @layr: Pointer to the current layer the receive function
|
||||
* is implemented for (this pointer).
|
||||
* @ctrl: Control Command.
|
||||
*/
|
||||
void (*ctrlcmd) (struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid);
|
||||
|
||||
/*
|
||||
* modemctrl() - Control Function used for controlling the modem.
|
||||
* Used to signal down-wards in the CAIF stack.
|
||||
* Returns 0 on success, < 0 upon failure.
|
||||
*
|
||||
* @layr: Pointer to the current layer the receive function
|
||||
* is implemented for (this pointer).
|
||||
* @ctrl: Control Command.
|
||||
*/
|
||||
int (*modemcmd) (struct cflayer *layr, enum caif_modemcmd ctrl);
|
||||
|
||||
unsigned int id;
|
||||
char name[CAIF_LAYER_NAME_SZ];
|
||||
};
|
||||
|
||||
/**
|
||||
* layer_set_up() - Set the up pointer for a specified layer.
|
||||
* @layr: Layer where up pointer shall be set.
|
||||
* @above: Layer above.
|
||||
*/
|
||||
#define layer_set_up(layr, above) ((layr)->up = (struct cflayer *)(above))
|
||||
|
||||
/**
|
||||
* layer_set_dn() - Set the down pointer for a specified layer.
|
||||
* @layr: Layer where down pointer shall be set.
|
||||
* @below: Layer below.
|
||||
*/
|
||||
#define layer_set_dn(layr, below) ((layr)->dn = (struct cflayer *)(below))
|
||||
|
||||
/**
|
||||
* struct dev_info - Physical Device info information about physical layer.
|
||||
* @dev: Pointer to native physical device.
|
||||
* @id: Physical ID of the physical connection used by the
|
||||
* logical CAIF connection. Used by service layers to
|
||||
* identify their physical id to Caif MUX (CFMUXL)so
|
||||
* that the MUX can add the correct physical ID to the
|
||||
* packet.
|
||||
*/
|
||||
struct dev_info {
|
||||
void *dev;
|
||||
unsigned int id;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct caif_payload_info - Payload information embedded in packet (sk_buff).
|
||||
*
|
||||
* @dev_info: Information about the receiving device.
|
||||
*
|
||||
* @hdr_len: Header length, used to align pay load on 32bit boundary.
|
||||
*
|
||||
* @channel_id: Channel ID of the logical CAIF connection.
|
||||
* Used by mux to insert channel id into the caif packet.
|
||||
*/
|
||||
struct caif_payload_info {
|
||||
struct dev_info *dev_info;
|
||||
unsigned short hdr_len;
|
||||
unsigned short channel_id;
|
||||
};
|
||||
|
||||
#endif /* CAIF_LAYER_H_ */
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFCNFG_H_
|
||||
#define CFCNFG_H_
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfctrl.h>
|
||||
|
||||
struct cfcnfg;
|
||||
|
||||
/**
|
||||
* enum cfcnfg_phy_preference - Physical preference HW Abstraction
|
||||
*
|
||||
* @CFPHYPREF_UNSPECIFIED: Default physical interface
|
||||
*
|
||||
* @CFPHYPREF_LOW_LAT: Default physical interface for low-latency
|
||||
* traffic
|
||||
* @CFPHYPREF_HIGH_BW: Default physical interface for high-bandwidth
|
||||
* traffic
|
||||
* @CFPHYPREF_LOOP: TEST only Loopback interface simulating modem
|
||||
* responses.
|
||||
*
|
||||
*/
|
||||
enum cfcnfg_phy_preference {
|
||||
CFPHYPREF_UNSPECIFIED,
|
||||
CFPHYPREF_LOW_LAT,
|
||||
CFPHYPREF_HIGH_BW,
|
||||
CFPHYPREF_LOOP
|
||||
};
|
||||
|
||||
/**
|
||||
* cfcnfg_create() - Get the CAIF configuration object given network.
|
||||
* @net: Network for the CAIF configuration object.
|
||||
*/
|
||||
struct cfcnfg *get_cfcnfg(struct net *net);
|
||||
|
||||
/**
|
||||
* cfcnfg_create() - Create the CAIF configuration object.
|
||||
*/
|
||||
struct cfcnfg *cfcnfg_create(void);
|
||||
|
||||
/**
|
||||
* cfcnfg_remove() - Remove the CFCNFG object
|
||||
* @cfg: config object
|
||||
*/
|
||||
void cfcnfg_remove(struct cfcnfg *cfg);
|
||||
|
||||
/**
|
||||
* cfcnfg_add_phy_layer() - Adds a physical layer to the CAIF stack.
|
||||
* @cnfg: Pointer to a CAIF configuration object, created by
|
||||
* cfcnfg_create().
|
||||
* @dev: Pointer to link layer device
|
||||
* @phy_layer: Specify the physical layer. The transmit function
|
||||
* MUST be set in the structure.
|
||||
* @pref: The phy (link layer) preference.
|
||||
* @link_support: Protocol implementation for link layer specific protocol.
|
||||
* @fcs: Specify if checksum is used in CAIF Framing Layer.
|
||||
* @head_room: Head space needed by link specific protocol.
|
||||
*/
|
||||
int
|
||||
cfcnfg_add_phy_layer(struct cfcnfg *cnfg,
|
||||
struct net_device *dev, struct cflayer *phy_layer,
|
||||
enum cfcnfg_phy_preference pref,
|
||||
struct cflayer *link_support,
|
||||
bool fcs, int head_room);
|
||||
|
||||
/**
|
||||
* cfcnfg_del_phy_layer - Deletes an phy layer from the CAIF stack.
|
||||
*
|
||||
* @cnfg: Pointer to a CAIF configuration object, created by
|
||||
* cfcnfg_create().
|
||||
* @phy_layer: Adaptation layer to be removed.
|
||||
*/
|
||||
int cfcnfg_del_phy_layer(struct cfcnfg *cnfg, struct cflayer *phy_layer);
|
||||
|
||||
/**
|
||||
* cfcnfg_set_phy_state() - Set the state of the physical interface device.
|
||||
* @cnfg: Configuration object
|
||||
* @phy_layer: Physical Layer representation
|
||||
* @up: State of device
|
||||
*/
|
||||
int cfcnfg_set_phy_state(struct cfcnfg *cnfg, struct cflayer *phy_layer,
|
||||
bool up);
|
||||
|
||||
#endif /* CFCNFG_H_ */
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFCTRL_H_
|
||||
#define CFCTRL_H_
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
|
||||
/* CAIF Control packet commands */
|
||||
enum cfctrl_cmd {
|
||||
CFCTRL_CMD_LINK_SETUP = 0,
|
||||
CFCTRL_CMD_LINK_DESTROY = 1,
|
||||
CFCTRL_CMD_LINK_ERR = 2,
|
||||
CFCTRL_CMD_ENUM = 3,
|
||||
CFCTRL_CMD_SLEEP = 4,
|
||||
CFCTRL_CMD_WAKE = 5,
|
||||
CFCTRL_CMD_LINK_RECONF = 6,
|
||||
CFCTRL_CMD_START_REASON = 7,
|
||||
CFCTRL_CMD_RADIO_SET = 8,
|
||||
CFCTRL_CMD_MODEM_SET = 9,
|
||||
CFCTRL_CMD_MASK = 0xf
|
||||
};
|
||||
|
||||
/* Channel types */
|
||||
enum cfctrl_srv {
|
||||
CFCTRL_SRV_DECM = 0,
|
||||
CFCTRL_SRV_VEI = 1,
|
||||
CFCTRL_SRV_VIDEO = 2,
|
||||
CFCTRL_SRV_DBG = 3,
|
||||
CFCTRL_SRV_DATAGRAM = 4,
|
||||
CFCTRL_SRV_RFM = 5,
|
||||
CFCTRL_SRV_UTIL = 6,
|
||||
CFCTRL_SRV_MASK = 0xf
|
||||
};
|
||||
|
||||
#define CFCTRL_RSP_BIT 0x20
|
||||
#define CFCTRL_ERR_BIT 0x10
|
||||
|
||||
struct cfctrl_rsp {
|
||||
void (*linksetup_rsp)(struct cflayer *layer, u8 linkid,
|
||||
enum cfctrl_srv serv, u8 phyid,
|
||||
struct cflayer *adapt_layer);
|
||||
void (*linkdestroy_rsp)(struct cflayer *layer, u8 linkid);
|
||||
void (*linkerror_ind)(void);
|
||||
void (*enum_rsp)(void);
|
||||
void (*sleep_rsp)(void);
|
||||
void (*wake_rsp)(void);
|
||||
void (*restart_rsp)(void);
|
||||
void (*radioset_rsp)(void);
|
||||
void (*reject_rsp)(struct cflayer *layer, u8 linkid,
|
||||
struct cflayer *client_layer);
|
||||
};
|
||||
|
||||
/* Link Setup Parameters for CAIF-Links. */
|
||||
struct cfctrl_link_param {
|
||||
enum cfctrl_srv linktype;/* (T3,T0) Type of Channel */
|
||||
u8 priority; /* (P4,P0) Priority of the channel */
|
||||
u8 phyid; /* (U2-U0) Physical interface to connect */
|
||||
u8 endpoint; /* (E1,E0) Endpoint for data channels */
|
||||
u8 chtype; /* (H1,H0) Channel-Type, applies to
|
||||
* VEI, DEBUG */
|
||||
union {
|
||||
struct {
|
||||
u8 connid; /* (D7,D0) Video LinkId */
|
||||
} video;
|
||||
|
||||
struct {
|
||||
u32 connid; /* (N31,Ngit0) Connection ID used
|
||||
* for Datagram */
|
||||
} datagram;
|
||||
|
||||
struct {
|
||||
u32 connid; /* Connection ID used for RFM */
|
||||
char volume[20]; /* Volume to mount for RFM */
|
||||
} rfm; /* Configuration for RFM */
|
||||
|
||||
struct {
|
||||
u16 fifosize_kb; /* Psock FIFO size in KB */
|
||||
u16 fifosize_bufs; /* Psock # signal buffers */
|
||||
char name[16]; /* Name of the PSOCK service */
|
||||
u8 params[255]; /* Link setup Parameters> */
|
||||
u16 paramlen; /* Length of Link Setup
|
||||
* Parameters */
|
||||
} utility; /* Configuration for Utility Links (Psock) */
|
||||
} u;
|
||||
};
|
||||
|
||||
/* This structure is used internally in CFCTRL */
|
||||
struct cfctrl_request_info {
|
||||
int sequence_no;
|
||||
enum cfctrl_cmd cmd;
|
||||
u8 channel_id;
|
||||
struct cfctrl_link_param param;
|
||||
struct cflayer *client_layer;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct cfctrl {
|
||||
struct cfsrvl serv;
|
||||
struct cfctrl_rsp res;
|
||||
atomic_t req_seq_no;
|
||||
atomic_t rsp_seq_no;
|
||||
struct list_head list;
|
||||
/* Protects from simultaneous access to first_req list */
|
||||
spinlock_t info_list_lock;
|
||||
#ifndef CAIF_NO_LOOP
|
||||
u8 loop_linkid;
|
||||
int loop_linkused[256];
|
||||
/* Protects simultaneous access to loop_linkid and loop_linkused */
|
||||
spinlock_t loop_linkid_lock;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
void cfctrl_enum_req(struct cflayer *cfctrl, u8 physlinkid);
|
||||
int cfctrl_linkup_request(struct cflayer *cfctrl,
|
||||
struct cfctrl_link_param *param,
|
||||
struct cflayer *user_layer);
|
||||
int cfctrl_linkdown_req(struct cflayer *cfctrl, u8 linkid,
|
||||
struct cflayer *client);
|
||||
|
||||
struct cflayer *cfctrl_create(void);
|
||||
struct cfctrl_rsp *cfctrl_get_respfuncs(struct cflayer *layer);
|
||||
int cfctrl_cancel_req(struct cflayer *layr, struct cflayer *adap_layer);
|
||||
void cfctrl_remove(struct cflayer *layr);
|
||||
|
||||
#endif /* CFCTRL_H_ */
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFFRML_H_
|
||||
#define CFFRML_H_
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
struct cffrml;
|
||||
struct cflayer *cffrml_create(u16 phyid, bool use_fcs);
|
||||
void cffrml_free(struct cflayer *layr);
|
||||
void cffrml_set_uplayer(struct cflayer *this, struct cflayer *up);
|
||||
void cffrml_set_dnlayer(struct cflayer *this, struct cflayer *dn);
|
||||
void cffrml_put(struct cflayer *layr);
|
||||
void cffrml_hold(struct cflayer *layr);
|
||||
int cffrml_refcnt_read(struct cflayer *layr);
|
||||
|
||||
#endif /* CFFRML_H_ */
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFMUXL_H_
|
||||
#define CFMUXL_H_
|
||||
#include <net/caif/caif_layer.h>
|
||||
|
||||
struct cfsrvl;
|
||||
struct cffrml;
|
||||
|
||||
struct cflayer *cfmuxl_create(void);
|
||||
int cfmuxl_set_uplayer(struct cflayer *layr, struct cflayer *up, u8 linkid);
|
||||
struct cflayer *cfmuxl_remove_dnlayer(struct cflayer *layr, u8 phyid);
|
||||
int cfmuxl_set_dnlayer(struct cflayer *layr, struct cflayer *up, u8 phyid);
|
||||
struct cflayer *cfmuxl_remove_uplayer(struct cflayer *layr, u8 linkid);
|
||||
|
||||
#endif /* CFMUXL_H_ */
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFPKT_H_
|
||||
#define CFPKT_H_
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <linux/types.h>
|
||||
struct cfpkt;
|
||||
|
||||
/* Create a CAIF packet.
|
||||
* len: Length of packet to be created
|
||||
* @return New packet.
|
||||
*/
|
||||
struct cfpkt *cfpkt_create(u16 len);
|
||||
|
||||
/*
|
||||
* Destroy a CAIF Packet.
|
||||
* pkt Packet to be destroyed.
|
||||
*/
|
||||
void cfpkt_destroy(struct cfpkt *pkt);
|
||||
|
||||
/*
|
||||
* Extract header from packet.
|
||||
*
|
||||
* pkt Packet to extract header data from.
|
||||
* data Pointer to copy the header data into.
|
||||
* len Length of head data to copy.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_extr_head(struct cfpkt *pkt, void *data, u16 len);
|
||||
|
||||
static inline u8 cfpkt_extr_head_u8(struct cfpkt *pkt)
|
||||
{
|
||||
u8 tmp;
|
||||
|
||||
cfpkt_extr_head(pkt, &tmp, 1);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static inline u16 cfpkt_extr_head_u16(struct cfpkt *pkt)
|
||||
{
|
||||
__le16 tmp;
|
||||
|
||||
cfpkt_extr_head(pkt, &tmp, 2);
|
||||
|
||||
return le16_to_cpu(tmp);
|
||||
}
|
||||
|
||||
static inline u32 cfpkt_extr_head_u32(struct cfpkt *pkt)
|
||||
{
|
||||
__le32 tmp;
|
||||
|
||||
cfpkt_extr_head(pkt, &tmp, 4);
|
||||
|
||||
return le32_to_cpu(tmp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Peek header from packet.
|
||||
* Reads data from packet without changing packet.
|
||||
*
|
||||
* pkt Packet to extract header data from.
|
||||
* data Pointer to copy the header data into.
|
||||
* len Length of head data to copy.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_peek_head(struct cfpkt *pkt, void *data, u16 len);
|
||||
|
||||
/*
|
||||
* Extract header from trailer (end of packet).
|
||||
*
|
||||
* pkt Packet to extract header data from.
|
||||
* data Pointer to copy the trailer data into.
|
||||
* len Length of header data to copy.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_extr_trail(struct cfpkt *pkt, void *data, u16 len);
|
||||
|
||||
/*
|
||||
* Add header to packet.
|
||||
*
|
||||
*
|
||||
* pkt Packet to add header data to.
|
||||
* data Pointer to data to copy into the header.
|
||||
* len Length of header data to copy.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_add_head(struct cfpkt *pkt, const void *data, u16 len);
|
||||
|
||||
/*
|
||||
* Add trailer to packet.
|
||||
*
|
||||
*
|
||||
* pkt Packet to add trailer data to.
|
||||
* data Pointer to data to copy into the trailer.
|
||||
* len Length of trailer data to copy.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_add_trail(struct cfpkt *pkt, const void *data, u16 len);
|
||||
|
||||
/*
|
||||
* Pad trailer on packet.
|
||||
* Moves data pointer in packet, no content copied.
|
||||
*
|
||||
* pkt Packet in which to pad trailer.
|
||||
* len Length of padding to add.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_pad_trail(struct cfpkt *pkt, u16 len);
|
||||
|
||||
/*
|
||||
* Add a single byte to packet body (tail).
|
||||
*
|
||||
* pkt Packet in which to add byte.
|
||||
* data Byte to add.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_addbdy(struct cfpkt *pkt, const u8 data);
|
||||
|
||||
/*
|
||||
* Add a data to packet body (tail).
|
||||
*
|
||||
* pkt Packet in which to add data.
|
||||
* data Pointer to data to copy into the packet body.
|
||||
* len Length of data to add.
|
||||
* @return zero on success and error code upon failure
|
||||
*/
|
||||
int cfpkt_add_body(struct cfpkt *pkt, const void *data, u16 len);
|
||||
|
||||
/*
|
||||
* Checks whether there are more data to process in packet.
|
||||
* pkt Packet to check.
|
||||
* @return true if more data are available in packet false otherwise
|
||||
*/
|
||||
bool cfpkt_more(struct cfpkt *pkt);
|
||||
|
||||
/*
|
||||
* Checks whether the packet is erroneous,
|
||||
* i.e. if it has been attempted to extract more data than available in packet
|
||||
* or writing more data than has been allocated in cfpkt_create().
|
||||
* pkt Packet to check.
|
||||
* @return true on error false otherwise
|
||||
*/
|
||||
bool cfpkt_erroneous(struct cfpkt *pkt);
|
||||
|
||||
/*
|
||||
* Get the packet length.
|
||||
* pkt Packet to get length from.
|
||||
* @return Number of bytes in packet.
|
||||
*/
|
||||
u16 cfpkt_getlen(struct cfpkt *pkt);
|
||||
|
||||
/*
|
||||
* Set the packet length, by adjusting the trailer pointer according to length.
|
||||
* pkt Packet to set length.
|
||||
* len Packet length.
|
||||
* @return Number of bytes in packet.
|
||||
*/
|
||||
int cfpkt_setlen(struct cfpkt *pkt, u16 len);
|
||||
|
||||
/*
|
||||
* cfpkt_append - Appends a packet's data to another packet.
|
||||
* dstpkt: Packet to append data into, WILL BE FREED BY THIS FUNCTION
|
||||
* addpkt: Packet to be appended and automatically released,
|
||||
* WILL BE FREED BY THIS FUNCTION.
|
||||
* expectlen: Packet's expected total length. This should be considered
|
||||
* as a hint.
|
||||
* NB: Input packets will be destroyed after appending and cannot be used
|
||||
* after calling this function.
|
||||
* @return The new appended packet.
|
||||
*/
|
||||
struct cfpkt *cfpkt_append(struct cfpkt *dstpkt, struct cfpkt *addpkt,
|
||||
u16 expectlen);
|
||||
|
||||
/*
|
||||
* cfpkt_split - Split a packet into two packets at the specified split point.
|
||||
* pkt: Packet to be split (will contain the first part of the data on exit)
|
||||
* pos: Position to split packet in two parts.
|
||||
* @return The new packet, containing the second part of the data.
|
||||
*/
|
||||
struct cfpkt *cfpkt_split(struct cfpkt *pkt, u16 pos);
|
||||
|
||||
/*
|
||||
* Iteration function, iterates the packet buffers from start to end.
|
||||
*
|
||||
* Checksum iteration function used to iterate buffers
|
||||
* (we may have packets consisting of a chain of buffers)
|
||||
* pkt: Packet to calculate checksum for
|
||||
* iter_func: Function pointer to iteration function
|
||||
* chks: Checksum calculated so far.
|
||||
* buf: Pointer to the buffer to checksum
|
||||
* len: Length of buf.
|
||||
* data: Initial checksum value.
|
||||
* @return Checksum of buffer.
|
||||
*/
|
||||
|
||||
int cfpkt_iterate(struct cfpkt *pkt,
|
||||
u16 (*iter_func)(u16 chks, void *buf, u16 len),
|
||||
u16 data);
|
||||
|
||||
/* Map from a "native" packet (e.g. Linux Socket Buffer) to a CAIF packet.
|
||||
* dir - Direction indicating whether this packet is to be sent or received.
|
||||
* nativepkt - The native packet to be transformed to a CAIF packet
|
||||
* @return The mapped CAIF Packet CFPKT.
|
||||
*/
|
||||
struct cfpkt *cfpkt_fromnative(enum caif_direction dir, void *nativepkt);
|
||||
|
||||
/* Map from a CAIF packet to a "native" packet (e.g. Linux Socket Buffer).
|
||||
* pkt - The CAIF packet to be transformed into a "native" packet.
|
||||
* @return The native packet transformed from a CAIF packet.
|
||||
*/
|
||||
void *cfpkt_tonative(struct cfpkt *pkt);
|
||||
|
||||
/*
|
||||
* Returns packet information for a packet.
|
||||
* pkt Packet to get info from;
|
||||
* @return Packet information
|
||||
*/
|
||||
struct caif_payload_info *cfpkt_info(struct cfpkt *pkt);
|
||||
|
||||
/** cfpkt_set_prio - set priority for a CAIF packet.
|
||||
*
|
||||
* @pkt: The CAIF packet to be adjusted.
|
||||
* @prio: one of TC_PRIO_ constants.
|
||||
*/
|
||||
void cfpkt_set_prio(struct cfpkt *pkt, int prio);
|
||||
|
||||
#endif /* CFPKT_H_ */
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFSERL_H_
|
||||
#define CFSERL_H_
|
||||
#include <net/caif/caif_layer.h>
|
||||
|
||||
struct cflayer *cfserl_create(int instance, bool use_stx);
|
||||
void cfserl_release(struct cflayer *layer);
|
||||
#endif
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#ifndef CFSRVL_H_
|
||||
#define CFSRVL_H_
|
||||
#include <linux/list.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/rculist.h>
|
||||
|
||||
struct cfsrvl {
|
||||
struct cflayer layer;
|
||||
bool open;
|
||||
bool phy_flow_on;
|
||||
bool modem_flow_on;
|
||||
bool supports_flowctrl;
|
||||
void (*release)(struct cflayer *layer);
|
||||
struct dev_info dev_info;
|
||||
void (*hold)(struct cflayer *lyr);
|
||||
void (*put)(struct cflayer *lyr);
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
struct cflayer *cfvei_create(u8 linkid, struct dev_info *dev_info);
|
||||
struct cflayer *cfdgml_create(u8 linkid, struct dev_info *dev_info);
|
||||
struct cflayer *cfutill_create(u8 linkid, struct dev_info *dev_info);
|
||||
struct cflayer *cfvidl_create(u8 linkid, struct dev_info *dev_info);
|
||||
struct cflayer *cfrfml_create(u8 linkid, struct dev_info *dev_info,
|
||||
int mtu_size);
|
||||
struct cflayer *cfdbgl_create(u8 linkid, struct dev_info *dev_info);
|
||||
|
||||
bool cfsrvl_phyid_match(struct cflayer *layer, int phyid);
|
||||
|
||||
void cfsrvl_init(struct cfsrvl *service,
|
||||
u8 channel_id,
|
||||
struct dev_info *dev_info,
|
||||
bool supports_flowctrl);
|
||||
bool cfsrvl_ready(struct cfsrvl *service, int *err);
|
||||
|
||||
static inline void cfsrvl_get(struct cflayer *layr)
|
||||
{
|
||||
struct cfsrvl *s = container_of(layr, struct cfsrvl, layer);
|
||||
if (layr == NULL || layr->up == NULL || s->hold == NULL)
|
||||
return;
|
||||
|
||||
s->hold(layr->up);
|
||||
}
|
||||
|
||||
static inline void cfsrvl_put(struct cflayer *layr)
|
||||
{
|
||||
struct cfsrvl *s = container_of(layr, struct cfsrvl, layer);
|
||||
if (layr == NULL || layr->up == NULL || s->hold == NULL)
|
||||
return;
|
||||
|
||||
s->put(layr->up);
|
||||
}
|
||||
#endif /* CFSRVL_H_ */
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/* linux/caif_socket.h
|
||||
* CAIF Definitions for CAIF socket and network layer
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
* License terms: GNU General Public License (GPL) version 2
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_CAIF_SOCKET_H
|
||||
#define _LINUX_CAIF_SOCKET_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
/**
|
||||
* enum caif_link_selector - Physical Link Selection.
|
||||
* @CAIF_LINK_HIGH_BANDW: Physical interface for high-bandwidth
|
||||
* traffic.
|
||||
* @CAIF_LINK_LOW_LATENCY: Physical interface for low-latency
|
||||
* traffic.
|
||||
*
|
||||
* CAIF Link Layers can register their link properties.
|
||||
* This enum is used for choosing between CAIF Link Layers when
|
||||
* setting up CAIF Channels when multiple CAIF Link Layers exists.
|
||||
*/
|
||||
enum caif_link_selector {
|
||||
CAIF_LINK_HIGH_BANDW,
|
||||
CAIF_LINK_LOW_LATENCY
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_channel_priority - CAIF channel priorities.
|
||||
*
|
||||
* @CAIF_PRIO_MIN: Min priority for a channel.
|
||||
* @CAIF_PRIO_LOW: Low-priority channel.
|
||||
* @CAIF_PRIO_NORMAL: Normal/default priority level.
|
||||
* @CAIF_PRIO_HIGH: High priority level
|
||||
* @CAIF_PRIO_MAX: Max priority for channel
|
||||
*
|
||||
* Priority can be set on CAIF Channels in order to
|
||||
* prioritize between traffic on different CAIF Channels.
|
||||
* These priority levels are recommended, but the priority value
|
||||
* is not restricted to the values defined in this enum, any value
|
||||
* between CAIF_PRIO_MIN and CAIF_PRIO_MAX could be used.
|
||||
*/
|
||||
enum caif_channel_priority {
|
||||
CAIF_PRIO_MIN = 0x01,
|
||||
CAIF_PRIO_LOW = 0x04,
|
||||
CAIF_PRIO_NORMAL = 0x0f,
|
||||
CAIF_PRIO_HIGH = 0x14,
|
||||
CAIF_PRIO_MAX = 0x1F
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_protocol_type - CAIF Channel type.
|
||||
* @CAIFPROTO_AT: Classic AT channel.
|
||||
* @CAIFPROTO_DATAGRAM: Datagram channel.
|
||||
* @CAIFPROTO_DATAGRAM_LOOP: Datagram loopback channel, used for testing.
|
||||
* @CAIFPROTO_UTIL: Utility (Psock) channel.
|
||||
* @CAIFPROTO_RFM: Remote File Manager
|
||||
* @CAIFPROTO_DEBUG: Debug link
|
||||
*
|
||||
* This enum defines the CAIF Channel type to be used. This defines
|
||||
* the service to connect to on the modem.
|
||||
*/
|
||||
enum caif_protocol_type {
|
||||
CAIFPROTO_AT,
|
||||
CAIFPROTO_DATAGRAM,
|
||||
CAIFPROTO_DATAGRAM_LOOP,
|
||||
CAIFPROTO_UTIL,
|
||||
CAIFPROTO_RFM,
|
||||
CAIFPROTO_DEBUG,
|
||||
_CAIFPROTO_MAX
|
||||
};
|
||||
#define CAIFPROTO_MAX _CAIFPROTO_MAX
|
||||
|
||||
/**
|
||||
* enum caif_at_type - AT Service Endpoint
|
||||
* @CAIF_ATTYPE_PLAIN: Connects to a plain vanilla AT channel.
|
||||
*/
|
||||
enum caif_at_type {
|
||||
CAIF_ATTYPE_PLAIN = 2
|
||||
};
|
||||
/**
|
||||
* enum caif_debug_type - Content selection for debug connection
|
||||
* @CAIF_DEBUG_TRACE_INTERACTIVE: Connection will contain
|
||||
* both trace and interactive debug.
|
||||
* @CAIF_DEBUG_TRACE: Connection contains trace only.
|
||||
* @CAIF_DEBUG_INTERACTIVE: Connection to interactive debug.
|
||||
*/
|
||||
enum caif_debug_type {
|
||||
CAIF_DEBUG_TRACE_INTERACTIVE = 0,
|
||||
CAIF_DEBUG_TRACE,
|
||||
CAIF_DEBUG_INTERACTIVE,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_debug_service - Debug Service Endpoint
|
||||
* @CAIF_RADIO_DEBUG_SERVICE: Debug service on the Radio sub-system
|
||||
* @CAIF_APP_DEBUG_SERVICE: Debug for the applications sub-system
|
||||
*/
|
||||
enum caif_debug_service {
|
||||
CAIF_RADIO_DEBUG_SERVICE = 1,
|
||||
CAIF_APP_DEBUG_SERVICE
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sockaddr_caif - the sockaddr structure for CAIF sockets.
|
||||
* @family: Address family number, must be AF_CAIF.
|
||||
* @u: Union of address data 'switched' by family.
|
||||
* :
|
||||
* @u.at: Applies when family = CAIFPROTO_AT.
|
||||
*
|
||||
* @u.at.type: Type of AT link to set up (enum caif_at_type).
|
||||
*
|
||||
* @u.util: Applies when family = CAIFPROTO_UTIL
|
||||
*
|
||||
* @u.util.service: Utility service name.
|
||||
*
|
||||
* @u.dgm: Applies when family = CAIFPROTO_DATAGRAM
|
||||
*
|
||||
* @u.dgm.connection_id: Datagram connection id.
|
||||
*
|
||||
* @u.dgm.nsapi: NSAPI of the PDP-Context.
|
||||
*
|
||||
* @u.rfm: Applies when family = CAIFPROTO_RFM
|
||||
*
|
||||
* @u.rfm.connection_id: Connection ID for RFM.
|
||||
*
|
||||
* @u.rfm.volume: Volume to mount.
|
||||
*
|
||||
* @u.dbg: Applies when family = CAIFPROTO_DEBUG.
|
||||
*
|
||||
* @u.dbg.type: Type of debug connection to set up
|
||||
* (caif_debug_type).
|
||||
*
|
||||
* @u.dbg.service: Service sub-system to connect (caif_debug_service
|
||||
* Description:
|
||||
* This structure holds the connect parameters used for setting up a
|
||||
* CAIF Channel. It defines the service to connect to on the modem.
|
||||
*/
|
||||
struct sockaddr_caif {
|
||||
__kernel_sa_family_t family;
|
||||
union {
|
||||
struct {
|
||||
__u8 type; /* type: enum caif_at_type */
|
||||
} at; /* CAIFPROTO_AT */
|
||||
struct {
|
||||
char service[16];
|
||||
} util; /* CAIFPROTO_UTIL */
|
||||
union {
|
||||
__u32 connection_id;
|
||||
__u8 nsapi;
|
||||
} dgm; /* CAIFPROTO_DATAGRAM(_LOOP)*/
|
||||
struct {
|
||||
__u32 connection_id;
|
||||
char volume[16];
|
||||
} rfm; /* CAIFPROTO_RFM */
|
||||
struct {
|
||||
__u8 type; /* type:enum caif_debug_type */
|
||||
__u8 service; /* service:caif_debug_service */
|
||||
} dbg; /* CAIFPROTO_DEBUG */
|
||||
} u;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum caif_socket_opts - CAIF option values for getsockopt and setsockopt.
|
||||
*
|
||||
* @CAIFSO_LINK_SELECT: Selector used if multiple CAIF Link layers are
|
||||
* available. Either a high bandwidth
|
||||
* link can be selected (CAIF_LINK_HIGH_BANDW) or
|
||||
* a low latency link (CAIF_LINK_LOW_LATENCY).
|
||||
* This option is of type __u32.
|
||||
* Alternatively SO_BINDTODEVICE can be used.
|
||||
*
|
||||
* @CAIFSO_REQ_PARAM: Used to set the request parameters for a
|
||||
* utility channel. (maximum 256 bytes). This
|
||||
* option must be set before connecting.
|
||||
*
|
||||
* @CAIFSO_RSP_PARAM: Gets the response parameters for a utility
|
||||
* channel. (maximum 256 bytes). This option
|
||||
* is valid after a successful connect.
|
||||
*
|
||||
*
|
||||
* This enum defines the CAIF Socket options to be used on a socket
|
||||
* of type PF_CAIF.
|
||||
*
|
||||
*/
|
||||
enum caif_socket_opts {
|
||||
CAIFSO_LINK_SELECT = 127,
|
||||
CAIFSO_REQ_PARAM = 128,
|
||||
CAIFSO_RSP_PARAM = 129,
|
||||
};
|
||||
|
||||
#endif /* _LINUX_CAIF_SOCKET_H */
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
* License terms: GNU General Public License (GPL) version 2
|
||||
*/
|
||||
|
||||
#ifndef IF_CAIF_H_
|
||||
#define IF_CAIF_H_
|
||||
#include <linux/sockios.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
/**
|
||||
* enum ifla_caif - CAIF NetlinkRT parameters.
|
||||
* @IFLA_CAIF_IPV4_CONNID: Connection ID for IPv4 PDP Context.
|
||||
* The type of attribute is NLA_U32.
|
||||
* @IFLA_CAIF_IPV6_CONNID: Connection ID for IPv6 PDP Context.
|
||||
* The type of attribute is NLA_U32.
|
||||
* @IFLA_CAIF_LOOPBACK: If different from zero, device is doing loopback
|
||||
* The type of attribute is NLA_U8.
|
||||
*
|
||||
* When using RT Netlink to create, destroy or configure a CAIF IP interface,
|
||||
* enum ifla_caif is used to specify the configuration attributes.
|
||||
*/
|
||||
enum ifla_caif {
|
||||
__IFLA_CAIF_UNSPEC,
|
||||
IFLA_CAIF_IPV4_CONNID,
|
||||
IFLA_CAIF_IPV6_CONNID,
|
||||
IFLA_CAIF_LOOPBACK,
|
||||
__IFLA_CAIF_MAX
|
||||
};
|
||||
#define IFLA_CAIF_MAX (__IFLA_CAIF_MAX-1)
|
||||
|
||||
#endif /*IF_CAIF_H_*/
|
||||
|
|
@ -439,7 +439,6 @@ endif # WIRELESS
|
|||
|
||||
source "net/rfkill/Kconfig"
|
||||
source "net/9p/Kconfig"
|
||||
source "net/caif/Kconfig"
|
||||
source "net/ceph/Kconfig"
|
||||
source "net/nfc/Kconfig"
|
||||
source "net/psample/Kconfig"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ obj-$(CONFIG_IUCV) += iucv/
|
|||
obj-$(CONFIG_SMC) += smc/
|
||||
obj-$(CONFIG_RFKILL) += rfkill/
|
||||
obj-$(CONFIG_NET_9P) += 9p/
|
||||
obj-$(CONFIG_CAIF) += caif/
|
||||
obj-$(CONFIG_DCB) += dcb/
|
||||
obj-$(CONFIG_6LOWPAN) += 6lowpan/
|
||||
obj-$(CONFIG_IEEE802154) += ieee802154/
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# CAIF net configurations
|
||||
#
|
||||
|
||||
menuconfig CAIF
|
||||
tristate "CAIF support"
|
||||
select CRC_CCITT
|
||||
default n
|
||||
help
|
||||
The "Communication CPU to Application CPU Interface" (CAIF) is a packet
|
||||
based connection-oriented MUX protocol developed by ST-Ericsson for use
|
||||
with its modems. It is accessed from user space as sockets (PF_CAIF).
|
||||
|
||||
Say Y (or M) here if you build for a phone product (e.g. Android or
|
||||
MeeGo) that uses CAIF as transport. If unsure say N.
|
||||
|
||||
If you select to build it as module then CAIF_NETDEV also needs to be
|
||||
built as a module. You will also need to say Y (or M) to any CAIF
|
||||
physical devices that your platform requires.
|
||||
|
||||
See Documentation/networking/caif for a further explanation on how to
|
||||
use and configure CAIF.
|
||||
|
||||
config CAIF_DEBUG
|
||||
bool "Enable Debug"
|
||||
depends on CAIF
|
||||
default n
|
||||
help
|
||||
Enable the inclusion of debug code in the CAIF stack.
|
||||
Be aware that doing this will impact performance.
|
||||
If unsure say N.
|
||||
|
||||
config CAIF_NETDEV
|
||||
tristate "CAIF GPRS Network device"
|
||||
depends on CAIF
|
||||
default CAIF
|
||||
help
|
||||
Say Y if you will be using a CAIF based GPRS network device.
|
||||
This can be either built-in or a loadable module.
|
||||
If you select to build it as a built-in then the main CAIF device must
|
||||
also be a built-in.
|
||||
If unsure say Y.
|
||||
|
||||
config CAIF_USB
|
||||
tristate "CAIF USB support"
|
||||
depends on CAIF
|
||||
default n
|
||||
help
|
||||
Say Y if you are using CAIF over USB CDC NCM.
|
||||
This can be either built-in or a loadable module.
|
||||
If you select to build it as a built-in then the main CAIF device must
|
||||
also be a built-in.
|
||||
If unsure say N.
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
ccflags-$(CONFIG_CAIF_DEBUG) := -DDEBUG
|
||||
|
||||
caif-y := caif_dev.o \
|
||||
cfcnfg.o cfmuxl.o cfctrl.o \
|
||||
cffrml.o cfveil.o cfdbgl.o\
|
||||
cfserl.o cfdgml.o \
|
||||
cfrfml.o cfvidl.o cfutill.o \
|
||||
cfsrvl.o cfpkt_skbuff.o
|
||||
|
||||
obj-$(CONFIG_CAIF) += caif.o
|
||||
obj-$(CONFIG_CAIF_NETDEV) += chnl_net.o
|
||||
obj-$(CONFIG_CAIF) += caif_socket.o
|
||||
obj-$(CONFIG_CAIF_USB) += caif_usb.o
|
||||
|
||||
export-y := caif.o
|
||||
|
|
@ -1,586 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* CAIF Interface registration.
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*
|
||||
* Borrowed heavily from file: pn_dev.c. Thanks to Remi Denis-Courmont
|
||||
* and Sakari Ailus <sakari.ailus@nokia.com>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <net/netns/generic.h>
|
||||
#include <net/net_namespace.h>
|
||||
#include <net/pkt_sched.h>
|
||||
#include <net/caif/caif_device.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfcnfg.h>
|
||||
#include <net/caif/cfserl.h>
|
||||
|
||||
MODULE_DESCRIPTION("ST-Ericsson CAIF modem protocol support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/* Used for local tracking of the CAIF net devices */
|
||||
struct caif_device_entry {
|
||||
struct cflayer layer;
|
||||
struct list_head list;
|
||||
struct net_device *netdev;
|
||||
int __percpu *pcpu_refcnt;
|
||||
spinlock_t flow_lock;
|
||||
struct sk_buff *xoff_skb;
|
||||
void (*xoff_skb_dtor)(struct sk_buff *skb);
|
||||
bool xoff;
|
||||
};
|
||||
|
||||
struct caif_device_entry_list {
|
||||
struct list_head list;
|
||||
/* Protects simulanous deletes in list */
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct caif_net {
|
||||
struct cfcnfg *cfg;
|
||||
struct caif_device_entry_list caifdevs;
|
||||
};
|
||||
|
||||
static unsigned int caif_net_id;
|
||||
static int q_high = 50; /* Percent */
|
||||
|
||||
struct cfcnfg *get_cfcnfg(struct net *net)
|
||||
{
|
||||
struct caif_net *caifn;
|
||||
caifn = net_generic(net, caif_net_id);
|
||||
return caifn->cfg;
|
||||
}
|
||||
EXPORT_SYMBOL(get_cfcnfg);
|
||||
|
||||
static struct caif_device_entry_list *caif_device_list(struct net *net)
|
||||
{
|
||||
struct caif_net *caifn;
|
||||
caifn = net_generic(net, caif_net_id);
|
||||
return &caifn->caifdevs;
|
||||
}
|
||||
|
||||
static void caifd_put(struct caif_device_entry *e)
|
||||
{
|
||||
this_cpu_dec(*e->pcpu_refcnt);
|
||||
}
|
||||
|
||||
static void caifd_hold(struct caif_device_entry *e)
|
||||
{
|
||||
this_cpu_inc(*e->pcpu_refcnt);
|
||||
}
|
||||
|
||||
static int caifd_refcnt_read(struct caif_device_entry *e)
|
||||
{
|
||||
int i, refcnt = 0;
|
||||
for_each_possible_cpu(i)
|
||||
refcnt += *per_cpu_ptr(e->pcpu_refcnt, i);
|
||||
return refcnt;
|
||||
}
|
||||
|
||||
/* Allocate new CAIF device. */
|
||||
static struct caif_device_entry *caif_device_alloc(struct net_device *dev)
|
||||
{
|
||||
struct caif_device_entry *caifd;
|
||||
|
||||
caifd = kzalloc_obj(*caifd);
|
||||
if (!caifd)
|
||||
return NULL;
|
||||
caifd->pcpu_refcnt = alloc_percpu(int);
|
||||
if (!caifd->pcpu_refcnt) {
|
||||
kfree(caifd);
|
||||
return NULL;
|
||||
}
|
||||
caifd->netdev = dev;
|
||||
dev_hold(dev);
|
||||
return caifd;
|
||||
}
|
||||
|
||||
static struct caif_device_entry *caif_get(struct net_device *dev)
|
||||
{
|
||||
struct caif_device_entry_list *caifdevs =
|
||||
caif_device_list(dev_net(dev));
|
||||
struct caif_device_entry *caifd;
|
||||
|
||||
list_for_each_entry_rcu(caifd, &caifdevs->list, list,
|
||||
lockdep_rtnl_is_held()) {
|
||||
if (caifd->netdev == dev)
|
||||
return caifd;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void caif_flow_cb(struct sk_buff *skb)
|
||||
{
|
||||
struct caif_device_entry *caifd;
|
||||
void (*dtor)(struct sk_buff *skb) = NULL;
|
||||
bool send_xoff;
|
||||
|
||||
WARN_ON(skb->dev == NULL);
|
||||
|
||||
rcu_read_lock();
|
||||
caifd = caif_get(skb->dev);
|
||||
|
||||
WARN_ON(caifd == NULL);
|
||||
if (!caifd) {
|
||||
rcu_read_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
caifd_hold(caifd);
|
||||
rcu_read_unlock();
|
||||
|
||||
spin_lock_bh(&caifd->flow_lock);
|
||||
send_xoff = caifd->xoff;
|
||||
caifd->xoff = false;
|
||||
dtor = caifd->xoff_skb_dtor;
|
||||
|
||||
if (WARN_ON(caifd->xoff_skb != skb))
|
||||
skb = NULL;
|
||||
|
||||
caifd->xoff_skb = NULL;
|
||||
caifd->xoff_skb_dtor = NULL;
|
||||
|
||||
spin_unlock_bh(&caifd->flow_lock);
|
||||
|
||||
if (dtor && skb)
|
||||
dtor(skb);
|
||||
|
||||
if (send_xoff)
|
||||
caifd->layer.up->
|
||||
ctrlcmd(caifd->layer.up,
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND,
|
||||
caifd->layer.id);
|
||||
caifd_put(caifd);
|
||||
}
|
||||
|
||||
static int transmit(struct cflayer *layer, struct cfpkt *pkt)
|
||||
{
|
||||
int err, high = 0, qlen = 0;
|
||||
struct caif_device_entry *caifd =
|
||||
container_of(layer, struct caif_device_entry, layer);
|
||||
struct sk_buff *skb;
|
||||
struct netdev_queue *txq;
|
||||
|
||||
rcu_read_lock_bh();
|
||||
|
||||
skb = cfpkt_tonative(pkt);
|
||||
skb->dev = caifd->netdev;
|
||||
skb_reset_network_header(skb);
|
||||
skb->protocol = htons(ETH_P_CAIF);
|
||||
|
||||
/* Check if we need to handle xoff */
|
||||
if (likely(caifd->netdev->priv_flags & IFF_NO_QUEUE))
|
||||
goto noxoff;
|
||||
|
||||
if (unlikely(caifd->xoff))
|
||||
goto noxoff;
|
||||
|
||||
if (likely(!netif_queue_stopped(caifd->netdev))) {
|
||||
struct Qdisc *sch;
|
||||
|
||||
/* If we run with a TX queue, check if the queue is too long*/
|
||||
txq = netdev_get_tx_queue(skb->dev, 0);
|
||||
sch = rcu_dereference_bh(txq->qdisc);
|
||||
if (likely(qdisc_is_empty(sch)))
|
||||
goto noxoff;
|
||||
|
||||
/* can check for explicit qdisc len value only !NOLOCK,
|
||||
* always set flow off otherwise
|
||||
*/
|
||||
high = (caifd->netdev->tx_queue_len * q_high) / 100;
|
||||
if (!(sch->flags & TCQ_F_NOLOCK) && likely(sch->q.qlen < high))
|
||||
goto noxoff;
|
||||
}
|
||||
|
||||
/* Hold lock while accessing xoff */
|
||||
spin_lock_bh(&caifd->flow_lock);
|
||||
if (caifd->xoff) {
|
||||
spin_unlock_bh(&caifd->flow_lock);
|
||||
goto noxoff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle flow off, we do this by temporary hi-jacking this
|
||||
* skb's destructor function, and replace it with our own
|
||||
* flow-on callback. The callback will set flow-on and call
|
||||
* the original destructor.
|
||||
*/
|
||||
|
||||
pr_debug("queue has stopped(%d) or is full (%d > %d)\n",
|
||||
netif_queue_stopped(caifd->netdev),
|
||||
qlen, high);
|
||||
caifd->xoff = true;
|
||||
caifd->xoff_skb = skb;
|
||||
caifd->xoff_skb_dtor = skb->destructor;
|
||||
skb->destructor = caif_flow_cb;
|
||||
spin_unlock_bh(&caifd->flow_lock);
|
||||
|
||||
caifd->layer.up->ctrlcmd(caifd->layer.up,
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
|
||||
caifd->layer.id);
|
||||
noxoff:
|
||||
rcu_read_unlock_bh();
|
||||
|
||||
err = dev_queue_xmit(skb);
|
||||
if (err > 0)
|
||||
err = -EIO;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stuff received packets into the CAIF stack.
|
||||
* On error, returns non-zero and releases the skb.
|
||||
*/
|
||||
static int receive(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pkttype, struct net_device *orig_dev)
|
||||
{
|
||||
struct cfpkt *pkt;
|
||||
struct caif_device_entry *caifd;
|
||||
int err;
|
||||
|
||||
pkt = cfpkt_fromnative(CAIF_DIR_IN, skb);
|
||||
|
||||
rcu_read_lock();
|
||||
caifd = caif_get(dev);
|
||||
|
||||
if (!caifd || !caifd->layer.up || !caifd->layer.up->receive ||
|
||||
!netif_oper_up(caifd->netdev)) {
|
||||
rcu_read_unlock();
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
/* Hold reference to netdevice while using CAIF stack */
|
||||
caifd_hold(caifd);
|
||||
rcu_read_unlock();
|
||||
|
||||
err = caifd->layer.up->receive(caifd->layer.up, pkt);
|
||||
|
||||
/* For -EILSEQ the packet is not freed so free it now */
|
||||
if (err == -EILSEQ)
|
||||
cfpkt_destroy(pkt);
|
||||
|
||||
/* Release reference to stack upwards */
|
||||
caifd_put(caifd);
|
||||
|
||||
if (err != 0)
|
||||
err = NET_RX_DROP;
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct packet_type caif_packet_type __read_mostly = {
|
||||
.type = cpu_to_be16(ETH_P_CAIF),
|
||||
.func = receive,
|
||||
};
|
||||
|
||||
static void dev_flowctrl(struct net_device *dev, int on)
|
||||
{
|
||||
struct caif_device_entry *caifd;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
caifd = caif_get(dev);
|
||||
if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
|
||||
rcu_read_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
caifd_hold(caifd);
|
||||
rcu_read_unlock();
|
||||
|
||||
caifd->layer.up->ctrlcmd(caifd->layer.up,
|
||||
on ?
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_ON_IND :
|
||||
_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND,
|
||||
caifd->layer.id);
|
||||
caifd_put(caifd);
|
||||
}
|
||||
|
||||
int caif_enroll_dev(struct net_device *dev, struct caif_dev_common *caifdev,
|
||||
struct cflayer *link_support, int head_room,
|
||||
struct cflayer **layer,
|
||||
int (**rcv_func)(struct sk_buff *, struct net_device *,
|
||||
struct packet_type *,
|
||||
struct net_device *))
|
||||
{
|
||||
struct caif_device_entry *caifd;
|
||||
enum cfcnfg_phy_preference pref;
|
||||
struct cfcnfg *cfg = get_cfcnfg(dev_net(dev));
|
||||
struct caif_device_entry_list *caifdevs;
|
||||
int res;
|
||||
|
||||
caifdevs = caif_device_list(dev_net(dev));
|
||||
caifd = caif_device_alloc(dev);
|
||||
if (!caifd)
|
||||
return -ENOMEM;
|
||||
*layer = &caifd->layer;
|
||||
spin_lock_init(&caifd->flow_lock);
|
||||
|
||||
switch (caifdev->link_select) {
|
||||
case CAIF_LINK_HIGH_BANDW:
|
||||
pref = CFPHYPREF_HIGH_BW;
|
||||
break;
|
||||
case CAIF_LINK_LOW_LATENCY:
|
||||
pref = CFPHYPREF_LOW_LAT;
|
||||
break;
|
||||
default:
|
||||
pref = CFPHYPREF_HIGH_BW;
|
||||
break;
|
||||
}
|
||||
mutex_lock(&caifdevs->lock);
|
||||
list_add_rcu(&caifd->list, &caifdevs->list);
|
||||
|
||||
strscpy(caifd->layer.name, dev->name,
|
||||
sizeof(caifd->layer.name));
|
||||
caifd->layer.transmit = transmit;
|
||||
res = cfcnfg_add_phy_layer(cfg,
|
||||
dev,
|
||||
&caifd->layer,
|
||||
pref,
|
||||
link_support,
|
||||
caifdev->use_fcs,
|
||||
head_room);
|
||||
mutex_unlock(&caifdevs->lock);
|
||||
if (rcv_func)
|
||||
*rcv_func = receive;
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL(caif_enroll_dev);
|
||||
|
||||
/* notify Caif of device events */
|
||||
static int caif_device_notify(struct notifier_block *me, unsigned long what,
|
||||
void *ptr)
|
||||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
struct caif_device_entry *caifd = NULL;
|
||||
struct caif_dev_common *caifdev;
|
||||
struct cfcnfg *cfg;
|
||||
struct cflayer *layer, *link_support;
|
||||
int head_room = 0;
|
||||
struct caif_device_entry_list *caifdevs;
|
||||
int res;
|
||||
|
||||
cfg = get_cfcnfg(dev_net(dev));
|
||||
caifdevs = caif_device_list(dev_net(dev));
|
||||
|
||||
caifd = caif_get(dev);
|
||||
if (caifd == NULL && dev->type != ARPHRD_CAIF)
|
||||
return 0;
|
||||
|
||||
switch (what) {
|
||||
case NETDEV_REGISTER:
|
||||
if (caifd != NULL)
|
||||
break;
|
||||
|
||||
caifdev = netdev_priv(dev);
|
||||
|
||||
link_support = NULL;
|
||||
if (caifdev->use_frag) {
|
||||
head_room = 1;
|
||||
link_support = cfserl_create(dev->ifindex,
|
||||
caifdev->use_stx);
|
||||
if (!link_support) {
|
||||
pr_warn("Out of memory\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
res = caif_enroll_dev(dev, caifdev, link_support, head_room,
|
||||
&layer, NULL);
|
||||
if (res)
|
||||
cfserl_release(link_support);
|
||||
caifdev->flowctrl = dev_flowctrl;
|
||||
break;
|
||||
|
||||
case NETDEV_UP:
|
||||
rcu_read_lock();
|
||||
|
||||
caifd = caif_get(dev);
|
||||
if (caifd == NULL) {
|
||||
rcu_read_unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
caifd->xoff = false;
|
||||
cfcnfg_set_phy_state(cfg, &caifd->layer, true);
|
||||
rcu_read_unlock();
|
||||
|
||||
break;
|
||||
|
||||
case NETDEV_DOWN:
|
||||
rcu_read_lock();
|
||||
|
||||
caifd = caif_get(dev);
|
||||
if (!caifd || !caifd->layer.up || !caifd->layer.up->ctrlcmd) {
|
||||
rcu_read_unlock();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cfcnfg_set_phy_state(cfg, &caifd->layer, false);
|
||||
caifd_hold(caifd);
|
||||
rcu_read_unlock();
|
||||
|
||||
caifd->layer.up->ctrlcmd(caifd->layer.up,
|
||||
_CAIF_CTRLCMD_PHYIF_DOWN_IND,
|
||||
caifd->layer.id);
|
||||
|
||||
spin_lock_bh(&caifd->flow_lock);
|
||||
|
||||
/*
|
||||
* Replace our xoff-destructor with original destructor.
|
||||
* We trust that skb->destructor *always* is called before
|
||||
* the skb reference is invalid. The hijacked SKB destructor
|
||||
* takes the flow_lock so manipulating the skb->destructor here
|
||||
* should be safe.
|
||||
*/
|
||||
if (caifd->xoff_skb_dtor != NULL && caifd->xoff_skb != NULL)
|
||||
caifd->xoff_skb->destructor = caifd->xoff_skb_dtor;
|
||||
|
||||
caifd->xoff = false;
|
||||
caifd->xoff_skb_dtor = NULL;
|
||||
caifd->xoff_skb = NULL;
|
||||
|
||||
spin_unlock_bh(&caifd->flow_lock);
|
||||
caifd_put(caifd);
|
||||
break;
|
||||
|
||||
case NETDEV_UNREGISTER:
|
||||
mutex_lock(&caifdevs->lock);
|
||||
|
||||
caifd = caif_get(dev);
|
||||
if (caifd == NULL) {
|
||||
mutex_unlock(&caifdevs->lock);
|
||||
break;
|
||||
}
|
||||
list_del_rcu(&caifd->list);
|
||||
|
||||
/*
|
||||
* NETDEV_UNREGISTER is called repeatedly until all reference
|
||||
* counts for the net-device are released. If references to
|
||||
* caifd is taken, simply ignore NETDEV_UNREGISTER and wait for
|
||||
* the next call to NETDEV_UNREGISTER.
|
||||
*
|
||||
* If any packets are in flight down the CAIF Stack,
|
||||
* cfcnfg_del_phy_layer will return nonzero.
|
||||
* If no packets are in flight, the CAIF Stack associated
|
||||
* with the net-device un-registering is freed.
|
||||
*/
|
||||
|
||||
if (caifd_refcnt_read(caifd) != 0 ||
|
||||
cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0) {
|
||||
|
||||
pr_info("Wait for device inuse\n");
|
||||
/* Enrole device if CAIF Stack is still in use */
|
||||
list_add_rcu(&caifd->list, &caifdevs->list);
|
||||
mutex_unlock(&caifdevs->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
synchronize_rcu();
|
||||
dev_put(caifd->netdev);
|
||||
free_percpu(caifd->pcpu_refcnt);
|
||||
kfree(caifd);
|
||||
|
||||
mutex_unlock(&caifdevs->lock);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block caif_device_notifier = {
|
||||
.notifier_call = caif_device_notify,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
/* Per-namespace Caif devices handling */
|
||||
static int caif_init_net(struct net *net)
|
||||
{
|
||||
struct caif_net *caifn = net_generic(net, caif_net_id);
|
||||
INIT_LIST_HEAD(&caifn->caifdevs.list);
|
||||
mutex_init(&caifn->caifdevs.lock);
|
||||
|
||||
caifn->cfg = cfcnfg_create();
|
||||
if (!caifn->cfg)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void caif_exit_net(struct net *net)
|
||||
{
|
||||
struct caif_device_entry *caifd, *tmp;
|
||||
struct caif_device_entry_list *caifdevs =
|
||||
caif_device_list(net);
|
||||
struct cfcnfg *cfg = get_cfcnfg(net);
|
||||
|
||||
rtnl_lock();
|
||||
mutex_lock(&caifdevs->lock);
|
||||
|
||||
list_for_each_entry_safe(caifd, tmp, &caifdevs->list, list) {
|
||||
int i = 0;
|
||||
list_del_rcu(&caifd->list);
|
||||
cfcnfg_set_phy_state(cfg, &caifd->layer, false);
|
||||
|
||||
while (i < 10 &&
|
||||
(caifd_refcnt_read(caifd) != 0 ||
|
||||
cfcnfg_del_phy_layer(cfg, &caifd->layer) != 0)) {
|
||||
|
||||
pr_info("Wait for device inuse\n");
|
||||
msleep(250);
|
||||
i++;
|
||||
}
|
||||
synchronize_rcu();
|
||||
dev_put(caifd->netdev);
|
||||
free_percpu(caifd->pcpu_refcnt);
|
||||
kfree(caifd);
|
||||
}
|
||||
cfcnfg_remove(cfg);
|
||||
|
||||
mutex_unlock(&caifdevs->lock);
|
||||
rtnl_unlock();
|
||||
}
|
||||
|
||||
static struct pernet_operations caif_net_ops = {
|
||||
.init = caif_init_net,
|
||||
.exit = caif_exit_net,
|
||||
.id = &caif_net_id,
|
||||
.size = sizeof(struct caif_net),
|
||||
};
|
||||
|
||||
/* Initialize Caif devices list */
|
||||
static int __init caif_device_init(void)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = register_pernet_subsys(&caif_net_ops);
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
register_netdevice_notifier(&caif_device_notifier);
|
||||
dev_add_pack(&caif_packet_type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void __exit caif_device_exit(void)
|
||||
{
|
||||
unregister_netdevice_notifier(&caif_device_notifier);
|
||||
dev_remove_pack(&caif_packet_type);
|
||||
unregister_pernet_subsys(&caif_net_ops);
|
||||
}
|
||||
|
||||
module_init(caif_device_init);
|
||||
module_exit(caif_device_exit);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,216 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* CAIF USB handler
|
||||
* Copyright (C) ST-Ericsson AB 2011
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mii.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/usbnet.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <net/netns/generic.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfcnfg.h>
|
||||
|
||||
MODULE_DESCRIPTION("ST-Ericsson CAIF modem protocol USB support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define CFUSB_PAD_DESCR_SZ 1 /* Alignment descriptor length */
|
||||
#define CFUSB_ALIGNMENT 4 /* Number of bytes to align. */
|
||||
#define CFUSB_MAX_HEADLEN (CFUSB_PAD_DESCR_SZ + CFUSB_ALIGNMENT-1)
|
||||
#define STE_USB_VID 0x04cc /* USB Product ID for ST-Ericsson */
|
||||
#define STE_USB_PID_CAIF 0x230f /* Product id for CAIF Modems */
|
||||
|
||||
struct cfusbl {
|
||||
struct cflayer layer;
|
||||
u8 tx_eth_hdr[ETH_HLEN];
|
||||
};
|
||||
|
||||
static bool pack_added;
|
||||
|
||||
static int cfusbl_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 hpad;
|
||||
|
||||
/* Remove padding. */
|
||||
cfpkt_extr_head(pkt, &hpad, 1);
|
||||
cfpkt_extr_head(pkt, NULL, hpad);
|
||||
return layr->up->receive(layr->up, pkt);
|
||||
}
|
||||
|
||||
static int cfusbl_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
struct caif_payload_info *info;
|
||||
u8 hpad;
|
||||
u8 zeros[CFUSB_ALIGNMENT];
|
||||
struct sk_buff *skb;
|
||||
struct cfusbl *usbl = container_of(layr, struct cfusbl, layer);
|
||||
|
||||
skb = cfpkt_tonative(pkt);
|
||||
|
||||
skb_reset_network_header(skb);
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
|
||||
info = cfpkt_info(pkt);
|
||||
hpad = (info->hdr_len + CFUSB_PAD_DESCR_SZ) & (CFUSB_ALIGNMENT - 1);
|
||||
|
||||
if (skb_headroom(skb) < ETH_HLEN + CFUSB_PAD_DESCR_SZ + hpad) {
|
||||
pr_warn("Headroom too small\n");
|
||||
kfree_skb(skb);
|
||||
return -EIO;
|
||||
}
|
||||
memset(zeros, 0, hpad);
|
||||
|
||||
cfpkt_add_head(pkt, zeros, hpad);
|
||||
cfpkt_add_head(pkt, &hpad, 1);
|
||||
cfpkt_add_head(pkt, usbl->tx_eth_hdr, sizeof(usbl->tx_eth_hdr));
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
||||
static void cfusbl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
if (layr->up && layr->up->ctrlcmd)
|
||||
layr->up->ctrlcmd(layr->up, ctrl, layr->id);
|
||||
}
|
||||
|
||||
static struct cflayer *cfusbl_create(int phyid, const u8 ethaddr[ETH_ALEN],
|
||||
u8 braddr[ETH_ALEN])
|
||||
{
|
||||
struct cfusbl *this = kmalloc_obj(struct cfusbl, GFP_ATOMIC);
|
||||
|
||||
if (!this)
|
||||
return NULL;
|
||||
|
||||
caif_assert(offsetof(struct cfusbl, layer) == 0);
|
||||
|
||||
memset(&this->layer, 0, sizeof(this->layer));
|
||||
this->layer.receive = cfusbl_receive;
|
||||
this->layer.transmit = cfusbl_transmit;
|
||||
this->layer.ctrlcmd = cfusbl_ctrlcmd;
|
||||
snprintf(this->layer.name, CAIF_LAYER_NAME_SZ, "usb%d", phyid);
|
||||
this->layer.id = phyid;
|
||||
|
||||
/*
|
||||
* Construct TX ethernet header:
|
||||
* 0-5 destination address
|
||||
* 5-11 source address
|
||||
* 12-13 protocol type
|
||||
*/
|
||||
ether_addr_copy(&this->tx_eth_hdr[ETH_ALEN], braddr);
|
||||
ether_addr_copy(&this->tx_eth_hdr[ETH_ALEN], ethaddr);
|
||||
this->tx_eth_hdr[12] = cpu_to_be16(ETH_P_802_EX1) & 0xff;
|
||||
this->tx_eth_hdr[13] = (cpu_to_be16(ETH_P_802_EX1) >> 8) & 0xff;
|
||||
pr_debug("caif ethernet TX-header dst:%pM src:%pM type:%02x%02x\n",
|
||||
this->tx_eth_hdr, this->tx_eth_hdr + ETH_ALEN,
|
||||
this->tx_eth_hdr[12], this->tx_eth_hdr[13]);
|
||||
|
||||
return (struct cflayer *) this;
|
||||
}
|
||||
|
||||
static void cfusbl_release(struct cflayer *layer)
|
||||
{
|
||||
kfree(layer);
|
||||
}
|
||||
|
||||
static struct packet_type caif_usb_type __read_mostly = {
|
||||
.type = cpu_to_be16(ETH_P_802_EX1),
|
||||
};
|
||||
|
||||
static int cfusbl_device_notify(struct notifier_block *me, unsigned long what,
|
||||
void *ptr)
|
||||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
struct caif_dev_common common;
|
||||
struct cflayer *layer, *link_support;
|
||||
struct usbnet *usbnet;
|
||||
struct usb_device *usbdev;
|
||||
int res;
|
||||
|
||||
if (what == NETDEV_UNREGISTER && dev->reg_state >= NETREG_UNREGISTERED)
|
||||
return 0;
|
||||
|
||||
/* Check whether we have a NCM device, and find its VID/PID. */
|
||||
if (!(dev->dev.parent && dev->dev.parent->driver &&
|
||||
strcmp(dev->dev.parent->driver->name, "cdc_ncm") == 0))
|
||||
return 0;
|
||||
|
||||
usbnet = netdev_priv(dev);
|
||||
usbdev = usbnet->udev;
|
||||
|
||||
pr_debug("USB CDC NCM device VID:0x%4x PID:0x%4x\n",
|
||||
le16_to_cpu(usbdev->descriptor.idVendor),
|
||||
le16_to_cpu(usbdev->descriptor.idProduct));
|
||||
|
||||
/* Check for VID/PID that supports CAIF */
|
||||
if (!(le16_to_cpu(usbdev->descriptor.idVendor) == STE_USB_VID &&
|
||||
le16_to_cpu(usbdev->descriptor.idProduct) == STE_USB_PID_CAIF))
|
||||
return 0;
|
||||
|
||||
if (what == NETDEV_UNREGISTER)
|
||||
module_put(THIS_MODULE);
|
||||
|
||||
if (what != NETDEV_REGISTER)
|
||||
return 0;
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
|
||||
memset(&common, 0, sizeof(common));
|
||||
common.use_frag = false;
|
||||
common.use_fcs = false;
|
||||
common.use_stx = false;
|
||||
common.link_select = CAIF_LINK_HIGH_BANDW;
|
||||
common.flowctrl = NULL;
|
||||
|
||||
link_support = cfusbl_create(dev->ifindex, dev->dev_addr,
|
||||
dev->broadcast);
|
||||
|
||||
if (!link_support)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dev->num_tx_queues > 1)
|
||||
pr_warn("USB device uses more than one tx queue\n");
|
||||
|
||||
res = caif_enroll_dev(dev, &common, link_support, CFUSB_MAX_HEADLEN,
|
||||
&layer, &caif_usb_type.func);
|
||||
if (res)
|
||||
goto err;
|
||||
|
||||
if (!pack_added)
|
||||
dev_add_pack(&caif_usb_type);
|
||||
pack_added = true;
|
||||
|
||||
strscpy(layer->name, dev->name, sizeof(layer->name));
|
||||
|
||||
return 0;
|
||||
err:
|
||||
cfusbl_release(link_support);
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct notifier_block caif_device_notifier = {
|
||||
.notifier_call = cfusbl_device_notify,
|
||||
.priority = 0,
|
||||
};
|
||||
|
||||
static int __init cfusbl_init(void)
|
||||
{
|
||||
return register_netdevice_notifier(&caif_device_notifier);
|
||||
}
|
||||
|
||||
static void __exit cfusbl_exit(void)
|
||||
{
|
||||
unregister_netdevice_notifier(&caif_device_notifier);
|
||||
dev_remove_pack(&caif_usb_type);
|
||||
}
|
||||
|
||||
module_init(cfusbl_init);
|
||||
module_exit(cfusbl_exit);
|
||||
|
|
@ -1,612 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfcnfg.h>
|
||||
#include <net/caif/cfctrl.h>
|
||||
#include <net/caif/cfmuxl.h>
|
||||
#include <net/caif/cffrml.h>
|
||||
#include <net/caif/cfserl.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfcnfg, layer)
|
||||
|
||||
/* Information about CAIF physical interfaces held by Config Module in order
|
||||
* to manage physical interfaces
|
||||
*/
|
||||
struct cfcnfg_phyinfo {
|
||||
struct list_head node;
|
||||
bool up;
|
||||
|
||||
/* Pointer to the layer below the MUX (framing layer) */
|
||||
struct cflayer *frm_layer;
|
||||
/* Pointer to the lowest actual physical layer */
|
||||
struct cflayer *phy_layer;
|
||||
/* Unique identifier of the physical interface */
|
||||
unsigned int id;
|
||||
/* Preference of the physical in interface */
|
||||
enum cfcnfg_phy_preference pref;
|
||||
|
||||
/* Information about the physical device */
|
||||
struct dev_info dev_info;
|
||||
|
||||
/* Interface index */
|
||||
int ifindex;
|
||||
|
||||
/* Protocol head room added for CAIF link layer */
|
||||
int head_room;
|
||||
|
||||
/* Use Start of frame checksum */
|
||||
bool use_fcs;
|
||||
};
|
||||
|
||||
struct cfcnfg {
|
||||
struct cflayer layer;
|
||||
struct cflayer *ctrl;
|
||||
struct cflayer *mux;
|
||||
struct list_head phys;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static void cfcnfg_linkup_rsp(struct cflayer *layer, u8 channel_id,
|
||||
enum cfctrl_srv serv, u8 phyid,
|
||||
struct cflayer *adapt_layer);
|
||||
static void cfcnfg_linkdestroy_rsp(struct cflayer *layer, u8 channel_id);
|
||||
static void cfcnfg_reject_rsp(struct cflayer *layer, u8 channel_id,
|
||||
struct cflayer *adapt_layer);
|
||||
static void cfctrl_resp_func(void);
|
||||
static void cfctrl_enum_resp(void);
|
||||
|
||||
struct cfcnfg *cfcnfg_create(void)
|
||||
{
|
||||
struct cfcnfg *this;
|
||||
struct cfctrl_rsp *resp;
|
||||
|
||||
might_sleep();
|
||||
|
||||
/* Initiate this layer */
|
||||
this = kzalloc_obj(struct cfcnfg, GFP_ATOMIC);
|
||||
if (!this)
|
||||
return NULL;
|
||||
this->mux = cfmuxl_create();
|
||||
if (!this->mux)
|
||||
goto out_of_mem;
|
||||
this->ctrl = cfctrl_create();
|
||||
if (!this->ctrl)
|
||||
goto out_of_mem;
|
||||
/* Initiate response functions */
|
||||
resp = cfctrl_get_respfuncs(this->ctrl);
|
||||
resp->enum_rsp = cfctrl_enum_resp;
|
||||
resp->linkerror_ind = cfctrl_resp_func;
|
||||
resp->linkdestroy_rsp = cfcnfg_linkdestroy_rsp;
|
||||
resp->sleep_rsp = cfctrl_resp_func;
|
||||
resp->wake_rsp = cfctrl_resp_func;
|
||||
resp->restart_rsp = cfctrl_resp_func;
|
||||
resp->radioset_rsp = cfctrl_resp_func;
|
||||
resp->linksetup_rsp = cfcnfg_linkup_rsp;
|
||||
resp->reject_rsp = cfcnfg_reject_rsp;
|
||||
INIT_LIST_HEAD(&this->phys);
|
||||
|
||||
cfmuxl_set_uplayer(this->mux, this->ctrl, 0);
|
||||
layer_set_dn(this->ctrl, this->mux);
|
||||
layer_set_up(this->ctrl, this);
|
||||
mutex_init(&this->lock);
|
||||
|
||||
return this;
|
||||
out_of_mem:
|
||||
synchronize_rcu();
|
||||
|
||||
kfree(this->mux);
|
||||
kfree(this->ctrl);
|
||||
kfree(this);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cfcnfg_remove(struct cfcnfg *cfg)
|
||||
{
|
||||
might_sleep();
|
||||
if (cfg) {
|
||||
synchronize_rcu();
|
||||
|
||||
kfree(cfg->mux);
|
||||
cfctrl_remove(cfg->ctrl);
|
||||
kfree(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
static void cfctrl_resp_func(void)
|
||||
{
|
||||
}
|
||||
|
||||
static struct cfcnfg_phyinfo *cfcnfg_get_phyinfo_rcu(struct cfcnfg *cnfg,
|
||||
u8 phyid)
|
||||
{
|
||||
struct cfcnfg_phyinfo *phy;
|
||||
|
||||
list_for_each_entry_rcu(phy, &cnfg->phys, node)
|
||||
if (phy->id == phyid)
|
||||
return phy;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cfctrl_enum_resp(void)
|
||||
{
|
||||
}
|
||||
|
||||
static struct dev_info *cfcnfg_get_phyid(struct cfcnfg *cnfg,
|
||||
enum cfcnfg_phy_preference phy_pref)
|
||||
{
|
||||
/* Try to match with specified preference */
|
||||
struct cfcnfg_phyinfo *phy;
|
||||
|
||||
list_for_each_entry_rcu(phy, &cnfg->phys, node) {
|
||||
if (phy->up && phy->pref == phy_pref &&
|
||||
phy->frm_layer != NULL)
|
||||
|
||||
return &phy->dev_info;
|
||||
}
|
||||
|
||||
/* Otherwise just return something */
|
||||
list_for_each_entry_rcu(phy, &cnfg->phys, node)
|
||||
if (phy->up)
|
||||
return &phy->dev_info;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int cfcnfg_get_id_from_ifi(struct cfcnfg *cnfg, int ifi)
|
||||
{
|
||||
struct cfcnfg_phyinfo *phy;
|
||||
|
||||
list_for_each_entry_rcu(phy, &cnfg->phys, node)
|
||||
if (phy->ifindex == ifi && phy->up)
|
||||
return phy->id;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int caif_disconnect_client(struct net *net, struct cflayer *adap_layer)
|
||||
{
|
||||
u8 channel_id;
|
||||
struct cfcnfg *cfg = get_cfcnfg(net);
|
||||
|
||||
caif_assert(adap_layer != NULL);
|
||||
cfctrl_cancel_req(cfg->ctrl, adap_layer);
|
||||
channel_id = adap_layer->id;
|
||||
if (channel_id != 0) {
|
||||
struct cflayer *servl;
|
||||
servl = cfmuxl_remove_uplayer(cfg->mux, channel_id);
|
||||
cfctrl_linkdown_req(cfg->ctrl, channel_id, adap_layer);
|
||||
if (servl != NULL)
|
||||
layer_set_up(servl, NULL);
|
||||
} else
|
||||
pr_debug("nothing to disconnect\n");
|
||||
|
||||
/* Do RCU sync before initiating cleanup */
|
||||
synchronize_rcu();
|
||||
if (adap_layer->ctrlcmd != NULL)
|
||||
adap_layer->ctrlcmd(adap_layer, CAIF_CTRLCMD_DEINIT_RSP, 0);
|
||||
return 0;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL(caif_disconnect_client);
|
||||
|
||||
static void cfcnfg_linkdestroy_rsp(struct cflayer *layer, u8 channel_id)
|
||||
{
|
||||
}
|
||||
|
||||
static const int protohead[CFCTRL_SRV_MASK] = {
|
||||
[CFCTRL_SRV_VEI] = 4,
|
||||
[CFCTRL_SRV_DATAGRAM] = 7,
|
||||
[CFCTRL_SRV_UTIL] = 4,
|
||||
[CFCTRL_SRV_RFM] = 3,
|
||||
[CFCTRL_SRV_DBG] = 3,
|
||||
};
|
||||
|
||||
|
||||
static int caif_connect_req_to_link_param(struct cfcnfg *cnfg,
|
||||
struct caif_connect_request *s,
|
||||
struct cfctrl_link_param *l)
|
||||
{
|
||||
struct dev_info *dev_info;
|
||||
enum cfcnfg_phy_preference pref;
|
||||
int res;
|
||||
|
||||
memset(l, 0, sizeof(*l));
|
||||
/* In caif protocol low value is high priority */
|
||||
l->priority = CAIF_PRIO_MAX - s->priority + 1;
|
||||
|
||||
if (s->ifindex != 0) {
|
||||
res = cfcnfg_get_id_from_ifi(cnfg, s->ifindex);
|
||||
if (res < 0)
|
||||
return res;
|
||||
l->phyid = res;
|
||||
} else {
|
||||
switch (s->link_selector) {
|
||||
case CAIF_LINK_HIGH_BANDW:
|
||||
pref = CFPHYPREF_HIGH_BW;
|
||||
break;
|
||||
case CAIF_LINK_LOW_LATENCY:
|
||||
pref = CFPHYPREF_LOW_LAT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_info = cfcnfg_get_phyid(cnfg, pref);
|
||||
if (dev_info == NULL)
|
||||
return -ENODEV;
|
||||
l->phyid = dev_info->id;
|
||||
}
|
||||
switch (s->protocol) {
|
||||
case CAIFPROTO_AT:
|
||||
l->linktype = CFCTRL_SRV_VEI;
|
||||
l->endpoint = (s->sockaddr.u.at.type >> 2) & 0x3;
|
||||
l->chtype = s->sockaddr.u.at.type & 0x3;
|
||||
break;
|
||||
case CAIFPROTO_DATAGRAM:
|
||||
l->linktype = CFCTRL_SRV_DATAGRAM;
|
||||
l->chtype = 0x00;
|
||||
l->u.datagram.connid = s->sockaddr.u.dgm.connection_id;
|
||||
break;
|
||||
case CAIFPROTO_DATAGRAM_LOOP:
|
||||
l->linktype = CFCTRL_SRV_DATAGRAM;
|
||||
l->chtype = 0x03;
|
||||
l->endpoint = 0x00;
|
||||
l->u.datagram.connid = s->sockaddr.u.dgm.connection_id;
|
||||
break;
|
||||
case CAIFPROTO_RFM:
|
||||
l->linktype = CFCTRL_SRV_RFM;
|
||||
l->u.datagram.connid = s->sockaddr.u.rfm.connection_id;
|
||||
strscpy(l->u.rfm.volume, s->sockaddr.u.rfm.volume,
|
||||
sizeof(l->u.rfm.volume));
|
||||
break;
|
||||
case CAIFPROTO_UTIL:
|
||||
l->linktype = CFCTRL_SRV_UTIL;
|
||||
l->endpoint = 0x00;
|
||||
l->chtype = 0x00;
|
||||
strscpy(l->u.utility.name, s->sockaddr.u.util.service,
|
||||
sizeof(l->u.utility.name));
|
||||
caif_assert(sizeof(l->u.utility.name) > 10);
|
||||
l->u.utility.paramlen = s->param.size;
|
||||
if (l->u.utility.paramlen > sizeof(l->u.utility.params))
|
||||
l->u.utility.paramlen = sizeof(l->u.utility.params);
|
||||
|
||||
memcpy(l->u.utility.params, s->param.data,
|
||||
l->u.utility.paramlen);
|
||||
|
||||
break;
|
||||
case CAIFPROTO_DEBUG:
|
||||
l->linktype = CFCTRL_SRV_DBG;
|
||||
l->endpoint = s->sockaddr.u.dbg.service;
|
||||
l->chtype = s->sockaddr.u.dbg.type;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int caif_connect_client(struct net *net, struct caif_connect_request *conn_req,
|
||||
struct cflayer *adap_layer, int *ifindex,
|
||||
int *proto_head, int *proto_tail)
|
||||
{
|
||||
struct cflayer *frml;
|
||||
struct cfcnfg_phyinfo *phy;
|
||||
int err;
|
||||
struct cfctrl_link_param param;
|
||||
struct cfcnfg *cfg = get_cfcnfg(net);
|
||||
|
||||
rcu_read_lock();
|
||||
err = caif_connect_req_to_link_param(cfg, conn_req, ¶m);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
phy = cfcnfg_get_phyinfo_rcu(cfg, param.phyid);
|
||||
if (!phy) {
|
||||
err = -ENODEV;
|
||||
goto unlock;
|
||||
}
|
||||
err = -EINVAL;
|
||||
|
||||
if (adap_layer == NULL) {
|
||||
pr_err("adap_layer is zero\n");
|
||||
goto unlock;
|
||||
}
|
||||
if (adap_layer->receive == NULL) {
|
||||
pr_err("adap_layer->receive is NULL\n");
|
||||
goto unlock;
|
||||
}
|
||||
if (adap_layer->ctrlcmd == NULL) {
|
||||
pr_err("adap_layer->ctrlcmd == NULL\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
err = -ENODEV;
|
||||
frml = phy->frm_layer;
|
||||
if (frml == NULL) {
|
||||
pr_err("Specified PHY type does not exist!\n");
|
||||
goto unlock;
|
||||
}
|
||||
caif_assert(param.phyid == phy->id);
|
||||
caif_assert(phy->frm_layer->id ==
|
||||
param.phyid);
|
||||
caif_assert(phy->phy_layer->id ==
|
||||
param.phyid);
|
||||
|
||||
*ifindex = phy->ifindex;
|
||||
*proto_tail = 2;
|
||||
*proto_head = protohead[param.linktype] + phy->head_room;
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
/* FIXME: ENUMERATE INITIALLY WHEN ACTIVATING PHYSICAL INTERFACE */
|
||||
cfctrl_enum_req(cfg->ctrl, param.phyid);
|
||||
return cfctrl_linkup_request(cfg->ctrl, ¶m, adap_layer);
|
||||
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(caif_connect_client);
|
||||
|
||||
static void cfcnfg_reject_rsp(struct cflayer *layer, u8 channel_id,
|
||||
struct cflayer *adapt_layer)
|
||||
{
|
||||
if (adapt_layer != NULL && adapt_layer->ctrlcmd != NULL)
|
||||
adapt_layer->ctrlcmd(adapt_layer,
|
||||
CAIF_CTRLCMD_INIT_FAIL_RSP, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
cfcnfg_linkup_rsp(struct cflayer *layer, u8 channel_id, enum cfctrl_srv serv,
|
||||
u8 phyid, struct cflayer *adapt_layer)
|
||||
{
|
||||
struct cfcnfg *cnfg = container_obj(layer);
|
||||
struct cflayer *servicel = NULL;
|
||||
struct cfcnfg_phyinfo *phyinfo;
|
||||
struct net_device *netdev;
|
||||
|
||||
if (channel_id == 0) {
|
||||
pr_warn("received channel_id zero\n");
|
||||
if (adapt_layer != NULL && adapt_layer->ctrlcmd != NULL)
|
||||
adapt_layer->ctrlcmd(adapt_layer,
|
||||
CAIF_CTRLCMD_INIT_FAIL_RSP, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
if (adapt_layer == NULL) {
|
||||
pr_debug("link setup response but no client exist, send linkdown back\n");
|
||||
cfctrl_linkdown_req(cnfg->ctrl, channel_id, NULL);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
caif_assert(cnfg != NULL);
|
||||
caif_assert(phyid != 0);
|
||||
|
||||
phyinfo = cfcnfg_get_phyinfo_rcu(cnfg, phyid);
|
||||
if (phyinfo == NULL) {
|
||||
pr_err("ERROR: Link Layer Device disappeared while connecting\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
caif_assert(phyinfo != NULL);
|
||||
caif_assert(phyinfo->id == phyid);
|
||||
caif_assert(phyinfo->phy_layer != NULL);
|
||||
caif_assert(phyinfo->phy_layer->id == phyid);
|
||||
|
||||
adapt_layer->id = channel_id;
|
||||
|
||||
switch (serv) {
|
||||
case CFCTRL_SRV_VEI:
|
||||
servicel = cfvei_create(channel_id, &phyinfo->dev_info);
|
||||
break;
|
||||
case CFCTRL_SRV_DATAGRAM:
|
||||
servicel = cfdgml_create(channel_id,
|
||||
&phyinfo->dev_info);
|
||||
break;
|
||||
case CFCTRL_SRV_RFM:
|
||||
netdev = phyinfo->dev_info.dev;
|
||||
servicel = cfrfml_create(channel_id, &phyinfo->dev_info,
|
||||
netdev->mtu);
|
||||
break;
|
||||
case CFCTRL_SRV_UTIL:
|
||||
servicel = cfutill_create(channel_id, &phyinfo->dev_info);
|
||||
break;
|
||||
case CFCTRL_SRV_VIDEO:
|
||||
servicel = cfvidl_create(channel_id, &phyinfo->dev_info);
|
||||
break;
|
||||
case CFCTRL_SRV_DBG:
|
||||
servicel = cfdbgl_create(channel_id, &phyinfo->dev_info);
|
||||
break;
|
||||
default:
|
||||
pr_err("Protocol error. Link setup response - unknown channel type\n");
|
||||
goto unlock;
|
||||
}
|
||||
if (!servicel)
|
||||
goto unlock;
|
||||
layer_set_dn(servicel, cnfg->mux);
|
||||
cfmuxl_set_uplayer(cnfg->mux, servicel, channel_id);
|
||||
layer_set_up(servicel, adapt_layer);
|
||||
layer_set_dn(adapt_layer, servicel);
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
servicel->ctrlcmd(servicel, CAIF_CTRLCMD_INIT_RSP, 0);
|
||||
return;
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
int
|
||||
cfcnfg_add_phy_layer(struct cfcnfg *cnfg,
|
||||
struct net_device *dev, struct cflayer *phy_layer,
|
||||
enum cfcnfg_phy_preference pref,
|
||||
struct cflayer *link_support,
|
||||
bool fcs, int head_room)
|
||||
{
|
||||
struct cflayer *frml;
|
||||
struct cfcnfg_phyinfo *phyinfo = NULL;
|
||||
int i, res = 0;
|
||||
u8 phyid;
|
||||
|
||||
mutex_lock(&cnfg->lock);
|
||||
|
||||
/* CAIF protocol allow maximum 6 link-layers */
|
||||
for (i = 0; i < 7; i++) {
|
||||
phyid = (dev->ifindex + i) & 0x7;
|
||||
if (phyid == 0)
|
||||
continue;
|
||||
if (cfcnfg_get_phyinfo_rcu(cnfg, phyid) == NULL)
|
||||
goto got_phyid;
|
||||
}
|
||||
pr_warn("Too many CAIF Link Layers (max 6)\n");
|
||||
res = -EEXIST;
|
||||
goto out;
|
||||
|
||||
got_phyid:
|
||||
phyinfo = kzalloc_obj(struct cfcnfg_phyinfo, GFP_ATOMIC);
|
||||
if (!phyinfo) {
|
||||
res = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
phy_layer->id = phyid;
|
||||
phyinfo->pref = pref;
|
||||
phyinfo->id = phyid;
|
||||
phyinfo->dev_info.id = phyid;
|
||||
phyinfo->dev_info.dev = dev;
|
||||
phyinfo->phy_layer = phy_layer;
|
||||
phyinfo->ifindex = dev->ifindex;
|
||||
phyinfo->head_room = head_room;
|
||||
phyinfo->use_fcs = fcs;
|
||||
|
||||
frml = cffrml_create(phyid, fcs);
|
||||
|
||||
if (!frml) {
|
||||
res = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
phyinfo->frm_layer = frml;
|
||||
layer_set_up(frml, cnfg->mux);
|
||||
|
||||
if (link_support != NULL) {
|
||||
link_support->id = phyid;
|
||||
layer_set_dn(frml, link_support);
|
||||
layer_set_up(link_support, frml);
|
||||
layer_set_dn(link_support, phy_layer);
|
||||
layer_set_up(phy_layer, link_support);
|
||||
} else {
|
||||
layer_set_dn(frml, phy_layer);
|
||||
layer_set_up(phy_layer, frml);
|
||||
}
|
||||
|
||||
list_add_rcu(&phyinfo->node, &cnfg->phys);
|
||||
out:
|
||||
mutex_unlock(&cnfg->lock);
|
||||
return res;
|
||||
|
||||
out_err:
|
||||
kfree(phyinfo);
|
||||
mutex_unlock(&cnfg->lock);
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL(cfcnfg_add_phy_layer);
|
||||
|
||||
int cfcnfg_set_phy_state(struct cfcnfg *cnfg, struct cflayer *phy_layer,
|
||||
bool up)
|
||||
{
|
||||
struct cfcnfg_phyinfo *phyinfo;
|
||||
|
||||
rcu_read_lock();
|
||||
phyinfo = cfcnfg_get_phyinfo_rcu(cnfg, phy_layer->id);
|
||||
if (phyinfo == NULL) {
|
||||
rcu_read_unlock();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (phyinfo->up == up) {
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
||||
phyinfo->up = up;
|
||||
|
||||
if (up) {
|
||||
cffrml_hold(phyinfo->frm_layer);
|
||||
cfmuxl_set_dnlayer(cnfg->mux, phyinfo->frm_layer,
|
||||
phy_layer->id);
|
||||
} else {
|
||||
cfmuxl_remove_dnlayer(cnfg->mux, phy_layer->id);
|
||||
cffrml_put(phyinfo->frm_layer);
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cfcnfg_set_phy_state);
|
||||
|
||||
int cfcnfg_del_phy_layer(struct cfcnfg *cnfg, struct cflayer *phy_layer)
|
||||
{
|
||||
struct cflayer *frml, *frml_dn;
|
||||
u16 phyid;
|
||||
struct cfcnfg_phyinfo *phyinfo;
|
||||
|
||||
might_sleep();
|
||||
|
||||
mutex_lock(&cnfg->lock);
|
||||
|
||||
phyid = phy_layer->id;
|
||||
phyinfo = cfcnfg_get_phyinfo_rcu(cnfg, phyid);
|
||||
|
||||
if (phyinfo == NULL) {
|
||||
mutex_unlock(&cnfg->lock);
|
||||
return 0;
|
||||
}
|
||||
caif_assert(phyid == phyinfo->id);
|
||||
caif_assert(phy_layer == phyinfo->phy_layer);
|
||||
caif_assert(phy_layer->id == phyid);
|
||||
caif_assert(phyinfo->frm_layer->id == phyid);
|
||||
|
||||
list_del_rcu(&phyinfo->node);
|
||||
synchronize_rcu();
|
||||
|
||||
/* Fail if reference count is not zero */
|
||||
if (cffrml_refcnt_read(phyinfo->frm_layer) != 0) {
|
||||
pr_info("Wait for device inuse\n");
|
||||
list_add_rcu(&phyinfo->node, &cnfg->phys);
|
||||
mutex_unlock(&cnfg->lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
frml = phyinfo->frm_layer;
|
||||
frml_dn = frml->dn;
|
||||
cffrml_set_uplayer(frml, NULL);
|
||||
cffrml_set_dnlayer(frml, NULL);
|
||||
if (phy_layer != frml_dn) {
|
||||
layer_set_up(frml_dn, NULL);
|
||||
layer_set_dn(frml_dn, NULL);
|
||||
}
|
||||
layer_set_up(phy_layer, NULL);
|
||||
|
||||
if (phyinfo->phy_layer != frml_dn)
|
||||
kfree(frml_dn);
|
||||
|
||||
cffrml_free(frml);
|
||||
kfree(phyinfo);
|
||||
mutex_unlock(&cnfg->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cfcnfg_del_phy_layer);
|
||||
|
|
@ -1,631 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pkt_sched.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfctrl.h>
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfctrl, serv.layer)
|
||||
#define UTILITY_NAME_LENGTH 16
|
||||
#define CFPKT_CTRL_PKT_LEN 20
|
||||
|
||||
#ifdef CAIF_NO_LOOP
|
||||
static int handle_loop(struct cfctrl *ctrl,
|
||||
int cmd, struct cfpkt *pkt){
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
static int handle_loop(struct cfctrl *ctrl,
|
||||
int cmd, struct cfpkt *pkt);
|
||||
#endif
|
||||
static int cfctrl_recv(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static void cfctrl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid);
|
||||
|
||||
|
||||
struct cflayer *cfctrl_create(void)
|
||||
{
|
||||
struct dev_info dev_info;
|
||||
struct cfctrl *this =
|
||||
kzalloc_obj(struct cfctrl, GFP_ATOMIC);
|
||||
if (!this)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfctrl, serv.layer) == 0);
|
||||
memset(&dev_info, 0, sizeof(dev_info));
|
||||
dev_info.id = 0xff;
|
||||
cfsrvl_init(&this->serv, 0, &dev_info, false);
|
||||
atomic_set(&this->req_seq_no, 1);
|
||||
atomic_set(&this->rsp_seq_no, 1);
|
||||
this->serv.layer.receive = cfctrl_recv;
|
||||
sprintf(this->serv.layer.name, "ctrl");
|
||||
this->serv.layer.ctrlcmd = cfctrl_ctrlcmd;
|
||||
#ifndef CAIF_NO_LOOP
|
||||
spin_lock_init(&this->loop_linkid_lock);
|
||||
this->loop_linkid = 1;
|
||||
#endif
|
||||
spin_lock_init(&this->info_list_lock);
|
||||
INIT_LIST_HEAD(&this->list);
|
||||
return &this->serv.layer;
|
||||
}
|
||||
|
||||
void cfctrl_remove(struct cflayer *layer)
|
||||
{
|
||||
struct cfctrl_request_info *p, *tmp;
|
||||
struct cfctrl *ctrl = container_obj(layer);
|
||||
|
||||
spin_lock_bh(&ctrl->info_list_lock);
|
||||
list_for_each_entry_safe(p, tmp, &ctrl->list, list) {
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
spin_unlock_bh(&ctrl->info_list_lock);
|
||||
kfree(layer);
|
||||
}
|
||||
|
||||
static bool param_eq(const struct cfctrl_link_param *p1,
|
||||
const struct cfctrl_link_param *p2)
|
||||
{
|
||||
bool eq =
|
||||
p1->linktype == p2->linktype &&
|
||||
p1->priority == p2->priority &&
|
||||
p1->phyid == p2->phyid &&
|
||||
p1->endpoint == p2->endpoint && p1->chtype == p2->chtype;
|
||||
|
||||
if (!eq)
|
||||
return false;
|
||||
|
||||
switch (p1->linktype) {
|
||||
case CFCTRL_SRV_VEI:
|
||||
return true;
|
||||
case CFCTRL_SRV_DATAGRAM:
|
||||
return p1->u.datagram.connid == p2->u.datagram.connid;
|
||||
case CFCTRL_SRV_RFM:
|
||||
return
|
||||
p1->u.rfm.connid == p2->u.rfm.connid &&
|
||||
strcmp(p1->u.rfm.volume, p2->u.rfm.volume) == 0;
|
||||
case CFCTRL_SRV_UTIL:
|
||||
return
|
||||
p1->u.utility.fifosize_kb == p2->u.utility.fifosize_kb
|
||||
&& p1->u.utility.fifosize_bufs ==
|
||||
p2->u.utility.fifosize_bufs
|
||||
&& strcmp(p1->u.utility.name, p2->u.utility.name) == 0
|
||||
&& p1->u.utility.paramlen == p2->u.utility.paramlen
|
||||
&& memcmp(p1->u.utility.params, p2->u.utility.params,
|
||||
p1->u.utility.paramlen) == 0;
|
||||
|
||||
case CFCTRL_SRV_VIDEO:
|
||||
return p1->u.video.connid == p2->u.video.connid;
|
||||
case CFCTRL_SRV_DBG:
|
||||
return true;
|
||||
case CFCTRL_SRV_DECM:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cfctrl_req_eq(const struct cfctrl_request_info *r1,
|
||||
const struct cfctrl_request_info *r2)
|
||||
{
|
||||
if (r1->cmd != r2->cmd)
|
||||
return false;
|
||||
if (r1->cmd == CFCTRL_CMD_LINK_SETUP)
|
||||
return param_eq(&r1->param, &r2->param);
|
||||
else
|
||||
return r1->channel_id == r2->channel_id;
|
||||
}
|
||||
|
||||
/* Insert request at the end */
|
||||
static void cfctrl_insert_req(struct cfctrl *ctrl,
|
||||
struct cfctrl_request_info *req)
|
||||
{
|
||||
spin_lock_bh(&ctrl->info_list_lock);
|
||||
atomic_inc(&ctrl->req_seq_no);
|
||||
req->sequence_no = atomic_read(&ctrl->req_seq_no);
|
||||
list_add_tail(&req->list, &ctrl->list);
|
||||
spin_unlock_bh(&ctrl->info_list_lock);
|
||||
}
|
||||
|
||||
/* Compare and remove request */
|
||||
static struct cfctrl_request_info *cfctrl_remove_req(struct cfctrl *ctrl,
|
||||
struct cfctrl_request_info *req)
|
||||
{
|
||||
struct cfctrl_request_info *p, *tmp, *first;
|
||||
|
||||
first = list_first_entry(&ctrl->list, struct cfctrl_request_info, list);
|
||||
|
||||
list_for_each_entry_safe(p, tmp, &ctrl->list, list) {
|
||||
if (cfctrl_req_eq(req, p)) {
|
||||
if (p != first)
|
||||
pr_warn("Requests are not received in order\n");
|
||||
|
||||
atomic_set(&ctrl->rsp_seq_no,
|
||||
p->sequence_no);
|
||||
list_del(&p->list);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
p = NULL;
|
||||
out:
|
||||
return p;
|
||||
}
|
||||
|
||||
struct cfctrl_rsp *cfctrl_get_respfuncs(struct cflayer *layer)
|
||||
{
|
||||
struct cfctrl *this = container_obj(layer);
|
||||
return &this->res;
|
||||
}
|
||||
|
||||
static void init_info(struct caif_payload_info *info, struct cfctrl *cfctrl)
|
||||
{
|
||||
info->hdr_len = 0;
|
||||
info->channel_id = cfctrl->serv.layer.id;
|
||||
info->dev_info = &cfctrl->serv.dev_info;
|
||||
}
|
||||
|
||||
void cfctrl_enum_req(struct cflayer *layer, u8 physlinkid)
|
||||
{
|
||||
struct cfpkt *pkt;
|
||||
struct cfctrl *cfctrl = container_obj(layer);
|
||||
struct cflayer *dn = cfctrl->serv.layer.dn;
|
||||
|
||||
if (!dn) {
|
||||
pr_debug("not able to send enum request\n");
|
||||
return;
|
||||
}
|
||||
pkt = cfpkt_create(CFPKT_CTRL_PKT_LEN);
|
||||
if (!pkt)
|
||||
return;
|
||||
caif_assert(offsetof(struct cfctrl, serv.layer) == 0);
|
||||
init_info(cfpkt_info(pkt), cfctrl);
|
||||
cfpkt_info(pkt)->dev_info->id = physlinkid;
|
||||
cfctrl->serv.dev_info.id = physlinkid;
|
||||
cfpkt_addbdy(pkt, CFCTRL_CMD_ENUM);
|
||||
cfpkt_addbdy(pkt, physlinkid);
|
||||
cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
|
||||
dn->transmit(dn, pkt);
|
||||
}
|
||||
|
||||
int cfctrl_linkup_request(struct cflayer *layer,
|
||||
struct cfctrl_link_param *param,
|
||||
struct cflayer *user_layer)
|
||||
{
|
||||
struct cfctrl *cfctrl = container_obj(layer);
|
||||
struct cflayer *dn = cfctrl->serv.layer.dn;
|
||||
char utility_name[UTILITY_NAME_LENGTH];
|
||||
struct cfctrl_request_info *req;
|
||||
struct cfpkt *pkt;
|
||||
u32 tmp32;
|
||||
u16 tmp16;
|
||||
u8 tmp8;
|
||||
int ret;
|
||||
|
||||
if (!dn) {
|
||||
pr_debug("not able to send linkup request\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (cfctrl_cancel_req(layer, user_layer) > 0) {
|
||||
/* Slight Paranoia, check if already connecting */
|
||||
pr_err("Duplicate connect request for same client\n");
|
||||
WARN_ON(1);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
pkt = cfpkt_create(CFPKT_CTRL_PKT_LEN);
|
||||
if (!pkt)
|
||||
return -ENOMEM;
|
||||
cfpkt_addbdy(pkt, CFCTRL_CMD_LINK_SETUP);
|
||||
cfpkt_addbdy(pkt, (param->chtype << 4) | param->linktype);
|
||||
cfpkt_addbdy(pkt, (param->priority << 3) | param->phyid);
|
||||
cfpkt_addbdy(pkt, param->endpoint & 0x03);
|
||||
|
||||
switch (param->linktype) {
|
||||
case CFCTRL_SRV_VEI:
|
||||
break;
|
||||
case CFCTRL_SRV_VIDEO:
|
||||
cfpkt_addbdy(pkt, (u8) param->u.video.connid);
|
||||
break;
|
||||
case CFCTRL_SRV_DBG:
|
||||
break;
|
||||
case CFCTRL_SRV_DATAGRAM:
|
||||
tmp32 = cpu_to_le32(param->u.datagram.connid);
|
||||
cfpkt_add_body(pkt, &tmp32, 4);
|
||||
break;
|
||||
case CFCTRL_SRV_RFM:
|
||||
/* Construct a frame, convert DatagramConnectionID to network
|
||||
* format long and copy it out...
|
||||
*/
|
||||
tmp32 = cpu_to_le32(param->u.rfm.connid);
|
||||
cfpkt_add_body(pkt, &tmp32, 4);
|
||||
/* Add volume name, including zero termination... */
|
||||
cfpkt_add_body(pkt, param->u.rfm.volume,
|
||||
strlen(param->u.rfm.volume) + 1);
|
||||
break;
|
||||
case CFCTRL_SRV_UTIL:
|
||||
tmp16 = cpu_to_le16(param->u.utility.fifosize_kb);
|
||||
cfpkt_add_body(pkt, &tmp16, 2);
|
||||
tmp16 = cpu_to_le16(param->u.utility.fifosize_bufs);
|
||||
cfpkt_add_body(pkt, &tmp16, 2);
|
||||
strscpy_pad(utility_name, param->u.utility.name);
|
||||
cfpkt_add_body(pkt, utility_name, UTILITY_NAME_LENGTH);
|
||||
tmp8 = param->u.utility.paramlen;
|
||||
cfpkt_add_body(pkt, &tmp8, 1);
|
||||
cfpkt_add_body(pkt, param->u.utility.params,
|
||||
param->u.utility.paramlen);
|
||||
break;
|
||||
default:
|
||||
pr_warn("Request setup of bad link type = %d\n",
|
||||
param->linktype);
|
||||
cfpkt_destroy(pkt);
|
||||
return -EINVAL;
|
||||
}
|
||||
req = kzalloc_obj(*req);
|
||||
if (!req) {
|
||||
cfpkt_destroy(pkt);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->client_layer = user_layer;
|
||||
req->cmd = CFCTRL_CMD_LINK_SETUP;
|
||||
req->param = *param;
|
||||
cfctrl_insert_req(cfctrl, req);
|
||||
init_info(cfpkt_info(pkt), cfctrl);
|
||||
/*
|
||||
* NOTE:Always send linkup and linkdown request on the same
|
||||
* device as the payload. Otherwise old queued up payload
|
||||
* might arrive with the newly allocated channel ID.
|
||||
*/
|
||||
cfpkt_info(pkt)->dev_info->id = param->phyid;
|
||||
cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
|
||||
ret =
|
||||
dn->transmit(dn, pkt);
|
||||
if (ret < 0) {
|
||||
int count;
|
||||
|
||||
count = cfctrl_cancel_req(&cfctrl->serv.layer,
|
||||
user_layer);
|
||||
if (count != 1) {
|
||||
pr_err("Could not remove request (%d)", count);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cfctrl_linkdown_req(struct cflayer *layer, u8 channelid,
|
||||
struct cflayer *client)
|
||||
{
|
||||
int ret;
|
||||
struct cfpkt *pkt;
|
||||
struct cfctrl *cfctrl = container_obj(layer);
|
||||
struct cflayer *dn = cfctrl->serv.layer.dn;
|
||||
|
||||
if (!dn) {
|
||||
pr_debug("not able to send link-down request\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pkt = cfpkt_create(CFPKT_CTRL_PKT_LEN);
|
||||
if (!pkt)
|
||||
return -ENOMEM;
|
||||
cfpkt_addbdy(pkt, CFCTRL_CMD_LINK_DESTROY);
|
||||
cfpkt_addbdy(pkt, channelid);
|
||||
init_info(cfpkt_info(pkt), cfctrl);
|
||||
cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
|
||||
ret =
|
||||
dn->transmit(dn, pkt);
|
||||
#ifndef CAIF_NO_LOOP
|
||||
cfctrl->loop_linkused[channelid] = 0;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cfctrl_cancel_req(struct cflayer *layr, struct cflayer *adap_layer)
|
||||
{
|
||||
struct cfctrl_request_info *p, *tmp;
|
||||
struct cfctrl *ctrl = container_obj(layr);
|
||||
int found = 0;
|
||||
spin_lock_bh(&ctrl->info_list_lock);
|
||||
|
||||
list_for_each_entry_safe(p, tmp, &ctrl->list, list) {
|
||||
if (p->client_layer == adap_layer) {
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_bh(&ctrl->info_list_lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int cfctrl_link_setup(struct cfctrl *cfctrl, struct cfpkt *pkt, u8 cmdrsp)
|
||||
{
|
||||
u8 len;
|
||||
u8 linkid = 0;
|
||||
enum cfctrl_srv serv;
|
||||
enum cfctrl_srv servtype;
|
||||
u8 endpoint;
|
||||
u8 physlinkid;
|
||||
u8 prio;
|
||||
u8 tmp;
|
||||
u8 *cp;
|
||||
int i;
|
||||
struct cfctrl_link_param linkparam;
|
||||
struct cfctrl_request_info rsp, *req;
|
||||
|
||||
memset(&linkparam, 0, sizeof(linkparam));
|
||||
|
||||
tmp = cfpkt_extr_head_u8(pkt);
|
||||
|
||||
serv = tmp & CFCTRL_SRV_MASK;
|
||||
linkparam.linktype = serv;
|
||||
|
||||
servtype = tmp >> 4;
|
||||
linkparam.chtype = servtype;
|
||||
|
||||
tmp = cfpkt_extr_head_u8(pkt);
|
||||
physlinkid = tmp & 0x07;
|
||||
prio = tmp >> 3;
|
||||
|
||||
linkparam.priority = prio;
|
||||
linkparam.phyid = physlinkid;
|
||||
endpoint = cfpkt_extr_head_u8(pkt);
|
||||
linkparam.endpoint = endpoint & 0x03;
|
||||
|
||||
switch (serv) {
|
||||
case CFCTRL_SRV_VEI:
|
||||
case CFCTRL_SRV_DBG:
|
||||
if (CFCTRL_ERR_BIT & cmdrsp)
|
||||
break;
|
||||
/* Link ID */
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
break;
|
||||
case CFCTRL_SRV_VIDEO:
|
||||
tmp = cfpkt_extr_head_u8(pkt);
|
||||
linkparam.u.video.connid = tmp;
|
||||
if (CFCTRL_ERR_BIT & cmdrsp)
|
||||
break;
|
||||
/* Link ID */
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
break;
|
||||
|
||||
case CFCTRL_SRV_DATAGRAM:
|
||||
linkparam.u.datagram.connid = cfpkt_extr_head_u32(pkt);
|
||||
if (CFCTRL_ERR_BIT & cmdrsp)
|
||||
break;
|
||||
/* Link ID */
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
break;
|
||||
case CFCTRL_SRV_RFM:
|
||||
/* Construct a frame, convert
|
||||
* DatagramConnectionID
|
||||
* to network format long and copy it out...
|
||||
*/
|
||||
linkparam.u.rfm.connid = cfpkt_extr_head_u32(pkt);
|
||||
cp = (u8 *) linkparam.u.rfm.volume;
|
||||
for (tmp = cfpkt_extr_head_u8(pkt);
|
||||
cfpkt_more(pkt) && tmp != '\0';
|
||||
tmp = cfpkt_extr_head_u8(pkt))
|
||||
*cp++ = tmp;
|
||||
*cp = '\0';
|
||||
|
||||
if (CFCTRL_ERR_BIT & cmdrsp)
|
||||
break;
|
||||
/* Link ID */
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
|
||||
break;
|
||||
case CFCTRL_SRV_UTIL:
|
||||
/* Construct a frame, convert
|
||||
* DatagramConnectionID
|
||||
* to network format long and copy it out...
|
||||
*/
|
||||
/* Fifosize KB */
|
||||
linkparam.u.utility.fifosize_kb = cfpkt_extr_head_u16(pkt);
|
||||
/* Fifosize bufs */
|
||||
linkparam.u.utility.fifosize_bufs = cfpkt_extr_head_u16(pkt);
|
||||
/* name */
|
||||
cp = (u8 *) linkparam.u.utility.name;
|
||||
caif_assert(sizeof(linkparam.u.utility.name)
|
||||
>= UTILITY_NAME_LENGTH);
|
||||
for (i = 0; i < UTILITY_NAME_LENGTH && cfpkt_more(pkt); i++) {
|
||||
tmp = cfpkt_extr_head_u8(pkt);
|
||||
*cp++ = tmp;
|
||||
}
|
||||
/* Length */
|
||||
len = cfpkt_extr_head_u8(pkt);
|
||||
linkparam.u.utility.paramlen = len;
|
||||
/* Param Data */
|
||||
cp = linkparam.u.utility.params;
|
||||
while (cfpkt_more(pkt) && len--) {
|
||||
tmp = cfpkt_extr_head_u8(pkt);
|
||||
*cp++ = tmp;
|
||||
}
|
||||
if (CFCTRL_ERR_BIT & cmdrsp)
|
||||
break;
|
||||
/* Link ID */
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
/* Length */
|
||||
len = cfpkt_extr_head_u8(pkt);
|
||||
/* Param Data */
|
||||
cfpkt_extr_head(pkt, NULL, len);
|
||||
break;
|
||||
default:
|
||||
pr_warn("Request setup, invalid type (%d)\n", serv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rsp.cmd = CFCTRL_CMD_LINK_SETUP;
|
||||
rsp.param = linkparam;
|
||||
spin_lock_bh(&cfctrl->info_list_lock);
|
||||
req = cfctrl_remove_req(cfctrl, &rsp);
|
||||
|
||||
if (CFCTRL_ERR_BIT == (CFCTRL_ERR_BIT & cmdrsp) ||
|
||||
cfpkt_erroneous(pkt)) {
|
||||
pr_err("Invalid O/E bit or parse error "
|
||||
"on CAIF control channel\n");
|
||||
cfctrl->res.reject_rsp(cfctrl->serv.layer.up, 0,
|
||||
req ? req->client_layer : NULL);
|
||||
} else {
|
||||
cfctrl->res.linksetup_rsp(cfctrl->serv.layer.up, linkid,
|
||||
serv, physlinkid,
|
||||
req ? req->client_layer : NULL);
|
||||
}
|
||||
|
||||
kfree(req);
|
||||
|
||||
spin_unlock_bh(&cfctrl->info_list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cfctrl_recv(struct cflayer *layer, struct cfpkt *pkt)
|
||||
{
|
||||
u8 cmdrsp;
|
||||
u8 cmd;
|
||||
int ret = 0;
|
||||
u8 linkid = 0;
|
||||
struct cfctrl *cfctrl = container_obj(layer);
|
||||
|
||||
cmdrsp = cfpkt_extr_head_u8(pkt);
|
||||
cmd = cmdrsp & CFCTRL_CMD_MASK;
|
||||
if (cmd != CFCTRL_CMD_LINK_ERR
|
||||
&& CFCTRL_RSP_BIT != (CFCTRL_RSP_BIT & cmdrsp)
|
||||
&& CFCTRL_ERR_BIT != (CFCTRL_ERR_BIT & cmdrsp)) {
|
||||
if (handle_loop(cfctrl, cmd, pkt) != 0)
|
||||
cmdrsp |= CFCTRL_ERR_BIT;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case CFCTRL_CMD_LINK_SETUP:
|
||||
ret = cfctrl_link_setup(cfctrl, pkt, cmdrsp);
|
||||
break;
|
||||
case CFCTRL_CMD_LINK_DESTROY:
|
||||
linkid = cfpkt_extr_head_u8(pkt);
|
||||
cfctrl->res.linkdestroy_rsp(cfctrl->serv.layer.up, linkid);
|
||||
break;
|
||||
case CFCTRL_CMD_LINK_ERR:
|
||||
pr_err("Frame Error Indication received\n");
|
||||
cfctrl->res.linkerror_ind();
|
||||
break;
|
||||
case CFCTRL_CMD_ENUM:
|
||||
cfctrl->res.enum_rsp();
|
||||
break;
|
||||
case CFCTRL_CMD_SLEEP:
|
||||
cfctrl->res.sleep_rsp();
|
||||
break;
|
||||
case CFCTRL_CMD_WAKE:
|
||||
cfctrl->res.wake_rsp();
|
||||
break;
|
||||
case CFCTRL_CMD_LINK_RECONF:
|
||||
cfctrl->res.restart_rsp();
|
||||
break;
|
||||
case CFCTRL_CMD_RADIO_SET:
|
||||
cfctrl->res.radioset_rsp();
|
||||
break;
|
||||
default:
|
||||
pr_err("Unrecognized Control Frame\n");
|
||||
ret = -1;
|
||||
goto error;
|
||||
}
|
||||
error:
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cfctrl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
struct cfctrl *this = container_obj(layr);
|
||||
switch (ctrl) {
|
||||
case _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND:
|
||||
case CAIF_CTRLCMD_FLOW_OFF_IND:
|
||||
spin_lock_bh(&this->info_list_lock);
|
||||
if (!list_empty(&this->list))
|
||||
pr_debug("Received flow off in control layer\n");
|
||||
spin_unlock_bh(&this->info_list_lock);
|
||||
break;
|
||||
case _CAIF_CTRLCMD_PHYIF_DOWN_IND: {
|
||||
struct cfctrl_request_info *p, *tmp;
|
||||
|
||||
/* Find all connect request and report failure */
|
||||
spin_lock_bh(&this->info_list_lock);
|
||||
list_for_each_entry_safe(p, tmp, &this->list, list) {
|
||||
if (p->param.phyid == phyid) {
|
||||
list_del(&p->list);
|
||||
p->client_layer->ctrlcmd(p->client_layer,
|
||||
CAIF_CTRLCMD_INIT_FAIL_RSP,
|
||||
phyid);
|
||||
kfree(p);
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&this->info_list_lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef CAIF_NO_LOOP
|
||||
static int handle_loop(struct cfctrl *ctrl, int cmd, struct cfpkt *pkt)
|
||||
{
|
||||
static int last_linkid;
|
||||
static int dec;
|
||||
u8 linkid, linktype, tmp;
|
||||
switch (cmd) {
|
||||
case CFCTRL_CMD_LINK_SETUP:
|
||||
spin_lock_bh(&ctrl->loop_linkid_lock);
|
||||
if (!dec) {
|
||||
for (linkid = last_linkid + 1; linkid < 254; linkid++)
|
||||
if (!ctrl->loop_linkused[linkid])
|
||||
goto found;
|
||||
}
|
||||
dec = 1;
|
||||
for (linkid = last_linkid - 1; linkid > 1; linkid--)
|
||||
if (!ctrl->loop_linkused[linkid])
|
||||
goto found;
|
||||
spin_unlock_bh(&ctrl->loop_linkid_lock);
|
||||
return -1;
|
||||
found:
|
||||
if (linkid < 10)
|
||||
dec = 0;
|
||||
|
||||
if (!ctrl->loop_linkused[linkid])
|
||||
ctrl->loop_linkused[linkid] = 1;
|
||||
|
||||
last_linkid = linkid;
|
||||
|
||||
cfpkt_add_trail(pkt, &linkid, 1);
|
||||
spin_unlock_bh(&ctrl->loop_linkid_lock);
|
||||
cfpkt_peek_head(pkt, &linktype, 1);
|
||||
if (linktype == CFCTRL_SRV_UTIL) {
|
||||
tmp = 0x01;
|
||||
cfpkt_add_trail(pkt, &tmp, 1);
|
||||
cfpkt_add_trail(pkt, &tmp, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case CFCTRL_CMD_LINK_DESTROY:
|
||||
spin_lock_bh(&ctrl->loop_linkid_lock);
|
||||
cfpkt_peek_head(pkt, &linkid, 1);
|
||||
ctrl->loop_linkused[linkid] = 0;
|
||||
spin_unlock_bh(&ctrl->loop_linkid_lock);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define container_obj(layr) ((struct cfsrvl *) layr)
|
||||
|
||||
static int cfdbgl_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfdbgl_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cflayer *cfdbgl_create(u8 channel_id, struct dev_info *dev_info)
|
||||
{
|
||||
struct cfsrvl *dbg = kzalloc_obj(struct cfsrvl, GFP_ATOMIC);
|
||||
if (!dbg)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
cfsrvl_init(dbg, channel_id, dev_info, false);
|
||||
dbg->layer.receive = cfdbgl_receive;
|
||||
dbg->layer.transmit = cfdbgl_transmit;
|
||||
snprintf(dbg->layer.name, CAIF_LAYER_NAME_SZ, "dbg%d", channel_id);
|
||||
return &dbg->layer;
|
||||
}
|
||||
|
||||
static int cfdbgl_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
return layr->up->receive(layr->up, pkt);
|
||||
}
|
||||
|
||||
static int cfdbgl_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
struct caif_payload_info *info;
|
||||
int ret;
|
||||
|
||||
if (!cfsrvl_ready(service, &ret)) {
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Add info for MUX-layer to route the packet out */
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
info->dev_info = &service->dev_info;
|
||||
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
|
||||
#define container_obj(layr) ((struct cfsrvl *) layr)
|
||||
|
||||
#define DGM_CMD_BIT 0x80
|
||||
#define DGM_FLOW_OFF 0x81
|
||||
#define DGM_FLOW_ON 0x80
|
||||
#define DGM_MTU 1500
|
||||
|
||||
static int cfdgml_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfdgml_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cflayer *cfdgml_create(u8 channel_id, struct dev_info *dev_info)
|
||||
{
|
||||
struct cfsrvl *dgm = kzalloc_obj(struct cfsrvl, GFP_ATOMIC);
|
||||
if (!dgm)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
cfsrvl_init(dgm, channel_id, dev_info, true);
|
||||
dgm->layer.receive = cfdgml_receive;
|
||||
dgm->layer.transmit = cfdgml_transmit;
|
||||
snprintf(dgm->layer.name, CAIF_LAYER_NAME_SZ, "dgm%d", channel_id);
|
||||
return &dgm->layer;
|
||||
}
|
||||
|
||||
static int cfdgml_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 cmd = -1;
|
||||
u8 dgmhdr[3];
|
||||
int ret;
|
||||
caif_assert(layr->up != NULL);
|
||||
caif_assert(layr->receive != NULL);
|
||||
caif_assert(layr->ctrlcmd != NULL);
|
||||
|
||||
if (cfpkt_extr_head(pkt, &cmd, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if ((cmd & DGM_CMD_BIT) == 0) {
|
||||
if (cfpkt_extr_head(pkt, &dgmhdr, 3) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
ret = layr->up->receive(layr->up, pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case DGM_FLOW_OFF: /* FLOW OFF */
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_OFF_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
case DGM_FLOW_ON: /* FLOW ON */
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_ON_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
default:
|
||||
cfpkt_destroy(pkt);
|
||||
pr_info("Unknown datagram control %d (0x%x)\n", cmd, cmd);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
static int cfdgml_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 packet_type;
|
||||
u32 zero = 0;
|
||||
struct caif_payload_info *info;
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
int ret;
|
||||
|
||||
if (!cfsrvl_ready(service, &ret)) {
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* STE Modem cannot handle more than 1500 bytes datagrams */
|
||||
if (cfpkt_getlen(pkt) > DGM_MTU) {
|
||||
cfpkt_destroy(pkt);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
cfpkt_add_head(pkt, &zero, 3);
|
||||
packet_type = 0x08; /* B9 set - UNCLASSIFIED */
|
||||
cfpkt_add_head(pkt, &packet_type, 1);
|
||||
|
||||
/* Add info for MUX-layer to route the packet out. */
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
/* To optimize alignment, we add up the size of CAIF header
|
||||
* before payload.
|
||||
*/
|
||||
info->hdr_len = 4;
|
||||
info->dev_info = &service->dev_info;
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* CAIF Framing Layer.
|
||||
*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/crc-ccitt.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cffrml.h>
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cffrml, layer)
|
||||
|
||||
struct cffrml {
|
||||
struct cflayer layer;
|
||||
bool dofcs; /* !< FCS active */
|
||||
int __percpu *pcpu_refcnt;
|
||||
};
|
||||
|
||||
static int cffrml_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cffrml_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static void cffrml_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid);
|
||||
|
||||
static u32 cffrml_rcv_error;
|
||||
static u32 cffrml_rcv_checsum_error;
|
||||
struct cflayer *cffrml_create(u16 phyid, bool use_fcs)
|
||||
{
|
||||
struct cffrml *this = kzalloc_obj(struct cffrml, GFP_ATOMIC);
|
||||
if (!this)
|
||||
return NULL;
|
||||
this->pcpu_refcnt = alloc_percpu(int);
|
||||
if (this->pcpu_refcnt == NULL) {
|
||||
kfree(this);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
caif_assert(offsetof(struct cffrml, layer) == 0);
|
||||
|
||||
this->layer.receive = cffrml_receive;
|
||||
this->layer.transmit = cffrml_transmit;
|
||||
this->layer.ctrlcmd = cffrml_ctrlcmd;
|
||||
snprintf(this->layer.name, CAIF_LAYER_NAME_SZ, "frm%d", phyid);
|
||||
this->dofcs = use_fcs;
|
||||
this->layer.id = phyid;
|
||||
return (struct cflayer *) this;
|
||||
}
|
||||
|
||||
void cffrml_free(struct cflayer *layer)
|
||||
{
|
||||
struct cffrml *this = container_obj(layer);
|
||||
free_percpu(this->pcpu_refcnt);
|
||||
kfree(layer);
|
||||
}
|
||||
|
||||
void cffrml_set_uplayer(struct cflayer *this, struct cflayer *up)
|
||||
{
|
||||
this->up = up;
|
||||
}
|
||||
|
||||
void cffrml_set_dnlayer(struct cflayer *this, struct cflayer *dn)
|
||||
{
|
||||
this->dn = dn;
|
||||
}
|
||||
|
||||
static u16 cffrml_checksum(u16 chks, void *buf, u16 len)
|
||||
{
|
||||
/* FIXME: FCS should be moved to glue in order to use OS-Specific
|
||||
* solutions
|
||||
*/
|
||||
return crc_ccitt(chks, buf, len);
|
||||
}
|
||||
|
||||
static int cffrml_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u16 tmp;
|
||||
u16 len;
|
||||
u16 hdrchks;
|
||||
int pktchks;
|
||||
struct cffrml *this;
|
||||
this = container_obj(layr);
|
||||
|
||||
cfpkt_extr_head(pkt, &tmp, 2);
|
||||
len = le16_to_cpu(tmp);
|
||||
|
||||
/* Subtract for FCS on length if FCS is not used. */
|
||||
if (!this->dofcs) {
|
||||
if (len < 2) {
|
||||
++cffrml_rcv_error;
|
||||
pr_err("Invalid frame length (%d)\n", len);
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
if (cfpkt_setlen(pkt, len) < 0) {
|
||||
++cffrml_rcv_error;
|
||||
pr_err("Framing length error (%d)\n", len);
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
/*
|
||||
* Don't do extract if FCS is false, rather do setlen - then we don't
|
||||
* get a cache-miss.
|
||||
*/
|
||||
if (this->dofcs) {
|
||||
cfpkt_extr_trail(pkt, &tmp, 2);
|
||||
hdrchks = le16_to_cpu(tmp);
|
||||
pktchks = cfpkt_iterate(pkt, cffrml_checksum, 0xffff);
|
||||
if (pktchks != hdrchks) {
|
||||
cfpkt_add_trail(pkt, &tmp, 2);
|
||||
++cffrml_rcv_error;
|
||||
++cffrml_rcv_checsum_error;
|
||||
pr_info("Frame checksum error (0x%x != 0x%x)\n",
|
||||
hdrchks, pktchks);
|
||||
return -EILSEQ;
|
||||
}
|
||||
}
|
||||
if (cfpkt_erroneous(pkt)) {
|
||||
++cffrml_rcv_error;
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (layr->up == NULL) {
|
||||
pr_err("Layr up is missing!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return layr->up->receive(layr->up, pkt);
|
||||
}
|
||||
|
||||
static int cffrml_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u16 chks;
|
||||
u16 len;
|
||||
__le16 data;
|
||||
|
||||
struct cffrml *this = container_obj(layr);
|
||||
if (this->dofcs) {
|
||||
chks = cfpkt_iterate(pkt, cffrml_checksum, 0xffff);
|
||||
data = cpu_to_le16(chks);
|
||||
cfpkt_add_trail(pkt, &data, 2);
|
||||
} else {
|
||||
cfpkt_pad_trail(pkt, 2);
|
||||
}
|
||||
len = cfpkt_getlen(pkt);
|
||||
data = cpu_to_le16(len);
|
||||
cfpkt_add_head(pkt, &data, 2);
|
||||
cfpkt_info(pkt)->hdr_len += 2;
|
||||
if (cfpkt_erroneous(pkt)) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (layr->dn == NULL) {
|
||||
cfpkt_destroy(pkt);
|
||||
return -ENODEV;
|
||||
|
||||
}
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
||||
static void cffrml_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
if (layr->up && layr->up->ctrlcmd)
|
||||
layr->up->ctrlcmd(layr->up, ctrl, layr->id);
|
||||
}
|
||||
|
||||
void cffrml_put(struct cflayer *layr)
|
||||
{
|
||||
struct cffrml *this = container_obj(layr);
|
||||
if (layr != NULL && this->pcpu_refcnt != NULL)
|
||||
this_cpu_dec(*this->pcpu_refcnt);
|
||||
}
|
||||
|
||||
void cffrml_hold(struct cflayer *layr)
|
||||
{
|
||||
struct cffrml *this = container_obj(layr);
|
||||
if (layr != NULL && this->pcpu_refcnt != NULL)
|
||||
this_cpu_inc(*this->pcpu_refcnt);
|
||||
}
|
||||
|
||||
int cffrml_refcnt_read(struct cflayer *layr)
|
||||
{
|
||||
int i, refcnt = 0;
|
||||
struct cffrml *this = container_obj(layr);
|
||||
for_each_possible_cpu(i)
|
||||
refcnt += *per_cpu_ptr(this->pcpu_refcnt, i);
|
||||
return refcnt;
|
||||
}
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfmuxl.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cffrml.h>
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfmuxl, layer)
|
||||
|
||||
#define CAIF_CTRL_CHANNEL 0
|
||||
#define UP_CACHE_SIZE 8
|
||||
#define DN_CACHE_SIZE 8
|
||||
|
||||
struct cfmuxl {
|
||||
struct cflayer layer;
|
||||
struct list_head srvl_list;
|
||||
struct list_head frml_list;
|
||||
struct cflayer *up_cache[UP_CACHE_SIZE];
|
||||
struct cflayer *dn_cache[DN_CACHE_SIZE];
|
||||
/*
|
||||
* Set when inserting or removing downwards layers.
|
||||
*/
|
||||
spinlock_t transmit_lock;
|
||||
|
||||
/*
|
||||
* Set when inserting or removing upwards layers.
|
||||
*/
|
||||
spinlock_t receive_lock;
|
||||
|
||||
};
|
||||
|
||||
static int cfmuxl_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfmuxl_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static void cfmuxl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid);
|
||||
static struct cflayer *get_up(struct cfmuxl *muxl, u16 id);
|
||||
|
||||
struct cflayer *cfmuxl_create(void)
|
||||
{
|
||||
struct cfmuxl *this = kzalloc_obj(struct cfmuxl, GFP_ATOMIC);
|
||||
|
||||
if (!this)
|
||||
return NULL;
|
||||
this->layer.receive = cfmuxl_receive;
|
||||
this->layer.transmit = cfmuxl_transmit;
|
||||
this->layer.ctrlcmd = cfmuxl_ctrlcmd;
|
||||
INIT_LIST_HEAD(&this->srvl_list);
|
||||
INIT_LIST_HEAD(&this->frml_list);
|
||||
spin_lock_init(&this->transmit_lock);
|
||||
spin_lock_init(&this->receive_lock);
|
||||
snprintf(this->layer.name, CAIF_LAYER_NAME_SZ, "mux");
|
||||
return &this->layer;
|
||||
}
|
||||
|
||||
int cfmuxl_set_dnlayer(struct cflayer *layr, struct cflayer *dn, u8 phyid)
|
||||
{
|
||||
struct cfmuxl *muxl = (struct cfmuxl *) layr;
|
||||
|
||||
spin_lock_bh(&muxl->transmit_lock);
|
||||
list_add_rcu(&dn->node, &muxl->frml_list);
|
||||
spin_unlock_bh(&muxl->transmit_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cflayer *get_from_id(struct list_head *list, u16 id)
|
||||
{
|
||||
struct cflayer *lyr;
|
||||
list_for_each_entry_rcu(lyr, list, node) {
|
||||
if (lyr->id == id)
|
||||
return lyr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cfmuxl_set_uplayer(struct cflayer *layr, struct cflayer *up, u8 linkid)
|
||||
{
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
struct cflayer *old;
|
||||
|
||||
spin_lock_bh(&muxl->receive_lock);
|
||||
|
||||
/* Two entries with same id is wrong, so remove old layer from mux */
|
||||
old = get_from_id(&muxl->srvl_list, linkid);
|
||||
if (old != NULL)
|
||||
list_del_rcu(&old->node);
|
||||
|
||||
list_add_rcu(&up->node, &muxl->srvl_list);
|
||||
spin_unlock_bh(&muxl->receive_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct cflayer *cfmuxl_remove_dnlayer(struct cflayer *layr, u8 phyid)
|
||||
{
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
struct cflayer *dn;
|
||||
int idx = phyid % DN_CACHE_SIZE;
|
||||
|
||||
spin_lock_bh(&muxl->transmit_lock);
|
||||
RCU_INIT_POINTER(muxl->dn_cache[idx], NULL);
|
||||
dn = get_from_id(&muxl->frml_list, phyid);
|
||||
if (dn == NULL)
|
||||
goto out;
|
||||
|
||||
list_del_rcu(&dn->node);
|
||||
caif_assert(dn != NULL);
|
||||
out:
|
||||
spin_unlock_bh(&muxl->transmit_lock);
|
||||
return dn;
|
||||
}
|
||||
|
||||
static struct cflayer *get_up(struct cfmuxl *muxl, u16 id)
|
||||
{
|
||||
struct cflayer *up;
|
||||
int idx = id % UP_CACHE_SIZE;
|
||||
up = rcu_dereference(muxl->up_cache[idx]);
|
||||
if (up == NULL || up->id != id) {
|
||||
spin_lock_bh(&muxl->receive_lock);
|
||||
up = get_from_id(&muxl->srvl_list, id);
|
||||
rcu_assign_pointer(muxl->up_cache[idx], up);
|
||||
spin_unlock_bh(&muxl->receive_lock);
|
||||
}
|
||||
return up;
|
||||
}
|
||||
|
||||
static struct cflayer *get_dn(struct cfmuxl *muxl, struct dev_info *dev_info)
|
||||
{
|
||||
struct cflayer *dn;
|
||||
int idx = dev_info->id % DN_CACHE_SIZE;
|
||||
dn = rcu_dereference(muxl->dn_cache[idx]);
|
||||
if (dn == NULL || dn->id != dev_info->id) {
|
||||
spin_lock_bh(&muxl->transmit_lock);
|
||||
dn = get_from_id(&muxl->frml_list, dev_info->id);
|
||||
rcu_assign_pointer(muxl->dn_cache[idx], dn);
|
||||
spin_unlock_bh(&muxl->transmit_lock);
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
struct cflayer *cfmuxl_remove_uplayer(struct cflayer *layr, u8 id)
|
||||
{
|
||||
struct cflayer *up;
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
int idx = id % UP_CACHE_SIZE;
|
||||
|
||||
if (id == 0) {
|
||||
pr_warn("Trying to remove control layer\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spin_lock_bh(&muxl->receive_lock);
|
||||
up = get_from_id(&muxl->srvl_list, id);
|
||||
if (up == NULL)
|
||||
goto out;
|
||||
|
||||
RCU_INIT_POINTER(muxl->up_cache[idx], NULL);
|
||||
list_del_rcu(&up->node);
|
||||
out:
|
||||
spin_unlock_bh(&muxl->receive_lock);
|
||||
return up;
|
||||
}
|
||||
|
||||
static int cfmuxl_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
int ret;
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
u8 id;
|
||||
struct cflayer *up;
|
||||
if (cfpkt_extr_head(pkt, &id, 1) < 0) {
|
||||
pr_err("erroneous Caif Packet\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
rcu_read_lock();
|
||||
up = get_up(muxl, id);
|
||||
|
||||
if (up == NULL) {
|
||||
pr_debug("Received data on unknown link ID = %d (0x%x)"
|
||||
" up == NULL", id, id);
|
||||
cfpkt_destroy(pkt);
|
||||
/*
|
||||
* Don't return ERROR, since modem misbehaves and sends out
|
||||
* flow on before linksetup response.
|
||||
*/
|
||||
|
||||
rcu_read_unlock();
|
||||
return /* CFGLU_EPROT; */ 0;
|
||||
}
|
||||
|
||||
/* We can't hold rcu_lock during receive, so take a ref count instead */
|
||||
cfsrvl_get(up);
|
||||
rcu_read_unlock();
|
||||
|
||||
ret = up->receive(up, pkt);
|
||||
|
||||
cfsrvl_put(up);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cfmuxl_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
int err;
|
||||
u8 linkid;
|
||||
struct cflayer *dn;
|
||||
struct caif_payload_info *info = cfpkt_info(pkt);
|
||||
BUG_ON(!info);
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
dn = get_dn(muxl, info->dev_info);
|
||||
if (dn == NULL) {
|
||||
pr_debug("Send data on unknown phy ID = %d (0x%x)\n",
|
||||
info->dev_info->id, info->dev_info->id);
|
||||
rcu_read_unlock();
|
||||
cfpkt_destroy(pkt);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
info->hdr_len += 1;
|
||||
linkid = info->channel_id;
|
||||
cfpkt_add_head(pkt, &linkid, 1);
|
||||
|
||||
/* We can't hold rcu_lock during receive, so take a ref count instead */
|
||||
cffrml_hold(dn);
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
err = dn->transmit(dn, pkt);
|
||||
|
||||
cffrml_put(dn);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cfmuxl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
struct cfmuxl *muxl = container_obj(layr);
|
||||
struct cflayer *layer;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(layer, &muxl->srvl_list, node) {
|
||||
|
||||
if (cfsrvl_phyid_match(layer, phyid) && layer->ctrlcmd) {
|
||||
|
||||
if ((ctrl == _CAIF_CTRLCMD_PHYIF_DOWN_IND ||
|
||||
ctrl == CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND) &&
|
||||
layer->id != 0)
|
||||
cfmuxl_remove_uplayer(layr, layer->id);
|
||||
|
||||
/* NOTE: ctrlcmd is not allowed to block */
|
||||
layer->ctrlcmd(layer, ctrl, phyid);
|
||||
}
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
|
@ -1,373 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/string.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/export.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define PKT_PREFIX 48
|
||||
#define PKT_POSTFIX 2
|
||||
#define PKT_LEN_WHEN_EXTENDING 128
|
||||
#define PKT_ERROR(pkt, errmsg) \
|
||||
do { \
|
||||
cfpkt_priv(pkt)->erronous = true; \
|
||||
skb_reset_tail_pointer(&pkt->skb); \
|
||||
pr_warn(errmsg); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* net/caif/ is generic and does not
|
||||
* understand SKB, so we do this typecast
|
||||
*/
|
||||
struct cfpkt {
|
||||
struct sk_buff skb;
|
||||
};
|
||||
|
||||
/* Private data inside SKB */
|
||||
struct cfpkt_priv_data {
|
||||
struct dev_info dev_info;
|
||||
bool erronous;
|
||||
};
|
||||
|
||||
static inline struct cfpkt_priv_data *cfpkt_priv(struct cfpkt *pkt)
|
||||
{
|
||||
return (struct cfpkt_priv_data *) pkt->skb.cb;
|
||||
}
|
||||
|
||||
static inline bool is_erronous(struct cfpkt *pkt)
|
||||
{
|
||||
return cfpkt_priv(pkt)->erronous;
|
||||
}
|
||||
|
||||
static inline struct sk_buff *pkt_to_skb(struct cfpkt *pkt)
|
||||
{
|
||||
return &pkt->skb;
|
||||
}
|
||||
|
||||
static inline struct cfpkt *skb_to_pkt(struct sk_buff *skb)
|
||||
{
|
||||
return (struct cfpkt *) skb;
|
||||
}
|
||||
|
||||
struct cfpkt *cfpkt_fromnative(enum caif_direction dir, void *nativepkt)
|
||||
{
|
||||
struct cfpkt *pkt = skb_to_pkt(nativepkt);
|
||||
cfpkt_priv(pkt)->erronous = false;
|
||||
return pkt;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_fromnative);
|
||||
|
||||
void *cfpkt_tonative(struct cfpkt *pkt)
|
||||
{
|
||||
return (void *) pkt;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_tonative);
|
||||
|
||||
static struct cfpkt *cfpkt_create_pfx(u16 len, u16 pfx)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = alloc_skb(len + pfx, GFP_ATOMIC);
|
||||
if (unlikely(skb == NULL))
|
||||
return NULL;
|
||||
|
||||
skb_reserve(skb, pfx);
|
||||
return skb_to_pkt(skb);
|
||||
}
|
||||
|
||||
inline struct cfpkt *cfpkt_create(u16 len)
|
||||
{
|
||||
return cfpkt_create_pfx(len + PKT_POSTFIX, PKT_PREFIX);
|
||||
}
|
||||
|
||||
void cfpkt_destroy(struct cfpkt *pkt)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
inline bool cfpkt_more(struct cfpkt *pkt)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
return skb->len > 0;
|
||||
}
|
||||
|
||||
int cfpkt_peek_head(struct cfpkt *pkt, void *data, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
if (skb_headlen(skb) >= len) {
|
||||
memcpy(data, skb->data, len);
|
||||
return 0;
|
||||
}
|
||||
return !cfpkt_extr_head(pkt, data, len) &&
|
||||
!cfpkt_add_head(pkt, data, len);
|
||||
}
|
||||
|
||||
int cfpkt_extr_head(struct cfpkt *pkt, void *data, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
u8 *from;
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
|
||||
if (unlikely(len > skb->len)) {
|
||||
PKT_ERROR(pkt, "read beyond end of packet\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (unlikely(len > skb_headlen(skb))) {
|
||||
if (unlikely(skb_linearize(skb) != 0)) {
|
||||
PKT_ERROR(pkt, "linearize failed\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
from = skb_pull(skb, len);
|
||||
from -= len;
|
||||
if (data)
|
||||
memcpy(data, from, len);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_extr_head);
|
||||
|
||||
int cfpkt_extr_trail(struct cfpkt *pkt, void *dta, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
u8 *data = dta;
|
||||
u8 *from;
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
|
||||
if (unlikely(skb_linearize(skb) != 0)) {
|
||||
PKT_ERROR(pkt, "linearize failed\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
if (unlikely(skb->data + len > skb_tail_pointer(skb))) {
|
||||
PKT_ERROR(pkt, "read beyond end of packet\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
from = skb_tail_pointer(skb) - len;
|
||||
skb_trim(skb, skb->len - len);
|
||||
memcpy(data, from, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cfpkt_pad_trail(struct cfpkt *pkt, u16 len)
|
||||
{
|
||||
return cfpkt_add_body(pkt, NULL, len);
|
||||
}
|
||||
|
||||
int cfpkt_add_body(struct cfpkt *pkt, const void *data, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
struct sk_buff *lastskb;
|
||||
u8 *to;
|
||||
u16 addlen = 0;
|
||||
|
||||
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
|
||||
lastskb = skb;
|
||||
|
||||
/* Check whether we need to add space at the tail */
|
||||
if (unlikely(skb_tailroom(skb) < len)) {
|
||||
if (likely(len < PKT_LEN_WHEN_EXTENDING))
|
||||
addlen = PKT_LEN_WHEN_EXTENDING;
|
||||
else
|
||||
addlen = len;
|
||||
}
|
||||
|
||||
/* Check whether we need to change the SKB before writing to the tail */
|
||||
if (unlikely((addlen > 0) || skb_cloned(skb) || skb_shared(skb))) {
|
||||
|
||||
/* Make sure data is writable */
|
||||
if (unlikely(skb_cow_data(skb, addlen, &lastskb) < 0)) {
|
||||
PKT_ERROR(pkt, "cow failed\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
/* All set to put the last SKB and optionally write data there. */
|
||||
to = pskb_put(skb, lastskb, len);
|
||||
if (likely(data))
|
||||
memcpy(to, data, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int cfpkt_addbdy(struct cfpkt *pkt, u8 data)
|
||||
{
|
||||
return cfpkt_add_body(pkt, &data, 1);
|
||||
}
|
||||
|
||||
int cfpkt_add_head(struct cfpkt *pkt, const void *data2, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
struct sk_buff *lastskb;
|
||||
u8 *to;
|
||||
const u8 *data = data2;
|
||||
int ret;
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
if (unlikely(skb_headroom(skb) < len)) {
|
||||
PKT_ERROR(pkt, "no headroom\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
/* Make sure data is writable */
|
||||
ret = skb_cow_data(skb, 0, &lastskb);
|
||||
if (unlikely(ret < 0)) {
|
||||
PKT_ERROR(pkt, "cow failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
to = skb_push(skb, len);
|
||||
memcpy(to, data, len);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_add_head);
|
||||
|
||||
inline int cfpkt_add_trail(struct cfpkt *pkt, const void *data, u16 len)
|
||||
{
|
||||
return cfpkt_add_body(pkt, data, len);
|
||||
}
|
||||
|
||||
inline u16 cfpkt_getlen(struct cfpkt *pkt)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
return skb->len;
|
||||
}
|
||||
|
||||
int cfpkt_iterate(struct cfpkt *pkt,
|
||||
u16 (*iter_func)(u16, void *, u16),
|
||||
u16 data)
|
||||
{
|
||||
/*
|
||||
* Don't care about the performance hit of linearizing,
|
||||
* Checksum should not be used on high-speed interfaces anyway.
|
||||
*/
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
if (unlikely(skb_linearize(&pkt->skb) != 0)) {
|
||||
PKT_ERROR(pkt, "linearize failed\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
return iter_func(data, pkt->skb.data, cfpkt_getlen(pkt));
|
||||
}
|
||||
|
||||
int cfpkt_setlen(struct cfpkt *pkt, u16 len)
|
||||
{
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
|
||||
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return -EPROTO;
|
||||
|
||||
if (likely(len <= skb->len)) {
|
||||
if (unlikely(skb->data_len))
|
||||
___pskb_trim(skb, len);
|
||||
else
|
||||
skb_trim(skb, len);
|
||||
|
||||
return cfpkt_getlen(pkt);
|
||||
}
|
||||
|
||||
/* Need to expand SKB */
|
||||
if (unlikely(!cfpkt_pad_trail(pkt, len - skb->len)))
|
||||
PKT_ERROR(pkt, "skb_pad_trail failed\n");
|
||||
|
||||
return cfpkt_getlen(pkt);
|
||||
}
|
||||
|
||||
struct cfpkt *cfpkt_append(struct cfpkt *dstpkt,
|
||||
struct cfpkt *addpkt,
|
||||
u16 expectlen)
|
||||
{
|
||||
struct sk_buff *dst = pkt_to_skb(dstpkt);
|
||||
struct sk_buff *add = pkt_to_skb(addpkt);
|
||||
u16 addlen = skb_headlen(add);
|
||||
u16 neededtailspace;
|
||||
struct sk_buff *tmp;
|
||||
u16 dstlen;
|
||||
u16 createlen;
|
||||
if (unlikely(is_erronous(dstpkt) || is_erronous(addpkt))) {
|
||||
return dstpkt;
|
||||
}
|
||||
|
||||
neededtailspace = max(expectlen, addlen);
|
||||
|
||||
if (dst->tail + neededtailspace > dst->end) {
|
||||
/* Create a dumplicate of 'dst' with more tail space */
|
||||
struct cfpkt *tmppkt;
|
||||
dstlen = skb_headlen(dst);
|
||||
createlen = dstlen + neededtailspace;
|
||||
tmppkt = cfpkt_create(createlen + PKT_PREFIX + PKT_POSTFIX);
|
||||
if (tmppkt == NULL)
|
||||
return NULL;
|
||||
tmp = pkt_to_skb(tmppkt);
|
||||
skb_put_data(tmp, dst->data, dstlen);
|
||||
cfpkt_destroy(dstpkt);
|
||||
dst = tmp;
|
||||
}
|
||||
skb_put_data(dst, add->data, skb_headlen(add));
|
||||
cfpkt_destroy(addpkt);
|
||||
return skb_to_pkt(dst);
|
||||
}
|
||||
|
||||
struct cfpkt *cfpkt_split(struct cfpkt *pkt, u16 pos)
|
||||
{
|
||||
struct sk_buff *skb2;
|
||||
struct sk_buff *skb = pkt_to_skb(pkt);
|
||||
struct cfpkt *tmppkt;
|
||||
u8 *split = skb->data + pos;
|
||||
u16 len2nd = skb_tail_pointer(skb) - split;
|
||||
|
||||
if (unlikely(is_erronous(pkt)))
|
||||
return NULL;
|
||||
|
||||
if (skb->data + pos > skb_tail_pointer(skb)) {
|
||||
PKT_ERROR(pkt, "trying to split beyond end of packet\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create a new packet for the second part of the data */
|
||||
tmppkt = cfpkt_create_pfx(len2nd + PKT_PREFIX + PKT_POSTFIX,
|
||||
PKT_PREFIX);
|
||||
if (tmppkt == NULL)
|
||||
return NULL;
|
||||
skb2 = pkt_to_skb(tmppkt);
|
||||
|
||||
|
||||
if (skb2 == NULL)
|
||||
return NULL;
|
||||
|
||||
skb_put_data(skb2, split, len2nd);
|
||||
|
||||
/* Reduce the length of the original packet */
|
||||
skb_trim(skb, pos);
|
||||
|
||||
skb2->priority = skb->priority;
|
||||
return skb_to_pkt(skb2);
|
||||
}
|
||||
|
||||
bool cfpkt_erroneous(struct cfpkt *pkt)
|
||||
{
|
||||
return cfpkt_priv(pkt)->erronous;
|
||||
}
|
||||
|
||||
struct caif_payload_info *cfpkt_info(struct cfpkt *pkt)
|
||||
{
|
||||
return (struct caif_payload_info *)&pkt_to_skb(pkt)->cb;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_info);
|
||||
|
||||
void cfpkt_set_prio(struct cfpkt *pkt, int prio)
|
||||
{
|
||||
pkt_to_skb(pkt)->priority = prio;
|
||||
}
|
||||
EXPORT_SYMBOL(cfpkt_set_prio);
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfrfml, serv.layer)
|
||||
#define RFM_SEGMENTATION_BIT 0x01
|
||||
#define RFM_HEAD_SIZE 7
|
||||
|
||||
static int cfrfml_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfrfml_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cfrfml {
|
||||
struct cfsrvl serv;
|
||||
struct cfpkt *incomplete_frm;
|
||||
int fragment_size;
|
||||
u8 seghead[6];
|
||||
u16 pdu_size;
|
||||
/* Protects serialized processing of packets */
|
||||
spinlock_t sync;
|
||||
};
|
||||
|
||||
static void cfrfml_release(struct cflayer *layer)
|
||||
{
|
||||
struct cfsrvl *srvl = container_of(layer, struct cfsrvl, layer);
|
||||
struct cfrfml *rfml = container_obj(&srvl->layer);
|
||||
|
||||
if (rfml->incomplete_frm)
|
||||
cfpkt_destroy(rfml->incomplete_frm);
|
||||
|
||||
kfree(srvl);
|
||||
}
|
||||
|
||||
struct cflayer *cfrfml_create(u8 channel_id, struct dev_info *dev_info,
|
||||
int mtu_size)
|
||||
{
|
||||
int tmp;
|
||||
struct cfrfml *this = kzalloc_obj(struct cfrfml, GFP_ATOMIC);
|
||||
|
||||
if (!this)
|
||||
return NULL;
|
||||
|
||||
cfsrvl_init(&this->serv, channel_id, dev_info, false);
|
||||
this->serv.release = cfrfml_release;
|
||||
this->serv.layer.receive = cfrfml_receive;
|
||||
this->serv.layer.transmit = cfrfml_transmit;
|
||||
|
||||
/* Round down to closest multiple of 16 */
|
||||
tmp = (mtu_size - RFM_HEAD_SIZE - 6) / 16;
|
||||
tmp *= 16;
|
||||
|
||||
this->fragment_size = tmp;
|
||||
spin_lock_init(&this->sync);
|
||||
snprintf(this->serv.layer.name, CAIF_LAYER_NAME_SZ,
|
||||
"rfm%d", channel_id);
|
||||
|
||||
return &this->serv.layer;
|
||||
}
|
||||
|
||||
static struct cfpkt *rfm_append(struct cfrfml *rfml, char *seghead,
|
||||
struct cfpkt *pkt, int *err)
|
||||
{
|
||||
struct cfpkt *tmppkt;
|
||||
*err = -EPROTO;
|
||||
/* n-th but not last segment */
|
||||
|
||||
if (cfpkt_extr_head(pkt, seghead, 6) < 0)
|
||||
return NULL;
|
||||
|
||||
/* Verify correct header */
|
||||
if (memcmp(seghead, rfml->seghead, 6) != 0)
|
||||
return NULL;
|
||||
|
||||
tmppkt = cfpkt_append(rfml->incomplete_frm, pkt,
|
||||
rfml->pdu_size + RFM_HEAD_SIZE);
|
||||
|
||||
/* If cfpkt_append failes input pkts are not freed */
|
||||
*err = -ENOMEM;
|
||||
if (tmppkt == NULL)
|
||||
return NULL;
|
||||
|
||||
*err = 0;
|
||||
return tmppkt;
|
||||
}
|
||||
|
||||
static int cfrfml_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 tmp;
|
||||
bool segmented;
|
||||
int err;
|
||||
u8 seghead[6];
|
||||
struct cfrfml *rfml;
|
||||
struct cfpkt *tmppkt = NULL;
|
||||
|
||||
caif_assert(layr->up != NULL);
|
||||
caif_assert(layr->receive != NULL);
|
||||
rfml = container_obj(layr);
|
||||
spin_lock(&rfml->sync);
|
||||
|
||||
err = -EPROTO;
|
||||
if (cfpkt_extr_head(pkt, &tmp, 1) < 0)
|
||||
goto out;
|
||||
segmented = tmp & RFM_SEGMENTATION_BIT;
|
||||
|
||||
if (segmented) {
|
||||
if (rfml->incomplete_frm == NULL) {
|
||||
/* Initial Segment */
|
||||
if (cfpkt_peek_head(pkt, rfml->seghead, 6) != 0)
|
||||
goto out;
|
||||
|
||||
rfml->pdu_size = get_unaligned_le16(rfml->seghead+4);
|
||||
|
||||
if (cfpkt_erroneous(pkt))
|
||||
goto out;
|
||||
rfml->incomplete_frm = pkt;
|
||||
pkt = NULL;
|
||||
} else {
|
||||
|
||||
tmppkt = rfm_append(rfml, seghead, pkt, &err);
|
||||
if (tmppkt == NULL)
|
||||
goto out;
|
||||
|
||||
if (cfpkt_erroneous(tmppkt))
|
||||
goto out;
|
||||
|
||||
rfml->incomplete_frm = tmppkt;
|
||||
|
||||
|
||||
if (cfpkt_erroneous(tmppkt))
|
||||
goto out;
|
||||
}
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rfml->incomplete_frm) {
|
||||
|
||||
/* Last Segment */
|
||||
tmppkt = rfm_append(rfml, seghead, pkt, &err);
|
||||
if (tmppkt == NULL)
|
||||
goto out;
|
||||
|
||||
if (cfpkt_erroneous(tmppkt))
|
||||
goto out;
|
||||
|
||||
rfml->incomplete_frm = NULL;
|
||||
pkt = tmppkt;
|
||||
tmppkt = NULL;
|
||||
|
||||
/* Verify that length is correct */
|
||||
err = -EPROTO;
|
||||
if (rfml->pdu_size != cfpkt_getlen(pkt) - RFM_HEAD_SIZE + 1)
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = rfml->serv.layer.up->receive(rfml->serv.layer.up, pkt);
|
||||
|
||||
out:
|
||||
|
||||
if (err != 0) {
|
||||
if (tmppkt)
|
||||
cfpkt_destroy(tmppkt);
|
||||
if (pkt)
|
||||
cfpkt_destroy(pkt);
|
||||
if (rfml->incomplete_frm)
|
||||
cfpkt_destroy(rfml->incomplete_frm);
|
||||
rfml->incomplete_frm = NULL;
|
||||
|
||||
pr_info("Connection error %d triggered on RFM link\n", err);
|
||||
|
||||
/* Trigger connection error upon failure.*/
|
||||
layr->up->ctrlcmd(layr->up, CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND,
|
||||
rfml->serv.dev_info.id);
|
||||
}
|
||||
spin_unlock(&rfml->sync);
|
||||
|
||||
if (unlikely(err == -EAGAIN))
|
||||
/* It is not possible to recover after drop of a fragment */
|
||||
err = -EIO;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int cfrfml_transmit_segment(struct cfrfml *rfml, struct cfpkt *pkt)
|
||||
{
|
||||
caif_assert(cfpkt_getlen(pkt) < rfml->fragment_size + RFM_HEAD_SIZE);
|
||||
|
||||
/* Add info for MUX-layer to route the packet out. */
|
||||
cfpkt_info(pkt)->channel_id = rfml->serv.layer.id;
|
||||
|
||||
/*
|
||||
* To optimize alignment, we add up the size of CAIF header before
|
||||
* payload.
|
||||
*/
|
||||
cfpkt_info(pkt)->hdr_len = RFM_HEAD_SIZE;
|
||||
cfpkt_info(pkt)->dev_info = &rfml->serv.dev_info;
|
||||
|
||||
return rfml->serv.layer.dn->transmit(rfml->serv.layer.dn, pkt);
|
||||
}
|
||||
|
||||
static int cfrfml_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
int err;
|
||||
u8 seg;
|
||||
u8 head[6];
|
||||
struct cfpkt *rearpkt = NULL;
|
||||
struct cfpkt *frontpkt = pkt;
|
||||
struct cfrfml *rfml = container_obj(layr);
|
||||
|
||||
caif_assert(layr->dn != NULL);
|
||||
caif_assert(layr->dn->transmit != NULL);
|
||||
|
||||
if (!cfsrvl_ready(&rfml->serv, &err))
|
||||
goto out;
|
||||
|
||||
err = -EPROTO;
|
||||
if (cfpkt_getlen(pkt) <= RFM_HEAD_SIZE-1)
|
||||
goto out;
|
||||
|
||||
err = 0;
|
||||
if (cfpkt_getlen(pkt) > rfml->fragment_size + RFM_HEAD_SIZE)
|
||||
err = cfpkt_peek_head(pkt, head, 6);
|
||||
|
||||
if (err != 0)
|
||||
goto out;
|
||||
|
||||
while (cfpkt_getlen(frontpkt) > rfml->fragment_size + RFM_HEAD_SIZE) {
|
||||
|
||||
seg = 1;
|
||||
err = -EPROTO;
|
||||
|
||||
if (cfpkt_add_head(frontpkt, &seg, 1) < 0)
|
||||
goto out;
|
||||
/*
|
||||
* On OOM error cfpkt_split returns NULL.
|
||||
*
|
||||
* NOTE: Segmented pdu is not correctly aligned.
|
||||
* This has negative performance impact.
|
||||
*/
|
||||
|
||||
rearpkt = cfpkt_split(frontpkt, rfml->fragment_size);
|
||||
if (rearpkt == NULL)
|
||||
goto out;
|
||||
|
||||
err = cfrfml_transmit_segment(rfml, frontpkt);
|
||||
|
||||
if (err != 0) {
|
||||
frontpkt = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
frontpkt = rearpkt;
|
||||
rearpkt = NULL;
|
||||
|
||||
err = -EPROTO;
|
||||
if (cfpkt_add_head(frontpkt, head, 6) < 0)
|
||||
goto out;
|
||||
|
||||
}
|
||||
|
||||
seg = 0;
|
||||
err = -EPROTO;
|
||||
|
||||
if (cfpkt_add_head(frontpkt, &seg, 1) < 0)
|
||||
goto out;
|
||||
|
||||
err = cfrfml_transmit_segment(rfml, frontpkt);
|
||||
|
||||
frontpkt = NULL;
|
||||
out:
|
||||
|
||||
if (err != 0) {
|
||||
pr_info("Connection error %d triggered on RFM link\n", err);
|
||||
/* Trigger connection error upon failure.*/
|
||||
|
||||
layr->up->ctrlcmd(layr->up, CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND,
|
||||
rfml->serv.dev_info.id);
|
||||
|
||||
if (rearpkt)
|
||||
cfpkt_destroy(rearpkt);
|
||||
|
||||
if (frontpkt)
|
||||
cfpkt_destroy(frontpkt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/cfserl.h>
|
||||
|
||||
#define container_obj(layr) ((struct cfserl *) layr)
|
||||
|
||||
#define CFSERL_STX 0x02
|
||||
#define SERIAL_MINIUM_PACKET_SIZE 4
|
||||
#define SERIAL_MAX_FRAMESIZE 4096
|
||||
struct cfserl {
|
||||
struct cflayer layer;
|
||||
struct cfpkt *incomplete_frm;
|
||||
/* Protects parallel processing of incoming packets */
|
||||
spinlock_t sync;
|
||||
bool usestx;
|
||||
};
|
||||
|
||||
static int cfserl_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfserl_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static void cfserl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid);
|
||||
|
||||
void cfserl_release(struct cflayer *layer)
|
||||
{
|
||||
kfree(layer);
|
||||
}
|
||||
|
||||
struct cflayer *cfserl_create(int instance, bool use_stx)
|
||||
{
|
||||
struct cfserl *this = kzalloc_obj(struct cfserl, GFP_ATOMIC);
|
||||
if (!this)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfserl, layer) == 0);
|
||||
this->layer.receive = cfserl_receive;
|
||||
this->layer.transmit = cfserl_transmit;
|
||||
this->layer.ctrlcmd = cfserl_ctrlcmd;
|
||||
this->usestx = use_stx;
|
||||
spin_lock_init(&this->sync);
|
||||
snprintf(this->layer.name, CAIF_LAYER_NAME_SZ, "ser1");
|
||||
return &this->layer;
|
||||
}
|
||||
|
||||
static int cfserl_receive(struct cflayer *l, struct cfpkt *newpkt)
|
||||
{
|
||||
struct cfserl *layr = container_obj(l);
|
||||
u16 pkt_len;
|
||||
struct cfpkt *pkt = NULL;
|
||||
struct cfpkt *tail_pkt = NULL;
|
||||
u8 tmp8;
|
||||
u16 tmp;
|
||||
u8 stx = CFSERL_STX;
|
||||
int ret;
|
||||
u16 expectlen = 0;
|
||||
|
||||
caif_assert(newpkt != NULL);
|
||||
spin_lock(&layr->sync);
|
||||
|
||||
if (layr->incomplete_frm != NULL) {
|
||||
layr->incomplete_frm =
|
||||
cfpkt_append(layr->incomplete_frm, newpkt, expectlen);
|
||||
pkt = layr->incomplete_frm;
|
||||
if (pkt == NULL) {
|
||||
spin_unlock(&layr->sync);
|
||||
return -ENOMEM;
|
||||
}
|
||||
} else {
|
||||
pkt = newpkt;
|
||||
}
|
||||
layr->incomplete_frm = NULL;
|
||||
|
||||
do {
|
||||
/* Search for STX at start of pkt if STX is used */
|
||||
if (layr->usestx) {
|
||||
cfpkt_extr_head(pkt, &tmp8, 1);
|
||||
if (tmp8 != CFSERL_STX) {
|
||||
while (cfpkt_more(pkt)
|
||||
&& tmp8 != CFSERL_STX) {
|
||||
cfpkt_extr_head(pkt, &tmp8, 1);
|
||||
}
|
||||
if (!cfpkt_more(pkt)) {
|
||||
cfpkt_destroy(pkt);
|
||||
layr->incomplete_frm = NULL;
|
||||
spin_unlock(&layr->sync);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pkt_len = cfpkt_getlen(pkt);
|
||||
|
||||
/*
|
||||
* pkt_len is the accumulated length of the packet data
|
||||
* we have received so far.
|
||||
* Exit if frame doesn't hold length.
|
||||
*/
|
||||
|
||||
if (pkt_len < 2) {
|
||||
if (layr->usestx)
|
||||
cfpkt_add_head(pkt, &stx, 1);
|
||||
layr->incomplete_frm = pkt;
|
||||
spin_unlock(&layr->sync);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find length of frame.
|
||||
* expectlen is the length we need for a full frame.
|
||||
*/
|
||||
cfpkt_peek_head(pkt, &tmp, 2);
|
||||
expectlen = le16_to_cpu(tmp) + 2;
|
||||
/*
|
||||
* Frame error handling
|
||||
*/
|
||||
if (expectlen < SERIAL_MINIUM_PACKET_SIZE
|
||||
|| expectlen > SERIAL_MAX_FRAMESIZE) {
|
||||
if (!layr->usestx) {
|
||||
if (pkt != NULL)
|
||||
cfpkt_destroy(pkt);
|
||||
layr->incomplete_frm = NULL;
|
||||
spin_unlock(&layr->sync);
|
||||
return -EPROTO;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pkt_len < expectlen) {
|
||||
/* Too little received data */
|
||||
if (layr->usestx)
|
||||
cfpkt_add_head(pkt, &stx, 1);
|
||||
layr->incomplete_frm = pkt;
|
||||
spin_unlock(&layr->sync);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enough data for at least one frame.
|
||||
* Split the frame, if too long
|
||||
*/
|
||||
if (pkt_len > expectlen)
|
||||
tail_pkt = cfpkt_split(pkt, expectlen);
|
||||
else
|
||||
tail_pkt = NULL;
|
||||
|
||||
/* Send the first part of packet upwards.*/
|
||||
spin_unlock(&layr->sync);
|
||||
ret = layr->layer.up->receive(layr->layer.up, pkt);
|
||||
spin_lock(&layr->sync);
|
||||
if (ret == -EILSEQ) {
|
||||
if (layr->usestx) {
|
||||
if (tail_pkt != NULL)
|
||||
pkt = cfpkt_append(pkt, tail_pkt, 0);
|
||||
/* Start search for next STX if frame failed */
|
||||
continue;
|
||||
} else {
|
||||
cfpkt_destroy(pkt);
|
||||
pkt = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
pkt = tail_pkt;
|
||||
|
||||
} while (pkt != NULL);
|
||||
|
||||
spin_unlock(&layr->sync);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cfserl_transmit(struct cflayer *layer, struct cfpkt *newpkt)
|
||||
{
|
||||
struct cfserl *layr = container_obj(layer);
|
||||
u8 tmp8 = CFSERL_STX;
|
||||
if (layr->usestx)
|
||||
cfpkt_add_head(newpkt, &tmp8, 1);
|
||||
return layer->dn->transmit(layer->dn, newpkt);
|
||||
}
|
||||
|
||||
static void cfserl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
layr->up->ctrlcmd(layr->up, ctrl, phyid);
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pkt_sched.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
|
||||
#define SRVL_CTRL_PKT_SIZE 1
|
||||
#define SRVL_FLOW_OFF 0x81
|
||||
#define SRVL_FLOW_ON 0x80
|
||||
#define SRVL_SET_PIN 0x82
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfsrvl, layer)
|
||||
|
||||
static void cfservl_ctrlcmd(struct cflayer *layr, enum caif_ctrlcmd ctrl,
|
||||
int phyid)
|
||||
{
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
|
||||
if (layr->up == NULL || layr->up->ctrlcmd == NULL)
|
||||
return;
|
||||
|
||||
switch (ctrl) {
|
||||
case CAIF_CTRLCMD_INIT_RSP:
|
||||
service->open = true;
|
||||
layr->up->ctrlcmd(layr->up, ctrl, phyid);
|
||||
break;
|
||||
case CAIF_CTRLCMD_DEINIT_RSP:
|
||||
case CAIF_CTRLCMD_INIT_FAIL_RSP:
|
||||
service->open = false;
|
||||
layr->up->ctrlcmd(layr->up, ctrl, phyid);
|
||||
break;
|
||||
case _CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND:
|
||||
if (phyid != service->dev_info.id)
|
||||
break;
|
||||
if (service->modem_flow_on)
|
||||
layr->up->ctrlcmd(layr->up,
|
||||
CAIF_CTRLCMD_FLOW_OFF_IND, phyid);
|
||||
service->phy_flow_on = false;
|
||||
break;
|
||||
case _CAIF_CTRLCMD_PHYIF_FLOW_ON_IND:
|
||||
if (phyid != service->dev_info.id)
|
||||
return;
|
||||
if (service->modem_flow_on) {
|
||||
layr->up->ctrlcmd(layr->up,
|
||||
CAIF_CTRLCMD_FLOW_ON_IND,
|
||||
phyid);
|
||||
}
|
||||
service->phy_flow_on = true;
|
||||
break;
|
||||
case CAIF_CTRLCMD_FLOW_OFF_IND:
|
||||
if (service->phy_flow_on) {
|
||||
layr->up->ctrlcmd(layr->up,
|
||||
CAIF_CTRLCMD_FLOW_OFF_IND, phyid);
|
||||
}
|
||||
service->modem_flow_on = false;
|
||||
break;
|
||||
case CAIF_CTRLCMD_FLOW_ON_IND:
|
||||
if (service->phy_flow_on) {
|
||||
layr->up->ctrlcmd(layr->up,
|
||||
CAIF_CTRLCMD_FLOW_ON_IND, phyid);
|
||||
}
|
||||
service->modem_flow_on = true;
|
||||
break;
|
||||
case _CAIF_CTRLCMD_PHYIF_DOWN_IND:
|
||||
/* In case interface is down, let's fake a remove shutdown */
|
||||
layr->up->ctrlcmd(layr->up,
|
||||
CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND, phyid);
|
||||
break;
|
||||
case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND:
|
||||
layr->up->ctrlcmd(layr->up, ctrl, phyid);
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unexpected ctrl in cfsrvl (%d)\n", ctrl);
|
||||
/* We have both modem and phy flow on, send flow on */
|
||||
layr->up->ctrlcmd(layr->up, ctrl, phyid);
|
||||
service->phy_flow_on = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int cfservl_modemcmd(struct cflayer *layr, enum caif_modemcmd ctrl)
|
||||
{
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
|
||||
caif_assert(layr != NULL);
|
||||
caif_assert(layr->dn != NULL);
|
||||
caif_assert(layr->dn->transmit != NULL);
|
||||
|
||||
if (!service->supports_flowctrl)
|
||||
return 0;
|
||||
|
||||
switch (ctrl) {
|
||||
case CAIF_MODEMCMD_FLOW_ON_REQ:
|
||||
{
|
||||
struct cfpkt *pkt;
|
||||
struct caif_payload_info *info;
|
||||
u8 flow_on = SRVL_FLOW_ON;
|
||||
pkt = cfpkt_create(SRVL_CTRL_PKT_SIZE);
|
||||
if (!pkt)
|
||||
return -ENOMEM;
|
||||
|
||||
if (cfpkt_add_head(pkt, &flow_on, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
info->hdr_len = 1;
|
||||
info->dev_info = &service->dev_info;
|
||||
cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
case CAIF_MODEMCMD_FLOW_OFF_REQ:
|
||||
{
|
||||
struct cfpkt *pkt;
|
||||
struct caif_payload_info *info;
|
||||
u8 flow_off = SRVL_FLOW_OFF;
|
||||
pkt = cfpkt_create(SRVL_CTRL_PKT_SIZE);
|
||||
if (!pkt)
|
||||
return -ENOMEM;
|
||||
|
||||
if (cfpkt_add_head(pkt, &flow_off, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
info->hdr_len = 1;
|
||||
info->dev_info = &service->dev_info;
|
||||
cfpkt_set_prio(pkt, TC_PRIO_CONTROL);
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void cfsrvl_release(struct cflayer *layer)
|
||||
{
|
||||
struct cfsrvl *service = container_of(layer, struct cfsrvl, layer);
|
||||
kfree(service);
|
||||
}
|
||||
|
||||
void cfsrvl_init(struct cfsrvl *service,
|
||||
u8 channel_id,
|
||||
struct dev_info *dev_info,
|
||||
bool supports_flowctrl)
|
||||
{
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
service->open = false;
|
||||
service->modem_flow_on = true;
|
||||
service->phy_flow_on = true;
|
||||
service->layer.id = channel_id;
|
||||
service->layer.ctrlcmd = cfservl_ctrlcmd;
|
||||
service->layer.modemcmd = cfservl_modemcmd;
|
||||
service->dev_info = *dev_info;
|
||||
service->supports_flowctrl = supports_flowctrl;
|
||||
service->release = cfsrvl_release;
|
||||
}
|
||||
|
||||
bool cfsrvl_ready(struct cfsrvl *service, int *err)
|
||||
{
|
||||
if (!service->open) {
|
||||
*err = -ENOTCONN;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cfsrvl_phyid_match(struct cflayer *layer, int phyid)
|
||||
{
|
||||
struct cfsrvl *servl = container_obj(layer);
|
||||
return servl->dev_info.id == phyid;
|
||||
}
|
||||
|
||||
void caif_free_client(struct cflayer *adap_layer)
|
||||
{
|
||||
struct cflayer *serv_layer;
|
||||
struct cfsrvl *servl;
|
||||
|
||||
if (!adap_layer)
|
||||
return;
|
||||
|
||||
serv_layer = adap_layer->dn;
|
||||
if (!serv_layer)
|
||||
return;
|
||||
|
||||
layer_set_dn(adap_layer, NULL);
|
||||
layer_set_up(serv_layer, NULL);
|
||||
|
||||
servl = container_obj(serv_layer);
|
||||
servl->release(&servl->layer);
|
||||
}
|
||||
EXPORT_SYMBOL(caif_free_client);
|
||||
|
||||
void caif_client_register_refcnt(struct cflayer *adapt_layer,
|
||||
void (*hold)(struct cflayer *lyr),
|
||||
void (*put)(struct cflayer *lyr))
|
||||
{
|
||||
struct cfsrvl *service;
|
||||
|
||||
if (WARN_ON(adapt_layer == NULL || adapt_layer->dn == NULL))
|
||||
return;
|
||||
service = container_of(adapt_layer->dn, struct cfsrvl, layer);
|
||||
service->hold = hold;
|
||||
service->put = put;
|
||||
}
|
||||
EXPORT_SYMBOL(caif_client_register_refcnt);
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define container_obj(layr) ((struct cfsrvl *) layr)
|
||||
#define UTIL_PAYLOAD 0x00
|
||||
#define UTIL_CMD_BIT 0x80
|
||||
#define UTIL_REMOTE_SHUTDOWN 0x82
|
||||
#define UTIL_FLOW_OFF 0x81
|
||||
#define UTIL_FLOW_ON 0x80
|
||||
|
||||
static int cfutill_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfutill_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cflayer *cfutill_create(u8 channel_id, struct dev_info *dev_info)
|
||||
{
|
||||
struct cfsrvl *util = kzalloc_obj(struct cfsrvl, GFP_ATOMIC);
|
||||
if (!util)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
cfsrvl_init(util, channel_id, dev_info, true);
|
||||
util->layer.receive = cfutill_receive;
|
||||
util->layer.transmit = cfutill_transmit;
|
||||
snprintf(util->layer.name, CAIF_LAYER_NAME_SZ, "util1");
|
||||
return &util->layer;
|
||||
}
|
||||
|
||||
static int cfutill_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 cmd = -1;
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
caif_assert(layr != NULL);
|
||||
caif_assert(layr->up != NULL);
|
||||
caif_assert(layr->up->receive != NULL);
|
||||
caif_assert(layr->up->ctrlcmd != NULL);
|
||||
if (cfpkt_extr_head(pkt, &cmd, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case UTIL_PAYLOAD:
|
||||
return layr->up->receive(layr->up, pkt);
|
||||
case UTIL_FLOW_OFF:
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_OFF_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
case UTIL_FLOW_ON:
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_ON_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
case UTIL_REMOTE_SHUTDOWN: /* Remote Shutdown Request */
|
||||
pr_err("REMOTE SHUTDOWN REQUEST RECEIVED\n");
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND, 0);
|
||||
service->open = false;
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
default:
|
||||
cfpkt_destroy(pkt);
|
||||
pr_warn("Unknown service control %d (0x%x)\n", cmd, cmd);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
static int cfutill_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 zero = 0;
|
||||
struct caif_payload_info *info;
|
||||
int ret;
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
caif_assert(layr != NULL);
|
||||
caif_assert(layr->dn != NULL);
|
||||
caif_assert(layr->dn->transmit != NULL);
|
||||
|
||||
if (!cfsrvl_ready(service, &ret)) {
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cfpkt_add_head(pkt, &zero, 1);
|
||||
/* Add info for MUX-layer to route the packet out. */
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
/*
|
||||
* To optimize alignment, we add up the size of CAIF header before
|
||||
* payload.
|
||||
*/
|
||||
info->hdr_len = 1;
|
||||
info->dev_info = &service->dev_info;
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define VEI_PAYLOAD 0x00
|
||||
#define VEI_CMD_BIT 0x80
|
||||
#define VEI_FLOW_OFF 0x81
|
||||
#define VEI_FLOW_ON 0x80
|
||||
#define VEI_SET_PIN 0x82
|
||||
|
||||
#define container_obj(layr) container_of(layr, struct cfsrvl, layer)
|
||||
|
||||
static int cfvei_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfvei_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cflayer *cfvei_create(u8 channel_id, struct dev_info *dev_info)
|
||||
{
|
||||
struct cfsrvl *vei = kzalloc_obj(struct cfsrvl, GFP_ATOMIC);
|
||||
if (!vei)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
cfsrvl_init(vei, channel_id, dev_info, true);
|
||||
vei->layer.receive = cfvei_receive;
|
||||
vei->layer.transmit = cfvei_transmit;
|
||||
snprintf(vei->layer.name, CAIF_LAYER_NAME_SZ, "vei%d", channel_id);
|
||||
return &vei->layer;
|
||||
}
|
||||
|
||||
static int cfvei_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 cmd;
|
||||
int ret;
|
||||
caif_assert(layr->up != NULL);
|
||||
caif_assert(layr->receive != NULL);
|
||||
caif_assert(layr->ctrlcmd != NULL);
|
||||
|
||||
|
||||
if (cfpkt_extr_head(pkt, &cmd, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
switch (cmd) {
|
||||
case VEI_PAYLOAD:
|
||||
ret = layr->up->receive(layr->up, pkt);
|
||||
return ret;
|
||||
case VEI_FLOW_OFF:
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_OFF_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
case VEI_FLOW_ON:
|
||||
layr->ctrlcmd(layr, CAIF_CTRLCMD_FLOW_ON_IND, 0);
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
case VEI_SET_PIN: /* SET RS232 PIN */
|
||||
cfpkt_destroy(pkt);
|
||||
return 0;
|
||||
default: /* SET RS232 PIN */
|
||||
pr_warn("Unknown VEI control packet %d (0x%x)!\n", cmd, cmd);
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
static int cfvei_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u8 tmp = 0;
|
||||
struct caif_payload_info *info;
|
||||
int ret;
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
if (!cfsrvl_ready(service, &ret))
|
||||
goto err;
|
||||
caif_assert(layr->dn != NULL);
|
||||
caif_assert(layr->dn->transmit != NULL);
|
||||
|
||||
if (cfpkt_add_head(pkt, &tmp, 1) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
ret = -EPROTO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Add info-> for MUX-layer to route the packet out. */
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
info->hdr_len = 1;
|
||||
info->dev_info = &service->dev_info;
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
err:
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Author: Sjur Brendeland
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfsrvl.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
|
||||
#define container_obj(layr) ((struct cfsrvl *) layr)
|
||||
|
||||
static int cfvidl_receive(struct cflayer *layr, struct cfpkt *pkt);
|
||||
static int cfvidl_transmit(struct cflayer *layr, struct cfpkt *pkt);
|
||||
|
||||
struct cflayer *cfvidl_create(u8 channel_id, struct dev_info *dev_info)
|
||||
{
|
||||
struct cfsrvl *vid = kzalloc_obj(struct cfsrvl, GFP_ATOMIC);
|
||||
if (!vid)
|
||||
return NULL;
|
||||
caif_assert(offsetof(struct cfsrvl, layer) == 0);
|
||||
|
||||
cfsrvl_init(vid, channel_id, dev_info, false);
|
||||
vid->layer.receive = cfvidl_receive;
|
||||
vid->layer.transmit = cfvidl_transmit;
|
||||
snprintf(vid->layer.name, CAIF_LAYER_NAME_SZ, "vid1");
|
||||
return &vid->layer;
|
||||
}
|
||||
|
||||
static int cfvidl_receive(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
u32 videoheader;
|
||||
if (cfpkt_extr_head(pkt, &videoheader, 4) < 0) {
|
||||
pr_err("Packet is erroneous!\n");
|
||||
cfpkt_destroy(pkt);
|
||||
return -EPROTO;
|
||||
}
|
||||
return layr->up->receive(layr->up, pkt);
|
||||
}
|
||||
|
||||
static int cfvidl_transmit(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
struct cfsrvl *service = container_obj(layr);
|
||||
struct caif_payload_info *info;
|
||||
u32 videoheader = 0;
|
||||
int ret;
|
||||
|
||||
if (!cfsrvl_ready(service, &ret)) {
|
||||
cfpkt_destroy(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cfpkt_add_head(pkt, &videoheader, 4);
|
||||
/* Add info for MUX-layer to route the packet out */
|
||||
info = cfpkt_info(pkt);
|
||||
info->channel_id = service->layer.id;
|
||||
info->dev_info = &service->dev_info;
|
||||
return layr->dn->transmit(layr->dn, pkt);
|
||||
}
|
||||
|
|
@ -1,531 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2010
|
||||
* Authors: Sjur Brendeland
|
||||
* Daniel Martensson
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <linux/caif/if_caif.h>
|
||||
#include <net/rtnetlink.h>
|
||||
#include <net/caif/caif_layer.h>
|
||||
#include <net/caif/cfpkt.h>
|
||||
#include <net/caif/caif_dev.h>
|
||||
|
||||
/* GPRS PDP connection has MTU to 1500 */
|
||||
#define GPRS_PDP_MTU 1500
|
||||
/* 5 sec. connect timeout */
|
||||
#define CONNECT_TIMEOUT (5 * HZ)
|
||||
#define CAIF_NET_DEFAULT_QUEUE_LEN 500
|
||||
#define UNDEF_CONNID 0xffffffff
|
||||
|
||||
/*This list is protected by the rtnl lock. */
|
||||
static LIST_HEAD(chnl_net_list);
|
||||
|
||||
MODULE_DESCRIPTION("ST-Ericsson CAIF modem protocol GPRS network device");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_RTNL_LINK("caif");
|
||||
|
||||
enum caif_states {
|
||||
CAIF_CONNECTED = 1,
|
||||
CAIF_CONNECTING,
|
||||
CAIF_DISCONNECTED,
|
||||
CAIF_SHUTDOWN
|
||||
};
|
||||
|
||||
struct chnl_net {
|
||||
struct cflayer chnl;
|
||||
struct caif_connect_request conn_req;
|
||||
struct list_head list_field;
|
||||
struct net_device *netdev;
|
||||
wait_queue_head_t netmgmt_wq;
|
||||
/* Flow status to remember and control the transmission. */
|
||||
bool flowenabled;
|
||||
enum caif_states state;
|
||||
};
|
||||
|
||||
static int chnl_recv_cb(struct cflayer *layr, struct cfpkt *pkt)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct chnl_net *priv;
|
||||
int pktlen;
|
||||
const u8 *ip_version;
|
||||
u8 buf;
|
||||
|
||||
priv = container_of(layr, struct chnl_net, chnl);
|
||||
|
||||
skb = (struct sk_buff *) cfpkt_tonative(pkt);
|
||||
|
||||
/* Get length of CAIF packet. */
|
||||
pktlen = skb->len;
|
||||
|
||||
/* Pass some minimum information and
|
||||
* send the packet to the net stack.
|
||||
*/
|
||||
skb->dev = priv->netdev;
|
||||
|
||||
/* check the version of IP */
|
||||
ip_version = skb_header_pointer(skb, 0, 1, &buf);
|
||||
if (!ip_version) {
|
||||
kfree_skb(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (*ip_version >> 4) {
|
||||
case 4:
|
||||
skb->protocol = htons(ETH_P_IP);
|
||||
break;
|
||||
case 6:
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
break;
|
||||
default:
|
||||
kfree_skb(skb);
|
||||
priv->netdev->stats.rx_errors++;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* If we change the header in loop mode, the checksum is corrupted. */
|
||||
if (priv->conn_req.protocol == CAIFPROTO_DATAGRAM_LOOP)
|
||||
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
||||
else
|
||||
skb->ip_summed = CHECKSUM_NONE;
|
||||
|
||||
netif_rx(skb);
|
||||
|
||||
/* Update statistics. */
|
||||
priv->netdev->stats.rx_packets++;
|
||||
priv->netdev->stats.rx_bytes += pktlen;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int delete_device(struct chnl_net *dev)
|
||||
{
|
||||
ASSERT_RTNL();
|
||||
if (dev->netdev)
|
||||
unregister_netdevice(dev->netdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void close_work(struct work_struct *work)
|
||||
{
|
||||
struct chnl_net *dev = NULL;
|
||||
struct list_head *list_node;
|
||||
struct list_head *_tmp;
|
||||
|
||||
rtnl_lock();
|
||||
list_for_each_safe(list_node, _tmp, &chnl_net_list) {
|
||||
dev = list_entry(list_node, struct chnl_net, list_field);
|
||||
if (dev->state == CAIF_SHUTDOWN)
|
||||
dev_close(dev->netdev);
|
||||
}
|
||||
rtnl_unlock();
|
||||
}
|
||||
static DECLARE_WORK(close_worker, close_work);
|
||||
|
||||
static void chnl_hold(struct cflayer *lyr)
|
||||
{
|
||||
struct chnl_net *priv = container_of(lyr, struct chnl_net, chnl);
|
||||
dev_hold(priv->netdev);
|
||||
}
|
||||
|
||||
static void chnl_put(struct cflayer *lyr)
|
||||
{
|
||||
struct chnl_net *priv = container_of(lyr, struct chnl_net, chnl);
|
||||
dev_put(priv->netdev);
|
||||
}
|
||||
|
||||
static void chnl_flowctrl_cb(struct cflayer *layr, enum caif_ctrlcmd flow,
|
||||
int phyid)
|
||||
{
|
||||
struct chnl_net *priv = container_of(layr, struct chnl_net, chnl);
|
||||
pr_debug("NET flowctrl func called flow: %s\n",
|
||||
flow == CAIF_CTRLCMD_FLOW_ON_IND ? "ON" :
|
||||
flow == CAIF_CTRLCMD_INIT_RSP ? "INIT" :
|
||||
flow == CAIF_CTRLCMD_FLOW_OFF_IND ? "OFF" :
|
||||
flow == CAIF_CTRLCMD_DEINIT_RSP ? "CLOSE/DEINIT" :
|
||||
flow == CAIF_CTRLCMD_INIT_FAIL_RSP ? "OPEN_FAIL" :
|
||||
flow == CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND ?
|
||||
"REMOTE_SHUTDOWN" : "UNKNOWN CTRL COMMAND");
|
||||
|
||||
|
||||
|
||||
switch (flow) {
|
||||
case CAIF_CTRLCMD_FLOW_OFF_IND:
|
||||
priv->flowenabled = false;
|
||||
netif_stop_queue(priv->netdev);
|
||||
break;
|
||||
case CAIF_CTRLCMD_DEINIT_RSP:
|
||||
priv->state = CAIF_DISCONNECTED;
|
||||
break;
|
||||
case CAIF_CTRLCMD_INIT_FAIL_RSP:
|
||||
priv->state = CAIF_DISCONNECTED;
|
||||
wake_up_interruptible(&priv->netmgmt_wq);
|
||||
break;
|
||||
case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND:
|
||||
priv->state = CAIF_SHUTDOWN;
|
||||
netif_tx_disable(priv->netdev);
|
||||
schedule_work(&close_worker);
|
||||
break;
|
||||
case CAIF_CTRLCMD_FLOW_ON_IND:
|
||||
priv->flowenabled = true;
|
||||
netif_wake_queue(priv->netdev);
|
||||
break;
|
||||
case CAIF_CTRLCMD_INIT_RSP:
|
||||
caif_client_register_refcnt(&priv->chnl, chnl_hold, chnl_put);
|
||||
priv->state = CAIF_CONNECTED;
|
||||
priv->flowenabled = true;
|
||||
netif_wake_queue(priv->netdev);
|
||||
wake_up_interruptible(&priv->netmgmt_wq);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static netdev_tx_t chnl_net_start_xmit(struct sk_buff *skb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
struct cfpkt *pkt = NULL;
|
||||
int len;
|
||||
int result = -1;
|
||||
/* Get our private data. */
|
||||
priv = netdev_priv(dev);
|
||||
|
||||
if (skb->len > priv->netdev->mtu) {
|
||||
pr_warn("Size of skb exceeded MTU\n");
|
||||
kfree_skb(skb);
|
||||
dev->stats.tx_errors++;
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
if (!priv->flowenabled) {
|
||||
pr_debug("dropping packets flow off\n");
|
||||
kfree_skb(skb);
|
||||
dev->stats.tx_dropped++;
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
if (priv->conn_req.protocol == CAIFPROTO_DATAGRAM_LOOP)
|
||||
swap(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr);
|
||||
|
||||
/* Store original SKB length. */
|
||||
len = skb->len;
|
||||
|
||||
pkt = cfpkt_fromnative(CAIF_DIR_OUT, (void *) skb);
|
||||
|
||||
/* Send the packet down the stack. */
|
||||
result = priv->chnl.dn->transmit(priv->chnl.dn, pkt);
|
||||
if (result) {
|
||||
dev->stats.tx_dropped++;
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
/* Update statistics. */
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += len;
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int chnl_net_open(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv = NULL;
|
||||
int result = -1;
|
||||
int llifindex, headroom, tailroom, mtu;
|
||||
struct net_device *lldev;
|
||||
ASSERT_RTNL();
|
||||
priv = netdev_priv(dev);
|
||||
if (!priv) {
|
||||
pr_debug("chnl_net_open: no priv\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (priv->state != CAIF_CONNECTING) {
|
||||
priv->state = CAIF_CONNECTING;
|
||||
result = caif_connect_client(dev_net(dev), &priv->conn_req,
|
||||
&priv->chnl, &llifindex,
|
||||
&headroom, &tailroom);
|
||||
if (result != 0) {
|
||||
pr_debug("err: "
|
||||
"Unable to register and open device,"
|
||||
" Err:%d\n",
|
||||
result);
|
||||
goto error;
|
||||
}
|
||||
|
||||
lldev = __dev_get_by_index(dev_net(dev), llifindex);
|
||||
|
||||
if (lldev == NULL) {
|
||||
pr_debug("no interface?\n");
|
||||
result = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev->needed_tailroom = tailroom + lldev->needed_tailroom;
|
||||
dev->hard_header_len = headroom + lldev->hard_header_len +
|
||||
lldev->needed_tailroom;
|
||||
|
||||
/*
|
||||
* MTU, head-room etc is not know before we have a
|
||||
* CAIF link layer device available. MTU calculation may
|
||||
* override initial RTNL configuration.
|
||||
* MTU is minimum of current mtu, link layer mtu pluss
|
||||
* CAIF head and tail, and PDP GPRS contexts max MTU.
|
||||
*/
|
||||
mtu = min_t(int, dev->mtu, lldev->mtu - (headroom + tailroom));
|
||||
mtu = min_t(int, GPRS_PDP_MTU, mtu);
|
||||
dev_set_mtu(dev, mtu);
|
||||
|
||||
if (mtu < 100) {
|
||||
pr_warn("CAIF Interface MTU too small (%d)\n", mtu);
|
||||
result = -ENODEV;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rtnl_unlock(); /* Release RTNL lock during connect wait */
|
||||
|
||||
result = wait_event_interruptible_timeout(priv->netmgmt_wq,
|
||||
priv->state != CAIF_CONNECTING,
|
||||
CONNECT_TIMEOUT);
|
||||
|
||||
rtnl_lock();
|
||||
|
||||
if (result == -ERESTARTSYS) {
|
||||
pr_debug("wait_event_interruptible woken by a signal\n");
|
||||
result = -ERESTARTSYS;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
pr_debug("connect timeout\n");
|
||||
result = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (priv->state != CAIF_CONNECTED) {
|
||||
pr_debug("connect failed\n");
|
||||
result = -ECONNREFUSED;
|
||||
goto error;
|
||||
}
|
||||
pr_debug("CAIF Netdevice connected\n");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
caif_disconnect_client(dev_net(dev), &priv->chnl);
|
||||
priv->state = CAIF_DISCONNECTED;
|
||||
pr_debug("state disconnected\n");
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static int chnl_net_stop(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
|
||||
ASSERT_RTNL();
|
||||
priv = netdev_priv(dev);
|
||||
priv->state = CAIF_DISCONNECTED;
|
||||
caif_disconnect_client(dev_net(dev), &priv->chnl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int chnl_net_init(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
ASSERT_RTNL();
|
||||
priv = netdev_priv(dev);
|
||||
INIT_LIST_HEAD(&priv->list_field);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void chnl_net_uninit(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
ASSERT_RTNL();
|
||||
priv = netdev_priv(dev);
|
||||
list_del_init(&priv->list_field);
|
||||
}
|
||||
|
||||
static const struct net_device_ops netdev_ops = {
|
||||
.ndo_open = chnl_net_open,
|
||||
.ndo_stop = chnl_net_stop,
|
||||
.ndo_init = chnl_net_init,
|
||||
.ndo_uninit = chnl_net_uninit,
|
||||
.ndo_start_xmit = chnl_net_start_xmit,
|
||||
};
|
||||
|
||||
static void chnl_net_destructor(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv = netdev_priv(dev);
|
||||
caif_free_client(&priv->chnl);
|
||||
}
|
||||
|
||||
static void ipcaif_net_setup(struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
dev->netdev_ops = &netdev_ops;
|
||||
dev->needs_free_netdev = true;
|
||||
dev->priv_destructor = chnl_net_destructor;
|
||||
dev->flags |= IFF_NOARP;
|
||||
dev->flags |= IFF_POINTOPOINT;
|
||||
dev->mtu = GPRS_PDP_MTU;
|
||||
dev->tx_queue_len = CAIF_NET_DEFAULT_QUEUE_LEN;
|
||||
|
||||
priv = netdev_priv(dev);
|
||||
priv->chnl.receive = chnl_recv_cb;
|
||||
priv->chnl.ctrlcmd = chnl_flowctrl_cb;
|
||||
priv->netdev = dev;
|
||||
priv->conn_req.protocol = CAIFPROTO_DATAGRAM;
|
||||
priv->conn_req.link_selector = CAIF_LINK_HIGH_BANDW;
|
||||
priv->conn_req.priority = CAIF_PRIO_LOW;
|
||||
/* Insert illegal value */
|
||||
priv->conn_req.sockaddr.u.dgm.connection_id = UNDEF_CONNID;
|
||||
priv->flowenabled = false;
|
||||
|
||||
init_waitqueue_head(&priv->netmgmt_wq);
|
||||
}
|
||||
|
||||
|
||||
static int ipcaif_fill_info(struct sk_buff *skb, const struct net_device *dev)
|
||||
{
|
||||
struct chnl_net *priv;
|
||||
u8 loop;
|
||||
priv = netdev_priv(dev);
|
||||
if (nla_put_u32(skb, IFLA_CAIF_IPV4_CONNID,
|
||||
priv->conn_req.sockaddr.u.dgm.connection_id) ||
|
||||
nla_put_u32(skb, IFLA_CAIF_IPV6_CONNID,
|
||||
priv->conn_req.sockaddr.u.dgm.connection_id))
|
||||
goto nla_put_failure;
|
||||
loop = priv->conn_req.protocol == CAIFPROTO_DATAGRAM_LOOP;
|
||||
if (nla_put_u8(skb, IFLA_CAIF_LOOPBACK, loop))
|
||||
goto nla_put_failure;
|
||||
return 0;
|
||||
nla_put_failure:
|
||||
return -EMSGSIZE;
|
||||
|
||||
}
|
||||
|
||||
static void caif_netlink_parms(struct nlattr *data[],
|
||||
struct caif_connect_request *conn_req)
|
||||
{
|
||||
if (!data) {
|
||||
pr_warn("no params data found\n");
|
||||
return;
|
||||
}
|
||||
if (data[IFLA_CAIF_IPV4_CONNID])
|
||||
conn_req->sockaddr.u.dgm.connection_id =
|
||||
nla_get_u32(data[IFLA_CAIF_IPV4_CONNID]);
|
||||
if (data[IFLA_CAIF_IPV6_CONNID])
|
||||
conn_req->sockaddr.u.dgm.connection_id =
|
||||
nla_get_u32(data[IFLA_CAIF_IPV6_CONNID]);
|
||||
if (data[IFLA_CAIF_LOOPBACK]) {
|
||||
if (nla_get_u8(data[IFLA_CAIF_LOOPBACK]))
|
||||
conn_req->protocol = CAIFPROTO_DATAGRAM_LOOP;
|
||||
else
|
||||
conn_req->protocol = CAIFPROTO_DATAGRAM;
|
||||
}
|
||||
}
|
||||
|
||||
static int ipcaif_newlink(struct net_device *dev,
|
||||
struct rtnl_newlink_params *params,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlattr **data = params->data;
|
||||
int ret;
|
||||
struct chnl_net *caifdev;
|
||||
ASSERT_RTNL();
|
||||
caifdev = netdev_priv(dev);
|
||||
caif_netlink_parms(data, &caifdev->conn_req);
|
||||
|
||||
ret = register_netdevice(dev);
|
||||
if (ret)
|
||||
pr_warn("device rtml registration failed\n");
|
||||
else
|
||||
list_add(&caifdev->list_field, &chnl_net_list);
|
||||
|
||||
/* Use ifindex as connection id, and use loopback channel default. */
|
||||
if (caifdev->conn_req.sockaddr.u.dgm.connection_id == UNDEF_CONNID) {
|
||||
caifdev->conn_req.sockaddr.u.dgm.connection_id = dev->ifindex;
|
||||
caifdev->conn_req.protocol = CAIFPROTO_DATAGRAM_LOOP;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ipcaif_changelink(struct net_device *dev, struct nlattr *tb[],
|
||||
struct nlattr *data[],
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct chnl_net *caifdev;
|
||||
ASSERT_RTNL();
|
||||
caifdev = netdev_priv(dev);
|
||||
caif_netlink_parms(data, &caifdev->conn_req);
|
||||
netdev_state_change(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t ipcaif_get_size(const struct net_device *dev)
|
||||
{
|
||||
return
|
||||
/* IFLA_CAIF_IPV4_CONNID */
|
||||
nla_total_size(4) +
|
||||
/* IFLA_CAIF_IPV6_CONNID */
|
||||
nla_total_size(4) +
|
||||
/* IFLA_CAIF_LOOPBACK */
|
||||
nla_total_size(2) +
|
||||
0;
|
||||
}
|
||||
|
||||
static const struct nla_policy ipcaif_policy[IFLA_CAIF_MAX + 1] = {
|
||||
[IFLA_CAIF_IPV4_CONNID] = { .type = NLA_U32 },
|
||||
[IFLA_CAIF_IPV6_CONNID] = { .type = NLA_U32 },
|
||||
[IFLA_CAIF_LOOPBACK] = { .type = NLA_U8 }
|
||||
};
|
||||
|
||||
|
||||
static struct rtnl_link_ops ipcaif_link_ops __read_mostly = {
|
||||
.kind = "caif",
|
||||
.priv_size = sizeof(struct chnl_net),
|
||||
.setup = ipcaif_net_setup,
|
||||
.maxtype = IFLA_CAIF_MAX,
|
||||
.policy = ipcaif_policy,
|
||||
.newlink = ipcaif_newlink,
|
||||
.changelink = ipcaif_changelink,
|
||||
.get_size = ipcaif_get_size,
|
||||
.fill_info = ipcaif_fill_info,
|
||||
|
||||
};
|
||||
|
||||
static int __init chnl_init_module(void)
|
||||
{
|
||||
return rtnl_link_register(&ipcaif_link_ops);
|
||||
}
|
||||
|
||||
static void __exit chnl_exit_module(void)
|
||||
{
|
||||
struct chnl_net *dev = NULL;
|
||||
struct list_head *list_node;
|
||||
struct list_head *_tmp;
|
||||
rtnl_link_unregister(&ipcaif_link_ops);
|
||||
rtnl_lock();
|
||||
list_for_each_safe(list_node, _tmp, &chnl_net_list) {
|
||||
dev = list_entry(list_node, struct chnl_net, list_field);
|
||||
list_del_init(list_node);
|
||||
delete_device(dev);
|
||||
}
|
||||
rtnl_unlock();
|
||||
}
|
||||
|
||||
module_init(chnl_init_module);
|
||||
module_exit(chnl_exit_module);
|
||||
Loading…
Reference in New Issue
Block a user