add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

16
kernel/net/phonet/Kconfig Normal file
View File

@@ -0,0 +1,16 @@
#
# Phonet protocol
#
config PHONET
tristate "Phonet protocols family"
help
The Phone Network protocol (PhoNet) is a packet-oriented
communication protocol developed by Nokia for use with its modems.
This is required for Maemo to use cellular data connectivity (if
supported). It can also be used to control Nokia phones
from a Linux computer, although AT commands may be easier to use.
To compile this driver as a module, choose M here: the module
will be called phonet. If unsure, say N.

View File

@@ -0,0 +1,11 @@
obj-$(CONFIG_PHONET) += phonet.o pn_pep.o
phonet-objs := \
pn_dev.o \
pn_netlink.o \
socket.o \
datagram.o \
sysctl.o \
af_phonet.o
pn_pep-objs := pep.o pep-gprs.o

View File

@@ -0,0 +1,479 @@
/*
* File: af_phonet.c
*
* Phonet protocols family
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
* Original author: Sakari Ailus <sakari.ailus@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/unaligned.h>
#include <net/sock.h>
#include <linux/if_phonet.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
#include <net/phonet/pn_dev.h>
/* Transport protocol registration */
static struct phonet_protocol *proto_tab[PHONET_NPROTO] __read_mostly;
static DEFINE_SPINLOCK(proto_tab_lock);
static struct phonet_protocol *phonet_proto_get(int protocol)
{
struct phonet_protocol *pp;
if (protocol >= PHONET_NPROTO)
return NULL;
spin_lock(&proto_tab_lock);
pp = proto_tab[protocol];
if (pp && !try_module_get(pp->prot->owner))
pp = NULL;
spin_unlock(&proto_tab_lock);
return pp;
}
static inline void phonet_proto_put(struct phonet_protocol *pp)
{
module_put(pp->prot->owner);
}
/* protocol family functions */
static int pn_socket_create(struct net *net, struct socket *sock, int protocol)
{
struct sock *sk;
struct pn_sock *pn;
struct phonet_protocol *pnp;
int err;
if (!net_eq(net, &init_net))
return -EAFNOSUPPORT;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (protocol == 0) {
/* Default protocol selection */
switch (sock->type) {
case SOCK_DGRAM:
protocol = PN_PROTO_PHONET;
break;
case SOCK_SEQPACKET:
protocol = PN_PROTO_PIPE;
break;
default:
return -EPROTONOSUPPORT;
}
}
pnp = phonet_proto_get(protocol);
if (pnp == NULL &&
request_module("net-pf-%d-proto-%d", PF_PHONET, protocol) == 0)
pnp = phonet_proto_get(protocol);
if (pnp == NULL)
return -EPROTONOSUPPORT;
if (sock->type != pnp->sock_type) {
err = -EPROTONOSUPPORT;
goto out;
}
sk = sk_alloc(net, PF_PHONET, GFP_KERNEL, pnp->prot);
if (sk == NULL) {
err = -ENOMEM;
goto out;
}
sock_init_data(sock, sk);
sock->state = SS_UNCONNECTED;
sock->ops = pnp->ops;
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
sk->sk_protocol = protocol;
pn = pn_sk(sk);
pn->sobject = 0;
pn->resource = 0;
sk->sk_prot->init(sk);
err = 0;
out:
phonet_proto_put(pnp);
return err;
}
static struct net_proto_family phonet_proto_family = {
.family = PF_PHONET,
.create = pn_socket_create,
.owner = THIS_MODULE,
};
/* Phonet device header operations */
static int pn_header_create(struct sk_buff *skb, struct net_device *dev,
unsigned short type, const void *daddr,
const void *saddr, unsigned len)
{
u8 *media = skb_push(skb, 1);
if (type != ETH_P_PHONET)
return -1;
if (!saddr)
saddr = dev->dev_addr;
*media = *(const u8 *)saddr;
return 1;
}
static int pn_header_parse(const struct sk_buff *skb, unsigned char *haddr)
{
const u8 *media = skb_mac_header(skb);
*haddr = *media;
return 1;
}
struct header_ops phonet_header_ops = {
.create = pn_header_create,
.parse = pn_header_parse,
};
EXPORT_SYMBOL(phonet_header_ops);
/*
* Prepends an ISI header and sends a datagram.
*/
static int pn_send(struct sk_buff *skb, struct net_device *dev,
u16 dst, u16 src, u8 res, u8 irq)
{
struct phonethdr *ph;
int err;
if (skb->len + 2 > 0xffff /* Phonet length field limit */ ||
skb->len + sizeof(struct phonethdr) > dev->mtu) {
err = -EMSGSIZE;
goto drop;
}
/* Broadcast sending is not implemented */
if (pn_addr(dst) == PNADDR_BROADCAST) {
err = -EOPNOTSUPP;
goto drop;
}
skb_reset_transport_header(skb);
WARN_ON(skb_headroom(skb) & 1); /* HW assumes word alignment */
skb_push(skb, sizeof(struct phonethdr));
skb_reset_network_header(skb);
ph = pn_hdr(skb);
ph->pn_rdev = pn_dev(dst);
ph->pn_sdev = pn_dev(src);
ph->pn_res = res;
ph->pn_length = __cpu_to_be16(skb->len + 2 - sizeof(*ph));
ph->pn_robj = pn_obj(dst);
ph->pn_sobj = pn_obj(src);
skb->protocol = htons(ETH_P_PHONET);
skb->priority = 0;
skb->dev = dev;
if (pn_addr(src) == pn_addr(dst)) {
skb_reset_mac_header(skb);
skb->pkt_type = PACKET_LOOPBACK;
skb_orphan(skb);
if (irq)
netif_rx(skb);
else
netif_rx_ni(skb);
err = 0;
} else {
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
NULL, NULL, skb->len);
if (err < 0) {
err = -EHOSTUNREACH;
goto drop;
}
err = dev_queue_xmit(skb);
}
return err;
drop:
kfree_skb(skb);
return err;
}
static int pn_raw_send(const void *data, int len, struct net_device *dev,
u16 dst, u16 src, u8 res)
{
struct sk_buff *skb = alloc_skb(MAX_PHONET_HEADER + len, GFP_ATOMIC);
if (skb == NULL)
return -ENOMEM;
skb_reserve(skb, MAX_PHONET_HEADER);
__skb_put(skb, len);
skb_copy_to_linear_data(skb, data, len);
return pn_send(skb, dev, dst, src, res, 1);
}
/*
* Create a Phonet header for the skb and send it out. Returns
* non-zero error code if failed. The skb is freed then.
*/
int pn_skb_send(struct sock *sk, struct sk_buff *skb,
const struct sockaddr_pn *target)
{
struct net_device *dev;
struct pn_sock *pn = pn_sk(sk);
int err;
u16 src;
u8 daddr = pn_sockaddr_get_addr(target), saddr = PN_NO_ADDR;
err = -EHOSTUNREACH;
if (sk->sk_bound_dev_if)
dev = dev_get_by_index(sock_net(sk), sk->sk_bound_dev_if);
else
dev = phonet_device_get(sock_net(sk));
if (!dev || !(dev->flags & IFF_UP))
goto drop;
saddr = phonet_address_get(dev, daddr);
if (saddr == PN_NO_ADDR)
goto drop;
src = pn->sobject;
if (!pn_addr(src))
src = pn_object(saddr, pn_obj(src));
err = pn_send(skb, dev, pn_sockaddr_get_object(target),
src, pn_sockaddr_get_resource(target), 0);
dev_put(dev);
return err;
drop:
kfree_skb(skb);
if (dev)
dev_put(dev);
return err;
}
EXPORT_SYMBOL(pn_skb_send);
/* Do not send an error message in response to an error message */
static inline int can_respond(struct sk_buff *skb)
{
const struct phonethdr *ph;
const struct phonetmsg *pm;
u8 submsg_id;
if (!pskb_may_pull(skb, 3))
return 0;
ph = pn_hdr(skb);
if (ph->pn_res == PN_PREFIX && !pskb_may_pull(skb, 5))
return 0;
if (ph->pn_res == PN_COMMGR) /* indications */
return 0;
ph = pn_hdr(skb); /* re-acquires the pointer */
pm = pn_msg(skb);
if (pm->pn_msg_id != PN_COMMON_MESSAGE)
return 1;
submsg_id = (ph->pn_res == PN_PREFIX)
? pm->pn_e_submsg_id : pm->pn_submsg_id;
if (submsg_id != PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP &&
pm->pn_e_submsg_id != PN_COMM_SERVICE_NOT_IDENTIFIED_RESP)
return 1;
return 0;
}
static int send_obj_unreachable(struct sk_buff *rskb)
{
const struct phonethdr *oph = pn_hdr(rskb);
const struct phonetmsg *opm = pn_msg(rskb);
struct phonetmsg resp;
memset(&resp, 0, sizeof(resp));
resp.pn_trans_id = opm->pn_trans_id;
resp.pn_msg_id = PN_COMMON_MESSAGE;
if (oph->pn_res == PN_PREFIX) {
resp.pn_e_res_id = opm->pn_e_res_id;
resp.pn_e_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP;
resp.pn_e_orig_msg_id = opm->pn_msg_id;
resp.pn_e_status = 0;
} else {
resp.pn_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP;
resp.pn_orig_msg_id = opm->pn_msg_id;
resp.pn_status = 0;
}
return pn_raw_send(&resp, sizeof(resp), rskb->dev,
pn_object(oph->pn_sdev, oph->pn_sobj),
pn_object(oph->pn_rdev, oph->pn_robj),
oph->pn_res);
}
static int send_reset_indications(struct sk_buff *rskb)
{
struct phonethdr *oph = pn_hdr(rskb);
static const u8 data[4] = {
0x00 /* trans ID */, 0x10 /* subscribe msg */,
0x00 /* subscription count */, 0x00 /* dummy */
};
return pn_raw_send(data, sizeof(data), rskb->dev,
pn_object(oph->pn_sdev, 0x00),
pn_object(oph->pn_rdev, oph->pn_robj),
PN_COMMGR);
}
/* packet type functions */
/*
* Stuff received packets to associated sockets.
* On error, returns non-zero and releases the skb.
*/
static int phonet_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pkttype,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);
struct phonethdr *ph;
struct sockaddr_pn sa;
u16 len;
if (!net_eq(net, &init_net))
goto out;
/* check we have at least a full Phonet header */
if (!pskb_pull(skb, sizeof(struct phonethdr)))
goto out;
/* check that the advertised length is correct */
ph = pn_hdr(skb);
len = get_unaligned_be16(&ph->pn_length);
if (len < 2)
goto out;
len -= 2;
if ((len > skb->len) || pskb_trim(skb, len))
goto out;
skb_reset_transport_header(skb);
pn_skb_get_dst_sockaddr(skb, &sa);
/* check if we are the destination */
if (phonet_address_lookup(net, pn_sockaddr_get_addr(&sa)) == 0) {
/* Phonet packet input */
struct sock *sk = pn_find_sock_by_sa(net, &sa);
if (sk)
return sk_receive_skb(sk, skb, 0);
if (can_respond(skb)) {
send_obj_unreachable(skb);
send_reset_indications(skb);
}
}
out:
kfree_skb(skb);
return NET_RX_DROP;
}
static struct packet_type phonet_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_PHONET),
.func = phonet_rcv,
};
int __init_or_module phonet_proto_register(int protocol,
struct phonet_protocol *pp)
{
int err = 0;
if (protocol >= PHONET_NPROTO)
return -EINVAL;
err = proto_register(pp->prot, 1);
if (err)
return err;
spin_lock(&proto_tab_lock);
if (proto_tab[protocol])
err = -EBUSY;
else
proto_tab[protocol] = pp;
spin_unlock(&proto_tab_lock);
return err;
}
EXPORT_SYMBOL(phonet_proto_register);
void phonet_proto_unregister(int protocol, struct phonet_protocol *pp)
{
spin_lock(&proto_tab_lock);
BUG_ON(proto_tab[protocol] != pp);
proto_tab[protocol] = NULL;
spin_unlock(&proto_tab_lock);
proto_unregister(pp->prot);
}
EXPORT_SYMBOL(phonet_proto_unregister);
/* Module registration */
static int __init phonet_init(void)
{
int err;
err = phonet_device_init();
if (err)
return err;
err = sock_register(&phonet_proto_family);
if (err) {
printk(KERN_ALERT
"phonet protocol family initialization failed\n");
goto err_sock;
}
dev_add_pack(&phonet_packet_type);
phonet_sysctl_init();
err = isi_register();
if (err)
goto err;
return 0;
err:
phonet_sysctl_exit();
sock_unregister(PF_PHONET);
dev_remove_pack(&phonet_packet_type);
err_sock:
phonet_device_exit();
return err;
}
static void __exit phonet_exit(void)
{
isi_unregister();
phonet_sysctl_exit();
sock_unregister(PF_PHONET);
dev_remove_pack(&phonet_packet_type);
phonet_device_exit();
}
module_init(phonet_init);
module_exit(phonet_exit);
MODULE_DESCRIPTION("Phonet protocol stack for Linux");
MODULE_LICENSE("GPL");
MODULE_ALIAS_NETPROTO(PF_PHONET);

View File

@@ -0,0 +1,200 @@
/*
* File: datagram.c
*
* Datagram (ISI) Phonet sockets
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
* Original author: Sakari Ailus <sakari.ailus@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/socket.h>
#include <asm/ioctls.h>
#include <net/sock.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
static int pn_backlog_rcv(struct sock *sk, struct sk_buff *skb);
/* associated socket ceases to exist */
static void pn_sock_close(struct sock *sk, long timeout)
{
sk_common_release(sk);
}
static int pn_ioctl(struct sock *sk, int cmd, unsigned long arg)
{
struct sk_buff *skb;
int answ;
switch (cmd) {
case SIOCINQ:
lock_sock(sk);
skb = skb_peek(&sk->sk_receive_queue);
answ = skb ? skb->len : 0;
release_sock(sk);
return put_user(answ, (int __user *)arg);
}
return -ENOIOCTLCMD;
}
/* Destroy socket. All references are gone. */
static void pn_destruct(struct sock *sk)
{
skb_queue_purge(&sk->sk_receive_queue);
}
static int pn_init(struct sock *sk)
{
sk->sk_destruct = pn_destruct;
return 0;
}
static int pn_sendmsg(struct kiocb *iocb, struct sock *sk,
struct msghdr *msg, size_t len)
{
struct sockaddr_pn *target;
struct sk_buff *skb;
int err;
if (msg->msg_flags & MSG_OOB)
return -EOPNOTSUPP;
if (msg->msg_name == NULL)
return -EDESTADDRREQ;
if (msg->msg_namelen < sizeof(struct sockaddr_pn))
return -EINVAL;
target = (struct sockaddr_pn *)msg->msg_name;
if (target->spn_family != AF_PHONET)
return -EAFNOSUPPORT;
skb = sock_alloc_send_skb(sk, MAX_PHONET_HEADER + len,
msg->msg_flags & MSG_DONTWAIT, &err);
if (skb == NULL)
return err;
skb_reserve(skb, MAX_PHONET_HEADER);
err = memcpy_fromiovec((void *)skb_put(skb, len), msg->msg_iov, len);
if (err < 0) {
kfree_skb(skb);
return err;
}
/*
* Fill in the Phonet header and
* finally pass the packet forwards.
*/
err = pn_skb_send(sk, skb, target);
/* If ok, return len. */
return (err >= 0) ? len : err;
}
static int pn_recvmsg(struct kiocb *iocb, struct sock *sk,
struct msghdr *msg, size_t len, int noblock,
int flags, int *addr_len)
{
struct sk_buff *skb = NULL;
struct sockaddr_pn sa;
int rval = -EOPNOTSUPP;
int copylen;
if (flags & MSG_OOB)
goto out_nofree;
if (addr_len)
*addr_len = sizeof(sa);
skb = skb_recv_datagram(sk, flags, noblock, &rval);
if (skb == NULL)
goto out_nofree;
pn_skb_get_src_sockaddr(skb, &sa);
copylen = skb->len;
if (len < copylen) {
msg->msg_flags |= MSG_TRUNC;
copylen = len;
}
rval = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copylen);
if (rval) {
rval = -EFAULT;
goto out;
}
rval = (flags & MSG_TRUNC) ? skb->len : copylen;
if (msg->msg_name != NULL)
memcpy(msg->msg_name, &sa, sizeof(struct sockaddr_pn));
out:
skb_free_datagram(sk, skb);
out_nofree:
return rval;
}
/* Queue an skb for a sock. */
static int pn_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
int err = sock_queue_rcv_skb(sk, skb);
if (err < 0) {
kfree_skb(skb);
if (err == -ENOMEM)
atomic_inc(&sk->sk_drops);
}
return err ? NET_RX_DROP : NET_RX_SUCCESS;
}
/* Module registration */
static struct proto pn_proto = {
.close = pn_sock_close,
.ioctl = pn_ioctl,
.init = pn_init,
.sendmsg = pn_sendmsg,
.recvmsg = pn_recvmsg,
.backlog_rcv = pn_backlog_rcv,
.hash = pn_sock_hash,
.unhash = pn_sock_unhash,
.get_port = pn_sock_get_port,
.obj_size = sizeof(struct pn_sock),
.owner = THIS_MODULE,
.name = "PHONET",
};
static struct phonet_protocol pn_dgram_proto = {
.ops = &phonet_dgram_ops,
.prot = &pn_proto,
.sock_type = SOCK_DGRAM,
};
int __init isi_register(void)
{
return phonet_proto_register(PN_PROTO_PHONET, &pn_dgram_proto);
}
void __exit isi_unregister(void)
{
phonet_proto_unregister(PN_PROTO_PHONET, &pn_dgram_proto);
}

View File

@@ -0,0 +1,328 @@
/*
* File: pep-gprs.c
*
* GPRS over Phonet pipe end point socket
*
* Copyright (C) 2008 Nokia Corporation.
*
* Author: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <net/sock.h>
#include <linux/if_phonet.h>
#include <net/tcp_states.h>
#include <net/phonet/gprs.h>
#define GPRS_DEFAULT_MTU 1400
struct gprs_dev {
struct sock *sk;
void (*old_state_change)(struct sock *);
void (*old_data_ready)(struct sock *, int);
void (*old_write_space)(struct sock *);
struct net_device *dev;
};
static __be16 gprs_type_trans(struct sk_buff *skb)
{
const u8 *pvfc;
u8 buf;
pvfc = skb_header_pointer(skb, 0, 1, &buf);
if (!pvfc)
return htons(0);
/* Look at IP version field */
switch (*pvfc >> 4) {
case 4:
return htons(ETH_P_IP);
case 6:
return htons(ETH_P_IPV6);
}
return htons(0);
}
static void gprs_writeable(struct gprs_dev *gp)
{
struct net_device *dev = gp->dev;
if (pep_writeable(gp->sk))
netif_wake_queue(dev);
}
/*
* Socket callbacks
*/
static void gprs_state_change(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
if (sk->sk_state == TCP_CLOSE_WAIT) {
struct net_device *dev = gp->dev;
netif_stop_queue(dev);
netif_carrier_off(dev);
}
}
static int gprs_recv(struct gprs_dev *gp, struct sk_buff *skb)
{
struct net_device *dev = gp->dev;
int err = 0;
__be16 protocol = gprs_type_trans(skb);
if (!protocol) {
err = -EINVAL;
goto drop;
}
if (likely(skb_headroom(skb) & 3)) {
struct sk_buff *rskb, *fs;
int flen = 0;
/* Phonet Pipe data header is misaligned (3 bytes),
* so wrap the IP packet as a single fragment of an head-less
* socket buffer. The network stack will pull what it needs,
* but at least, the whole IP payload is not memcpy'd. */
rskb = netdev_alloc_skb(dev, 0);
if (!rskb) {
err = -ENOBUFS;
goto drop;
}
skb_shinfo(rskb)->frag_list = skb;
rskb->len += skb->len;
rskb->data_len += rskb->len;
rskb->truesize += rskb->len;
/* Avoid nested fragments */
skb_walk_frags(skb, fs)
flen += fs->len;
skb->next = skb_shinfo(skb)->frag_list;
skb_frag_list_init(skb);
skb->len -= flen;
skb->data_len -= flen;
skb->truesize -= flen;
skb = rskb;
}
skb->protocol = protocol;
skb_reset_mac_header(skb);
skb->dev = dev;
if (likely(dev->flags & IFF_UP)) {
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
netif_rx(skb);
skb = NULL;
} else
err = -ENODEV;
drop:
if (skb) {
dev_kfree_skb(skb);
dev->stats.rx_dropped++;
}
return err;
}
static void gprs_data_ready(struct sock *sk, int len)
{
struct gprs_dev *gp = sk->sk_user_data;
struct sk_buff *skb;
while ((skb = pep_read(sk)) != NULL) {
skb_orphan(skb);
gprs_recv(gp, skb);
}
}
static void gprs_write_space(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
if (netif_running(gp->dev))
gprs_writeable(gp);
}
/*
* Network device callbacks
*/
static int gprs_open(struct net_device *dev)
{
struct gprs_dev *gp = netdev_priv(dev);
gprs_writeable(gp);
return 0;
}
static int gprs_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static netdev_tx_t gprs_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct gprs_dev *gp = netdev_priv(dev);
struct sock *sk = gp->sk;
int len, err;
switch (skb->protocol) {
case htons(ETH_P_IP):
case htons(ETH_P_IPV6):
break;
default:
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
skb_orphan(skb);
skb_set_owner_w(skb, sk);
len = skb->len;
err = pep_write(sk, skb);
if (err) {
LIMIT_NETDEBUG(KERN_WARNING"%s: TX error (%d)\n",
dev->name, err);
dev->stats.tx_aborted_errors++;
dev->stats.tx_errors++;
} else {
dev->stats.tx_packets++;
dev->stats.tx_bytes += len;
}
netif_stop_queue(dev);
if (pep_writeable(sk))
netif_wake_queue(dev);
return NETDEV_TX_OK;
}
static int gprs_set_mtu(struct net_device *dev, int new_mtu)
{
if ((new_mtu < 576) || (new_mtu > (PHONET_MAX_MTU - 11)))
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
static const struct net_device_ops gprs_netdev_ops = {
.ndo_open = gprs_open,
.ndo_stop = gprs_close,
.ndo_start_xmit = gprs_xmit,
.ndo_change_mtu = gprs_set_mtu,
};
static void gprs_setup(struct net_device *dev)
{
dev->features = NETIF_F_FRAGLIST;
dev->type = ARPHRD_PHONET_PIPE;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = GPRS_DEFAULT_MTU;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->tx_queue_len = 10;
dev->netdev_ops = &gprs_netdev_ops;
dev->destructor = free_netdev;
}
/*
* External interface
*/
/*
* Attach a GPRS interface to a datagram socket.
* Returns the interface index on success, negative error code on error.
*/
int gprs_attach(struct sock *sk)
{
static const char ifname[] = "gprs%d";
struct gprs_dev *gp;
struct net_device *dev;
int err;
if (unlikely(sk->sk_type == SOCK_STREAM))
return -EINVAL; /* need packet boundaries */
/* Create net device */
dev = alloc_netdev(sizeof(*gp), ifname, gprs_setup);
if (!dev)
return -ENOMEM;
gp = netdev_priv(dev);
gp->sk = sk;
gp->dev = dev;
netif_stop_queue(dev);
err = register_netdev(dev);
if (err) {
free_netdev(dev);
return err;
}
lock_sock(sk);
if (unlikely(sk->sk_user_data)) {
err = -EBUSY;
goto out_rel;
}
if (unlikely((1 << sk->sk_state & (TCPF_CLOSE|TCPF_LISTEN)) ||
sock_flag(sk, SOCK_DEAD))) {
err = -EINVAL;
goto out_rel;
}
sk->sk_user_data = gp;
gp->old_state_change = sk->sk_state_change;
gp->old_data_ready = sk->sk_data_ready;
gp->old_write_space = sk->sk_write_space;
sk->sk_state_change = gprs_state_change;
sk->sk_data_ready = gprs_data_ready;
sk->sk_write_space = gprs_write_space;
release_sock(sk);
sock_hold(sk);
printk(KERN_DEBUG"%s: attached\n", dev->name);
return dev->ifindex;
out_rel:
release_sock(sk);
unregister_netdev(dev);
return err;
}
void gprs_detach(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
struct net_device *dev = gp->dev;
lock_sock(sk);
sk->sk_user_data = NULL;
sk->sk_state_change = gp->old_state_change;
sk->sk_data_ready = gp->old_data_ready;
sk->sk_write_space = gp->old_write_space;
release_sock(sk);
printk(KERN_DEBUG"%s: detached\n", dev->name);
unregister_netdev(dev);
sock_put(sk);
}

1086
kernel/net/phonet/pep.c Normal file

File diff suppressed because it is too large Load Diff

313
kernel/net/phonet/pn_dev.c Normal file
View File

@@ -0,0 +1,313 @@
/*
* File: pn_dev.c
*
* Phonet network device
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
* Original author: Sakari Ailus <sakari.ailus@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/phonet.h>
#include <linux/proc_fs.h>
#include <linux/if_arp.h>
#include <net/sock.h>
#include <net/netns/generic.h>
#include <net/phonet/pn_dev.h>
struct phonet_net {
struct phonet_device_list pndevs;
};
int phonet_net_id;
struct phonet_device_list *phonet_device_list(struct net *net)
{
struct phonet_net *pnn = net_generic(net, phonet_net_id);
return &pnn->pndevs;
}
/* Allocate new Phonet device. */
static struct phonet_device *__phonet_device_alloc(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd = kmalloc(sizeof(*pnd), GFP_ATOMIC);
if (pnd == NULL)
return NULL;
pnd->netdev = dev;
bitmap_zero(pnd->addrs, 64);
list_add(&pnd->list, &pndevs->list);
return pnd;
}
static struct phonet_device *__phonet_get(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
list_for_each_entry(pnd, &pndevs->list, list) {
if (pnd->netdev == dev)
return pnd;
}
return NULL;
}
static void phonet_device_destroy(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
ASSERT_RTNL();
spin_lock_bh(&pndevs->lock);
pnd = __phonet_get(dev);
if (pnd)
list_del(&pnd->list);
spin_unlock_bh(&pndevs->lock);
if (pnd) {
u8 addr;
for (addr = find_first_bit(pnd->addrs, 64); addr < 64;
addr = find_next_bit(pnd->addrs, 64, 1+addr))
phonet_address_notify(RTM_DELADDR, dev, addr);
kfree(pnd);
}
}
struct net_device *phonet_device_get(struct net *net)
{
struct phonet_device_list *pndevs = phonet_device_list(net);
struct phonet_device *pnd;
struct net_device *dev = NULL;
spin_lock_bh(&pndevs->lock);
list_for_each_entry(pnd, &pndevs->list, list) {
dev = pnd->netdev;
BUG_ON(!dev);
if ((dev->reg_state == NETREG_REGISTERED) &&
((pnd->netdev->flags & IFF_UP)) == IFF_UP)
break;
dev = NULL;
}
if (dev)
dev_hold(dev);
spin_unlock_bh(&pndevs->lock);
return dev;
}
int phonet_address_add(struct net_device *dev, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
int err = 0;
spin_lock_bh(&pndevs->lock);
/* Find or create Phonet-specific device data */
pnd = __phonet_get(dev);
if (pnd == NULL)
pnd = __phonet_device_alloc(dev);
if (unlikely(pnd == NULL))
err = -ENOMEM;
else if (test_and_set_bit(addr >> 2, pnd->addrs))
err = -EEXIST;
spin_unlock_bh(&pndevs->lock);
return err;
}
int phonet_address_del(struct net_device *dev, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
int err = 0;
spin_lock_bh(&pndevs->lock);
pnd = __phonet_get(dev);
if (!pnd || !test_and_clear_bit(addr >> 2, pnd->addrs))
err = -EADDRNOTAVAIL;
else if (bitmap_empty(pnd->addrs, 64)) {
list_del(&pnd->list);
kfree(pnd);
}
spin_unlock_bh(&pndevs->lock);
return err;
}
/* Gets a source address toward a destination, through a interface. */
u8 phonet_address_get(struct net_device *dev, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
spin_lock_bh(&pndevs->lock);
pnd = __phonet_get(dev);
if (pnd) {
BUG_ON(bitmap_empty(pnd->addrs, 64));
/* Use same source address as destination, if possible */
if (!test_bit(addr >> 2, pnd->addrs))
addr = find_first_bit(pnd->addrs, 64) << 2;
} else
addr = PN_NO_ADDR;
spin_unlock_bh(&pndevs->lock);
return addr;
}
int phonet_address_lookup(struct net *net, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(net);
struct phonet_device *pnd;
int err = -EADDRNOTAVAIL;
spin_lock_bh(&pndevs->lock);
list_for_each_entry(pnd, &pndevs->list, list) {
/* Don't allow unregistering devices! */
if ((pnd->netdev->reg_state != NETREG_REGISTERED) ||
((pnd->netdev->flags & IFF_UP)) != IFF_UP)
continue;
if (test_bit(addr >> 2, pnd->addrs)) {
err = 0;
goto found;
}
}
found:
spin_unlock_bh(&pndevs->lock);
return err;
}
/* automatically configure a Phonet device, if supported */
static int phonet_device_autoconf(struct net_device *dev)
{
struct if_phonet_req req;
int ret;
if (!dev->netdev_ops->ndo_do_ioctl)
return -EOPNOTSUPP;
ret = dev->netdev_ops->ndo_do_ioctl(dev, (struct ifreq *)&req,
SIOCPNGAUTOCONF);
if (ret < 0)
return ret;
ASSERT_RTNL();
ret = phonet_address_add(dev, req.ifr_phonet_autoconf.device);
if (ret)
return ret;
phonet_address_notify(RTM_NEWADDR, dev,
req.ifr_phonet_autoconf.device);
return 0;
}
/* notify Phonet of device events */
static int phonet_device_notify(struct notifier_block *me, unsigned long what,
void *arg)
{
struct net_device *dev = arg;
if (!net_eq(dev_net(dev), &init_net))
return 0;
switch (what) {
case NETDEV_REGISTER:
if (dev->type == ARPHRD_PHONET)
phonet_device_autoconf(dev);
break;
case NETDEV_UNREGISTER:
phonet_device_destroy(dev);
break;
}
return 0;
}
static struct notifier_block phonet_device_notifier = {
.notifier_call = phonet_device_notify,
.priority = 0,
};
/* Per-namespace Phonet devices handling */
static int phonet_init_net(struct net *net)
{
struct phonet_net *pnn;
if (!net_eq(net, &init_net))
return 0;
pnn = kmalloc(sizeof(*pnn), GFP_KERNEL);
if (!pnn)
return -ENOMEM;
if (!proc_net_fops_create(net, "phonet", 0, &pn_sock_seq_fops)) {
kfree(pnn);
return -ENOMEM;
}
INIT_LIST_HEAD(&pnn->pndevs.list);
spin_lock_init(&pnn->pndevs.lock);
net_assign_generic(net, phonet_net_id, pnn);
return 0;
}
static void phonet_exit_net(struct net *net)
{
struct phonet_net *pnn;
struct net_device *dev;
if (!net_eq(net, &init_net))
return;
pnn = net_generic(net, phonet_net_id);
rtnl_lock();
for_each_netdev(net, dev)
phonet_device_destroy(dev);
rtnl_unlock();
proc_net_remove(net, "phonet");
kfree(pnn);
}
static struct pernet_operations phonet_net_ops = {
.init = phonet_init_net,
.exit = phonet_exit_net,
};
/* Initialize Phonet devices list */
int __init phonet_device_init(void)
{
int err = register_pernet_gen_device(&phonet_net_id, &phonet_net_ops);
if (err)
return err;
register_netdevice_notifier(&phonet_device_notifier);
err = phonet_netlink_register();
if (err)
phonet_device_exit();
return err;
}
void phonet_device_exit(void)
{
rtnl_unregister_all(PF_PHONET);
unregister_netdevice_notifier(&phonet_device_notifier);
unregister_pernet_gen_device(phonet_net_id, &phonet_net_ops);
}

View File

@@ -0,0 +1,180 @@
/*
* File: pn_netlink.c
*
* Phonet netlink interface
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
* Original author: Sakari Ailus <sakari.ailus@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <linux/phonet.h>
#include <net/sock.h>
#include <net/phonet/pn_dev.h>
static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
u32 pid, u32 seq, int event);
void phonet_address_notify(int event, struct net_device *dev, u8 addr)
{
struct sk_buff *skb;
int err = -ENOBUFS;
skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
nla_total_size(1), GFP_KERNEL);
if (skb == NULL)
goto errout;
err = fill_addr(skb, dev, addr, 0, 0, event);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
kfree_skb(skb);
goto errout;
}
rtnl_notify(skb, dev_net(dev), 0,
RTNLGRP_PHONET_IFADDR, NULL, GFP_KERNEL);
return;
errout:
if (err < 0)
rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_IFADDR, err);
}
static const struct nla_policy ifa_phonet_policy[IFA_MAX+1] = {
[IFA_LOCAL] = { .type = NLA_U8 },
};
static int addr_doit(struct sk_buff *skb, struct nlmsghdr *nlh, void *attr)
{
struct net *net = sock_net(skb->sk);
struct nlattr *tb[IFA_MAX+1];
struct net_device *dev;
struct ifaddrmsg *ifm;
int err;
u8 pnaddr;
if (!net_eq(net, &init_net))
return -EOPNOTSUPP;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
ASSERT_RTNL();
err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_phonet_policy);
if (err < 0)
return err;
ifm = nlmsg_data(nlh);
if (tb[IFA_LOCAL] == NULL)
return -EINVAL;
pnaddr = nla_get_u8(tb[IFA_LOCAL]);
if (pnaddr & 3)
/* Phonet addresses only have 6 high-order bits */
return -EINVAL;
dev = __dev_get_by_index(net, ifm->ifa_index);
if (dev == NULL)
return -ENODEV;
if (nlh->nlmsg_type == RTM_NEWADDR)
err = phonet_address_add(dev, pnaddr);
else
err = phonet_address_del(dev, pnaddr);
if (!err)
phonet_address_notify(nlh->nlmsg_type, dev, pnaddr);
return err;
}
static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
u32 pid, u32 seq, int event)
{
struct ifaddrmsg *ifm;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, pid, seq, event, sizeof(*ifm), 0);
if (nlh == NULL)
return -EMSGSIZE;
ifm = nlmsg_data(nlh);
ifm->ifa_family = AF_PHONET;
ifm->ifa_prefixlen = 0;
ifm->ifa_flags = IFA_F_PERMANENT;
ifm->ifa_scope = RT_SCOPE_LINK;
ifm->ifa_index = dev->ifindex;
NLA_PUT_U8(skb, IFA_LOCAL, addr);
return nlmsg_end(skb, nlh);
nla_put_failure:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
static int getaddr_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct net *net = sock_net(skb->sk);
struct phonet_device_list *pndevs;
struct phonet_device *pnd;
int dev_idx = 0, dev_start_idx = cb->args[0];
int addr_idx = 0, addr_start_idx = cb->args[1];
if (!net_eq(net, &init_net))
goto skip;
pndevs = phonet_device_list(net);
spin_lock_bh(&pndevs->lock);
list_for_each_entry(pnd, &pndevs->list, list) {
u8 addr;
if (dev_idx > dev_start_idx)
addr_start_idx = 0;
if (dev_idx++ < dev_start_idx)
continue;
addr_idx = 0;
for (addr = find_first_bit(pnd->addrs, 64); addr < 64;
addr = find_next_bit(pnd->addrs, 64, 1+addr)) {
if (addr_idx++ < addr_start_idx)
continue;
if (fill_addr(skb, pnd->netdev, addr << 2,
NETLINK_CB(cb->skb).pid,
cb->nlh->nlmsg_seq, RTM_NEWADDR) < 0)
goto out;
}
}
out:
spin_unlock_bh(&pndevs->lock);
skip:
cb->args[0] = dev_idx;
cb->args[1] = addr_idx;
return skb->len;
}
int __init phonet_netlink_register(void)
{
int err = __rtnl_register(PF_PHONET, RTM_NEWADDR, addr_doit, NULL);
if (err)
return err;
/* Further __rtnl_register() cannot fail */
__rtnl_register(PF_PHONET, RTM_DELADDR, addr_doit, NULL);
__rtnl_register(PF_PHONET, RTM_GETADDR, NULL, getaddr_dumpit);
return 0;
}

512
kernel/net/phonet/socket.c Normal file
View File

@@ -0,0 +1,512 @@
/*
* File: socket.c
*
* Phonet sockets
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
* Original author: Sakari Ailus <sakari.ailus@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/poll.h>
#include <net/sock.h>
#include <net/tcp_states.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
#include <net/phonet/pep.h>
#include <net/phonet/pn_dev.h>
static int pn_socket_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk) {
sock->sk = NULL;
sk->sk_prot->close(sk, 0);
}
return 0;
}
static struct {
struct hlist_head hlist;
spinlock_t lock;
} pnsocks = {
.hlist = HLIST_HEAD_INIT,
.lock = __SPIN_LOCK_UNLOCKED(pnsocks.lock),
};
/*
* Find address based on socket address, match only certain fields.
* Also grab sock if it was found. Remember to sock_put it later.
*/
struct sock *pn_find_sock_by_sa(struct net *net, const struct sockaddr_pn *spn)
{
struct hlist_node *node;
struct sock *sknode;
struct sock *rval = NULL;
u16 obj = pn_sockaddr_get_object(spn);
u8 res = spn->spn_resource;
spin_lock_bh(&pnsocks.lock);
sk_for_each(sknode, node, &pnsocks.hlist) {
struct pn_sock *pn = pn_sk(sknode);
BUG_ON(!pn->sobject); /* unbound socket */
if (!net_eq(sock_net(sknode), net))
continue;
if (pn_port(obj)) {
/* Look up socket by port */
if (pn_port(pn->sobject) != pn_port(obj))
continue;
} else {
/* If port is zero, look up by resource */
if (pn->resource != res)
continue;
}
if (pn_addr(pn->sobject)
&& pn_addr(pn->sobject) != pn_addr(obj))
continue;
rval = sknode;
sock_hold(sknode);
break;
}
spin_unlock_bh(&pnsocks.lock);
return rval;
}
void pn_sock_hash(struct sock *sk)
{
spin_lock_bh(&pnsocks.lock);
sk_add_node(sk, &pnsocks.hlist);
spin_unlock_bh(&pnsocks.lock);
}
EXPORT_SYMBOL(pn_sock_hash);
void pn_sock_unhash(struct sock *sk)
{
spin_lock_bh(&pnsocks.lock);
sk_del_node_init(sk);
spin_unlock_bh(&pnsocks.lock);
}
EXPORT_SYMBOL(pn_sock_unhash);
static DEFINE_MUTEX(port_mutex);
static int pn_socket_bind(struct socket *sock, struct sockaddr *addr, int len)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
struct sockaddr_pn *spn = (struct sockaddr_pn *)addr;
int err;
u16 handle;
u8 saddr;
if (sk->sk_prot->bind)
return sk->sk_prot->bind(sk, addr, len);
if (len < sizeof(struct sockaddr_pn))
return -EINVAL;
if (spn->spn_family != AF_PHONET)
return -EAFNOSUPPORT;
handle = pn_sockaddr_get_object((struct sockaddr_pn *)addr);
saddr = pn_addr(handle);
if (saddr && phonet_address_lookup(sock_net(sk), saddr))
return -EADDRNOTAVAIL;
lock_sock(sk);
if (sk->sk_state != TCP_CLOSE || pn_port(pn->sobject)) {
err = -EINVAL; /* attempt to rebind */
goto out;
}
WARN_ON(sk_hashed(sk));
mutex_lock(&port_mutex);
err = sk->sk_prot->get_port(sk, pn_port(handle));
if (err)
goto out_port;
/* get_port() sets the port, bind() sets the address if applicable */
pn->sobject = pn_object(saddr, pn_port(pn->sobject));
pn->resource = spn->spn_resource;
/* Enable RX on the socket */
sk->sk_prot->hash(sk);
out_port:
mutex_unlock(&port_mutex);
out:
release_sock(sk);
return err;
}
static int pn_socket_autobind(struct socket *sock)
{
struct sockaddr_pn sa;
int err;
memset(&sa, 0, sizeof(sa));
sa.spn_family = AF_PHONET;
err = pn_socket_bind(sock, (struct sockaddr *)&sa,
sizeof(struct sockaddr_pn));
if (err != -EINVAL)
return err;
BUG_ON(!pn_port(pn_sk(sock->sk)->sobject));
return 0; /* socket was already bound */
}
static int pn_socket_accept(struct socket *sock, struct socket *newsock,
int flags)
{
struct sock *sk = sock->sk;
struct sock *newsk;
int err;
newsk = sk->sk_prot->accept(sk, flags, &err);
if (!newsk)
return err;
lock_sock(newsk);
sock_graft(newsk, newsock);
newsock->state = SS_CONNECTED;
release_sock(newsk);
return 0;
}
static int pn_socket_getname(struct socket *sock, struct sockaddr *addr,
int *sockaddr_len, int peer)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
memset(addr, 0, sizeof(struct sockaddr_pn));
addr->sa_family = AF_PHONET;
if (!peer) /* Race with bind() here is userland's problem. */
pn_sockaddr_set_object((struct sockaddr_pn *)addr,
pn->sobject);
*sockaddr_len = sizeof(struct sockaddr_pn);
return 0;
}
static unsigned int pn_socket_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
struct sock *sk = sock->sk;
struct pep_sock *pn = pep_sk(sk);
unsigned int mask = 0;
poll_wait(file, &sock->wait, wait);
switch (sk->sk_state) {
case TCP_LISTEN:
return hlist_empty(&pn->ackq) ? 0 : POLLIN;
case TCP_CLOSE:
return POLLERR;
}
if (!skb_queue_empty(&sk->sk_receive_queue))
mask |= POLLIN | POLLRDNORM;
if (!skb_queue_empty(&pn->ctrlreq_queue))
mask |= POLLPRI;
if (!mask && sk->sk_state == TCP_CLOSE_WAIT)
return POLLHUP;
if (sk->sk_state == TCP_ESTABLISHED && atomic_read(&pn->tx_credits))
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
return mask;
}
static int pn_socket_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
if (cmd == SIOCPNGETOBJECT) {
struct net_device *dev;
u16 handle;
u8 saddr;
if (get_user(handle, (__u16 __user *)arg))
return -EFAULT;
lock_sock(sk);
if (sk->sk_bound_dev_if)
dev = dev_get_by_index(sock_net(sk),
sk->sk_bound_dev_if);
else
dev = phonet_device_get(sock_net(sk));
if (dev && (dev->flags & IFF_UP))
saddr = phonet_address_get(dev, pn_addr(handle));
else
saddr = PN_NO_ADDR;
release_sock(sk);
if (dev)
dev_put(dev);
if (saddr == PN_NO_ADDR)
return -EHOSTUNREACH;
handle = pn_object(saddr, pn_port(pn->sobject));
return put_user(handle, (__u16 __user *)arg);
}
return sk->sk_prot->ioctl(sk, cmd, arg);
}
static int pn_socket_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
int err = 0;
if (sock->state != SS_UNCONNECTED)
return -EINVAL;
if (pn_socket_autobind(sock))
return -ENOBUFS;
lock_sock(sk);
if (sk->sk_state != TCP_CLOSE) {
err = -EINVAL;
goto out;
}
sk->sk_state = TCP_LISTEN;
sk->sk_ack_backlog = 0;
sk->sk_max_ack_backlog = backlog;
out:
release_sock(sk);
return err;
}
static int pn_socket_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *m, size_t total_len)
{
struct sock *sk = sock->sk;
if (pn_socket_autobind(sock))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, m, total_len);
}
const struct proto_ops phonet_dgram_ops = {
.family = AF_PHONET,
.owner = THIS_MODULE,
.release = pn_socket_release,
.bind = pn_socket_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = pn_socket_getname,
.poll = datagram_poll,
.ioctl = pn_socket_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_no_setsockopt,
.getsockopt = sock_no_getsockopt,
#ifdef CONFIG_COMPAT
.compat_setsockopt = sock_no_setsockopt,
.compat_getsockopt = sock_no_getsockopt,
#endif
.sendmsg = pn_socket_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
const struct proto_ops phonet_stream_ops = {
.family = AF_PHONET,
.owner = THIS_MODULE,
.release = pn_socket_release,
.bind = pn_socket_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = pn_socket_accept,
.getname = pn_socket_getname,
.poll = pn_socket_poll,
.ioctl = pn_socket_ioctl,
.listen = pn_socket_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
.sendmsg = pn_socket_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
EXPORT_SYMBOL(phonet_stream_ops);
/* allocate port for a socket */
int pn_sock_get_port(struct sock *sk, unsigned short sport)
{
static int port_cur;
struct net *net = sock_net(sk);
struct pn_sock *pn = pn_sk(sk);
struct sockaddr_pn try_sa;
struct sock *tmpsk;
memset(&try_sa, 0, sizeof(struct sockaddr_pn));
try_sa.spn_family = AF_PHONET;
WARN_ON(!mutex_is_locked(&port_mutex));
if (!sport) {
/* search free port */
int port, pmin, pmax;
phonet_get_local_port_range(&pmin, &pmax);
for (port = pmin; port <= pmax; port++) {
port_cur++;
if (port_cur < pmin || port_cur > pmax)
port_cur = pmin;
pn_sockaddr_set_port(&try_sa, port_cur);
tmpsk = pn_find_sock_by_sa(net, &try_sa);
if (tmpsk == NULL) {
sport = port_cur;
goto found;
} else
sock_put(tmpsk);
}
} else {
/* try to find specific port */
pn_sockaddr_set_port(&try_sa, sport);
tmpsk = pn_find_sock_by_sa(net, &try_sa);
if (tmpsk == NULL)
/* No sock there! We can use that port... */
goto found;
else
sock_put(tmpsk);
}
/* the port must be in use already */
return -EADDRINUSE;
found:
pn->sobject = pn_object(pn_addr(pn->sobject), sport);
return 0;
}
EXPORT_SYMBOL(pn_sock_get_port);
#ifdef CONFIG_PROC_FS
static struct sock *pn_sock_get_idx(struct seq_file *seq, loff_t pos)
{
struct net *net = seq_file_net(seq);
struct hlist_node *node;
struct sock *sknode;
sk_for_each(sknode, node, &pnsocks.hlist) {
if (!net_eq(net, sock_net(sknode)))
continue;
if (!pos)
return sknode;
pos--;
}
return NULL;
}
static struct sock *pn_sock_get_next(struct seq_file *seq, struct sock *sk)
{
struct net *net = seq_file_net(seq);
do
sk = sk_next(sk);
while (sk && !net_eq(net, sock_net(sk)));
return sk;
}
static void *pn_sock_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(pnsocks.lock)
{
spin_lock_bh(&pnsocks.lock);
return *pos ? pn_sock_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
}
static void *pn_sock_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct sock *sk;
if (v == SEQ_START_TOKEN)
sk = pn_sock_get_idx(seq, 0);
else
sk = pn_sock_get_next(seq, v);
(*pos)++;
return sk;
}
static void pn_sock_seq_stop(struct seq_file *seq, void *v)
__releases(pnsocks.lock)
{
spin_unlock_bh(&pnsocks.lock);
}
static int pn_sock_seq_show(struct seq_file *seq, void *v)
{
int len;
if (v == SEQ_START_TOKEN)
seq_printf(seq, "%s%n", "pt loc rem rs st tx_queue rx_queue "
" uid inode ref pointer drops", &len);
else {
struct sock *sk = v;
struct pn_sock *pn = pn_sk(sk);
seq_printf(seq, "%2d %04X:%04X:%02X %02X %08X:%08X %5d %lu "
"%d %p %d%n",
sk->sk_protocol, pn->sobject, 0, pn->resource,
sk->sk_state,
sk_wmem_alloc_get(sk), sk_rmem_alloc_get(sk),
sock_i_uid(sk), sock_i_ino(sk),
atomic_read(&sk->sk_refcnt), sk,
atomic_read(&sk->sk_drops), &len);
}
seq_printf(seq, "%*s\n", 127 - len, "");
return 0;
}
static const struct seq_operations pn_sock_seq_ops = {
.start = pn_sock_seq_start,
.next = pn_sock_seq_next,
.stop = pn_sock_seq_stop,
.show = pn_sock_seq_show,
};
static int pn_sock_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &pn_sock_seq_ops,
sizeof(struct seq_net_private));
}
const struct file_operations pn_sock_seq_fops = {
.owner = THIS_MODULE,
.open = pn_sock_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
#endif

113
kernel/net/phonet/sysctl.c Normal file
View File

@@ -0,0 +1,113 @@
/*
* File: sysctl.c
*
* Phonet /proc/sys/net/phonet interface implementation
*
* Copyright (C) 2008 Nokia Corporation.
*
* Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/seqlock.h>
#include <linux/sysctl.h>
#include <linux/errno.h>
#include <linux/init.h>
#define DYNAMIC_PORT_MIN 0x40
#define DYNAMIC_PORT_MAX 0x7f
static DEFINE_SEQLOCK(local_port_range_lock);
static int local_port_range_min[2] = {0, 0};
static int local_port_range_max[2] = {1023, 1023};
static int local_port_range[2] = {DYNAMIC_PORT_MIN, DYNAMIC_PORT_MAX};
static struct ctl_table_header *phonet_table_hrd;
static void set_local_port_range(int range[2])
{
write_seqlock(&local_port_range_lock);
local_port_range[0] = range[0];
local_port_range[1] = range[1];
write_sequnlock(&local_port_range_lock);
}
void phonet_get_local_port_range(int *min, int *max)
{
unsigned seq;
do {
seq = read_seqbegin(&local_port_range_lock);
if (min)
*min = local_port_range[0];
if (max)
*max = local_port_range[1];
} while (read_seqretry(&local_port_range_lock, seq));
}
static int proc_local_port_range(ctl_table *table, int write,
void __user *buffer,
size_t *lenp, loff_t *ppos)
{
int ret;
int range[2] = {local_port_range[0], local_port_range[1]};
ctl_table tmp = {
.data = &range,
.maxlen = sizeof(range),
.mode = table->mode,
.extra1 = &local_port_range_min,
.extra2 = &local_port_range_max,
};
ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
if (write && ret == 0) {
if (range[1] < range[0])
ret = -EINVAL;
else
set_local_port_range(range);
}
return ret;
}
static struct ctl_table phonet_table[] = {
{
.ctl_name = CTL_UNNUMBERED,
.procname = "local_port_range",
.data = &local_port_range,
.maxlen = sizeof(local_port_range),
.mode = 0644,
.proc_handler = proc_local_port_range,
.strategy = NULL,
},
{ .ctl_name = 0 }
};
static struct ctl_path phonet_ctl_path[] = {
{ .procname = "net", .ctl_name = CTL_NET, },
{ .procname = "phonet", .ctl_name = CTL_UNNUMBERED, },
{ },
};
int __init phonet_sysctl_init(void)
{
phonet_table_hrd = register_sysctl_paths(phonet_ctl_path, phonet_table);
return phonet_table_hrd == NULL ? -ENOMEM : 0;
}
void phonet_sysctl_exit(void)
{
unregister_sysctl_table(phonet_table_hrd);
}