| /* |
| * |
| * 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); |