diff mbox series

[BlueZ,v1] hcitool - Added option for Peripheral Initiated Connection Parameter Update Request.

Message ID 20240724111747.5952-1-quic_nakella@quicinc.com
State Superseded
Headers show
Series [BlueZ,v1] hcitool - Added option for Peripheral Initiated Connection Parameter Update Request. | expand

Commit Message

Naga Bhavani Akella July 24, 2024, 11:17 a.m. UTC
There is no option in hcitool when Peripheral wants to
initiate Connection Parameter Update Request, hence
added provision to be able to send LL_CONNECTION_PARAM_REQ
using hcitool.

Required for PTS TC - GAP/CONN/CPUP/BV-02-C

Reference link for discussion :
https://lore.kernel.org/linux-bluetooth/
Search for Subject - LE Connection Update Disallowed
git code link :
https://gist.github.com/SandyChapman/4a64c9ea22cd27d935e3
---
 lib/hci.c       | 81 +++++++++++++++++++++++++++++++++++++++++++
 lib/hci_lib.h   | 35 +++++++++++++++++++
 tools/hcitool.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 208 insertions(+)

--

Comments

Luiz Augusto von Dentz July 24, 2024, 4:23 p.m. UTC | #1
Hi,

On Wed, Jul 24, 2024 at 7:20 AM Naga Bhavani Akella
<quic_nakella@quicinc.com> wrote:
>
> There is no option in hcitool when Peripheral wants to
> initiate Connection Parameter Update Request, hence
> added provision to be able to send LL_CONNECTION_PARAM_REQ
> using hcitool.
>
> Required for PTS TC - GAP/CONN/CPUP/BV-02-C
>
> Reference link for discussion :
> https://lore.kernel.org/linux-bluetooth/
> Search for Subject - LE Connection Update Disallowed
> git code link :
> https://gist.github.com/SandyChapman/4a64c9ea22cd27d935e3
> ---
>  lib/hci.c       | 81 +++++++++++++++++++++++++++++++++++++++++++
>  lib/hci_lib.h   | 35 +++++++++++++++++++
>  tools/hcitool.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 208 insertions(+)
>
> diff --git a/lib/hci.c b/lib/hci.c
> index 937e65d48..294b0bbd8 100644
> --- a/lib/hci.c
> +++ b/lib/hci.c
> @@ -1119,6 +1119,87 @@ int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
>         return 0;
>  }
>
> +int hci_send_acl_data(int dd, uint16_t handle, uint8_t dlen,
> +                       struct signal_hdr *sh, struct signal_payload_hdr *plh,
> +                       void *pl)
> +{
> +       uint8_t type = HCI_ACLDATA_PKT;
> +       hci_acl_hdr ha;
> +       struct iovec iv[5];
> +       int ivn;
> +
> +       ha.handle = handle;
> +       ha.dlen = dlen;
> +
> +       iv[0].iov_base = &type;
> +       iv[0].iov_len = 1;
> +       iv[1].iov_base = &ha;
> +       iv[1].iov_len = HCI_ACL_HDR_SIZE;
> +       ivn = 2;
> +
> +       printf("\nACL Packet details[handle:%x, length:%d]\n",
> +                       ha.handle, ha.dlen);
> +
> +       if (dlen) {
> +               iv[2].iov_base = sh;
> +               iv[2].iov_len = 4; //HCI_SIGNAL_HDR_SIZE;
> +               ivn = 3;
> +               printf("\nACL signal command details[length:%d, cid:%d]\n",
> +                               sh->len, sh->cid);
> +               if (sh->len > 0) {
> +                       iv[3].iov_base = plh;
> +                       iv[3].iov_len = 4; //HCI_SIGNAL_PAYLOAD_HDR_SIZE;
> +                       ivn = 4;
> +                       if (plh->len > 0) {
> +                               iv[4].iov_base = pl;
> +                               iv[4].iov_len = plh->len;
> +                               ivn = 5;
> +                       }
> +               }
> +       }
> +
> +       while (writev(dd, iv, ivn) < 0) {
> +               if (errno == EAGAIN || errno == EINTR)
> +                       continue;
> +               return -1;
> +       }
> +       return 0;
> +}
> +
> +int hci_signal_le_con_param_update_req(int dd, uint16_t handle,
> +                                               uint16_t interval_min,
> +                                               uint16_t interval_max,
> +                                               uint16_t slave_latency,
> +                                               uint16_t timeout_multiplier)
> +{
> +       struct signal_hdr sh;
> +       struct signal_payload_hdr pl;
> +       struct le_con_param_update_req ur;
> +
> +       uint16_t length = 0x0010;
> +
> +       memset(&sh, 0, sizeof(sh));
> +       memset(&pl, 0, sizeof(pl));
> +       memset(&ur, 0, sizeof(ur));
> +
> +       sh.len = HCI_SIGNAL_LE_CON_PARAM_UPDATE_REQ_SIZE;
> +       sh.cid = HCI_LE_CHANNEL_ID;
> +
> +       pl.code = LE_CON_PARAM_UPDATE_REQ_CODE;
> +       pl.id = 0x77;
> +       pl.len = LE_CON_PARAM_UPDATE_LEN;
> +
> +       ur.interval_min = interval_min;
> +       ur.interval_max = interval_max;
> +       ur.slave_latency = slave_latency;
> +       ur.timeout_multiplier = timeout_multiplier;
> +
> +       if (hci_send_acl_data(dd, handle, length, &sh, &pl, &ur) < 0)
> +               return -1;
> +
> +       return 0;
> +}
> +
>  int hci_send_req(int dd, struct hci_request *r, int to)
>  {
>         unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
> diff --git a/lib/hci_lib.h b/lib/hci_lib.h
> index baf3d3e12..fe0458a1b 100644
> --- a/lib/hci_lib.h
> +++ b/lib/hci_lib.h
> @@ -35,12 +35,47 @@ struct hci_version {
>         uint16_t lmp_subver;
>  };
>
> +struct hci_acl_hdr {
> +       uint16_t handle;
> +       uint16_t len;
> +};
> +
> +struct signal_hdr {
> +       uint16_t len;
> +       uint16_t cid;
> +};
> +
> +struct signal_payload_hdr {
> +       uint8_t  code;
> +       uint8_t  id;
> +       uint16_t len;
> +};
> +
> +struct le_con_param_update_req {
> +       uint16_t interval_min;
> +       uint16_t interval_max;
> +       uint16_t slave_latency;
> +       uint16_t timeout_multiplier;
> +};
> +#define HCI_SIGNAL_LE_CON_PARAM_UPDATE_REQ_SIZE 0x000C
> +#define HCI_LE_CHANNEL_ID                       0x0005
> +#define LE_CON_PARAM_UPDATE_REQ_CODE            0x12
> +#define LE_CON_PARAM_UPDATE_LEN                 0x0008
> +
>  int hci_open_dev(int dev_id);
>  int hci_close_dev(int dd);
>  int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param);
> +int hci_send_acl_data(int dd, uint16_t handle, uint8_t dlen,
> +                               struct signal_hdr *sh,
> +                               struct signal_payload_hdr *plh, void *pl);
>  int hci_send_req(int dd, struct hci_request *req, int timeout);
>
>  int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to);
> +int hci_signal_le_con_param_update_req(int dd, uint16_t handle,
> +                                               uint16_t interval_min,
> +                                               uint16_t interval_max,
> +                                               uint16_t slave_latency,
> +                                               uint16_t timeout_multiplier);
>  int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to);
>
>  int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags);
> diff --git a/tools/hcitool.c b/tools/hcitool.c
> index 639ee6a51..ce611bb72 100644
> --- a/tools/hcitool.c
> +++ b/tools/hcitool.c
> @@ -3369,6 +3369,97 @@ static void cmd_lecup(int dev_id, int argc, char **argv)
>         hci_close_dev(dd);
>  }
>
> +static const char *acl_lecup_help =
> +       "Usage:\n"
> +       "\tacllecup <handle> <min> <max> <latency> <timeout>\n"
> +       "\tOptions:\n"
> +       "\t    -H, --handle <0xXXXX>  LE connection handle\n"
> +       "\t    -m, --min <interval>   Range: 0x0006 to 0x0C80\n"
> +       "\t    -M, --max <interval>   Range: 0x0006 to 0x0C80\n"
> +       "\t    -l, --latency <range>  Slave latency. Range: 0x0000 to 0x03E8\n"
> +       "\t    -t, --timeout  <time>  N * 10ms. Range: 0x000A to 0x0C80\n"
> +       "\n\t min/max range: 7.5ms to 4s. Multiply factor: 1.25ms"
> +       "\n\t timeout range: 100ms to 32.0s. Larger than max interval\n";

Since to be the same as lecup, is the only difference being a
peripheral? We could perhaps attempt to detect if the handle is for a
peripheral, or add another parameter. Also perhaps this should have
been done via main.conf:

# LE default connection parameters.  These values are superceeded by any
# specific values provided via the Load Connection Parameters interface
#MinConnectionInterval=
#MaxConnectionInterval=
#ConnectionLatency=
#ConnectionSupervisionTimeout=

But perhaps the kernel is not attempting to use them when connected as
a peripheral? It probably should though.

> +static void cmd_acl_lecup(int dev_id, int argc, char **argv)
> +{
> +       uint16_t handle = 0, min, max, latency, timeout;
> +       int opt, dd, base;
> +       int options = 0;
> +
> +       /* Aleatory valid values */
> +       min = 0x0C8;
> +       max = 0x0960;
> +       latency = 0x0007;
> +       timeout = 0x0C80;
> +
> +       for_each_opt(opt, lecup_options, NULL) {
> +               if (optarg && strncasecmp("0x", optarg, 2) == 0)
> +                       base = 16;
> +               else
> +                       base = 10;
> +
> +               switch (opt) {
> +               case 'H':
> +                       handle = strtoul(optarg, NULL, base);
> +                       break;
> +               case 'm':
> +                       min = strtoul(optarg, NULL, base);
> +                       break;
> +               case 'M':
> +                       max = strtoul(optarg, NULL, base);
> +                       break;
> +               case 'l':
> +                       latency = strtoul(optarg, NULL, base);
> +                       break;
> +               case 't':
> +                       timeout = strtoul(optarg, NULL, base);
> +                       break;
> +               default:
> +                       printf("%s", acl_lecup_help);
> +                       return;
> +               }
> +               options = 1;
> +       }
> +
> +       if (options == 0) {
> +               helper_arg(5, 5, &argc, &argv, acl_lecup_help);
> +
> +               handle = strtoul(argv[0], NULL, 0);
> +               min = strtoul(argv[1], NULL, 0);
> +               max = strtoul(argv[2], NULL, 0);
> +               latency = strtoul(argv[3], NULL, 0);
> +               timeout = strtoul(argv[4], NULL, 0);
> +       }
> +
> +       if (handle == 0 || handle > 0x0EFF) {
> +               printf("%s", acl_lecup_help);
> +               return;
> +       }
> +
> +       if (dev_id < 0)
> +               dev_id = hci_get_route(NULL);
> +
> +       dd = hci_open_dev(dev_id);
> +       if (dd < 0) {
> +               fprintf(stderr, "HCI device open failed\n");
> +               exit(1);
> +       }
> +
> +       fprintf(stderr, "Signal LE Connection Update Request: %d %d %d %d %d\n",
> +                       handle, min, max, latency, timeout);
> +       if (hci_signal_le_con_param_update_req(dd, htobs(handle), htobs(min),
> +                                               htobs(max), htobs(latency),
> +                                               htobs(timeout)) < 0) {
> +               int err = -errno;
> +
> +               fprintf(stderr, "Could not change connection params: %s(%d)\n",
> +                                                       strerror(-err), -err);
> +       }
> +
> +       hci_close_dev(dd);
> +}
> +
>  static struct {
>         char *cmd;
>         void (*func)(int dev_id, int argc, char **argv);
> @@ -3417,6 +3508,7 @@ static struct {
>         { "lecc",     cmd_lecc,    "Create a LE Connection"               },
>         { "ledc",     cmd_ledc,    "Disconnect a LE Connection"           },
>         { "lecup",    cmd_lecup,   "LE Connection Update"                 },
> +       { "acllecup", cmd_acl_lecup, "LE ACL Connection Param Update Req" },
>         { NULL, NULL, 0 }
>  };
>
> --
>
diff mbox series

Patch

diff --git a/lib/hci.c b/lib/hci.c
index 937e65d48..294b0bbd8 100644
--- a/lib/hci.c
+++ b/lib/hci.c
@@ -1119,6 +1119,87 @@  int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
 	return 0;
 }
 
+int hci_send_acl_data(int dd, uint16_t handle, uint8_t dlen,
+			struct signal_hdr *sh, struct signal_payload_hdr *plh,
+			void *pl)
+{
+	uint8_t type = HCI_ACLDATA_PKT;
+	hci_acl_hdr ha;
+	struct iovec iv[5];
+	int ivn;
+
+	ha.handle = handle;
+	ha.dlen = dlen;
+
+	iv[0].iov_base = &type;
+	iv[0].iov_len = 1;
+	iv[1].iov_base = &ha;
+	iv[1].iov_len = HCI_ACL_HDR_SIZE;
+	ivn = 2;
+
+	printf("\nACL Packet details[handle:%x, length:%d]\n",
+			ha.handle, ha.dlen);
+
+	if (dlen) {
+		iv[2].iov_base = sh;
+		iv[2].iov_len = 4; //HCI_SIGNAL_HDR_SIZE;
+		ivn = 3;
+		printf("\nACL signal command details[length:%d, cid:%d]\n",
+				sh->len, sh->cid);
+		if (sh->len > 0) {
+			iv[3].iov_base = plh;
+			iv[3].iov_len = 4; //HCI_SIGNAL_PAYLOAD_HDR_SIZE;
+			ivn = 4;
+			if (plh->len > 0) {
+				iv[4].iov_base = pl;
+				iv[4].iov_len = plh->len;
+				ivn = 5;
+			}
+		}
+	}
+
+	while (writev(dd, iv, ivn) < 0) {
+		if (errno == EAGAIN || errno == EINTR)
+			continue;
+		return -1;
+	}
+	return 0;
+}
+
+int hci_signal_le_con_param_update_req(int dd, uint16_t handle,
+						uint16_t interval_min,
+						uint16_t interval_max,
+						uint16_t slave_latency,
+						uint16_t timeout_multiplier)
+{
+	struct signal_hdr sh;
+	struct signal_payload_hdr pl;
+	struct le_con_param_update_req ur;
+
+	uint16_t length = 0x0010;
+
+	memset(&sh, 0, sizeof(sh));
+	memset(&pl, 0, sizeof(pl));
+	memset(&ur, 0, sizeof(ur));
+
+	sh.len = HCI_SIGNAL_LE_CON_PARAM_UPDATE_REQ_SIZE;
+	sh.cid = HCI_LE_CHANNEL_ID;
+
+	pl.code = LE_CON_PARAM_UPDATE_REQ_CODE;
+	pl.id = 0x77;
+	pl.len = LE_CON_PARAM_UPDATE_LEN;
+
+	ur.interval_min = interval_min;
+	ur.interval_max = interval_max;
+	ur.slave_latency = slave_latency;
+	ur.timeout_multiplier = timeout_multiplier;
+
+	if (hci_send_acl_data(dd, handle, length, &sh, &pl, &ur) < 0)
+		return -1;
+
+	return 0;
+}
+
 int hci_send_req(int dd, struct hci_request *r, int to)
 {
 	unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
diff --git a/lib/hci_lib.h b/lib/hci_lib.h
index baf3d3e12..fe0458a1b 100644
--- a/lib/hci_lib.h
+++ b/lib/hci_lib.h
@@ -35,12 +35,47 @@  struct hci_version {
 	uint16_t lmp_subver;
 };
 
+struct hci_acl_hdr {
+	uint16_t handle;
+	uint16_t len;
+};
+
+struct signal_hdr {
+	uint16_t len;
+	uint16_t cid;
+};
+
+struct signal_payload_hdr {
+	uint8_t  code;
+	uint8_t  id;
+	uint16_t len;
+};
+
+struct le_con_param_update_req {
+	uint16_t interval_min;
+	uint16_t interval_max;
+	uint16_t slave_latency;
+	uint16_t timeout_multiplier;
+};
+#define HCI_SIGNAL_LE_CON_PARAM_UPDATE_REQ_SIZE 0x000C
+#define HCI_LE_CHANNEL_ID                       0x0005
+#define LE_CON_PARAM_UPDATE_REQ_CODE            0x12
+#define LE_CON_PARAM_UPDATE_LEN                 0x0008
+
 int hci_open_dev(int dev_id);
 int hci_close_dev(int dd);
 int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param);
+int hci_send_acl_data(int dd, uint16_t handle, uint8_t dlen,
+				struct signal_hdr *sh,
+				struct signal_payload_hdr *plh, void *pl);
 int hci_send_req(int dd, struct hci_request *req, int timeout);
 
 int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to);
+int hci_signal_le_con_param_update_req(int dd, uint16_t handle,
+						uint16_t interval_min,
+						uint16_t interval_max,
+						uint16_t slave_latency,
+						uint16_t timeout_multiplier);
 int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to);
 
 int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags);
diff --git a/tools/hcitool.c b/tools/hcitool.c
index 639ee6a51..ce611bb72 100644
--- a/tools/hcitool.c
+++ b/tools/hcitool.c
@@ -3369,6 +3369,97 @@  static void cmd_lecup(int dev_id, int argc, char **argv)
 	hci_close_dev(dd);
 }
 
+static const char *acl_lecup_help =
+	"Usage:\n"
+	"\tacllecup <handle> <min> <max> <latency> <timeout>\n"
+	"\tOptions:\n"
+	"\t    -H, --handle <0xXXXX>  LE connection handle\n"
+	"\t    -m, --min <interval>   Range: 0x0006 to 0x0C80\n"
+	"\t    -M, --max <interval>   Range: 0x0006 to 0x0C80\n"
+	"\t    -l, --latency <range>  Slave latency. Range: 0x0000 to 0x03E8\n"
+	"\t    -t, --timeout  <time>  N * 10ms. Range: 0x000A to 0x0C80\n"
+	"\n\t min/max range: 7.5ms to 4s. Multiply factor: 1.25ms"
+	"\n\t timeout range: 100ms to 32.0s. Larger than max interval\n";
+
+static void cmd_acl_lecup(int dev_id, int argc, char **argv)
+{
+	uint16_t handle = 0, min, max, latency, timeout;
+	int opt, dd, base;
+	int options = 0;
+
+	/* Aleatory valid values */
+	min = 0x0C8;
+	max = 0x0960;
+	latency = 0x0007;
+	timeout = 0x0C80;
+
+	for_each_opt(opt, lecup_options, NULL) {
+		if (optarg && strncasecmp("0x", optarg, 2) == 0)
+			base = 16;
+		else
+			base = 10;
+
+		switch (opt) {
+		case 'H':
+			handle = strtoul(optarg, NULL, base);
+			break;
+		case 'm':
+			min = strtoul(optarg, NULL, base);
+			break;
+		case 'M':
+			max = strtoul(optarg, NULL, base);
+			break;
+		case 'l':
+			latency = strtoul(optarg, NULL, base);
+			break;
+		case 't':
+			timeout = strtoul(optarg, NULL, base);
+			break;
+		default:
+			printf("%s", acl_lecup_help);
+			return;
+		}
+		options = 1;
+	}
+
+	if (options == 0) {
+		helper_arg(5, 5, &argc, &argv, acl_lecup_help);
+
+		handle = strtoul(argv[0], NULL, 0);
+		min = strtoul(argv[1], NULL, 0);
+		max = strtoul(argv[2], NULL, 0);
+		latency = strtoul(argv[3], NULL, 0);
+		timeout = strtoul(argv[4], NULL, 0);
+	}
+
+	if (handle == 0 || handle > 0x0EFF) {
+		printf("%s", acl_lecup_help);
+		return;
+	}
+
+	if (dev_id < 0)
+		dev_id = hci_get_route(NULL);
+
+	dd = hci_open_dev(dev_id);
+	if (dd < 0) {
+		fprintf(stderr, "HCI device open failed\n");
+		exit(1);
+	}
+
+	fprintf(stderr, "Signal LE Connection Update Request: %d %d %d %d %d\n",
+			handle, min, max, latency, timeout);
+	if (hci_signal_le_con_param_update_req(dd, htobs(handle), htobs(min),
+						htobs(max), htobs(latency),
+						htobs(timeout)) < 0) {
+		int err = -errno;
+
+		fprintf(stderr, "Could not change connection params: %s(%d)\n",
+							strerror(-err), -err);
+	}
+
+	hci_close_dev(dd);
+}
+
 static struct {
 	char *cmd;
 	void (*func)(int dev_id, int argc, char **argv);
@@ -3417,6 +3508,7 @@  static struct {
 	{ "lecc",     cmd_lecc,    "Create a LE Connection"               },
 	{ "ledc",     cmd_ledc,    "Disconnect a LE Connection"           },
 	{ "lecup",    cmd_lecup,   "LE Connection Update"                 },
+	{ "acllecup", cmd_acl_lecup, "LE ACL Connection Param Update Req" },
 	{ NULL, NULL, 0 }
 };