/* Copyright (c) 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. * * RMNET Data Smart Hash Workqueue Generic Netlink Functions * */ #include "rmnet_shs_wq_genl.h" #include #include MODULE_LICENSE("GPL v2"); static struct net *last_net; static u32 last_snd_portid; uint32_t rmnet_shs_genl_seqnum; int rmnet_shs_userspace_connected; /* Static Functions and Definitions */ static struct nla_policy rmnet_shs_genl_attr_policy[RMNET_SHS_GENL_ATTR_MAX + 1] = { [RMNET_SHS_GENL_ATTR_INT] = { .type = NLA_S32 }, [RMNET_SHS_GENL_ATTR_SUGG] = { .len = sizeof(struct rmnet_shs_wq_sugg_info) }, [RMNET_SHS_GENL_ATTR_SEG] = { .len = sizeof(struct rmnet_shs_wq_seg_info) }, [RMNET_SHS_GENL_ATTR_STR] = { .type = NLA_NUL_STRING }, }; #define RMNET_SHS_GENL_OP(_cmd, _func) \ { \ .cmd = _cmd, \ .policy = rmnet_shs_genl_attr_policy, \ .doit = _func, \ .dumpit = NULL, \ .flags = 0, \ } static const struct genl_ops rmnet_shs_genl_ops[] = { RMNET_SHS_GENL_OP(RMNET_SHS_GENL_CMD_INIT_DMA, rmnet_shs_genl_dma_init), RMNET_SHS_GENL_OP(RMNET_SHS_GENL_CMD_TRY_TO_MOVE_FLOW, rmnet_shs_genl_try_to_move_flow), RMNET_SHS_GENL_OP(RMNET_SHS_GENL_CMD_SET_FLOW_SEGMENTATION, rmnet_shs_genl_set_flow_segmentation), RMNET_SHS_GENL_OP(RMNET_SHS_GENL_CMD_MEM_SYNC, rmnet_shs_genl_mem_sync), }; struct genl_family rmnet_shs_genl_family = { .hdrsize = 0, .name = RMNET_SHS_GENL_FAMILY_NAME, .version = RMNET_SHS_GENL_VERSION, .maxattr = RMNET_SHS_GENL_ATTR_MAX, .ops = rmnet_shs_genl_ops, .n_ops = ARRAY_SIZE(rmnet_shs_genl_ops), }; int rmnet_shs_genl_send_int_to_userspace(struct genl_info *info, int val) { struct sk_buff *skb; void *msg_head; int rc; skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); if (skb == NULL) goto out; msg_head = genlmsg_put(skb, 0, info->snd_seq+1, &rmnet_shs_genl_family, 0, RMNET_SHS_GENL_CMD_INIT_DMA); if (msg_head == NULL) { rc = -ENOMEM; goto out; } rc = nla_put_u32(skb, RMNET_SHS_GENL_ATTR_INT, val); if (rc != 0) goto out; genlmsg_end(skb, msg_head); rc = genlmsg_unicast(genl_info_net(info), skb, info->snd_portid); if (rc != 0) goto out; rm_err("SHS_GNL: Successfully sent int %d\n", val); return 0; out: /* TODO: Need to free skb?? */ rm_err("SHS_GNL: FAILED to send int %d\n", val); return -1; } int rmnet_shs_genl_send_int_to_userspace_no_info(int val) { struct sk_buff *skb; void *msg_head; int rc; if (last_net == NULL) { rm_err("SHS_GNL: FAILED to send int %d - last_net is NULL\n", val); return -1; } skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); if (skb == NULL) goto out; msg_head = genlmsg_put(skb, 0, rmnet_shs_genl_seqnum++, &rmnet_shs_genl_family, 0, RMNET_SHS_GENL_CMD_INIT_DMA); if (msg_head == NULL) { rc = -ENOMEM; goto out; } rc = nla_put_u32(skb, RMNET_SHS_GENL_ATTR_INT, val); if (rc != 0) goto out; genlmsg_end(skb, msg_head); rc = genlmsg_unicast(last_net, skb, last_snd_portid); if (rc != 0) goto out; rm_err("SHS_GNL: Successfully sent int %d\n", val); return 0; out: /* TODO: Need to free skb?? */ rm_err("SHS_GNL: FAILED to send int %d\n", val); rmnet_shs_userspace_connected = 0; return -1; } int rmnet_shs_genl_send_msg_to_userspace(void) { struct sk_buff *skb; void *msg_head; int rc; int val = rmnet_shs_genl_seqnum++; rm_err("SHS_GNL: Trying to send msg %d\n", val); skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); if (skb == NULL) goto out; msg_head = genlmsg_put(skb, 0, rmnet_shs_genl_seqnum++, &rmnet_shs_genl_family, 0, RMNET_SHS_GENL_CMD_INIT_DMA); if (msg_head == NULL) { rc = -ENOMEM; goto out; } rc = nla_put_u32(skb, RMNET_SHS_GENL_ATTR_INT, val); if (rc != 0) goto out; genlmsg_end(skb, msg_head); genlmsg_multicast(&rmnet_shs_genl_family, skb, 0, 0, GFP_ATOMIC); rm_err("SHS_GNL: Successfully sent int %d\n", val); return 0; out: /* TODO: Need to free skb?? */ rm_err("SHS_GNL: FAILED to send int %d\n", val); rmnet_shs_userspace_connected = 0; return -1; } /* Currently unused - handles message from userspace to initialize the shared memory, * memory is inited by kernel wq automatically */ int rmnet_shs_genl_dma_init(struct sk_buff *skb_2, struct genl_info *info) { rm_err("%s", "SHS_GNL: rmnet_shs_genl_dma_init"); if (info == NULL) { rm_err("%s", "SHS_GNL: an error occured - info is null"); return -1; } return 0; } int rmnet_shs_genl_set_flow_segmentation(struct sk_buff *skb_2, struct genl_info *info) { struct nlattr *na; struct rmnet_shs_wq_seg_info seg_info; int rc = 0; rm_err("%s", "SHS_GNL: rmnet_shs_genl_set_flow_segmentation"); if (info == NULL) { rm_err("%s", "SHS_GNL: an error occured - info is null"); return -1; } na = info->attrs[RMNET_SHS_GENL_ATTR_SEG]; if (na) { if (nla_memcpy(&seg_info, na, sizeof(seg_info)) > 0) { rm_err("SHS_GNL: recv segmentation req " "hash_to_set = 0x%x segment_enable = %u", seg_info.hash_to_set, seg_info.segment_enable); rc = rmnet_shs_wq_set_flow_segmentation(seg_info.hash_to_set, seg_info.segment_enable); if (rc == 1) { rmnet_shs_genl_send_int_to_userspace(info, 0); trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_FLOW_SEG_SET_PASS, seg_info.hash_to_set, seg_info.segment_enable, 0xDEF, 0xDEF, NULL, NULL); } else { rmnet_shs_genl_send_int_to_userspace(info, -1); trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_FLOW_SEG_SET_FAIL, seg_info.hash_to_set, seg_info.segment_enable, 0xDEF, 0xDEF, NULL, NULL); return 0; } } else { rm_err("SHS_GNL: nla_memcpy failed %d\n", RMNET_SHS_GENL_ATTR_SEG); rmnet_shs_genl_send_int_to_userspace(info, -1); return 0; } } else { rm_err("SHS_GNL: no info->attrs %d\n", RMNET_SHS_GENL_ATTR_SEG); rmnet_shs_genl_send_int_to_userspace(info, -1); return 0; } return 0; } int rmnet_shs_genl_try_to_move_flow(struct sk_buff *skb_2, struct genl_info *info) { struct nlattr *na; struct rmnet_shs_wq_sugg_info sugg_info; int rc = 0; rm_err("%s", "SHS_GNL: rmnet_shs_genl_try_to_move_flow"); if (info == NULL) { rm_err("%s", "SHS_GNL: an error occured - info is null"); return -1; } na = info->attrs[RMNET_SHS_GENL_ATTR_SUGG]; if (na) { if (nla_memcpy(&sugg_info, na, sizeof(sugg_info)) > 0) { rm_err("SHS_GNL: cur_cpu =%u dest_cpu = %u " "hash_to_move = 0x%x sugg_type = %u", sugg_info.cur_cpu, sugg_info.dest_cpu, sugg_info.hash_to_move, sugg_info.sugg_type); rc = rmnet_shs_wq_try_to_move_flow(sugg_info.cur_cpu, sugg_info.dest_cpu, sugg_info.hash_to_move, sugg_info.sugg_type); if (rc == 1) { rmnet_shs_genl_send_int_to_userspace(info, 0); trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_TRY_PASS, sugg_info.cur_cpu, sugg_info.dest_cpu, sugg_info.hash_to_move, sugg_info.sugg_type, NULL, NULL); } else { rmnet_shs_genl_send_int_to_userspace(info, -1); trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_TRY_FAIL, sugg_info.cur_cpu, sugg_info.dest_cpu, sugg_info.hash_to_move, sugg_info.sugg_type, NULL, NULL); return 0; } } else { rm_err("SHS_GNL: nla_memcpy failed %d\n", RMNET_SHS_GENL_ATTR_SUGG); rmnet_shs_genl_send_int_to_userspace(info, -1); return 0; } } else { rm_err("SHS_GNL: no info->attrs %d\n", RMNET_SHS_GENL_ATTR_SUGG); rmnet_shs_genl_send_int_to_userspace(info, -1); return 0; } return 0; } int rmnet_shs_genl_mem_sync(struct sk_buff *skb_2, struct genl_info *info) { rm_err("%s", "SHS_GNL: rmnet_shs_genl_mem_sync"); if (!rmnet_shs_userspace_connected) rmnet_shs_userspace_connected = 1; /* Todo: detect when userspace is disconnected. If we dont get * a sync message in the next 2 wq ticks, we got disconnected */ trace_rmnet_shs_wq_high(RMNET_SHS_WQ_SHSUSR, RMNET_SHS_WQ_SHSUSR_SYNC_START, 0xDEF, 0xDEF, 0xDEF, 0xDEF, NULL, NULL); if (info == NULL) { rm_err("%s", "SHS_GNL: an error occured - info is null"); return -1; } last_net = genl_info_net(info); last_snd_portid = info->snd_portid; return 0; } /* register new generic netlink family */ int rmnet_shs_wq_genl_init(void) { int ret; rmnet_shs_userspace_connected = 0; ret = genl_register_family(&rmnet_shs_genl_family); if (ret != 0) { rm_err("SHS_GNL: register family failed: %i", ret); genl_unregister_family(&rmnet_shs_genl_family); return -1; } rm_err("SHS_GNL: successfully registered generic netlink familiy: %s", RMNET_SHS_GENL_FAMILY_NAME); return 0; } /* Unregister the generic netlink family */ int rmnet_shs_wq_genl_deinit(void) { int ret; ret = genl_unregister_family(&rmnet_shs_genl_family); if(ret != 0){ rm_err("SHS_GNL: unregister family failed: %i\n",ret); } rmnet_shs_userspace_connected = 0; return 0; }