ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/kernel/mfp/files/fp_cm.c b/package/kernel/mfp/files/fp_cm.c
new file mode 100644
index 0000000..541b41a
--- /dev/null
+++ b/package/kernel/mfp/files/fp_cm.c
@@ -0,0 +1,1100 @@
+/*
+ *	Fastpath Cm Interface
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU FP_ERR( Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/if_vlan.h>
+#include "fp_common.h"
+#include "fp_database.h"
+#include "fp_device.h"
+#include "fp_core.h"
+#include "../linux/drivers/marvell/toev2/toe.h"
+#include "../linux/drivers/marvell/toev2/toe_464xlat.h"
+
+
+#define MAXLEN 256
+#define DEVICE_NAME_MAXSIZE 64
+#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
+
+static u32 g_cm_nlpid = -1;
+static u32 speed_thresh = 1000; /* kbps */
+
+static int fp_cm_genl_get_tuple(struct sk_buff *skb, struct genl_info *info);
+static int fp_cm_genl_del_tuple(struct sk_buff *skb, struct genl_info *info);
+static int fp_cm_genl_set_tuple(struct sk_buff *skb, struct genl_info *info);
+static int fp_cm_genl_set_pid(struct sk_buff *skb, struct genl_info *info);
+static void fp_cm_update_genl_pid(u32 pid);
+static u32 fp_cm_get_genl_pid(void);
+
+/* attribute type */
+enum fp_cm_genl_attrs {
+	CM_ATTR_UNSPEC,
+	CM_ATTR_PID,
+	CM_ATTR_SRC_IP,
+	CM_ATTR_DST_IP,
+	CM_ATTR_SRC_IP6,		/* struct in6_addr */
+	CM_ATTR_DST_IP6,		/* struct in6_addr */
+	CM_ATTR_SRC_PORT,
+	CM_ATTR_DST_PORT,
+	CM_ATTR_PROTO,
+	CM_ATTR_SRC_MAC,
+	CM_ATTR_DST_MAC,
+	CM_ATTR_SNAT,
+	CM_ATTR_FWD,
+	CM_ATTR_NAT_PORT,
+	CM_ATTR_NAT_IP,
+	CM_ATTR_DEVICE_NAME,
+	CM_ATTR_MCID,
+	CM_ATTR_RBID,
+	CM_ATTR_QFI,
+	CM_ATTR_PDU,
+	CM_ATTR_IN_PKT,
+	CM_ATTR_OUT_PKT,
+	CM_ATTR_VLAN_EN,
+	CM_ATTR_VLANID,
+	CM_ATTR_XLAT_EN,
+	CM_ATTR_XLAT_INSTANCE,
+	CM_ATTR_UPDATE_TUPLE,
+/* private: internal use only */
+	__FP_CM_ATTR_AFTER_LAST
+};
+#define FP_CM_ATTR_MAX (__FP_CM_ATTR_AFTER_LAST - 1)
+
+/* commands */
+enum fp_cm_commands {
+	CM_CMD_UNSPEC,
+	CM_CMD_SET_PID,
+	CM_CMD_GET_TUPLE,
+	CM_CMD_SET_TUPLE,
+	CM_CMD_DEL_TUPLE,
+	__FP_CM_CMD_AFTER_LAST,
+};
+#define FP_CM_CMD_MAX (__FP_CM_CMD_AFTER_LAST - 1)
+
+#define ETH_TYPE_LEN		2
+#define FP_CM_NLMSG_DEFAULT_SIZE  256
+
+
+/* attribute policy */
+static struct nla_policy fp_cm_genl_policy[FP_CM_ATTR_MAX + 1] = {
+	[CM_ATTR_PID] = { .type = NLA_U32 },
+	[CM_ATTR_SRC_IP] = { .type = NLA_U32 },
+	[CM_ATTR_DST_IP] = { .type = NLA_U32 },
+	[CM_ATTR_SRC_IP6] = {
+		.type = NLA_BINARY,
+		.len = sizeof(struct in6_addr),
+	},
+	[CM_ATTR_DST_IP6] = {
+		.type = NLA_BINARY,
+		.len = sizeof(struct in6_addr),
+	},
+	[CM_ATTR_SRC_PORT] = { .type = NLA_U16 },
+	[CM_ATTR_DST_PORT] = { .type = NLA_U16 },
+	[CM_ATTR_PROTO] = { .type = NLA_U8 },
+	[CM_ATTR_SNAT] = { .type = NLA_U8 },
+	[CM_ATTR_FWD] = { .type = NLA_U8 },
+	[CM_ATTR_NAT_PORT] = { .type = NLA_U16 },
+	[CM_ATTR_NAT_IP] = { .type = NLA_U32 },
+	[CM_ATTR_SRC_MAC] = { .type = NLA_STRING},
+	[CM_ATTR_DST_MAC] = { .type = NLA_STRING},
+	[CM_ATTR_DEVICE_NAME] = { .type = NLA_STRING,
+			.len = DEVICE_NAME_MAXSIZE },
+	[CM_ATTR_MCID] = { .type = NLA_U8 },
+	[CM_ATTR_RBID] = { .type = NLA_U8 },
+	[CM_ATTR_QFI] = { .type = NLA_U8 },
+	[CM_ATTR_PDU] = { .type = NLA_U8 },
+	[CM_ATTR_IN_PKT] = { .type = NLA_U8 },
+	[CM_ATTR_OUT_PKT] = { .type = NLA_U8 },
+	[CM_ATTR_VLAN_EN] = { .type = NLA_U8 },
+	[CM_ATTR_VLANID] = { .type = NLA_U16 },
+	[CM_ATTR_XLAT_EN] = { .type = NLA_U32 },
+	[CM_ATTR_XLAT_INSTANCE] = { .type = NLA_STRING },
+	[CM_ATTR_UPDATE_TUPLE] = { .type = NLA_U8 },
+};
+
+/* operation definition */
+struct genl_ops fp_cm_genl_ops[] = {
+	{
+		.cmd = CM_CMD_SET_PID,
+		.flags = 0,
+		.doit = fp_cm_genl_set_pid,
+	},
+	{
+		.cmd = CM_CMD_GET_TUPLE,
+		.flags = 0,
+		.doit = fp_cm_genl_get_tuple,
+	},
+	{
+		.cmd = CM_CMD_SET_TUPLE,
+		.flags = 0,
+		.doit = fp_cm_genl_set_tuple,
+	},
+	{
+		.cmd = CM_CMD_DEL_TUPLE,
+		.flags = 0,
+		.doit = fp_cm_genl_del_tuple,
+	}
+};
+
+static struct genl_family fp_cm_genl_family = {
+	.hdrsize = 0,
+	.name = "fp_cm",
+	.version = 1,
+	.maxattr = FP_CM_ATTR_MAX,
+	.policy = fp_cm_genl_policy,
+	.ops	 = fp_cm_genl_ops,
+	.n_ops	 = ARRAY_SIZE(fp_cm_genl_ops),
+};
+
+static void fp_cm_update_genl_pid(u32 pid)
+{
+	g_cm_nlpid = pid;
+}
+
+static u32 fp_cm_get_genl_pid(void)
+{
+	return g_cm_nlpid;
+}
+
+static int fp_cm_genl_set_pid(struct sk_buff *skb, struct genl_info *info)
+{
+	u32 pid;
+
+	if (!info->attrs[CM_ATTR_PID])
+		return -EINVAL;
+
+	pid = nla_get_u32(info->attrs[CM_ATTR_PID]);
+	printk("%s, get cm pid: %d\n", __func__, pid);
+	fp_cm_update_genl_pid(pid);
+	return 0;
+}
+
+static int __fp_cm_genl_fill_tuple_info(struct sk_buff *msg, struct nf_conntrack_tuple *tuple,
+				struct fpdb_entry *el, u32 portid, u32 seq, int flags, int add)
+{
+	void *hdr;
+	struct hh_cache *hh;
+	int hh_len;
+	u8 proto = 0, in_pkt = 0, out_pkt = 0, fwd = 0, nat = 0;
+	u16 nat_port = 0;
+	u32 nat_ip = 0;
+	char src_mac[ETH_ALEN]={0}, dst_mac[ETH_ALEN]={0};
+	struct fp_net_device *dst, *src;
+	struct vlan_dev_priv *vlan;
+	struct net_device *src_dev, *dst_dev;
+
+	hh = &el->hh;
+	hh_len = hh->hh_len;
+
+	if (add)
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_GET_TUPLE);
+	else
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_DEL_TUPLE);
+	if (!hdr)
+		return -EMSGSIZE;
+
+	if (tuple->src.l3num == AF_INET) {
+		if (nla_put_u32(msg, CM_ATTR_SRC_IP, tuple->src.u3.ip) ||
+			nla_put_u32(msg, CM_ATTR_DST_IP, tuple->dst.u3.ip))
+			goto nla_put_failure;
+	} else if (tuple->src.l3num == AF_INET6) {
+		if (nla_put(msg, CM_ATTR_SRC_IP6, sizeof(struct in6_addr), &tuple->src.u3.in6) ||
+			nla_put(msg, CM_ATTR_DST_IP6, sizeof(struct in6_addr), &tuple->dst.u3.in6))
+			goto nla_put_failure;
+	}
+
+	if (tuple->dst.protonum == IPPROTO_UDP)
+		proto = TOE_UDP;
+	else if (tuple->dst.protonum == IPPROTO_TCP)
+		proto = TOE_TCP;
+	else
+		proto = TOE_MAX;
+
+	if (nla_put_u16(msg, CM_ATTR_SRC_PORT, ntohs(tuple->src.u.all)) ||
+		nla_put_u16(msg, CM_ATTR_DST_PORT, ntohs(tuple->dst.u.all)) ||
+		nla_put_u8(msg, CM_ATTR_PROTO, proto))
+		goto nla_put_failure;
+
+	src = rcu_dereference_bh(el->in_dev);
+	dst = rcu_dereference_bh(el->out_dev);
+
+	if (is_vlan_dev(src->dev)) {
+		vlan = vlan_dev_priv(src->dev);
+		src_dev = vlan->real_dev;
+		nla_put_u8(msg, CM_ATTR_VLAN_EN, 1);
+		nla_put_u16(msg, CM_ATTR_VLANID, vlan->vlan_id);
+	} else
+		src_dev = src->dev;
+
+	if (!strncasecmp(src_dev->name, "ccinet", 6))
+		in_pkt = PDU_PKT;
+	else if (!strncasecmp(src_dev->name, "usbnet", 6))
+		in_pkt = USB_PKT;
+	else if (!strncasecmp(src_dev->name, "wlan", 4))
+		in_pkt = WIFI_PKT;
+	else if (!strncasecmp(src_dev->name, "eth", 3))
+		in_pkt = ETH_PKT;
+	else
+		in_pkt = AP_PKT;
+
+	if (is_vlan_dev(dst->dev)) {
+		vlan = vlan_dev_priv(dst->dev);
+		dst_dev = vlan->real_dev;
+		nla_put_u8(msg, CM_ATTR_VLAN_EN, 1);
+		nla_put_u16(msg, CM_ATTR_VLANID, vlan->vlan_id);
+	} else
+		dst_dev = dst->dev;
+
+	if (!strncasecmp(dst_dev->name, "ccinet", 6))
+		out_pkt = PDU_PKT;
+	else if (!strncasecmp(dst_dev->name, "usbnet", 6))
+		out_pkt = USB_PKT;
+	else if (!strncasecmp(dst_dev->name, "wlan", 4))
+		out_pkt = WIFI_PKT;
+	else if (!strncasecmp(dst_dev->name, "eth", 3))
+		out_pkt = ETH_PKT;
+	else
+		out_pkt = AP_PKT;
+
+	fwd = (in_pkt != AP_PKT) && (out_pkt != AP_PKT);
+	if (fwd && (tuple->src.l3num == AF_INET)) {
+		if (in_pkt == PDU_PKT && (out_pkt == USB_PKT || out_pkt == WIFI_PKT || out_pkt == ETH_PKT)) {
+			nat = 1;
+			nat_ip = el->out_tuple.src.u3.ip;
+			nat_port = ntohs(el->out_tuple.src.u.all);
+		} else if ((in_pkt == USB_PKT || in_pkt == WIFI_PKT || in_pkt == ETH_PKT) && out_pkt == PDU_PKT) {
+			nat = 1;
+			nat_ip = el->out_tuple.dst.u3.ip;
+			nat_port = ntohs(el->out_tuple.dst.u.all);
+		} else
+			/* CP TOE WIFI/WIFI TOE CP no need nat */
+			nat = 0;
+	}
+
+	if (nla_put_u8(msg, CM_ATTR_IN_PKT, in_pkt) ||
+		nla_put_u8(msg, CM_ATTR_OUT_PKT, out_pkt) ||
+		nla_put_u8(msg, CM_ATTR_FWD, fwd) ||
+		nla_put_string(msg, CM_ATTR_DEVICE_NAME, dst->dev->name))
+		goto nla_put_failure;
+
+	if (tuple->src.l3num == AF_INET) {
+		if (nla_put_u8(msg, CM_ATTR_SNAT, nat) ||
+			nla_put_u16(msg, CM_ATTR_NAT_PORT, nat_port) ||
+			nla_put_u32(msg, CM_ATTR_NAT_IP, nat_ip))
+			goto nla_put_failure;
+	}
+
+	if (hh_len) {
+		if (likely(hh_len <= HH_DATA_MOD)) {
+			/* this is inlined by gcc */
+			char mac_header[HH_DATA_MOD];
+			memcpy(mac_header, hh->hh_data, HH_DATA_MOD);
+			memcpy(src_mac, &mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN], ETH_ALEN);
+			memcpy(dst_mac, &mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN*2], ETH_ALEN);
+		} else {
+			int hh_alen = HH_DATA_ALIGN(hh_len);
+			char *mac_header = kmalloc(hh_alen, GFP_ATOMIC);
+
+			memcpy(mac_header, hh->hh_data, hh_alen);
+			memcpy(src_mac, mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN), ETH_ALEN);
+			memcpy(dst_mac, mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN*2), ETH_ALEN);
+			kfree(mac_header);
+		}
+	}
+
+	if (nla_put(msg, CM_ATTR_SRC_MAC, ETH_ALEN, src_mac) ||
+		nla_put(msg, CM_ATTR_DST_MAC, ETH_ALEN, dst_mac))
+			goto nla_put_failure;
+
+	pr_debug("%s:\n in:%d, out:%d\n src_ip:0x%x\n dst_ip:0x%x\n src_port:%d\n dst_prot:%d\n"
+			" protocol:%d\n nat_port:%d\n nat_ip:0x%x\n fwd:%d\n snat:%d\n",
+			__func__, in_pkt, out_pkt, ntohl(tuple->src.u3.ip), ntohl(tuple->dst.u3.ip), ntohs(tuple->src.u.all),
+			ntohs(tuple->dst.u.all), proto, nat_port, ntohl(nat_ip), fwd, nat);
+
+	genlmsg_end(msg, hdr);
+	if (add)
+		el->nl_flag = 1;
+
+	return 0;
+
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	return -EMSGSIZE;
+}
+
+static int __fp_cm_genl_fill_464xlat_info(struct sk_buff *msg, struct nf_conntrack_tuple *tuple,
+				struct fpdb_entry *el, u32 portid, u32 seq, int flags, int add)
+{
+	void *hdr;
+	struct hh_cache *hh;
+	int hh_len;
+	u8 proto = 0, in_pkt = 0, out_pkt = 0, fwd = 0, nat = 0;
+	u16 nat_port = 0;
+	u32 nat_ip = 0;
+	char src_mac[ETH_ALEN]={0}, dst_mac[ETH_ALEN]={0};
+	struct fp_net_device *dst, *src;
+	struct vlan_dev_priv *vlan;
+	struct net_device *src_dev, *dst_dev;
+	nat46_instance_t *nat46;
+	nat46_netdev_priv_t *dev_priv;
+
+	hh = &el->hh;
+	hh_len = hh->hh_len;
+
+	if (tuple->src.l3num == AF_INET6) {
+		el->nl_flag = 1;
+		return 0;
+	}
+
+	if (add)
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_GET_TUPLE);
+	else
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_DEL_TUPLE);
+	if (!hdr)
+		return -EMSGSIZE;
+
+	if (tuple->src.l3num == AF_INET) {
+		if (nla_put_u32(msg, CM_ATTR_SRC_IP, tuple->src.u3.ip) ||
+			nla_put_u32(msg, CM_ATTR_DST_IP, tuple->dst.u3.ip))
+		goto nla_put_failure;
+	}
+
+	if (tuple->dst.protonum == IPPROTO_UDP)
+		proto = TOE_UDP;
+	else if (tuple->dst.protonum == IPPROTO_TCP)
+		proto = TOE_TCP;
+	else
+		proto = TOE_MAX;
+
+	if (nla_put_u16(msg, CM_ATTR_SRC_PORT, ntohs(tuple->src.u.all)) ||
+		nla_put_u16(msg, CM_ATTR_DST_PORT, ntohs(tuple->dst.u.all)) ||
+		nla_put_u8(msg, CM_ATTR_PROTO, proto))
+		goto nla_put_failure;
+
+	src = rcu_dereference_bh(el->in_dev);
+	dst = rcu_dereference_bh(el->out_dev);
+
+	if (is_vlan_dev(src->dev)) {
+		vlan = vlan_dev_priv(src->dev);
+		src_dev = vlan->real_dev;
+		nla_put_u8(msg, CM_ATTR_VLAN_EN, 1);
+		nla_put_u16(msg, CM_ATTR_VLANID, vlan->vlan_id);
+	} else
+		src_dev = src->dev;
+
+	if (is_nat46_dev(src_dev) && is_valid_nat46_instance(src_dev)) {
+		//RX:
+		in_pkt = PDU_PKT;
+		dev_priv = netdev_priv(src_dev);
+		nat46 = dev_priv->nat46;
+		pr_debug("%s:\nDL xlat enable\n, src:%pI6c, dst:%pI6c\n", __func__, nat46->pairs[0].remote.v6_pref.s6_addr32,
+				nat46->pairs[0].local.v6_pref.s6_addr32);
+
+		if (nla_put_u32(msg, CM_ATTR_XLAT_EN, 1) ||
+			nla_put(msg, CM_ATTR_SRC_IP6, sizeof(struct in6_addr), &nat46->pairs[0].remote.v6_pref) ||
+			nla_put(msg, CM_ATTR_DST_IP6, sizeof(struct in6_addr), &nat46->pairs[0].local.v6_pref))
+			goto nla_put_failure;
+	} else if (!strncasecmp(src_dev->name, "ccinet", 6))
+		in_pkt = PDU_PKT;
+	else if (!strncasecmp(src_dev->name, "usbnet", 6))
+		in_pkt = USB_PKT;
+	else if (!strncasecmp(src_dev->name, "wlan", 4))
+		in_pkt = WIFI_PKT;
+	else if (!strncasecmp(src_dev->name, "eth", 3))
+		in_pkt = ETH_PKT;
+	else
+		in_pkt = AP_PKT;
+
+	if (is_vlan_dev(dst->dev)) {
+		vlan = vlan_dev_priv(dst->dev);
+		dst_dev = vlan->real_dev;
+		nla_put_u8(msg, CM_ATTR_VLAN_EN, 1);
+		nla_put_u16(msg, CM_ATTR_VLANID, vlan->vlan_id);
+	} else
+		dst_dev = dst->dev;
+
+	if (is_nat46_dev(dst_dev) && is_valid_nat46_instance(dst_dev)) {
+		//TX
+		out_pkt = PDU_PKT;
+		dev_priv = netdev_priv(dst_dev);
+		nat46 = dev_priv->nat46;
+		pr_debug("%s:\nUL xlat enable\n, xlat instance: %s, src:%pI6c, dst:%pI6c\n", __func__, dst_dev->name,
+				nat46->pairs[0].local.v6_pref.s6_addr32, nat46->pairs[0].remote.v6_pref.s6_addr32);
+
+		if (nla_put_u32(msg, CM_ATTR_XLAT_EN, 1) ||
+			nla_put_string(msg, CM_ATTR_XLAT_INSTANCE, dst_dev->name) ||
+			nla_put(msg, CM_ATTR_SRC_IP6, sizeof(struct in6_addr), &nat46->pairs[0].local.v6_pref) ||
+			nla_put(msg, CM_ATTR_DST_IP6, sizeof(struct in6_addr), &nat46->pairs[0].remote.v6_pref))
+			goto nla_put_failure;
+	} else if (!strncasecmp(dst_dev->name, "ccinet", 6))
+		out_pkt = PDU_PKT;
+	else if (!strncasecmp(dst_dev->name, "usbnet", 6))
+		out_pkt = USB_PKT;
+	else if (!strncasecmp(dst_dev->name, "wlan", 4))
+		out_pkt = WIFI_PKT;
+	else if (!strncasecmp(dst_dev->name, "eth", 3))
+		out_pkt = ETH_PKT;
+	else
+		out_pkt = AP_PKT;
+
+	fwd = (in_pkt != AP_PKT) && (out_pkt != AP_PKT);
+	if (fwd && (tuple->src.l3num == AF_INET)) {
+		if (in_pkt == PDU_PKT && (out_pkt == USB_PKT || out_pkt == WIFI_PKT || out_pkt == ETH_PKT)) {
+			nat = 1;
+			nat_ip = el->out_tuple.src.u3.ip;
+			nat_port = ntohs(el->out_tuple.src.u.all);
+		} else if ((in_pkt == USB_PKT || in_pkt == WIFI_PKT || in_pkt == ETH_PKT) && out_pkt == PDU_PKT) {
+			nat = 1;
+			nat_ip = el->out_tuple.dst.u3.ip;
+			nat_port = ntohs(el->out_tuple.dst.u.all);
+		} else
+			/* not support*/
+			goto nla_put_failure;
+	}
+
+	if (nla_put_u8(msg, CM_ATTR_IN_PKT, in_pkt) ||
+		nla_put_u8(msg, CM_ATTR_OUT_PKT, out_pkt) ||
+		nla_put_u8(msg, CM_ATTR_FWD, fwd) ||
+		nla_put_string(msg, CM_ATTR_DEVICE_NAME, dst_dev->name))
+		goto nla_put_failure;
+
+	if (tuple->src.l3num == AF_INET) {
+		if (nla_put_u8(msg, CM_ATTR_SNAT, nat) ||
+			nla_put_u16(msg, CM_ATTR_NAT_PORT, nat_port) ||
+			nla_put_u32(msg, CM_ATTR_NAT_IP, nat_ip))
+			goto nla_put_failure;
+	}
+
+	if (hh_len) {
+		if (likely(hh_len <= HH_DATA_MOD)) {
+			/* this is inlined by gcc */
+			char mac_header[HH_DATA_MOD];
+			memcpy(mac_header, hh->hh_data, HH_DATA_MOD);
+			memcpy(src_mac, &mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN], ETH_ALEN);
+			memcpy(dst_mac, &mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN*2], ETH_ALEN);
+		} else {
+			int hh_alen = HH_DATA_ALIGN(hh_len);
+			char *mac_header = kmalloc(hh_alen, GFP_ATOMIC);
+
+			memcpy(mac_header, hh->hh_data, hh_alen);
+			memcpy(src_mac, mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN), ETH_ALEN);
+			memcpy(dst_mac, mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN*2), ETH_ALEN);
+			kfree(mac_header);
+		}
+	}
+
+	if (nla_put(msg, CM_ATTR_SRC_MAC, ETH_ALEN, src_mac) ||
+		nla_put(msg, CM_ATTR_DST_MAC, ETH_ALEN, dst_mac))
+			goto nla_put_failure;
+
+	pr_debug("%s:\n in:%d, out:%d\n src_ip:0x%x\n dst_ip:0x%x\n src_port:%d\n dst_prot:%d\n"
+			" protocol:%d\n nat_port:%d\n nat_ip:0x%x\n fwd:%d\n snat:%d\n\n",
+			__func__, in_pkt, out_pkt, ntohl(tuple->src.u3.ip), ntohl(tuple->dst.u3.ip), ntohs(tuple->src.u.all),
+			ntohs(tuple->dst.u.all), proto, nat_port, ntohl(nat_ip), fwd, nat);
+
+	genlmsg_end(msg, hdr);
+	el->nl_flag = 1;
+
+	return 0;
+
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	return -EMSGSIZE;
+}
+
+static int fp_cm_genl_fill_tuple_info(struct sk_buff *msg, struct nf_conntrack_tuple *tuple,
+				struct fpdb_entry *el, u32 portid, int add)
+{
+	struct fp_net_device *dst, *src;
+
+	if (unlikely(!tuple) || unlikely(!el))
+		return -EMSGSIZE;
+
+	src = rcu_dereference_bh(el->in_dev);
+	dst = rcu_dereference_bh(el->out_dev);
+	if (!src || !dst)
+		return -EMSGSIZE;
+
+	if (is_nat46_dev(src->dev) || is_nat46_dev(dst->dev)) {
+		return __fp_cm_genl_fill_464xlat_info(msg, tuple, el, portid, 0, 0, add);
+	} else
+		return __fp_cm_genl_fill_tuple_info(msg, tuple, el, portid, 0, 0, add);
+}
+
+static int fp_cm_genl_fill_tuple_info_for_test(struct sk_buff *msg, u32 portid, u32 seq,
+				int flags, int add)
+{
+	void *hdr;
+	struct in6_addr addr;
+	char mac[ETH_ALEN] = {0x0, 0x2, 0x3, 0x4, 0x5, 0x6};
+
+	if (add)
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_GET_TUPLE);
+	else
+		hdr = genlmsg_put(msg, portid, seq, &fp_cm_genl_family, flags,
+				  CM_CMD_DEL_TUPLE);
+	if (!hdr)
+		return -EMSGSIZE;
+
+	memset(&addr.s6_addr, 6, sizeof(struct in6_addr));
+
+	if (nla_put_u32(msg, CM_ATTR_SRC_IP, 0xC0A80101) ||
+		nla_put_u32(msg, CM_ATTR_DST_IP, 0xC0A80102) ||
+		/* nla_put(msg, CM_ATTR_SRC_IP6, sizeof(struct in6_addr), &addr) ||
+		nla_put(msg, CM_ATTR_DST_IP6, sizeof(struct in6_addr), &addr) || */
+		nla_put_u16(msg, CM_ATTR_SRC_PORT, 0x64) ||
+		nla_put_u16(msg, CM_ATTR_DST_PORT, 0xC8) ||
+		nla_put_u8(msg, CM_ATTR_PROTO, TOE_TCP) ||
+		nla_put_u16(msg, CM_ATTR_NAT_PORT, 0x64) ||
+		nla_put_u32(msg, CM_ATTR_NAT_IP, 0xC0A8010A) ||
+		nla_put(msg, CM_ATTR_SRC_MAC, ETH_ALEN, mac) ||
+		nla_put(msg, CM_ATTR_DST_MAC, ETH_ALEN, mac) ||
+		nla_put_string(msg, CM_ATTR_DEVICE_NAME, "ccinet0") ||
+		nla_put_u8(msg, CM_ATTR_IN_PKT, PDU_PKT) ||
+		nla_put_u8(msg, CM_ATTR_OUT_PKT, USB_PKT) ||
+		nla_put_u8(msg, CM_ATTR_FWD, 1) ||
+		nla_put_u8(msg, CM_ATTR_SNAT, 1) ||
+		nla_put_u8(msg, CM_ATTR_VLAN_EN, 1) ||
+		nla_put_u16(msg, CM_ATTR_VLANID, 0x64))
+		goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+	return 0;
+
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	return -EMSGSIZE;
+}
+
+static int fp_cm_genl_set_tuple(struct sk_buff *skb, struct genl_info *info)
+{
+	struct toe_tuple_buff toe_tuple, toe_tuple_tmp;
+	char dev_name[DEVICE_NAME_MAXSIZE] = {0};
+	char src_mac[ETH_ALEN]={0}, dst_mac[ETH_ALEN]={0};
+	char xlat_instance[16] = {0};
+	struct in6_addr *src_ip6 = NULL;
+	struct in6_addr *dst_ip6 = NULL;
+	u32 src_ip, dst_ip, nat_ip, xlat_en = 0;
+	u16 src_port, dst_port, nat_port, vlanid;
+	u8 rx_tx, prot, fwd, nat, in_pkt, out_pkt, pdu, qfi, rbid, mcid, vlan_en;
+	u8 update = 0;
+
+	if (!info->attrs[CM_ATTR_MCID] ||
+		!info->attrs[CM_ATTR_RBID] ||
+		!info->attrs[CM_ATTR_QFI] ||
+		!info->attrs[CM_ATTR_PDU])
+		return -EINVAL;
+
+	memset(&toe_tuple, 0, sizeof(struct toe_tuple_buff));
+	memset(&toe_tuple_tmp, 0, sizeof(struct toe_tuple_buff));
+
+	if (info->attrs[CM_ATTR_SRC_IP])
+		src_ip = nla_get_u32(info->attrs[CM_ATTR_SRC_IP]);
+
+	if (info->attrs[CM_ATTR_DST_IP])
+		dst_ip = nla_get_u32(info->attrs[CM_ATTR_DST_IP]);
+
+	if (info->attrs[CM_ATTR_SRC_IP6]) {
+		src_ip6 = nla_data(info->attrs[CM_ATTR_SRC_IP6]);
+		pr_debug("%s, src_ip6=%pI6c\n", __func__, src_ip6->s6_addr32);
+	}
+
+	if (info->attrs[CM_ATTR_DST_IP6]) {
+		dst_ip6 = nla_data(info->attrs[CM_ATTR_DST_IP6]);
+		pr_debug("%s, dst_ip6=%pI6c\n", __func__, dst_ip6->s6_addr32);
+	}
+
+	if (info->attrs[CM_ATTR_SRC_PORT])
+		src_port = nla_get_u16(info->attrs[CM_ATTR_SRC_PORT]);
+
+	if (info->attrs[CM_ATTR_DST_PORT])
+		dst_port = nla_get_u16(info->attrs[CM_ATTR_DST_PORT]);
+
+	if (info->attrs[CM_ATTR_PROTO])
+		prot = nla_get_u8(info->attrs[CM_ATTR_PROTO]);
+
+	if (info->attrs[CM_ATTR_SRC_MAC]) {
+		memcpy(src_mac, nla_data(info->attrs[CM_ATTR_SRC_MAC]), ETH_ALEN);
+		pr_debug("%s, src_mac: %02x%02x-%02x%02x-%02x%02x\n", __func__,
+			src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]);
+	}
+	if (info->attrs[CM_ATTR_DST_MAC]) {
+		memcpy(dst_mac, nla_data(info->attrs[CM_ATTR_DST_MAC]), ETH_ALEN);
+		pr_debug("%s, dst_mac: %02x%02x-%02x%02x-%02x%02x\n", __func__,
+			dst_mac[0], dst_mac[1], dst_mac[2], dst_mac[3], dst_mac[4], dst_mac[5]);
+	}
+
+	if (info->attrs[CM_ATTR_SNAT])
+		nat = nla_get_u8(info->attrs[CM_ATTR_SNAT]);
+
+	if (info->attrs[CM_ATTR_FWD])
+		fwd = nla_get_u8(info->attrs[CM_ATTR_FWD]);
+
+	if (info->attrs[CM_ATTR_NAT_PORT])
+		nat_port = nla_get_u16(info->attrs[CM_ATTR_NAT_PORT]);
+
+	if (info->attrs[CM_ATTR_NAT_IP])
+		nat_ip = nla_get_u32(info->attrs[CM_ATTR_NAT_IP]);
+
+	if (info->attrs[CM_ATTR_DEVICE_NAME]) {
+		/*nla_len = strlen(dev_name) + 1 + NLA_HDRLEN;*/
+		memcpy(dev_name, (char *)nla_data(info->attrs[CM_ATTR_DEVICE_NAME]),
+					info->attrs[CM_ATTR_DEVICE_NAME]->nla_len - NLA_HDRLEN -1);
+		pr_debug("%s, dev_name: %s\n", __func__, dev_name);
+	}
+
+	if (info->attrs[CM_ATTR_MCID])
+		mcid = nla_get_u8(info->attrs[CM_ATTR_MCID]);
+
+	if (info->attrs[CM_ATTR_RBID])
+		rbid = nla_get_u8(info->attrs[CM_ATTR_RBID]);
+
+	if (info->attrs[CM_ATTR_QFI])
+		qfi = nla_get_u8(info->attrs[CM_ATTR_QFI]);
+
+	if (info->attrs[CM_ATTR_PDU])
+		pdu = nla_get_u8(info->attrs[CM_ATTR_PDU]);
+
+	if (info->attrs[CM_ATTR_IN_PKT])
+		in_pkt = nla_get_u8(info->attrs[CM_ATTR_IN_PKT]);
+
+	if (info->attrs[CM_ATTR_OUT_PKT])
+		out_pkt = nla_get_u8(info->attrs[CM_ATTR_OUT_PKT]);
+
+	if (info->attrs[CM_ATTR_VLAN_EN])
+		vlan_en = nla_get_u8(info->attrs[CM_ATTR_VLAN_EN]);
+
+	if (info->attrs[CM_ATTR_VLANID])
+		vlanid = nla_get_u16(info->attrs[CM_ATTR_VLANID]);
+
+	if (info->attrs[CM_ATTR_XLAT_EN])
+		xlat_en = nla_get_u32(info->attrs[CM_ATTR_XLAT_EN]);
+
+	if (info->attrs[CM_ATTR_XLAT_INSTANCE]) {
+		memcpy(xlat_instance, (char *)nla_data(info->attrs[CM_ATTR_XLAT_INSTANCE]),
+					info->attrs[CM_ATTR_XLAT_INSTANCE]->nla_len - NLA_HDRLEN -1);
+		pr_debug("%s, xlat_instance: %s\n", __func__, xlat_instance);
+	}
+
+	if (info->attrs[CM_ATTR_UPDATE_TUPLE])
+		update = nla_get_u8(info->attrs[CM_ATTR_UPDATE_TUPLE]);
+
+	/* rx: cp -> ap, usb, wifi */
+	if (in_pkt == PDU_PKT)
+		rx_tx = 1;
+	/* rx: ap -> usb, ap -> wifi */
+	else if ((in_pkt == AP_PKT) && (out_pkt != PDU_PKT))
+		rx_tx = 1;
+	/*
+	 * tx:
+	 * ap -> cp
+	 * usb/wifi -> ap/cp */
+	else
+		rx_tx = 0;
+
+	if (src_ip6 && dst_ip6 && !xlat_en) {
+		memcpy(toe_tuple.src_ip6, src_ip6->s6_addr32, sizeof(toe_tuple.src_ip6));
+		memcpy(toe_tuple.dst_ip6, dst_ip6->s6_addr32, sizeof(toe_tuple.src_ip6));
+		toe_tuple.ip6 = 1;
+	} else {
+		toe_tuple.src_ip = ntohl(src_ip);
+		toe_tuple.dst_ip = ntohl(dst_ip);
+		toe_tuple.ip6 = 0;
+		toe_tuple.nat = nat;
+		toe_tuple.nat_port = nat_port;
+		toe_tuple.nat_ip = ntohl(nat_ip);
+	}
+
+	if (vlan_en) {
+		toe_tuple.vlan_en = vlan_en;
+		toe_tuple.vlanid = vlanid;
+	}
+
+	toe_tuple.src_port = src_port;
+	toe_tuple.dst_port = dst_port;
+	toe_tuple.prot = prot;
+	toe_tuple.urg = 0;
+	toe_tuple.fwd = fwd;
+	toe_tuple.crc = 1;
+	toe_tuple.rxtx = rx_tx;
+	toe_tuple.out_pkt = out_pkt;
+	toe_tuple.pdu = pdu;
+	toe_tuple.qfi = qfi ;
+	toe_tuple.rbid = rbid ;
+	toe_tuple.mcid = mcid;
+	toe_tuple.xlat_en = xlat_en;
+	toe_tuple.in_pkt = in_pkt;
+	memcpy(toe_tuple.smac, src_mac, sizeof(toe_tuple.smac));
+	memcpy(toe_tuple.dmac, dst_mac, sizeof(toe_tuple.dmac));
+	memcpy(toe_tuple.xlat_instance, xlat_instance, sizeof(xlat_instance));
+
+	pr_debug("%s:\n in:%d, out:%d, src_port:%d, dst_prot:%d,"
+			" protocol:%d, nat_port:%d, nat_ip:0x%x,fwd:%d,snat:%d, xlat instance:%s\n\n",
+			__func__, in_pkt, out_pkt, src_port, dst_port, prot,
+			nat_port, nat_ip, fwd, nat, xlat_instance);
+
+	if (update) {
+		memcpy(&toe_tuple_tmp, &toe_tuple, sizeof(struct toe_tuple_buff));
+		toe_del_connection(&toe_tuple_tmp);
+	}
+	toe_add_connection(&toe_tuple);
+
+	return 0;
+}
+
+static int fp_cm_genl_get_tuple(struct sk_buff *skb, struct genl_info *info)
+{
+	struct sk_buff *msg;
+	int rc;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = fp_cm_genl_fill_tuple_info_for_test(msg, info->snd_portid, info->snd_seq, 0, 1);
+	if (rc < 0)
+		goto out_free;
+
+	return genlmsg_reply(msg, info);
+
+out_free:
+	nlmsg_free(msg);
+	return rc;
+}
+
+static int fp_cm_genl_del_tuple(struct sk_buff *skb, struct genl_info *info)
+{
+	struct sk_buff *msg;
+	int rc;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = fp_cm_genl_fill_tuple_info_for_test(msg, info->snd_portid, info->snd_seq, 0, 0);
+	if (rc < 0)
+		goto out_free;
+
+	return genlmsg_reply(msg, info);
+
+out_free:
+	nlmsg_free(msg);
+	return rc;
+}
+
+static int __fp_eth_wan_set_tuple(struct nf_conntrack_tuple *tuple,
+				struct fpdb_entry *el, int add)
+{
+	struct hh_cache *hh;
+	int hh_len;
+
+	struct fp_net_device *dst, *src;
+	struct net_device *src_dev, *dst_dev;
+	struct toe_tuple_buff toe_tuple_ul; /* eth -> cp */
+	struct toe_tuple_buff toe_tuple_dl; /* cp->usb/wifi/eth */
+	struct vlan_dev_priv *vlan;
+	u8 src_vlan_en = 0, dst_vlan_en = 0;
+	u16 src_vlanid, dst_vlanid;
+
+	hh = &el->hh;
+	hh_len = hh->hh_len;
+
+	src = rcu_dereference_bh(el->in_dev);
+	dst = rcu_dereference_bh(el->out_dev);
+
+	if (is_vlan_dev(src->dev)) {
+		vlan = vlan_dev_priv(src->dev);
+		src_dev = vlan->real_dev;
+		src_vlan_en = 1;
+		src_vlanid = vlan->vlan_id;
+	} else
+		src_dev = src->dev;
+
+	if (is_vlan_dev(dst->dev)) {
+		vlan = vlan_dev_priv(dst->dev);
+		dst_dev = vlan->real_dev;
+		dst_vlan_en = 1;
+		dst_vlanid = vlan->vlan_id;
+	} else
+		dst_dev = dst->dev;
+
+	if (src->br) {
+		/* if src dev is under bridge such as usb/eth(lan)/wifi */
+
+		if (strncasecmp(dst_dev->name, "eth", 3))
+			/* dst dev is not eth */
+			return -1;
+		else {
+			if (!dst->br) {
+				/* usb/eth(lan)/wifi -> eth(wan)
+				 * don't add the ul path to toe,
+				 * and no need to send tuple to cm.
+				*/
+				return 0;
+			} else {
+				/* dst is eth lan */
+				return -1;
+			}
+		}
+	} else {
+		if (strncasecmp(src_dev->name, "eth", 3))
+			/* src dev is not eth */
+			return -1;
+	}
+
+	/* only eth wan as input go here */
+	printk(KERN_DEBUG "%s: %s -> %s\n", __func__,
+		src_dev->name, dst_dev->name);
+
+	memset(&toe_tuple_ul, 0, sizeof(toe_tuple_ul));
+	memset(&toe_tuple_dl, 0, sizeof(toe_tuple_dl));
+	toe_tuple_ul.in_pkt = ETH_PKT;
+	toe_tuple_ul.out_pkt = PDU_PKT;
+	toe_tuple_dl.in_pkt = PDU_PKT;
+
+	if (tuple->dst.protonum == IPPROTO_UDP)
+		toe_tuple_ul.prot = TOE_UDP;
+	else if (tuple->dst.protonum == IPPROTO_TCP)
+		toe_tuple_ul.prot = TOE_TCP;
+	else
+		return 1;
+	toe_tuple_dl.prot = toe_tuple_ul.prot;
+
+	if (!strncasecmp(dst_dev->name, "usbnet", 6))
+		toe_tuple_dl.out_pkt = USB_PKT;
+	else if (!strncasecmp(dst_dev->name, "wlan", 4))
+		toe_tuple_dl.out_pkt = WIFI_PKT;
+	else if (!strncasecmp(dst_dev->name, "eth", 3))
+		toe_tuple_dl.out_pkt = ETH_PKT;
+	else
+		return 2;
+
+	if (tuple->src.l3num == AF_INET) {
+		toe_tuple_ul.src_ip = ntohl(tuple->src.u3.ip);
+		toe_tuple_ul.dst_ip = ntohl(tuple->dst.u3.ip);
+
+		toe_tuple_ul.nat_ip = ntohl(tuple->dst.u3.ip);
+		toe_tuple_ul.nat_port = ntohs(el->out_tuple.dst.u.all);
+		toe_tuple_ul.nat = 0;
+
+		toe_tuple_dl.src_ip = toe_tuple_ul.src_ip;
+		toe_tuple_dl.dst_ip = toe_tuple_ul.dst_ip;
+		toe_tuple_dl.nat_ip = ntohl(el->out_tuple.src.u3.ip);
+		toe_tuple_dl.nat_port = ntohs(el->out_tuple.src.u.all);
+		toe_tuple_dl.nat = 1;
+	} else if (tuple->src.l3num == AF_INET6) {
+		memcpy(toe_tuple_ul.src_ip6,
+			tuple->src.u3.in6.s6_addr32, sizeof(toe_tuple_ul.src_ip6));
+		memcpy(toe_tuple_ul.dst_ip6,
+			tuple->dst.u3.in6.s6_addr32, sizeof(toe_tuple_ul.dst_ip6));
+		toe_tuple_dl.ip6 = toe_tuple_ul.ip6 = 1;
+	}
+
+	toe_tuple_dl.src_port = toe_tuple_ul.src_port = ntohs(tuple->src.u.all);
+	toe_tuple_dl.dst_port = toe_tuple_ul.dst_port = ntohs(tuple->dst.u.all);
+	toe_tuple_dl.crc = toe_tuple_ul.crc = 1;
+	toe_tuple_dl.fwd = toe_tuple_ul.fwd = 1;
+	toe_tuple_ul.rxtx = 0; /* ul is tx */
+	toe_tuple_dl.rxtx = 1; /* dl is rx */
+	toe_tuple_ul.pdu = 0xff;
+	toe_tuple_ul.qfi = 0xff;
+	toe_tuple_ul.rbid = 0xff;
+	toe_tuple_ul.mcid = 0xff;
+
+	if (src_vlan_en) {
+		toe_tuple_ul.vlan_en = 1;
+		toe_tuple_ul.vlanid = src_vlanid;
+	}
+
+	if (dst_vlan_en) {
+		toe_tuple_dl.vlan_en = 1;
+		toe_tuple_dl.vlanid = dst_vlanid;
+	}
+
+	if (hh_len) {
+		if (likely(hh_len <= HH_DATA_MOD)) {
+			/* this is inlined by gcc */
+			char mac_header[HH_DATA_MOD];
+			memcpy(mac_header, hh->hh_data, HH_DATA_MOD);
+			memcpy(toe_tuple_ul.smac,
+				&mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN], ETH_ALEN);
+			memcpy(toe_tuple_ul.dmac,
+				&mac_header[HH_DATA_MOD-ETH_TYPE_LEN-ETH_ALEN*2], ETH_ALEN);
+		} else {
+			int hh_alen = HH_DATA_ALIGN(hh_len);
+			char *mac_header = kmalloc(hh_alen, GFP_ATOMIC);
+
+			memcpy(mac_header, hh->hh_data, hh_alen);
+			memcpy(toe_tuple_ul.smac,
+				mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN), ETH_ALEN);
+			memcpy(toe_tuple_ul.dmac,
+				mac_header+(hh_alen-ETH_TYPE_LEN-ETH_ALEN*2), ETH_ALEN);
+			kfree(mac_header);
+		}
+
+		memcpy(toe_tuple_dl.smac, toe_tuple_ul.smac, ETH_ALEN);
+		memcpy(toe_tuple_dl.dmac, toe_tuple_ul.dmac, ETH_ALEN);
+	}
+
+	if (add) {
+		if (toe_add_connection(&toe_tuple_ul) >= 0 &&
+			toe_add_connection(&toe_tuple_dl) >= 0) {
+			el->nl_flag = 1;
+			return 0;
+		}
+	} else {
+		toe_del_connection(&toe_tuple_ul);
+		toe_del_connection(&toe_tuple_dl);
+		el->nl_flag = 0;
+		return 0;
+	}
+
+	return -1;
+}
+
+int fp_cm_genl_send_tuple(struct nf_conntrack_tuple *tuple, struct fpdb_entry *el,
+									 int add, int len)
+{
+	struct sk_buff *msg;
+	int rc;
+	u32 pid, ms;
+
+	if (add) {
+		if (!el->detect_speed_jiffies)
+			el->detect_speed_jiffies = jiffies;
+		el->detect_speed_bytes += len;
+		ms = jiffies_to_msecs(jiffies - el->detect_speed_jiffies);
+		if (ms >= 1000) {
+			el->speed = (el->detect_speed_bytes / ms) << 3; /* kbps */
+			el->detect_speed_jiffies = 0;
+			el->detect_speed_bytes = 0;
+		}
+
+		if (el->speed < speed_thresh)
+			return 0;
+	}
+
+	rc = __fp_eth_wan_set_tuple(tuple, el, add);
+	if (rc >= 0)
+		return rc;
+
+	pid = fp_cm_get_genl_pid();
+	if (pid == -1)
+		return -1;
+
+	msg = nlmsg_new(FP_CM_NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+	if (!msg)
+		return -ENOMEM;
+
+	rc = fp_cm_genl_fill_tuple_info(msg, tuple, el, pid, add);
+	if (rc < 0)
+		goto out_free;
+
+	rc = genlmsg_unicast(&init_net, msg, pid);
+	if (rc) {
+		pr_err_ratelimited("%s genlmsg_unicast fail, rc: %d", __func__, rc);
+		el->nl_flag = 0;
+	}
+
+	return rc;
+out_free:
+	nlmsg_free(msg);
+	return rc;
+}
+
+EXPORT_SYMBOL(fp_cm_genl_send_tuple);
+
+static ssize_t speed_thresh_show(struct fastpath_module *m, char *buf)
+{
+	return sprintf(buf, "speed_thresh: %dKbps\n", speed_thresh);
+}
+
+static ssize_t speed_thresh_store(struct fastpath_module *m, const char *buf,
+			      size_t count)
+{
+	sscanf(buf, "%u", &speed_thresh);
+	return count;
+}
+
+static FP_ATTR(speed_thresh, S_IRUGO|S_IWUSR, speed_thresh_show, speed_thresh_store);
+
+static struct attribute *fp_cm_attrs[] = {
+	&fp_attr_speed_thresh.attr,
+	NULL, /* need to NULL terminate the list of attributes */
+};
+
+void fp_cm_release(struct kobject *kobj)
+{
+	struct fastpath_module *module = to_fpmod(kobj);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
+			int i;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
+	for (i = ARRAY_SIZE(fp_cm_genl_ops) - 1; i >= 0; i--)
+		genl_unregister_ops(&fp_cm_genl_family, &fp_cm_genl_ops[i]);
+#endif
+		genl_unregister_family(&fp_cm_genl_family);
+
+		printk(KERN_ERR "fp_cm gennetlink unregister.....\n");
+		kfree(module);
+}
+
+static struct kobj_type ktype_fp_cm = {
+	.sysfs_ops	= &fp_sysfs_ops,
+	.default_attrs	= fp_cm_attrs,
+	.release	= fp_cm_release,
+};
+
+static int fp_cm_probe(struct fastpath_module *module)
+{
+	int ret;
+
+	module->priv = NULL;
+	snprintf(module->name, sizeof(module->name), "fp_cm");
+
+	ret = genl_register_family(&fp_cm_genl_family);
+
+	kobject_init(&module->kobj, &ktype_fp_cm);
+	ret = kobject_add(&module->kobj, module->fastpath->kobj, "%s", module->name);
+	if (ret < 0) {
+		pr_err("kobject_add failed (%d)\n", ret);
+		goto err_kobject_add;
+	}
+
+	kobject_uevent(&module->kobj, KOBJ_ADD);
+
+	pr_debug("fp_cm probed\n");
+	pr_info("fp_cm gennetlink register success!!!\n");
+
+	return 0;
+
+err_kobject_add:
+	kobject_put(&module->kobj);
+	genl_unregister_family(&fp_cm_genl_family);
+
+	return ret;
+}
+
+static int fp_cm_remove(struct fastpath_module *module)
+{
+	kobject_put(&module->kobj);
+
+	printk(KERN_ERR "fp_cm removed\n");
+	return 0;
+}
+
+struct fastpath_module_ops fp_cm_ops = {
+	.probe = fp_cm_probe,
+	.remove = fp_cm_remove,
+};
+