| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011-2014 Intel Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <sys/param.h> |
| #include <sys/epoll.h> |
| #include <sys/uio.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/hci.h" |
| |
| #include "src/shared/mainloop.h" |
| #include "btdev.h" |
| #include "serial.h" |
| |
| #define uninitialized_var(x) x = x |
| |
| struct serial { |
| enum serial_type type; |
| uint16_t id; |
| int fd; |
| char path[PATH_MAX]; |
| struct btdev *btdev; |
| uint8_t *pkt_data; |
| uint8_t pkt_type; |
| uint16_t pkt_expect; |
| uint16_t pkt_len; |
| uint16_t pkt_offset; |
| }; |
| |
| static void open_pty(struct serial *serial); |
| |
| static void serial_destroy(void *user_data) |
| { |
| struct serial *serial = user_data; |
| |
| btdev_destroy(serial->btdev); |
| serial->btdev = NULL; |
| |
| close(serial->fd); |
| serial->fd = -1; |
| } |
| |
| static void serial_write_callback(const struct iovec *iov, int iovlen, |
| void *user_data) |
| { |
| struct serial *serial = user_data; |
| ssize_t written; |
| |
| written = writev(serial->fd, iov, iovlen); |
| if (written < 0) |
| return; |
| } |
| |
| static void serial_read_callback(int fd, uint32_t events, void *user_data) |
| { |
| struct serial *serial = user_data; |
| static uint8_t buf[4096]; |
| uint8_t *ptr = buf; |
| ssize_t len; |
| uint16_t count; |
| |
| if (events & (EPOLLERR | EPOLLHUP)) { |
| mainloop_remove_fd(serial->fd); |
| open_pty(serial); |
| return; |
| } |
| |
| again: |
| len = read(serial->fd, buf + serial->pkt_offset, |
| sizeof(buf) - serial->pkt_offset); |
| if (len < 0) { |
| if (errno == EAGAIN) |
| goto again; |
| return; |
| } |
| |
| if (!serial->btdev) |
| return; |
| |
| count = serial->pkt_offset + len; |
| |
| while (count > 0) { |
| hci_command_hdr *cmd_hdr; |
| |
| if (!serial->pkt_data) { |
| serial->pkt_type = ptr[0]; |
| |
| switch (serial->pkt_type) { |
| case HCI_COMMAND_PKT: |
| if (count < HCI_COMMAND_HDR_SIZE + 1) { |
| serial->pkt_offset += len; |
| return; |
| } |
| cmd_hdr = (hci_command_hdr *) (ptr + 1); |
| serial->pkt_expect = HCI_COMMAND_HDR_SIZE + |
| cmd_hdr->plen + 1; |
| serial->pkt_data = malloc(serial->pkt_expect); |
| serial->pkt_len = 0; |
| break; |
| default: |
| printf("packet error\n"); |
| return; |
| } |
| |
| serial->pkt_offset = 0; |
| } |
| |
| if (count >= serial->pkt_expect) { |
| memcpy(serial->pkt_data + serial->pkt_len, |
| ptr, serial->pkt_expect); |
| ptr += serial->pkt_expect; |
| count -= serial->pkt_expect; |
| |
| btdev_receive_h4(serial->btdev, serial->pkt_data, |
| serial->pkt_len + serial->pkt_expect); |
| |
| free(serial->pkt_data); |
| serial->pkt_data = NULL; |
| } else { |
| memcpy(serial->pkt_data + serial->pkt_len, ptr, count); |
| serial->pkt_len += count; |
| serial->pkt_expect -= count; |
| count = 0; |
| } |
| } |
| } |
| |
| static void open_pty(struct serial *serial) |
| { |
| enum btdev_type uninitialized_var(type); |
| |
| serial->fd = posix_openpt(O_RDWR | O_NOCTTY); |
| if (serial->fd < 0) { |
| perror("Failed to get central pseudo terminal"); |
| return; |
| } |
| |
| if (grantpt(serial->fd) < 0) { |
| perror("Failed to grant peripheral pseudo terminal"); |
| close(serial->fd); |
| serial->fd = -1; |
| return; |
| } |
| |
| if (unlockpt(serial->fd) < 0) { |
| perror("Failed to unlock peripheral pseudo terminal"); |
| close(serial->fd); |
| serial->fd = -1; |
| return; |
| } |
| |
| ptsname_r(serial->fd, serial->path, sizeof(serial->path)); |
| |
| printf("Pseudo terminal at %s\n", serial->path); |
| |
| switch (serial->type) { |
| case SERIAL_TYPE_BREDRLE: |
| type = BTDEV_TYPE_BREDRLE; |
| break; |
| case SERIAL_TYPE_BREDR: |
| type = BTDEV_TYPE_BREDR; |
| break; |
| case SERIAL_TYPE_LE: |
| type = BTDEV_TYPE_LE; |
| break; |
| case SERIAL_TYPE_AMP: |
| type = BTDEV_TYPE_AMP; |
| break; |
| } |
| |
| serial->btdev = btdev_create(type, serial->id); |
| if (!serial->btdev) { |
| close(serial->fd); |
| serial->fd = -1; |
| return; |
| } |
| |
| btdev_set_send_handler(serial->btdev, serial_write_callback, serial); |
| |
| if (mainloop_add_fd(serial->fd, EPOLLIN, serial_read_callback, |
| serial, serial_destroy) < 0) { |
| btdev_destroy(serial->btdev); |
| serial->btdev = NULL; |
| close(serial->fd); |
| serial->fd = -1; |
| return; |
| } |
| } |
| |
| struct serial *serial_open(enum serial_type type) |
| { |
| struct serial *serial; |
| enum btdev_type uninitialized_var(dev_type); |
| |
| serial = malloc(sizeof(*serial)); |
| if (!serial) |
| return NULL; |
| |
| memset(serial, 0, sizeof(*serial)); |
| serial->type = type; |
| serial->id = 0x42; |
| |
| open_pty(serial); |
| |
| return serial; |
| } |
| |
| void serial_close(struct serial *serial) |
| { |
| if (!serial) |
| return; |
| |
| mainloop_remove_fd(serial->fd); |
| |
| free(serial); |
| } |