blob: d1276206d7b59d90f47a6bb0844366d0795b8ca3 [file] [log] [blame]
/*
*
* Copyright (C) 2007 Mindspeed Technologies, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/version.h>
#include <linux/socket.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/timer.h>
#include <linux/time.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
#include <net/net_namespace.h>
#endif
#include "fci.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mindspeed Technologies");
MODULE_DESCRIPTION("Fast Control Interface");
static char __initdata fci_version[] = "0.04";
/* Statics functions prototypes */
static int fci_fe_inbound_parser(FCI_MSG *fci_msg, FCI_MSG *fci_rep);
static void fci_dump_msg(FCI_MSG *fci_msg);
static int fci_fe_register(void);
static void fci_fe_unregister(void);
static void __fci_fe_inbound_data(struct sk_buff *skb);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
static void fci_fe_inbound_data(struct sock *sk, int len);
#endif
static int fci_type_to_nl_type (int fci_nl_type);
static int fci_fe_init(void);
static void fci_fe_exit(void);
static int fci_outbound_fe_data(unsigned short fcode, unsigned short len, unsigned short *payload);
/* Global struture managing the FCI module */
static FCI *this_fci;
/************************** COMMON FUNCTIONS *********************************/
/*
* fci_init -
*
*
*/
static int fci_init(void)
{
int rc;
if ((this_fci = kmalloc (sizeof (FCI), GFP_KERNEL)) == NULL)
{
printk(KERN_ERR "FCI: out of memory (%d)\n", sizeof (FCI));
this_fci->stats.mem_alloc_err++;
rc = -ENOMEM;
goto err0;
}
/* zeroed the fci main structure */
memset(this_fci, 0, sizeof(FCI));
/* Initialize Fast Forward support in FCI */
if((rc = fci_fe_init()) < 0)
{
printk(KERN_ERR "FCI: fci_fe_init() failed\n");
goto err1;
}
/* Open others required sockets here */
return 0;
err1:
kfree(this_fci);
err0:
return rc;
}
static void fci_exit(void)
{
fci_fe_exit();
kfree(this_fci);
}
/*
* fci_open_netlink -
*
* Create new NETLINK socket for the given protocol
*/
static int fci_open_netlink (unsigned long proto)
{
FCI_PRINTK(FCI_NL, "fci_open_netlink() FCI type %ld\n", proto);
if(proto == FCI_NL_FF)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
if((this_fci->fci_nl_sock[FCI_NL_FF] = netlink_kernel_create (&init_net, NETLINK_FF, NL_FF_GROUP,
__fci_fe_inbound_data, NULL, THIS_MODULE)) == 0)
#else
if((this_fci->fci_nl_sock[FCI_NL_FF] = netlink_kernel_create (NETLINK_FF, NL_FF_GROUP, fci_fe_inbound_data, THIS_MODULE)) == 0)
#endif
{
this_fci->stats.kernel_create_err++;
return -ENOMEM;
}
}
else
{
this_fci->stats.unknown_sock_type++;
return -ESOCKTNOSUPPORT;
}
return 0;
}
static void fci_close_netlink (unsigned long proto)
{
/* release netlink socket */
sock_release(this_fci->fci_nl_sock[proto]->sk_socket);
}
/*
* fci_outbound_unicast -
*
* This callback is invoked whenever the kernel space sends
* message to the user space.
*/
static void fci_outbound_unicast(int nl_type, struct sk_buff *skb, u32 pid)
{
FCI_PRINTK(FCI_OUTBOUND, "%s: size=%d bytes, FCI nl_sock_type %d pid %u\n", __func__, skb->len, fci_type_to_nl_type(nl_type), pid);
NETLINK_CB(skb).pid = 0; /* from kernel */
/* unicast */
NETLINK_CB(skb).dst_group = 0;
/* send message to user space process */
netlink_unicast (this_fci->fci_nl_sock[nl_type], skb, pid, MSG_DONTWAIT);
/* Update sucess stats */
this_fci->stats.tx_msg++;
this_fci->stats.sock_stats[nl_type].tx_msg++;
}
/*
* fci_outbound_multicast -
*
* This callback is invoked whenever the kernel space sends
* message to the user space.
*/
static int fci_outbound_multicast(int nl_type, struct sk_buff *skb, int group)
{
gfp_t allocation = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
int rc = 0;
FCI_PRINTK(FCI_OUTBOUND, "%s: size=%d bytes, FCI nl_sock_type %d group %x\n", __func__, skb->len, fci_type_to_nl_type(nl_type), group);
NETLINK_CB(skb).pid = 0; /* from kernel */
/* send message only if at least one user space client is listening the socket */
if (netlink_has_listeners(this_fci->fci_nl_sock[nl_type], group))
{
/* the group we want to talk to */
NETLINK_CB(skb).dst_group = group;
/* send message all user space listeners */
rc = netlink_broadcast(this_fci->fci_nl_sock[nl_type], skb, 0, group, allocation);
if (rc < 0)
{
if (printk_ratelimit())
printk(KERN_ERR "FCI: netlink_broadcast() failed (rc=%d)\n", rc);
goto err_exit;
}
}
else
{
/* this skb has not been used */
kfree_skb(skb);
}
/* Update sucess stats */
this_fci->stats.tx_msg++;
this_fci->stats.sock_stats[nl_type].tx_msg++;
return 0;
err_exit:
/* Update error stats */
this_fci->stats.tx_msg_err++;
this_fci->stats.sock_stats[nl_type].tx_msg_err++;
return rc;
}
/*
* fci_outbound_err -
*
* This callback is invoked whenever the kernel space sends
* message to the user space.
*/
static void fci_outbound_err(int nl_type, struct sk_buff *skb, u32 pid, struct nlmsghdr *nlh, int err)
{
struct nlmsgerr *errmsg;
struct nlmsghdr *rep;
FCI_PRINTK(FCI_OUTBOUND, "%s: size=%d bytes, FCI nl_sock_type %d pid %u\n", __func__, skb->len, fci_type_to_nl_type(nl_type), pid);
rep = __nlmsg_put(skb, pid, nlh->nlmsg_seq,
NLMSG_ERROR, sizeof(struct nlmsgerr), 0);
errmsg = nlmsg_data(rep);
errmsg->error = err;
memcpy(&errmsg->msg, nlh, err ? nlh->nlmsg_len : sizeof(*nlh));
NETLINK_CB(skb).pid = 0; /* from kernel */
/* unicast */
NETLINK_CB(skb).dst_group = 0;
/* send message to user space process */
netlink_unicast (this_fci->fci_nl_sock[nl_type], skb, pid, MSG_DONTWAIT);
/* Update sucess stats */
this_fci->stats.tx_msg++;
this_fci->stats.sock_stats[nl_type].tx_msg++;
}
/*
* fci_dump_msg -
*
*
*/
static void fci_dump_msg(FCI_MSG *fci_msg)
{
int i;
FCI_PRINTK(FCI_DUMP, "fci msg: fcode 0x%04x size %d bytes -> ", fci_msg->fcode, fci_msg->length);
for(i = 0; i < fci_msg->length / 2; i++)
FCI_PRINTK(FCI_DUMP, "%04x ", fci_msg->payload[i]);
FCI_PRINTK(FCI_DUMP, "\n");
}
/*
* fci_type_to_nl_type -
*
*
*/
static int fci_type_to_nl_type (int fci_nl_type)
{
int nl_type;
switch (fci_nl_type)
{
case FCI_NL_FF:
nl_type = NETLINK_FF;
break;
default:
nl_type = -1;
break;
}
return nl_type;
}
/****************************** Fast Forward Support ********************************/
/*
* fci_fe_init -
*
*
*/
static int fci_fe_init(void)
{
int rc;
/* Create netlink socket for Fast Forward */
if((rc = fci_open_netlink((unsigned long) FCI_NL_FF)) < 0)
{
printk(KERN_ERR "FCI: fci_open_netlink() failed (FCI type %d)\n", FCI_NL_FF);
goto err0;
}
/* Connect to the Forward Engine */
if((rc = fci_fe_register()) < 0)
{
printk(KERN_ERR "FCI: fci_fe_register() failed\n");
goto err1;
}
return 0;
err1:
fci_close_netlink(FCI_NL_FF);
err0:
return rc;
}
/*
* fci_fe_exit -
*
*
*/
static void fci_fe_exit(void)
{
fci_fe_unregister();
fci_close_netlink(FCI_NL_FF);
}
/*
* fci_fe_register -
*
*/
static int fci_fe_register(void)
{
int rc;
/* register the FCI module to the FPP Forward Engine */
if((rc = comcerto_fpp_register_event_cb((void *)fci_outbound_fe_data)) < 0)
{
printk(KERN_ERR "FCI: fpp_register_event_cb() failed !\n");
return rc;
}
return 0;
}
/*
* fci_fe_unregister -
*
*/
static void fci_fe_unregister(void)
{
/* disconect FCI module from the FPP Forward Engine */
comcerto_fpp_register_event_cb(NULL);
}
/*
* fci_alloc_msg - allocates a skb suitable for containing a FCI netlink message
*
*/
static struct sk_buff *fci_alloc_msg(void)
{
struct sk_buff *skb;
gfp_t flags = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
skb = nlmsg_new(FCI_MSG_SIZE, flags);
if (!skb)
{
printk(KERN_ERR "FCI: nlmsg_new() failed\n");
this_fci->stats.mem_alloc_err++;
goto err;
}
return skb;
err:
return NULL;
}
/*
* fci_outbound_fe_data -
*
* This callback is invoked whenever the forward engine sends
* message to the user space.
*/
static int fci_outbound_fe_data(u16 fcode, u16 len, u16 *payload)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
FCI_MSG *fci_msg;
int rc;
FCI_PRINTK(FCI_OUTBOUND, "\nFCI: fci_outbound_fe_data()\n");
skb = fci_alloc_msg();
if (!skb)
{
this_fci->stats.rx_msg_err++;
rc = -ENOMEM;
goto err;
}
nlh = nlmsg_put(skb, 0, 0, 0, len + FCI_MSG_HDR_SIZE, 0);
fci_msg = nlmsg_data(nlh);
fci_msg->fcode = fcode;
fci_msg->length = len;
/* build fci message with sender's data */
memcpy(fci_msg->payload, payload, len);
/* pass data to the netlink subsystem */
return fci_outbound_multicast(FCI_NL_FF, skb, NL_FF_GROUP);
err:
return rc;
}
/*
* __fci_fe_inbound_data -
*
* This callback is invoked whenever the user space sends a netlink
* message of the NETLINK_FF protocol type to the kernel.
*/
static void __fci_fe_inbound_data(struct sk_buff *skb)
{
struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
struct nlmsghdr *rep;
struct sk_buff *nskb;
FCI_MSG *fci_msg, *fci_rep;
int rc;
FCI_PRINTK(FCI_INBOUND, "FCI: %s\n", __func__);
/* extract fci message from skb */
fci_msg = nlmsg_data(nlh);
this_fci->stats.rx_msg++;
this_fci->stats.sock_stats[FCI_NL_FF].rx_msg++;
nskb = fci_alloc_msg();
if (nskb)
{
rep = nlmsg_put(nskb, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0, 0, 0);
fci_rep = nlmsg_data(rep);
/* Process command received from User Space */
rc = fci_fe_inbound_parser(fci_msg, fci_rep);
if (rc < 0)
{
nlmsg_cancel(nskb, rep);
fci_outbound_err(FCI_NL_FF, nskb, NETLINK_CB(skb).pid, nlh, rc);
this_fci->stats.rx_msg_err++;
}
else
{
skb_put(nskb, FCI_MSG_HDR_SIZE + fci_rep->length);
nlmsg_end(nskb, rep);
fci_outbound_unicast(FCI_NL_FF, nskb, NETLINK_CB(skb).pid);
}
}
else
{
#if 0 /* FIXME netlink_lookup is not exported */
struct sock *sk;
sk = netlink_lookup(sock_net(skb->sk),
skb->sk->sk_protocol,
NETLINK_CB(skb).pid);
if (sk) {
sk->sk_err = ENOBUFS;
sk->sk_error_report(sk);
sock_put(sk);
}
#endif
this_fci->stats.rx_msg_err++;
}
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
static void fci_fe_inbound_data(struct sock *sk, int len)
{
struct sk_buff *skb;
FCI_PRINTK(FCI_INBOUND, "FCI: %s\n", __func__);
while ((skb = skb_dequeue(&sk->sk_receive_queue)))
{
__fci_fe_inbound_data(skb);
kfree_skb(skb);
}
}
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33) */
/*
* fci_fe_inbound_parser -
*
*
*/
static int fci_fe_inbound_parser(FCI_MSG *fci_msg, FCI_MSG *fci_rep)
{
int rc;
FCI_PRINTK(FCI_INBOUND, "FCI: fci_fe_inbound_parser()\n");
fci_rep->length = 0;
rc = comcerto_fpp_send_command(fci_msg->fcode, fci_msg->length, fci_msg->payload, &fci_rep->length, fci_rep->payload);
if (fci_rep->length > FCI_MSG_MAX_PAYLOAD)
fci_rep->length = FCI_MSG_MAX_PAYLOAD;
fci_rep->fcode = fci_msg->fcode;
return rc;
}
/***************************** MISCS FUNCTIONS ********************************/
/**
* fci_proc_info -
*
*
*/
static int fci_proc_info(char* page, char** start, off_t off, int count, int *eof, void* data)
{
int len = 0;
len += sprintf(page+len, "\n");
len += sprintf(page+len, "FCI Messages:\n");
len += sprintf(page+len, "Sent:%ld\n", this_fci->stats.tx_msg);
len += sprintf(page+len, "Received:%ld\n", this_fci->stats.rx_msg);
len += sprintf(page+len, "Sent errors:%ld\n", this_fci->stats.tx_msg_err);
len += sprintf(page+len, "Received errors:%ld\n", this_fci->stats.rx_msg_err);
len += sprintf(page+len, "\n");
len += sprintf(page+len, "Fast Forward Messages:\n");
len += sprintf(page+len, "Sent:%ld\n", this_fci->stats.sock_stats[FCI_NL_FF].tx_msg);
len += sprintf(page+len, "Received:%ld\n", this_fci->stats.sock_stats[FCI_NL_FF].rx_msg);
len += sprintf(page+len, "Sent errors:%ld\n", this_fci->stats.sock_stats[FCI_NL_FF].tx_msg_err);
len += sprintf(page+len, "Received errors:%ld\n", this_fci->stats.sock_stats[FCI_NL_FF].rx_msg_err);
len += sprintf(page+len, "\n");
len += sprintf(page+len, "\n");
len += sprintf(page+len, "Errors:\n");
len += sprintf(page+len, "Memory allocation errors:%ld\n", this_fci->stats.mem_alloc_err);
len += sprintf(page+len, "Kernel socket creation errors:%ld\n", this_fci->stats.kernel_create_err);
len += sprintf(page+len, "Unknow socket type:%ld\n", this_fci->stats.unknown_sock_type);
return len;
}
/*
* fci_module_init -
*
*/
static int fci_module_init(void)
{
int rc;
FCI_PRINTK(FCI_INIT, "Initializing Fast Control Interface v%s\n", fci_version);
if((rc = fci_init()) < 0)
{
printk(KERN_ERR "FCI: fci init failed\n");
return rc;
}
/* Create /proc/fci entry */
create_proc_read_entry("fci", 0, 0, fci_proc_info, NULL);
return 0;
}
/*
* fci_module_exit -
*
*/
static void fci_module_exit(void)
{
FCI_PRINTK(FCI_INIT, "Unloading Fast Control Interface\n");
/* Remove /proc/fci entry */
remove_proc_entry("fci", NULL);
/* clean-up before leaving */
fci_exit();
}
module_init(fci_module_init);
module_exit(fci_module_exit);