librmnetctl: Add API to change underlying transport channel

This allows userpsace to move certain bearers to and from the dedicated
low latency channel in the rmnet driver.

Change-Id: I0afe9727507d339e8990b17b5431e6982b88d2f2
diff --git a/rmnetctl/cli/rmnetcli.c b/rmnetctl/cli/rmnetcli.c
index 11184d7..a365891 100644
--- a/rmnetctl/cli/rmnetcli.c
+++ b/rmnetctl/cli/rmnetcli.c
@@ -2,7 +2,7 @@
 
 			R M N E T C L I . C
 
-Copyright (c) 2013-2015, 2017-2020 The Linux Foundation. All rights reserved.
+Copyright (c) 2013-2015, 2017-2021 The Linux Foundation. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
@@ -259,7 +259,11 @@
 	printf("rmnetcli -n wdafreq       <real dev> set powersave poll freq\n\n");
 	printf(_2TABS" <vnd_name>              string - vnd device name\n\n");
 	printf(_2TABS" <freq>                  int - frequency\n\n");
-
+	printf("rmnetcli -n channelswitch  <real dev> change underlying transport channel for bearers");
+	printf(_2TABS" <vnd_name>              string - vnd device name");
+	printf(_2TABS" <switch type>           =0: from LL, !=0: to LL");
+	printf(_2TABS" <flags>                 masks. 1: wait for completion");
+	printf(_2TABS" <bearer id list>        int - list of bearer ids to switch\n");
 }
 
 static void print_rmnetctl_lib_errors(uint16_t error_number)
@@ -301,6 +305,38 @@
 }
 
 /*!
+* @brief Wait for rmnet LL switch status
+* @details Waits and displays LL switch status
+* @param num_bearers Number of bearers to wait for
+* @return RMNETCTL_SUCCESS if successful.
+*/
+static int rmnet_ll_wait_status(rmnetctl_hndl_t *hndl,
+				uint8_t num_bearers,
+				uint16_t *error_number)
+{
+	struct rmnetctl_ll_ack ll_ack;
+	int return_code = RMNETCTL_SUCCESS;
+
+	printf("LL switch initiated, waiting for completion...\n");
+	printf("Bearer/Status/Channel:\n");
+	while (num_bearers--) {
+		return_code = rtrmnet_get_ll_ack(hndl, &ll_ack, error_number);
+		if (return_code != RMNETCTL_SUCCESS)
+			return return_code;
+
+		printf("%u | %u (%s) | %u (%s)\n",
+		       ll_ack.bearer_id,
+		       ll_ack.status_code,
+		       rtrmnet_ll_status_to_text(
+				ll_ack.status_code),
+		       ll_ack.current_ch,
+		       ll_ack.current_ch ? "LL" : "Default");
+	}
+
+	return return_code;
+}
+
+/*!
 * @brief Method to make the API calls
 * @details Checks for each type of parameter and calls the appropriate
 * function based on the number of parameters and parameter type
@@ -501,6 +537,45 @@
 			return_code = rtrmnet_set_wda_freq(handle, argv[1], argv[2],
 							   _STRTOUI32(argv[3]),
 							   &error_number);
+		} else if (!strcmp(*argv, "channelswitch")) {
+			uint32_t ll_flags;
+			uint8_t *bearers;
+			int num_bearers = argc - 5;
+			int i;
+
+			_RMNETCLI_CHECKNULL(argv[1]);
+			_RMNETCLI_CHECKNULL(argv[2]);
+			_RMNETCLI_CHECKNULL(argv[3]);
+			_RMNETCLI_CHECKNULL(argv[4]);
+			/* Force at least one bearer to be provided */
+			_RMNETCLI_CHECKNULL(argv[5]);
+
+			bearers = calloc(sizeof(uint8_t), num_bearers);
+			if (!bearers) {
+				print_rmnet_api_status(RMNETCTL_INVALID_ARG,
+					RMNETCTL_CFG_FAILURE_NO_COMMAND);
+				rmnetctl_cleanup(handle);
+				return RMNETCTL_INVALID_ARG;
+			}
+
+			for (i = 0; i < num_bearers; i++)
+				bearers[i] = _STRTOUI8(argv[5 + i]);
+
+			ll_flags =  _STRTOUI32(argv[4]);
+			return_code = rtrmnet_change_bearer_channel(handle,
+								    argv[1],
+								    argv[2],
+								    _STRTOUI8(argv[3]),
+								    ll_flags,
+								    num_bearers,
+								    bearers,
+								    &error_number);
+			free(bearers);
+
+			if (return_code == RMNETCTL_SUCCESS &&
+			    (ll_flags & RMNETCTL_LL_MASK_ACK))
+				return_code = rmnet_ll_wait_status(
+					handle, num_bearers, &error_number);
 		}
 
 
diff --git a/rmnetctl/inc/librmnetctl.h b/rmnetctl/inc/librmnetctl.h
index b621528..1687ccd 100644
--- a/rmnetctl/inc/librmnetctl.h
+++ b/rmnetctl/inc/librmnetctl.h
@@ -2,7 +2,8 @@
 
 			  L I B R M N E T C T L . H
 
-Copyright (c) 2013-2015, 2017-2019 The Linux Foundation. All rights reserved.
+Copyright (c) 2013-2015, 2017-2019, 2021 The Linux Foundation.
+All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
@@ -174,6 +175,27 @@
 	"ERROR: Netlink message is too small to hold all data\n",
 };
 
+#define RMNETCTL_LL_MASK_ACK 1
+#define RMNETCTL_LL_MASK_RETRY 2
+
+enum rmnetctl_ll_status_e {
+	LL_STATUS_ERROR = 0,
+	LL_STATUS_SUCCESS = 1,
+	LL_STATUS_DEFAULT = 2,
+	LL_STATUS_LL = 3,
+	LL_STATUS_TEMP_FAIL = 4,
+	LL_STATUS_PERM_FAIL = 5,
+	LL_STATUS_NO_EFFECT = 0xFD,
+	LL_STATUS_TIMEOUT = 0xFE
+};
+
+struct rmnetctl_ll_ack
+{
+	uint8_t bearer_id;
+	uint8_t status_code;
+	uint8_t current_ch;
+};
+
 /*===========================================================================
 			 DEFINITIONS AND DECLARATIONS
 ===========================================================================*/
@@ -715,5 +737,20 @@
 			 uint32_t freq,
 			 uint16_t *error_code);
 
+int rtrmnet_change_bearer_channel(rmnetctl_hndl_t *hndl,
+				  char *devname,
+				  char *vndname,
+				  uint8_t switch_type,
+				  uint32_t flags,
+				  uint8_t num_bearers,
+				  uint8_t *bearers,
+				  uint16_t *error_code);
+
+int rtrmnet_get_ll_ack(rmnetctl_hndl_t *hndl,
+		       struct rmnetctl_ll_ack *ll_ack,
+		       uint16_t *error_code);
+
+const char *rtrmnet_ll_status_to_text(uint8_t status);
+
 #endif /* not defined LIBRMNETCTL_H */
 
diff --git a/rmnetctl/inc/librmnetctl_hndl.h b/rmnetctl/inc/librmnetctl_hndl.h
index 1a435ed..c362699 100644
--- a/rmnetctl/inc/librmnetctl_hndl.h
+++ b/rmnetctl/inc/librmnetctl_hndl.h
@@ -2,7 +2,7 @@
 
 			L I B R M N E T C T L _ H N D L. H
 
-Copyright (c) 2013, 2015 The Linux Foundation. All rights reserved.
+Copyright (c) 2013, 2015, 2021 The Linux Foundation. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
@@ -59,6 +59,7 @@
 	 uint32_t transaction_id;
 	 int netlink_fd;
 	 struct sockaddr_nl src_addr, dest_addr;
+	 struct rmnetctl_hndl_s *llc_hndl;
 };
 
 #endif /* not defined LIBRMNETCTL_HNDL_H */
diff --git a/rmnetctl/src/librmnetctl.c b/rmnetctl/src/librmnetctl.c
index 8ad1874..f58b966 100644
--- a/rmnetctl/src/librmnetctl.c
+++ b/rmnetctl/src/librmnetctl.c
@@ -2,7 +2,8 @@
 
 			L I B R M N E T C T L . C
 
-Copyright (c) 2013-2015, 2017-2019 The Linux Foundation. All rights reserved.
+Copyright (c) 2013-2015, 2017-2019, 2021 The Linux Foundation.
+All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
@@ -129,6 +130,8 @@
 	RMNET_FLOW_MSG_QMI_SCALE = 6,
 	/* Change powersave workqueue polling freq */
 	RMNET_FLOW_MSG_WDA_FREQ = 7,
+	/* Change underlying transport channel */
+	RMNET_FLOW_MSG_CHANNEL_SWITCH = 8,
 };
 
 /* 0 reserved, 1-15 for data, 16-30 for acks */
@@ -1225,12 +1228,13 @@
  * @param devindex The ifindex of the real physical device
  * @param *vndname The name of the VND we're modifying
  * @param *flowinfo The parameters sent to the DFC driver
+ * @param flowlen The length of the flowinfo parameter in bytes
  * @return RMENTCTL_LIB_ERR if there is not enough space to add the RTAs
  * @return RMNETCTL_SUCCESS if everything was added successfully
  */
 static int rmnet_fill_flow_msg(struct nlmsg *req, size_t *reqsize,
 			       unsigned int devindex, char *vndname,
-			       struct tcmsg *flowinfo)
+			       char *flowinfo, size_t flowlen)
 {
 	struct rtattr *linkinfo, *datainfo;
 	int rc;
@@ -1257,7 +1261,7 @@
 	if (rc != RMNETCTL_SUCCESS)
 		return rc;
 
-	rc = rta_put(req, reqsize, RMNETCTL_IFLA_DFC_QOS, sizeof(*flowinfo),
+	rc = rta_put(req, reqsize, RMNETCTL_IFLA_DFC_QOS, flowlen,
 		     flowinfo);
 	if (rc != RMNETCTL_SUCCESS)
 		return rc;
@@ -1322,7 +1326,7 @@
 {
 	struct sockaddr_nl __attribute__((__may_alias__)) *saddr_ptr;
 	int netlink_fd = -1;
-	pid_t pid = 0;
+	socklen_t addr_len = sizeof(struct sockaddr_nl);
 
 	if (!hndl || !error_code)
 		return RMNETCTL_INVALID_ARG;
@@ -1335,13 +1339,6 @@
 
 	memset(*hndl, 0, sizeof(rmnetctl_hndl_t));
 
-	pid = getpid();
-	if (pid  < MIN_VALID_PROCESS_ID) {
-		free(*hndl);
-		*error_code = RMNETCTL_INIT_ERR_PROCESS_ID;
-		return RMNETCTL_LIB_ERR;
-	}
-	(*hndl)->pid = KERNEL_PROCESS_ID;
 	netlink_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
 	if (netlink_fd < MIN_VALID_SOCKET_FD) {
 		free(*hndl);
@@ -1351,10 +1348,8 @@
 
 	(*hndl)->netlink_fd = netlink_fd;
 
-	memset(&(*hndl)->src_addr, 0, sizeof(struct sockaddr_nl));
-
 	(*hndl)->src_addr.nl_family = AF_NETLINK;
-	(*hndl)->src_addr.nl_pid = (*hndl)->pid;
+	(*hndl)->src_addr.nl_pid = 0; /* auto assign */
 
 	saddr_ptr = &(*hndl)->src_addr;
 	if (bind((*hndl)->netlink_fd,
@@ -1366,7 +1361,9 @@
 		return RMNETCTL_LIB_ERR;
 	}
 
-	memset(&(*hndl)->dest_addr, 0, sizeof(struct sockaddr_nl));
+	/* Get assigned port_id */
+	if (!getsockname(netlink_fd, (struct sockaddr *)saddr_ptr, &addr_len))
+		(*hndl)->pid = (*hndl)->src_addr.nl_pid;
 
 	(*hndl)->dest_addr.nl_family = AF_NETLINK;
 	(*hndl)->dest_addr.nl_pid = KERNEL_PROCESS_ID;
@@ -1380,6 +1377,9 @@
 	if (!hndl)
 		return RMNETCTL_SUCCESS;
 
+	if (hndl->llc_hndl)
+		rtrmnet_ctl_deinit(hndl->llc_hndl);
+
 	close(hndl->netlink_fd);
 	free(hndl);
 
@@ -1806,7 +1806,8 @@
 	flowinfo.tcm_ifindex = ip_type;
 	flowinfo.tcm_parent = flow_id;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -1861,7 +1862,8 @@
 	flowinfo.tcm__pad1 = bearer_id;
 	flowinfo.tcm_parent = flow_id;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo,sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -1917,7 +1919,8 @@
 	flowinfo.tcm_parent = ack;
 	flowinfo.tcm_info = grantsize;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -1974,7 +1977,8 @@
 	flowinfo.tcm_parent = ifaceid;
 	flowinfo.tcm_info = ep_type;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -2025,7 +2029,8 @@
 	flowinfo.tcm_handle = instance;
 	flowinfo.tcm_family = RMNET_FLOW_MSG_DOWN;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -2075,7 +2080,8 @@
 	flowinfo.tcm_ifindex = scale;
 	flowinfo.tcm_family = RMNET_FLOW_MSG_QMI_SCALE;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -2125,7 +2131,8 @@
 	flowinfo.tcm_ifindex = freq;
 	flowinfo.tcm_family = RMNET_FLOW_MSG_WDA_FREQ;
 
-	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname, &flowinfo);
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
 	if (rc != RMNETCTL_SUCCESS) {
 		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
 		return rc;
@@ -2138,3 +2145,144 @@
 
 	return rmnet_get_ack(hndl, error_code);
 }
+
+int rtrmnet_change_bearer_channel(rmnetctl_hndl_t *hndl,
+				  char *devname,
+				  char *vndname,
+				  uint8_t switch_type,
+				  uint32_t flags,
+				  uint8_t num_bearers,
+				  uint8_t *bearers,
+				  uint16_t *error_code)
+{
+	struct nlmsg req;
+	struct {
+		struct tcmsg tcm;
+		uint8_t data[16]; /* Max number of bearers */
+	} flowinfo;
+	unsigned int devindex;
+	size_t reqsize;
+	int rc;
+
+	memset(&req, 0, sizeof(req));
+	memset(&flowinfo, 0, sizeof(flowinfo));
+	if (!hndl || !devname || !error_code ||_rmnetctl_check_dev_name(devname) ||
+		_rmnetctl_check_dev_name(vndname) || num_bearers > 16 || !bearers)
+		return RMNETCTL_INVALID_ARG;
+
+	reqsize = NLMSG_DATA_SIZE - sizeof(struct rtattr);
+	req.nl_addr.nlmsg_type = RTM_NEWLINK;
+	req.nl_addr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	req.nl_addr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	req.nl_addr.nlmsg_seq = hndl->transaction_id;
+	hndl->transaction_id++;
+
+	/* Get index of devname*/
+	devindex = if_nametoindex(devname);
+	if (devindex == 0) {
+		*error_code = errno;
+		return RMNETCTL_KERNEL_ERR;
+	}
+
+	/* Create 2nd hndl to receive LLC status */
+	if ((flags & RMNETCTL_LL_MASK_ACK) && !hndl->llc_hndl) {
+		rc = rtrmnet_ctl_init(&hndl->llc_hndl, error_code);
+		if (rc != RMNETCTL_SUCCESS) {
+			hndl->llc_hndl = NULL;
+			return rc;
+		}
+	}
+
+	/* Fill the flow information */
+	flowinfo.tcm.tcm_family = RMNET_FLOW_MSG_CHANNEL_SWITCH;
+	flowinfo.tcm.tcm__pad1 = switch_type;
+	flowinfo.tcm.tcm__pad2 = num_bearers;
+	flowinfo.tcm.tcm_info = flags;
+	memscpy(flowinfo.data, 16, bearers, num_bearers);
+	/* DFC needs this to send the ACK back to LLC socket specifically */
+	if (hndl->llc_hndl) {
+		flowinfo.tcm.tcm_ifindex = hndl->llc_hndl->netlink_fd;
+		flowinfo.tcm.tcm_handle = hndl->llc_hndl->pid;
+		flowinfo.tcm.tcm_parent = hndl->transaction_id--;
+	}
+
+	rc = rmnet_fill_flow_msg(&req, &reqsize, devindex, vndname,
+				 (char *)&flowinfo, sizeof(flowinfo));
+	if (rc != RMNETCTL_SUCCESS) {
+		*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
+		return rc;
+	}
+
+	/* Fire away */
+	if (send(hndl->netlink_fd, &req, req.nl_addr.nlmsg_len, 0) < 0) {
+		*error_code = RMNETCTL_API_ERR_MESSAGE_SEND;
+		return RMNETCTL_LIB_ERR;
+	}
+
+	/* Initial ACK: The kernel got the message */
+	return rmnet_get_ack(hndl, error_code);
+}
+
+int rtrmnet_get_ll_ack(rmnetctl_hndl_t *hndl,
+		       struct rmnetctl_ll_ack *ll_ack,
+		       uint16_t *error_code)
+{
+	struct nlack {
+		struct nlmsghdr ackheader;
+		struct nlmsgerr ackdata;
+
+	} ack;
+	int i;
+
+	if (!hndl || !hndl->llc_hndl || !ll_ack || !error_code)
+		return RMNETCTL_INVALID_ARG;
+
+	if ((i = recv(hndl->llc_hndl->netlink_fd, &ack, sizeof(ack), 0)) < 0) {
+		*error_code = errno;
+		return RMNETCTL_API_ERR_MESSAGE_RECEIVE;
+	}
+
+	/*Ack should always be NLMSG_ERROR type*/
+	if (ack.ackheader.nlmsg_type == NLMSG_ERROR) {
+		if (ack.ackdata.error == 0) {
+			ll_ack->bearer_id =
+				(uint8_t)ack.ackdata.msg.nlmsg_type;
+			ll_ack->status_code =
+				(uint8_t)ack.ackdata.msg.nlmsg_flags;
+			ll_ack->current_ch =
+				(uint8_t)ack.ackdata.msg.nlmsg_seq;
+			*error_code = RMNETCTL_API_SUCCESS;
+			return RMNETCTL_SUCCESS;
+		} else {
+			*error_code = -ack.ackdata.error;
+			return RMNETCTL_KERNEL_ERR;
+		}
+	}
+
+	*error_code = RMNETCTL_API_ERR_RETURN_TYPE;
+	return RMNETCTL_API_FIRST_ERR;
+}
+
+static const char *rmnetctl_ll_status_text[] = {
+        "Error",
+        "Success",
+        "Switched to Default",
+        "Switched to LL",
+        "Temporary Failure",
+        "Permanent Failure"
+};
+
+const char *rtrmnet_ll_status_to_text(uint8_t status)
+{
+	if (status == LL_STATUS_TIMEOUT)
+		return "Time out";
+
+	if (status == LL_STATUS_NO_EFFECT)
+		return "No Effect";
+
+	if (status < sizeof(rmnetctl_ll_status_text) /
+		     sizeof(rmnetctl_ll_status_text[0]))
+		return rmnetctl_ll_status_text[status];
+
+	return "Unknown";
+}