From acf9b454ba68df068f9aab8c538a9fa377016a4c Mon Sep 17 00:00:00 2001 From: Baowei Liu Date: Mon, 22 Feb 2021 13:43:29 +0800 Subject: [PATCH] char: virtio_eavb: Add virtio_eavb frontend driver This driver provides unified interface for applications, and communicate with backend. So applications run in GVM but use backend eavb stack. Change-Id: Ief3d033e5477696fa336e7225b58867fbf6aac5e Signed-off-by: Baowei Liu --- drivers/char/Kconfig | 9 + drivers/char/Makefile | 1 + drivers/char/virtio_eavb/Makefile | 6 + drivers/char/virtio_eavb/vio_eavb.h | 318 +++++ drivers/char/virtio_eavb/virtio_eavb.c | 1652 ++++++++++++++++++++++++ include/uapi/linux/eavb_shared.h | 387 ++++++ 6 files changed, 2373 insertions(+) create mode 100644 drivers/char/virtio_eavb/Makefile create mode 100644 drivers/char/virtio_eavb/vio_eavb.h create mode 100644 drivers/char/virtio_eavb/virtio_eavb.c create mode 100644 include/uapi/linux/eavb_shared.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index d940aa2365ff..0278f4b33a31 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -572,6 +572,15 @@ config ADSPRPC_QGKI in a GKI kernel or not. Say Y if the driver is being compiled in a QGKI kernel. +config VIRTIO_EAVB + tristate "Virtio eavb driver" + depends on VIRTIO + help + This the virtual eavb frontend driver based on virtio. Eavb + means ethernet Audio Video Bridging. It provides unified ioctl + interface for userspace. Say Y if you want to support virtual + eavb. + config MSM_RDBG tristate "QTI Remote debug driver" help diff --git a/drivers/char/Makefile b/drivers/char/Makefile index babeeee732a0..4e52abdb72ac 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_MSM_ADSPRPC) += frpc-adsprpc.o obj-$(CONFIG_ADI) += adi.o obj-$(CONFIG_MSM_RDBG) += rdbg.o obj-$(CONFIG_MSM_FASTCVPD) += fastcvpd.o +obj-$(CONFIG_VIRTIO_EAVB) += virtio_eavb/ diff --git a/drivers/char/virtio_eavb/Makefile b/drivers/char/virtio_eavb/Makefile new file mode 100644 index 000000000000..a0428bc082f9 --- /dev/null +++ b/drivers/char/virtio_eavb/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +ccflags-y := -DEAVB_DEBUGFS + +obj-$(CONFIG_VIRTIO_EAVB) += eavb_fe.o + +eavb_fe-y := virtio_eavb.o diff --git a/drivers/char/virtio_eavb/vio_eavb.h b/drivers/char/virtio_eavb/vio_eavb.h new file mode 100644 index 000000000000..1e1b30bd5ab9 --- /dev/null +++ b/drivers/char/virtio_eavb/vio_eavb.h @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. + */ +#ifndef __VDEV_VIRTIO_EAVB_H__ +#define __VDEV_VIRTIO_EAVB_H__ + +#define IF_NAMESIZE 16 +#define STATION_ADDR_SIZE 8 +#define MAX_STREAM_NUM 8 +#define MAX_CONFIG_FILE_PATH 512 + +#define VERSION_MAJOR 0 +#define VERSION_MINOR 1 + +#define VIRTIO_EAVB_T_CREATE_STREAM 0 +#define VIRTIO_EAVB_T_GET_STREAM_INFO 1 +#define VIRTIO_EAVB_T_CONNECT_STREAM 2 +#define VIRTIO_EAVB_T_RECEIVE 3 +#define VIRTIO_EAVB_T_TRANSMIT 4 +#define VIRTIO_EAVB_T_DISCONNECT_STREAM 5 +#define VIRTIO_EAVB_T_DESTROY_STREAM 6 +#define VIRTIO_EAVB_T_CREATE_STREAM_PATH 7 +#define VIRTIO_EAVB_T_VERSION 8 +#define VIRTIO_EAVB_T_MMAP 9 +#define VIRTIO_EAVB_T_MUNMAP 10 +#define VIRTIO_EAVB_T_UPDATE_CLK 11 + +struct vio_msg_hdr { + uint16_t msgid; /* unique message id */ + uint16_t len; /* command total length */ + uint32_t cmd; /* command */ + uint64_t streamctx_hdl;/* streamCtx handle */ + int32_t stream_idx; /* streamCtx idx in BE */ + int32_t result; /* command result */ +} __packed; + + +/* + * EAVB_IOCTL_CREATE_STREAM + */ + +struct vio_stream_config { + uint16_t stream_id; + char eth_interface[IF_NAMESIZE]; + uint16_t vlan_id; + uint16_t ring_buffer_elem_count; + int ring_buffer_mode; + int avb_role; /* talker = 0 or listener = 1 */ + uint8_t dest_macaddr[STATION_ADDR_SIZE]; + uint8_t stream_addr[STATION_ADDR_SIZE]; + /* "crf_macaddr" or "crf_dest_macaddr" */ + uint8_t crf_dest_macaddr[STATION_ADDR_SIZE]; + uint8_t crf_stream_addr[STATION_ADDR_SIZE]; + int mapping_type; + int wakeup_interval; /* "wakeup_interval" or "tx_interval" */ + int tx_pkts_per_sec; /* if not set, do default */ + int max_stale_ms; /* int max_stale_ns = max_stale_ms*1000 */ + int presentation_time_ms; /* if not set, do default */ + int enforce_presentation_time; + int sr_class_type; /* A = 0 B = 1 AAF = 2 */ + /* sets number of items to se sent on each tx / rx */ + int packing_factor; + int bandwidth; + + /* H.264 */ + int max_payload; /* "max_payload" or "max_video_payload" */ + + int mrp_enabled; + + /* Audio Specific */ + int pcm_bit_depth; + int pcm_channels; + int sample_rate; /* in hz */ + unsigned char endianness; /* 0 = big 1 = little */ + + int ieee1722_standard; + + /* Thread priority in QNX side */ + int talker_priority; + int listener_priority; + int crf_priority; + + /* CRF */ + /* 0 - disabled + * 1 - CRF talker (listener drives reference) + * 2 - CRF with talker reference (talker has CRF talker) + */ + int crf_mode; + + /* 0 - custom + * 1- audio + * 2- video frame + * 3 - video line + * 4 - machine cycle + */ + int crf_type; + + /* time interval after how many events timestamp is to be produced + * (base_frequency * pull) / timestamp_interval = + * # of timestamps per second + */ + int crf_timestamping_interval; + int crf_timestamping_interval_remote; + int crf_timestamping_interval_local; + /* enables/disables dynamic IPG adjustments */ + int crf_allow_dynamic_tx_adjust; + /* indicates how many CRF timestamps per each CRF packet */ + int crf_num_timestamps_per_pkt; + + int64_t crf_mcr_adjust_min_ppm; + int64_t crf_mcr_adjust_max_ppm; + + uint16_t crf_stream_id; /* CRF stream ID */ + int32_t crf_base_frequency; /* CRF base frequency */ + + int32_t crf_listener_ts_smoothing; + int32_t crf_talker_ts_smoothing; + + /* multiplier for the base frequency; */ + int crf_pull; + /* indicates how often to issue MCR callback events + * how many packets will generate one callback. + */ + int crf_event_callback_interval; + /* Indicates how often to update IPG */ + int crf_dynamic_tx_adjust_interval; + + /* stats */ + int32_t enable_stats_reporting; + int32_t stats_reporting_interval; + int32_t stats_reporting_samples; + + /* packet tracking */ + int32_t enable_packet_tracking; + int32_t packet_tracking_interval; + int blocking_write_enabled; + double blocking_write_fill_level; + int app_tx_block_enabled; + int stream_interleaving_enabled; + int create_talker_thread; + int create_crf_threads; + int listener_bpf_pkts_per_buff; +} __packed; + + +struct vio_create_stream_msg { + struct vio_msg_hdr mhdr; + struct vio_stream_config cfg; + uint64_t streamCtx; + int32_t stream_idx; +} __packed; + + +struct vio_create_stream_path_msg { + struct vio_msg_hdr mhdr; + char path[MAX_CONFIG_FILE_PATH]; + uint64_t streamCtx; + int32_t stream_idx; +} __packed; + + + +/* + * EAVB_IOCTL_GET_STREAM_INFO + */ + +struct eavb_stream_info { + int role; + int mapping_type; + /* Max packet payload size */ + unsigned int max_payload; + /* Number of packets sent per wake */ + unsigned int pkts_per_wake; + /* Time to sleep between wakes */ + unsigned int wakeup_period_us; + + /* Audio Specific */ + /* Audio bit depth 8/16/24/32 */ + int pcm_bit_depth; + /* Audio channels 1/2 */ + int num_pcm_channels; + /* Audio sample rate in hz */ + int sample_rate; + /* Audio sample endianness 0(big)/1(little) */ + unsigned char endianness; + + /* Max buffer size (Bytes) allowed */ + unsigned int max_buffer_size; +} __packed; + + +struct vio_get_stream_info_msg { + struct vio_msg_hdr mhdr; + struct eavb_stream_info stream_info; +} __packed; + + +/* + * EAVB_IOCTL_CONNECT_STREAM + */ + +struct vio_connect_stream_msg { + struct vio_msg_hdr mhdr; +} __packed; + + + +/* + * EAVB_IOCTL_RECEIVE + */ + +struct eavb_buf_hdr { + /* This flag is used for H.264 and MJPEG streams: + * 1. H.264: Set for the very last packet of an access unit. + * 2. MJPEG Set the last packet of a video frame. + */ + uint32_t flag_end_of_frame:1; + + /* This flag is used in file transfer only: + * Set for the last packet in the file + */ + uint32_t flag_end_of_file:1; + + uint32_t flag_reserved:30; + + /* Audio event Layout D3scription Valid Channels + * event value + * 0 Static layout Based on config + * 1 Mono 0 + * 2 Stereo 0, 1 + * 3 5.1 0,1,2,3,4,5 + * 4 7.1 0,1,2,3,4,5,6,7 + * 5-15 Custom Defined by System + * Integrator + */ + uint32_t event; + + uint32_t reserved; + uint32_t payload_size; /* Size of the payload (bytes) */ +} __packed; + +struct eavb_buf_data { + struct eavb_buf_hdr hdr; + uint64_t gpa; /* GVM physical address of buffer */ +} __packed; + +struct vio_receive_msg { + struct vio_msg_hdr mhdr; + struct eavb_buf_data data; /* IN/OUT */ + int32_t received; +} __packed; + + +/* + * EAVB_IOCTL_TRANSMIT + */ + +struct vio_transmit_msg { + struct vio_msg_hdr mhdr; + struct eavb_buf_data data; /* IN/OUT */ + int32_t written; +} __packed; + + +/* + * EAVB_IOCTL_DISCONNECT_STREAM + */ + +struct vio_disconnect_stream_msg { + struct vio_msg_hdr mhdr; +} __packed; + + +/* + * EAVB_IOCTL_DESTROY_STREAM + */ + +struct vio_destroy_stream_msg { + struct vio_msg_hdr mhdr; +} __packed; + +/* + * EAVB_IOCTL_VERSION + */ +struct vio_version_msg { + struct vio_msg_hdr mhdr; /* IN */ + uint16_t major; /* IN */ + uint16_t minor; /* IN */ +} __packed; + +/* + * EAVB_IOCTL_MMAP + */ + +struct vio_mmap_msg { + struct vio_msg_hdr mhdr; /* IN */ + uint32_t size; /* IN */ + uint64_t gpa; /* IN */ +} __packed; + +/* + * EAVB_IOCTL_MUNMAP + */ +struct vio_munmap_msg { + struct vio_msg_hdr mhdr; /* IN */ + uint64_t gpa; /* IN */ +} __packed; + +/* + * EAVB_IOCTL_UPDATE_CLK + */ +struct vio_update_clk_msg { + struct vio_msg_hdr mhdr; /* IN */ + uint64_t clk; /* IN */ +} __packed; + +#endif /* __VDEV_VIRTIO_EAVB_H__ */ diff --git a/drivers/char/virtio_eavb/virtio_eavb.c b/drivers/char/virtio_eavb/virtio_eavb.c new file mode 100644 index 000000000000..a43dd87a4ecd --- /dev/null +++ b/drivers/char/virtio_eavb/virtio_eavb.c @@ -0,0 +1,1652 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. + */ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "vio_eavb.h" + +#define VIRTIO_ID_EAVB 36 /* virtio eavb */ + +/* support feature */ +#define VIRTIO_EAVB_F_SHMEM 1 + +#define MINOR_NUM_DEV 0 +#define DEVICE_NAME "virt-eavb" +#define DEVICE_NUM 1 + +#define LEVEL_DEBUG 1 +#define LEVEL_INFO 2 +#define LEVEL_ERR 3 + +static unsigned int log_level = LEVEL_INFO; + +static char *prix[] = {"", "debug", "info", "error"}; +#define LOG_EAVB(level, format, args...) \ +do { \ + if ((level) >= log_level) { \ + pr_info("eavb: pid %.8x: %s: %s(%d) "format, \ + current->pid, prix[0x3 & (level)], \ + __func__, __LINE__, ## args); \ + } \ +} while (0) + +#define ASSERT(x) \ +do { \ + if (unlikely(!(x))) { \ + pr_err("Assertion failed! %s %s:%d\n", \ + __func__, __FILE__, __LINE__); \ + dump_stack(); \ + } \ +} while (0) + +#define eavb_place_marker_once(log) \ +do { \ + static int times; \ + if (times < 1) { \ + place_marker(log); \ + times++; \ + } \ +} while (0) + +static unsigned int timeout_msec = 10000; /* default 10s */ + +/* + * device_priv (struct virtio_eavb_priv) + * | + * |-- file_priv (struct eavb_file) + * | | + * | |-- stream0 (struct stream) + * | |-- stream1 + * | |-- stream2 + */ + +struct fe_msg; +struct virtio_eavb_priv { + char *name; + struct virtio_device *vdev; + struct virtqueue *svq; + struct virtqueue *rvq; + spinlock_t rvqlock; + + bool has_shmem; + + struct mutex lock; + + struct device *dev; + struct cdev cdev; + struct class *class; + dev_t dev_no; + int file_index; + + spinlock_t msglock; +#define FE_MSG_MAX 256 /* 1<<8 */ +#define mod(index) ((index) & 0xff) /* ((index) % FE_MSG_MAX) */ + struct fe_msg *msgtable[FE_MSG_MAX]; +#define RX_BUF_MAX_LEN 1024 + void *rxbufs[FE_MSG_MAX]; + struct work_struct reclaim_work; + +#ifdef EAVB_DEBUGFS + struct dentry *debugfs_root; + + struct rx_record { + u32 msgid; + u64 ts; + } rxrecords[32]; + int next_rxrecords_index; +#endif + + u64 crf_ts; + struct completion crf_ts_update; +}; + +struct mapping { + struct list_head list; + u64 va; /* kernel virtual address */ + u64 ua; /* user address */ + u64 pa; /* phy address */ + size_t size; +}; + +struct eavb_file; +struct stream { + int index; + struct eavb_file *fl; + u64 hdl; + u32 idx; + enum stream_status { + UNCREATED = 0, + CREATED = 1, + CONNECTED = 2, + DISCONNECTED = 3, + DESTROYED = 4, + } status; +#ifdef EAVB_DEBUGFS + struct dentry *debugfs; + struct tx_record { + u32 msgid; + u64 begin; + u64 end; +#define RECORD_COUNT (1<<4) + } records[RECORD_COUNT]; + int next_records_index; + spinlock_t recordslock; +#endif +}; + +struct eavb_file { + int index; + struct virtio_eavb_priv *priv; + +#define MAPPING_MAX 8 + int mapping_count; + struct mapping mapping[MAPPING_MAX]; + spinlock_t mappinglock; + + struct stream streams[MAX_STREAM_NUM]; + int stream_count; + spinlock_t streamlock; +}; + +#ifdef EAVB_DEBUGFS +void update_crf_ts(struct virtio_eavb_priv *priv, u64 value); +static ssize_t log_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char mybuffer[32] = {0}; + size_t len = min(count, sizeof(mybuffer)); + long input; + + if (copy_from_user(mybuffer, buf, len)) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + if (kstrtol(mybuffer, 0, &input) == 0) { + log_level = input & 0x3; + LOG_EAVB(LEVEL_INFO, "update log_level to %d\n", log_level); + } + + return len; +} + +static int do_rxrecords_snapshoot(struct virtio_eavb_priv *priv, + char *buffer, size_t size) +{ + int i, index, len; + struct rx_record records[32]; + + memcpy(records, priv->rxrecords, sizeof(records)); + index = priv->next_rxrecords_index - 1; + + len = 0; + for (i = 0; i < 32; i++) { + u32 msgid; + u64 ts, rem_nsec; + int l; + + if (index < 0) + break; + msgid = records[index & 0x1f].msgid; + ts = records[index & 0x1f].ts; + rem_nsec = do_div(ts, 1000000000); + + l = scnprintf(&buffer[len], size - len, "%llu.%06llu: %d\n", + ts, rem_nsec/1000, msgid); + if (l <= 0) + break; + if (l + len > size) + break; + len += l; + index--; + } + return len; +} +static ssize_t log_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct virtio_eavb_priv *priv = file->private_data; + char *buffer; + int len; + ssize_t return_count; + + if (*ppos > 0) + return 0; +#define BUF_SIZE 512 + buffer = kzalloc(BUF_SIZE, GFP_KERNEL); + if (!buffer) + return 0; + len = do_rxrecords_snapshoot(priv, buffer, BUF_SIZE); +#undef BUF_SIZE + return_count = simple_read_from_buffer(userbuf, count, + ppos, buffer, len); + kfree(buffer); + return return_count; +} + +static ssize_t timeout_write(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + char mybuffer[32] = {0}; + size_t len = min(count, sizeof(mybuffer)); + long input; + + if (copy_from_user(mybuffer, buf, len)) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + if (kstrtol(mybuffer, 0, &input) == 0) { + timeout_msec = input; + LOG_EAVB(LEVEL_INFO, "set timeout to %d ms\n", timeout_msec); + } + + return len; +} +static ssize_t timeout_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + char *buffer; + int len; + ssize_t return_count; + + if (*ppos > 0) + return 0; +#define BUF_SIZE 512 + buffer = kzalloc(BUF_SIZE, GFP_KERNEL); + if (!buffer) + return 0; + len = scnprintf(buffer, BUF_SIZE, "%d msec\n", timeout_msec); +#undef BUF_SIZE + return_count = simple_read_from_buffer(userbuf, count, + ppos, buffer, len); + kfree(buffer); + return return_count; +} + +static int do_stream_snapshoot(struct stream *stream, char *buffer, size_t size) +{ + struct tx_record records[RECORD_COUNT]; + int current_records_index, save_index, i, len; + u64 total_taken_us = 0; + + spin_lock(&stream->recordslock); + memcpy(records, stream->records, sizeof(records)); + current_records_index = stream->next_records_index - 1; + spin_unlock(&stream->recordslock); + + len = scnprintf(buffer, size, + "stream%d: status(%d)\n", + stream->index, stream->status); + for (i = 0; i < RECORD_COUNT; i++) { + u64 taken_us; + int l; + struct tx_record *record; + + if (current_records_index < 0) + break; + save_index = current_records_index & (RECORD_COUNT - 1); + record = &records[save_index]; + taken_us = (record->end - record->begin)/1000; + + l = scnprintf(&buffer[len], size - len, + "[%d]msgid %d, %lld us\n", + current_records_index, record->msgid, taken_us); + if (l <= 0) + break; + if (l + len > size) + break; + len += l; + current_records_index--; + total_taken_us += taken_us; + } + if (i > 0 && len < size) { + /* average */ + int l = snprintf(&buffer[len], size - len, + "average %lld us\n", total_taken_us / i); + if (l > 0 && l + len <= size) + len += l; + } + return len; +} + +static ssize_t stream_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct stream *stream = file->private_data; + char *buffer; + int len; + ssize_t return_count; + + if (*ppos > 0) + return 0; +#define BUF_SIZE 512 + buffer = kzalloc(BUF_SIZE, GFP_KERNEL); + if (!buffer) + return 0; + len = do_stream_snapshoot(stream, buffer, BUF_SIZE); +#undef BUF_SIZE + return_count = simple_read_from_buffer(userbuf, count, + ppos, buffer, len); + kfree(buffer); + return return_count; +} + +static const struct file_operations fops_debugfs_log = { + .open = simple_open, + .write = log_write, + .read = log_read, + .owner = THIS_MODULE, +}; + +static const struct file_operations fops_debugfs_timeout = { + .open = simple_open, + .write = timeout_write, + .read = timeout_read, + .owner = THIS_MODULE, +}; + +static const struct file_operations fops_debugfs_stream = { + .open = simple_open, + .read = stream_read, + .owner = THIS_MODULE, +}; +#endif + +static struct stream *getStream(struct eavb_file *fl, u64 hdl) +{ + int i; + + spin_lock(&fl->streamlock); + for (i = 0; i < MAX_STREAM_NUM; i++) { + struct stream *stream = &fl->streams[i]; + + if (stream->hdl == hdl) { + if (hdl == 0) { + stream->index = i; + stream->hdl = 0xffffffffffffffff; + } + spin_unlock(&fl->streamlock); + return stream; + } + } + spin_unlock(&fl->streamlock); + return NULL; +} + +static void reset_stream(struct stream *stream) +{ + stream->hdl = 0; + stream->idx = 0; +} + +#ifdef EAVB_DEBUGFS +static void create_dbugfs_stream(struct stream *stream) +{ + struct eavb_file *fl = stream->fl; + struct virtio_eavb_priv *priv = fl->priv; + char name[16] = {0}; + + snprintf(name, sizeof(name), "stream%d", stream->index); + stream->debugfs = debugfs_create_dir(name, priv->debugfs_root); + debugfs_create_file("status", 00400 | 00200, + stream->debugfs, stream, + &fops_debugfs_stream); + stream->next_records_index = 0; + spin_lock_init(&stream->recordslock); +} + +static void destroy_dbugfs_stream(struct stream *stream) +{ + debugfs_remove_recursive(stream->debugfs); +} + +static void records(struct stream *stream, u32 msgid, u64 begin, u64 end) +{ + int index; + + spin_lock(&stream->recordslock); + index = (stream->next_records_index++) & 0x0f; + spin_unlock(&stream->recordslock); + + stream->records[index].msgid = msgid; + stream->records[index].begin = begin; + stream->records[index].end = end; +} +#else +#define create_dbugfs_stream(stream) +#define destroy_dbugfs_stream(stream) +#define records(stream, cmd, begin, end) +#endif + +static struct mapping *get_file_mapping(struct eavb_file *fl, u64 addr, u32 len) +{ + u64 start, end; + int i; + + for (i = 0; i < MAPPING_MAX; i++) { + start = fl->mapping[i].ua; + end = fl->mapping[i].ua + fl->mapping[i].size; + if (addr >= start && addr + len <= end) + return &fl->mapping[i]; + } + return NULL; +} +static u64 get_mapping_phyaddr(struct mapping *mapping, u64 ua) +{ + return mapping->pa + (ua - mapping->ua); +} + +static int add_mapping_info(struct eavb_file *fl, + u64 va, u64 ua, u64 pa, size_t size) +{ + int i; + + for (i = 0; i < MAPPING_MAX; i++) { + if (fl->mapping[i].va == 0) { + fl->mapping[i].size = size; + fl->mapping[i].va = va; + fl->mapping[i].ua = ua; + fl->mapping[i].pa = pa; + break; + } + } + return i; +} + +struct fe_msg { + u16 msgid; + struct completion work; + void *txbuf; + void *rxbuf; + u32 txbuf_size; + u32 rxbuf_used; + u64 ts_begin; + u64 ts_end; +}; + +static struct fe_msg *virt_alloc_msg(struct virtio_eavb_priv *priv, + int tsize, int rsize) +{ + struct fe_msg *msg; + unsigned long flags; + u8 *buf; + int i; + static int next; /* default 0 */ + + ASSERT(rsize < RX_BUF_MAX_LEN); + /* kzalloc needn't memset 0 */ + msg = kzalloc(sizeof(*msg) + tsize, GFP_KERNEL); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "cannot alloc msg memory\n"); + return NULL; + } + buf = kzalloc(tsize, GFP_KERNEL); + if (!buf) { + LOG_EAVB(LEVEL_ERR, "cannot alloc txbuf memory\n"); + kfree(msg); + return NULL; + } + + spin_lock_irqsave(&priv->msglock, flags); + for (i = 0; i < FE_MSG_MAX; i++) { + int index = mod(next + i); + + if (!priv->msgtable[index]) { + priv->msgtable[index] = msg; + msg->msgid = index; + next = mod(index + 1); + break; + } + } + spin_unlock_irqrestore(&priv->msglock, flags); + + if (i == FE_MSG_MAX) { + LOG_EAVB(LEVEL_ERR, "fe message queue is full\n"); + kfree(buf); + kfree(msg); + return NULL; + } + + msg->txbuf = buf; + msg->txbuf_size = tsize; + init_completion(&msg->work); + + return msg; +} + +static void reclaim_rxbuf(struct virtio_eavb_priv *priv, void *rxbuf) +{ + struct scatterlist sg; + unsigned long flags; + + sg_init_one(&sg, rxbuf, RX_BUF_MAX_LEN); + spin_lock_irqsave(&priv->rvqlock, flags); + virtqueue_add_inbuf(priv->rvq, &sg, 1, rxbuf, GFP_KERNEL); + virtqueue_kick(priv->rvq); + spin_unlock_irqrestore(&priv->rvqlock, flags); +} + +static void virt_free_msg(struct virtio_eavb_priv *priv, struct fe_msg *msg) +{ + unsigned long flags; + + if (msg->rxbuf_used) + reclaim_rxbuf(priv, msg->rxbuf); + + spin_lock_irqsave(&priv->msglock, flags); + if (priv->msgtable[msg->msgid] == msg) + priv->msgtable[msg->msgid] = NULL; + else + LOG_EAVB(LEVEL_ERR, "can't find msg %d in table\n", + msg->msgid); + spin_unlock_irqrestore(&priv->msglock, flags); + + kfree(msg); + schedule_work(&priv->reclaim_work); +} + +static void reclaim_function(struct work_struct *work) +{ + struct virtio_eavb_priv *priv; + + priv = container_of(work, struct virtio_eavb_priv, reclaim_work); + while (1) { + struct vio_msg_hdr *vhdr; + unsigned int len; + + mutex_lock(&priv->lock); + /* remove a used txbuf */ + vhdr = virtqueue_get_buf(priv->svq, &len); + mutex_unlock(&priv->lock); + + if (!vhdr) + break; + + ASSERT(vhdr->len == len); + LOG_EAVB(LEVEL_DEBUG, "msgid %d txbuf used\n", vhdr->msgid); + kfree(vhdr); + } +} + +#define fill_vmsg_hdr(msg, stream, cmdtype) \ + do { \ + struct vio_msg_hdr *vhdr; \ + vhdr = (struct vio_msg_hdr *)msg->txbuf; \ + vhdr->cmd = cmdtype; \ + vhdr->len = msg->txbuf_size; \ + vhdr->streamctx_hdl = stream->hdl; \ + vhdr->stream_idx = stream->idx; \ + } while (0) + +static int send_msg(struct virtio_eavb_priv *priv, struct fe_msg *msg) +{ + struct vio_msg_hdr *vhdr, *rsp; + struct scatterlist sg[1]; + int ret; + int rsv; + + vhdr = (struct vio_msg_hdr *)msg->txbuf; + msg->rxbuf = NULL; + + LOG_EAVB(LEVEL_DEBUG, "msgid %d\n", msg->msgid); + mutex_lock(&priv->lock); + vhdr->msgid = msg->msgid; + sg_init_one(sg, vhdr, vhdr->len); + ret = virtqueue_add_outbuf(priv->svq, sg, 1, vhdr, GFP_KERNEL); + if (ret) { + LOG_EAVB(LEVEL_ERR, "fail to add output buffer, return %d\n", + ret); + mutex_unlock(&priv->lock); + return ret; + } + virtqueue_kick(priv->svq); + mutex_unlock(&priv->lock); + + rsv = wait_for_completion_timeout(&msg->work, msecs_to_jiffies(timeout_msec)); + if (rsv == 0) { + LOG_EAVB(LEVEL_ERR, "msgid %d timeout\n", msg->msgid); + return -ETIMEDOUT; + } + + rsp = msg->rxbuf; + if (rsp) { + LOG_EAVB(LEVEL_DEBUG, "msgid %d, result = %d\n", + msg->msgid, rsp->result); + } + return ret; +} + +static int vio_eavb_version(struct eavb_file *fl, struct stream *stream) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_version_msg *vmsg; + int tsize, rsize; + int ret; + + tsize = rsize = sizeof(struct vio_version_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_VERSION); + vmsg = (struct vio_version_msg *)msg->txbuf; + vmsg->major = VERSION_MAJOR; + vmsg->minor = VERSION_MINOR; + + ret = send_msg(priv, msg); + + vmsg = (struct vio_version_msg *)msg->rxbuf; + if (!ret && vmsg) { + ret = vmsg->mhdr.result; + LOG_EAVB(LEVEL_INFO, "BE version(%d.%d)\n", + vmsg->major, vmsg->minor); + } + + virt_free_msg(priv, msg); + return 0; +} + +static int vio_eavb_disconnect(struct eavb_file *fl, struct stream *stream) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + int tsize, rsize; + int ret; + + tsize = rsize = sizeof(struct vio_disconnect_stream_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_DISCONNECT_STREAM); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + LOG_EAVB(LEVEL_DEBUG, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + stream->status = DISCONNECTED; + } + + virt_free_msg(priv, msg); + return 0; +} + +static int vio_eavb_destroy(struct eavb_file *fl, struct stream *stream) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + int tsize, rsize; + int ret; + + tsize = rsize = sizeof(struct vio_destroy_stream_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_DESTROY_STREAM); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + + destroy_dbugfs_stream(stream); + + reset_stream(stream); + stream->status = DESTROYED; + } + + virt_free_msg(priv, msg); + return 0; +} + +static int virtio_eavb_open(struct inode *inode, struct file *file) +{ + struct cdev *cdev = inode->i_cdev; + struct virtio_eavb_priv *priv; + struct eavb_file *fl; + + priv = container_of(cdev, struct virtio_eavb_priv, cdev); + fl = kzalloc(sizeof(*fl), GFP_KERNEL); + if (!fl) { + LOG_EAVB(LEVEL_ERR, "malloc fail\n"); + return -ENOMEM; + } + + memset(fl, 0, sizeof(*fl)); + fl->priv = priv; + file->private_data = fl; + spin_lock_init(&fl->mappinglock); + spin_lock_init(&fl->streamlock); + + fl->index = priv->file_index++; + LOG_EAVB(LEVEL_INFO, "fl->index=%d\n", fl->index); + return 0; +} + +static int virtio_eavb_release(struct inode *inode, struct file *file) +{ + struct eavb_file *fl = file->private_data; + int i; + + LOG_EAVB(LEVEL_INFO, "fl->index=%d\n", fl->index); + for (i = 0; i < MAX_STREAM_NUM; i++) { + struct stream *stream = &fl->streams[i]; + + LOG_EAVB(LEVEL_INFO, "[%d], status %d\n", + i, stream->status); + if (stream->status == CONNECTED) + vio_eavb_disconnect(fl, stream); + + if (stream->status == DISCONNECTED + || stream->status == CREATED) { + vio_eavb_destroy(fl, stream); + } + } + + file->private_data = NULL; + for (i = 0; i < MAPPING_MAX; i++) { + if (fl->mapping[i].va) + kfree((void *)fl->mapping[i].va); + } + memset(fl->mapping, 0, sizeof(fl->mapping)); + fl->mapping_count = 0; + + LOG_EAVB(LEVEL_INFO, "fl->index=%d\n", fl->index); + kfree(fl); + return 0; +} + +static int qavb_create_stream(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct vio_create_stream_msg *vmsg; + struct eavb_ioctl_create_stream create; + int tsize, rsize; + struct stream *stream; + int ret; + + if (copy_from_user(&create, buf, sizeof(create))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + LOG_EAVB(LEVEL_INFO, "fl->index=%d\n", fl->index); + + stream = getStream(fl, 0); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "stream full\n"); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_create_stream_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + reset_stream(stream); + return -ENOMEM; + } + + vhdr = (struct vio_msg_hdr *)msg->txbuf; + vhdr->cmd = VIRTIO_EAVB_T_CREATE_STREAM; + vhdr->len = msg->txbuf_size; + + ASSERT(sizeof(vmsg->cfg) == sizeof(create.config)); + vmsg = (struct vio_create_stream_msg *)vhdr; + memcpy(&vmsg->cfg, &create.config, sizeof(vmsg->cfg)); + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + + create.hdr.streamCtx = vhdr->streamctx_hdl; + stream->hdl = vhdr->streamctx_hdl; + stream->idx = vhdr->stream_idx; + stream->status = CREATED; + stream->fl = fl; + LOG_EAVB(LEVEL_INFO, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + + create_dbugfs_stream(stream); + } else { + reset_stream(stream); + } + + virt_free_msg(priv, msg); + + if (copy_to_user(buf, &create, sizeof(create))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + + return ret; +} + +static int qavb_create_stream_with_path(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct vio_create_stream_path_msg *vmsg; + struct eavb_ioctl_create_stream_with_path create; + int tsize, rsize; + struct stream *stream; + int ret; + + if (copy_from_user(&create, buf, sizeof(create))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + LOG_EAVB(LEVEL_INFO, "fl->index=%d\n", fl->index); + + stream = getStream(fl, 0); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "stream full\n"); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_create_stream_path_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + reset_stream(stream); + return -ENOMEM; + } + + vhdr = (struct vio_msg_hdr *)msg->txbuf; + vhdr->cmd = VIRTIO_EAVB_T_CREATE_STREAM_PATH; + vhdr->len = msg->txbuf_size; + + vmsg = (struct vio_create_stream_path_msg *)vhdr; + memcpy(vmsg->path, create.path, MAX_CONFIG_FILE_PATH); + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + + create.hdr.streamCtx = vhdr->streamctx_hdl; + + stream->hdl = vhdr->streamctx_hdl; + stream->idx = vhdr->stream_idx; + stream->status = CREATED; + stream->fl = fl; + LOG_EAVB(LEVEL_INFO, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + + create_dbugfs_stream(stream); + } else { + reset_stream(stream); + } + + virt_free_msg(priv, msg); + + if (copy_to_user(buf, &create, sizeof(create))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + + return ret; +} + +static int qavb_destroy_stream(struct eavb_file *fl, void __user *buf) +{ + struct eavb_ioctl_destroy_stream destroy; + struct stream *stream; + int ret; + + if (copy_from_user(&destroy, buf, sizeof(destroy))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + stream = getStream(fl, destroy.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + destroy.hdr.streamCtx); + return -EINVAL; + } + + ret = vio_eavb_destroy(fl, stream); + + return ret; +} + +static int qavb_get_stream_info(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct eavb_ioctl_get_stream_info get_info; + int tsize, rsize; + struct stream *stream; + int ret; + + if (copy_from_user(&get_info, buf, sizeof(get_info))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + LOG_EAVB(LEVEL_INFO, "streamCtx=0x%llx\n", get_info.hdr.streamCtx); + + stream = getStream(fl, get_info.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + get_info.hdr.streamCtx); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_get_stream_info_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_GET_STREAM_INFO); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + struct eavb_stream_info *info; + + ret = vhdr->result; + info = (struct eavb_stream_info *)(vhdr + 1); + ASSERT(sizeof(get_info.info) == sizeof(*info)); + memcpy(&get_info.info, info, sizeof(*info)); + } + + virt_free_msg(priv, msg); + if (copy_to_user(buf, &get_info, sizeof(get_info))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + + vio_eavb_version(fl, stream); + return ret; +} + +static int qavb_connect_stream(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct eavb_ioctl_connect_stream connect; + int tsize, rsize; + struct stream *stream; + int ret; + + if (copy_from_user(&connect, buf, sizeof(connect))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + LOG_EAVB(LEVEL_INFO, "streamCtx=0x%llx\n", connect.hdr.streamCtx); + + stream = getStream(fl, connect.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + connect.hdr.streamCtx); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_connect_stream_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_CONNECT_STREAM); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d)\n", + fl->index, stream->index, msg->msgid); + LOG_EAVB(LEVEL_DEBUG, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + ret = send_msg(priv, msg); + LOG_EAVB(LEVEL_DEBUG, "(%d:%d:%d) ret=%d\n", + fl->index, stream->index, msg->msgid, ret); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + stream->status = CONNECTED; + } + + virt_free_msg(priv, msg); + + return ret; +} + +static int qavb_disconnect_stream(struct eavb_file *fl, void __user *buf) +{ + struct eavb_ioctl_disconnect_stream disconnect; + struct stream *stream; + int ret; + + if (copy_from_user(&disconnect, buf, sizeof(disconnect))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + LOG_EAVB(LEVEL_INFO, "streamCtx=0x%llx\n", disconnect.hdr.streamCtx); + + stream = getStream(fl, disconnect.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + disconnect.hdr.streamCtx); + return -EINVAL; + } + + ret = vio_eavb_disconnect(fl, stream); + + return ret; +} + +static int qavb_receive(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct vio_receive_msg *vmsg; + struct eavb_ioctl_receive receive; + int tsize, rsize; + struct mapping *mapping; + struct stream *stream; + int ret; + + if (copy_from_user(&receive, buf, sizeof(receive))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + mapping = get_file_mapping(fl, receive.data.pbuf, + receive.data.hdr.payload_size); + if (!mapping) { + LOG_EAVB(LEVEL_ERR, "invalid buf address 0x%llx\n", + receive.data.pbuf); + return -EINVAL; + } + + stream = getStream(fl, receive.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + receive.hdr.streamCtx); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_receive_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_RECEIVE); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + vmsg = (struct vio_receive_msg *)vhdr; + ASSERT(sizeof(vmsg->data) == sizeof(receive.data)); + memcpy(&vmsg->data, &receive.data, sizeof(vmsg->data)); + + vmsg->data.gpa = get_mapping_phyaddr(mapping, receive.data.pbuf); + LOG_EAVB(LEVEL_DEBUG, "pass phy address 0x%llx\n", vmsg->data.gpa); + LOG_EAVB(LEVEL_DEBUG, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + + msg->ts_begin = local_clock(); + ret = send_msg(priv, msg); + msg->ts_end = local_clock(); + records(stream, msg->msgid, msg->ts_begin, msg->ts_end); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + vmsg = (struct vio_receive_msg *)vhdr; + receive.received = vmsg->received; + memcpy(&receive.data, &vmsg->data, + sizeof(struct eavb_buf_data)); + if (receive.received) + eavb_place_marker_once( + "M - DRIVER EAVB FE First received data"); + } + + virt_free_msg(priv, msg); + + if (copy_to_user(buf, &receive, sizeof(receive))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + return ret; +} + +static int qavb_recv_done(struct eavb_file *fl, void __user *buf) +{ + return 0; +} + +static int qavb_transmit(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + struct fe_msg *msg; + struct vio_msg_hdr *vhdr; + struct vio_transmit_msg *vmsg; + struct eavb_ioctl_transmit transmit; + int tsize, rsize; + struct mapping *mapping; + struct stream *stream; + int ret; + + if (copy_from_user(&transmit, buf, sizeof(transmit))) { + LOG_EAVB(LEVEL_ERR, "copy_from_user failed\n"); + return -EFAULT; + } + + mapping = get_file_mapping(fl, transmit.data.pbuf, + transmit.data.hdr.payload_size); + if (!mapping) { + LOG_EAVB(LEVEL_ERR, "invalid buf address 0x%llx\n", + transmit.data.pbuf); + return -EINVAL; + } + + stream = getStream(fl, transmit.hdr.streamCtx); + if (!stream) { + LOG_EAVB(LEVEL_ERR, "invalid streamCtx=0x%llx\n", + transmit.hdr.streamCtx); + return -EINVAL; + } + + tsize = rsize = sizeof(struct vio_transmit_msg); + msg = virt_alloc_msg(priv, tsize, rsize); + if (!msg) { + LOG_EAVB(LEVEL_ERR, "stream%d alloc msg fail!\n", + stream->index); + return -ENOMEM; + } + + fill_vmsg_hdr(msg, stream, VIRTIO_EAVB_T_TRANSMIT); + vhdr = (struct vio_msg_hdr *)msg->txbuf; + + vmsg = (struct vio_transmit_msg *)vhdr; + ASSERT(sizeof(vmsg->data) == sizeof(transmit.data)); + memcpy(&vmsg->data, &transmit.data, sizeof(vmsg->data)); + + vmsg->data.gpa = get_mapping_phyaddr(mapping, transmit.data.pbuf); + LOG_EAVB(LEVEL_DEBUG, "pass phy address 0x%llx\n", vmsg->data.gpa); + LOG_EAVB(LEVEL_DEBUG, "stream%d (ctx 0x%llx, idx %d)\n", + stream->index, vhdr->streamctx_hdl, vhdr->stream_idx); + + ret = send_msg(priv, msg); + + vhdr = (struct vio_msg_hdr *)msg->rxbuf; + if (!ret && vhdr) { + ret = vhdr->result; + vmsg = (struct vio_transmit_msg *)vhdr; + transmit.written = vmsg->written; + } + + virt_free_msg(priv, msg); + + if (copy_to_user(buf, &transmit, sizeof(transmit))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + return ret; +} + +static int qavb_get_crf_ts(struct eavb_file *fl, void __user *buf) +{ + struct virtio_eavb_priv *priv = fl->priv; + u64 value; + + init_completion(&priv->crf_ts_update); + wait_for_completion(&priv->crf_ts_update); + value = priv->crf_ts; + if (copy_to_user(buf, &value, sizeof(value))) { + LOG_EAVB(LEVEL_ERR, "copy_to_user failed\n"); + return -EFAULT; + } + return 0; +} + +void update_crf_ts(struct virtio_eavb_priv *priv, u64 value) +{ + LOG_EAVB(LEVEL_DEBUG, "update crf clock %llu\n", value); + priv->crf_ts = value; + complete_all(&priv->crf_ts_update); +} + +static long virtio_eavb_ioctl(struct file *file, unsigned int ioctl_cmd, + unsigned long ioctl_param) +{ + struct eavb_file *fl = file->private_data; + void __user *buf = (void __user *)ioctl_param; + int ret = 0; + + switch (ioctl_cmd) { + case EAVB_IOCTL_CREATE_STREAM: + ret = qavb_create_stream(fl, buf); + break; + case EAVB_IOCTL_CREATE_STREAM_WITH_PATH: + ret = qavb_create_stream_with_path(fl, buf); + break; + case EAVB_IOCTL_GET_STREAM_INFO: + ret = qavb_get_stream_info(fl, buf); + break; + case EAVB_IOCTL_CONNECT_STREAM: + ret = qavb_connect_stream(fl, buf); + break; + case EAVB_IOCTL_RECEIVE: + ret = qavb_receive(fl, buf); + break; + case EAVB_IOCTL_RECV_DONE: + ret = qavb_recv_done(fl, buf); + break; + case EAVB_IOCTL_TRANSMIT: + ret = qavb_transmit(fl, buf); + break; + case EAVB_IOCTL_DISCONNECT_STREAM: + ret = qavb_disconnect_stream(fl, buf); + break; + case EAVB_IOCTL_DESTROY_STREAM: + ret = qavb_destroy_stream(fl, buf); + break; + case EAVB_IOCTL_GET_CRF_TS: + ret = qavb_get_crf_ts(fl, buf); + break; + default: + ret = -ENOTTY; + LOG_EAVB(LEVEL_ERR, "unsupported ioctl 0x%x\n", ioctl_cmd); + + } + + return ret; +} + +static long virtio_eavb_compat_ioctl(struct file *file, + unsigned int ioctl_cmd, + unsigned long ioctl_param) +{ + return -EOPNOTSUPP; +} + +static int virtio_eavb_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct eavb_file *fl = file->private_data; + int ret = 0; + unsigned long vsize; + u8 *buf; + + spin_lock(&fl->mappinglock); + if (fl->mapping_count < MAPPING_MAX) + fl->mapping_count++; + else + ret = -EBUSY; + + spin_unlock(&fl->mappinglock); + if (ret) { + LOG_EAVB(LEVEL_ERR, "Too many mapping (%d):fl->index %d\n", + fl->mapping_count, fl->index); + return ret; + } + + vsize = vma->vm_end - vma->vm_start; + LOG_EAVB(LEVEL_DEBUG, "vsize = %ld(0x%lx~0x%lx)\n", + vsize, vma->vm_start, vma->vm_end); + buf = kmalloc(vsize, GFP_KERNEL); + if (!buf) { + spin_lock(&fl->mappinglock); + fl->mapping_count--; + spin_unlock(&fl->mappinglock); + LOG_EAVB(LEVEL_ERR, "kmalloc failed\n"); + return -ENOMEM; + } + vma->vm_flags |= VM_IO; + vma->vm_flags |= VM_LOCKED; + + if (remap_pfn_range(vma, vma->vm_start, + virt_to_phys(buf)>>PAGE_SHIFT, vsize, vma->vm_page_prot)) { + LOG_EAVB(LEVEL_ERR, "remap pfn range failed\n"); + spin_lock(&fl->mappinglock); + fl->mapping_count--; + spin_unlock(&fl->mappinglock); + ret = -EAGAIN; + } + memset(buf, 0, vsize); + + add_mapping_info(fl, (u64)buf, vma->vm_start, virt_to_phys(buf), vsize); + + return ret; +} + +static const struct file_operations fops = { + .open = virtio_eavb_open, + .release = virtio_eavb_release, + .unlocked_ioctl = virtio_eavb_ioctl, + .compat_ioctl = virtio_eavb_compat_ioctl, + .mmap = virtio_eavb_mmap, +}; + +static void fe_recv_done(struct virtqueue *rvq) +{ + struct virtio_eavb_priv *priv = rvq->vdev->priv; + struct vio_msg_hdr *rxvhdr, *rsp; + struct fe_msg *msg; + unsigned int len; + unsigned long flags; +#ifdef EAVB_DEBUGFS + int index; +#endif + + while (1) { + spin_lock_irqsave(&priv->rvqlock, flags); + rxvhdr = virtqueue_get_buf(rvq, &len); + spin_unlock_irqrestore(&priv->rvqlock, flags); + + if (!rxvhdr) + break; + + ASSERT(len == RX_BUF_MAX_LEN); + if (len < rxvhdr->len) { + LOG_EAVB(LEVEL_ERR, + "rxvhdr msgid %d len %d > received len %d!\n", + rxvhdr->msgid, rxvhdr->len, len); + continue; + } + + if (rxvhdr->cmd == VIRTIO_EAVB_T_UPDATE_CLK) { + struct vio_update_clk_msg *vmsg; + + vmsg = (struct vio_update_clk_msg *)rxvhdr; + update_crf_ts(priv, vmsg->clk); + reclaim_rxbuf(priv, rxvhdr); + continue; + } + + rsp = rxvhdr; + if (rsp->msgid > FE_MSG_MAX - 1) { + LOG_EAVB(LEVEL_ERR, "rsp msgid %d is invalid!\n", + rsp->msgid); + continue; + } + + msg = priv->msgtable[rsp->msgid]; + if (!msg) { + LOG_EAVB(LEVEL_ERR, "rsp msgid %d found no msg!\n", + rsp->msgid); + continue; + } + if (msg->msgid != rsp->msgid) { + LOG_EAVB(LEVEL_ERR, "rsp msgid %d mismatch %d!\n", + rsp->msgid, msg->msgid); + continue; + } + + msg->rxbuf = (void *)rsp; + msg->rxbuf_used = 1; + LOG_EAVB(LEVEL_DEBUG, "msgid %d rxbuf used\n", msg->msgid); +#ifdef EAVB_DEBUGFS + index = priv->next_rxrecords_index & 0x1f; + priv->rxrecords[index].msgid = msg->msgid; + priv->rxrecords[index].ts = local_clock(); + priv->next_rxrecords_index++; +#endif + complete(&msg->work); + } +} + +static int init_vqs(struct virtio_eavb_priv *priv) +{ + struct virtqueue *vqs[2]; + static const char *const names[] = { "eavb_tx", "eavb_rx" }; + vq_callback_t *cbs[] = {NULL, fe_recv_done}; + int ret; + + ret = virtio_find_vqs(priv->vdev, 2, vqs, cbs, names, NULL); + if (ret) { + LOG_EAVB(LEVEL_ERR, "virtio_find_vqs fail\n"); + return ret; + } + + priv->svq = vqs[0]; + priv->rvq = vqs[1]; + + return 0; +} + +static int virtio_eavb_probe(struct virtio_device *vdev) +{ + int ret; + struct device *dev = NULL; + struct virtio_eavb_priv *priv; + int i; + + LOG_EAVB(LEVEL_DEBUG, "\n"); + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto fail; + } + + if (virtio_has_feature(vdev, VIRTIO_EAVB_F_SHMEM)) + priv->has_shmem = true; + + mutex_init(&priv->lock); + spin_lock_init(&priv->msglock); + spin_lock_init(&priv->rvqlock); + INIT_WORK(&priv->reclaim_work, reclaim_function); + init_completion(&priv->crf_ts_update); + + vdev->priv = priv; + priv->vdev = vdev; + priv->dev = vdev->dev.parent; + priv->name = "eavb"; + + ret = init_vqs(priv); + if (ret) + goto free_priv; + + virtio_device_ready(vdev); + + ret = alloc_chrdev_region(&priv->dev_no, 0, DEVICE_NUM, DEVICE_NAME); + if (ret) + goto free_priv; + + cdev_init(&priv->cdev, &fops); + priv->cdev.owner = THIS_MODULE; + ret = cdev_add(&priv->cdev, MKDEV(MAJOR(priv->dev_no), 0), DEVICE_NUM); + if (ret) + goto free_chrdev; + + priv->class = class_create(THIS_MODULE, "virt-eavb"); + if (IS_ERR(priv->class)) + goto class_create_fail; + + dev = device_create(priv->class, NULL, + MKDEV(MAJOR(priv->dev_no), MINOR_NUM_DEV), + NULL, DEVICE_NAME); + if (IS_ERR_OR_NULL(dev)) + goto device_create_fail; + + priv->rxbufs[0] = kmalloc(FE_MSG_MAX * RX_BUF_MAX_LEN, GFP_KERNEL); + if (!priv->rxbufs[0]) { + ret = -ENOMEM; + goto alloc_rxbufs_fail; + } + + for (i = 0; i < FE_MSG_MAX; i++) { + struct scatterlist sg; + u8 *rxbuf; + + rxbuf = priv->rxbufs[0] + i * RX_BUF_MAX_LEN; + priv->rxbufs[i] = rxbuf; + + sg_init_one(&sg, rxbuf, RX_BUF_MAX_LEN); + ret = virtqueue_add_inbuf(priv->rvq, &sg, 1, rxbuf, GFP_KERNEL); + WARN_ON(ret); + } + virtqueue_disable_cb(priv->svq); + + virtqueue_enable_cb(priv->rvq); + virtqueue_kick(priv->rvq); + +#ifdef EAVB_DEBUGFS + priv->debugfs_root = debugfs_create_dir("eavb", NULL); + debugfs_create_file("log", 00400 | 00200, + priv->debugfs_root, priv, + &fops_debugfs_log); + debugfs_create_file("timeout", 00400 | 00200, + priv->debugfs_root, NULL, + &fops_debugfs_timeout); +#endif + place_marker("M - DRIVER EAVB FE Ready"); + return 0; + +alloc_rxbufs_fail: + device_destroy(priv->class, MKDEV(MAJOR(priv->dev_no), MINOR_NUM_DEV)); +device_create_fail: + if (!IS_ERR_OR_NULL(dev)) + device_destroy(priv->class, MKDEV(MAJOR(priv->dev_no), + MINOR_NUM_DEV)); + class_destroy(priv->class); +class_create_fail: + cdev_del(&priv->cdev); +free_chrdev: + unregister_chrdev_region(priv->dev_no, DEVICE_NUM); +free_priv: + kfree(priv); +fail: + return ret; +} + +static void virtio_eavb_remove(struct virtio_device *vdev) +{ + struct virtio_eavb_priv *priv; + + LOG_EAVB(LEVEL_DEBUG, "\n"); + priv = vdev->priv; + +#ifdef EAVB_DEBUGFS + debugfs_remove_recursive(priv->debugfs_root); +#endif + device_destroy(priv->class, MKDEV(MAJOR(priv->dev_no), MINOR_NUM_DEV)); + class_destroy(priv->class); + cdev_del(&priv->cdev); + unregister_chrdev_region(priv->dev_no, DEVICE_NUM); + + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + kfree(priv->rxbufs[0]); + kfree(priv); +} + + +const static struct virtio_device_id id_table[] = { + { VIRTIO_ID_EAVB, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static unsigned int features[] = { + VIRTIO_EAVB_F_SHMEM, +}; + +static struct virtio_driver virtio_eavb_driver = { + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = virtio_eavb_probe, + .remove = virtio_eavb_remove, +}; + +static int __init virtio_eavb_init(void) +{ + place_marker("M - DRIVER EAVB FE Init"); + return register_virtio_driver(&virtio_eavb_driver); +} + +static void __exit virtio_eavb_exit(void) +{ + unregister_virtio_driver(&virtio_eavb_driver); +} + +module_init(virtio_eavb_init); +module_exit(virtio_eavb_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/eavb_shared.h b/include/uapi/linux/eavb_shared.h new file mode 100644 index 000000000000..880036f1a7b7 --- /dev/null +++ b/include/uapi/linux/eavb_shared.h @@ -0,0 +1,387 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. + */ +#ifndef __EAVB_SHARED_H__ +#define __EAVB_SHARED_H__ + + +#include + +#define EAVB_IOCTL_MAGIC 'B' + +/* ioctl request */ +#define EAVB_IOCTL_CREATE_STREAM \ + _IOWR(EAVB_IOCTL_MAGIC, 1, struct eavb_ioctl_create_stream) +#define EAVB_IOCTL_GET_STREAM_INFO \ + _IOWR(EAVB_IOCTL_MAGIC, 2, struct eavb_ioctl_get_stream_info) + +/* DEPRECATED Do not use this in new code */ +#define EAVB_IOCTL_CONNECT_STREAM \ + _IOWR(EAVB_IOCTL_MAGIC, 3, struct eavb_ioctl_connect_stream) + +#define EAVB_IOCTL_RECEIVE \ + _IOWR(EAVB_IOCTL_MAGIC, 4, struct eavb_ioctl_receive) +#define EAVB_IOCTL_RECV_DONE \ + _IOWR(EAVB_IOCTL_MAGIC, 5, struct eavb_ioctl_recv_done) +#define EAVB_IOCTL_TRANSMIT \ + _IOWR(EAVB_IOCTL_MAGIC, 6, struct eavb_ioctl_transmit) +#define EAVB_IOCTL_DISCONNECT_STREAM \ + _IOW(EAVB_IOCTL_MAGIC, 9, struct eavb_ioctl_disconnect_stream) +#define EAVB_IOCTL_DESTROY_STREAM \ + _IOW(EAVB_IOCTL_MAGIC, 10, struct eavb_ioctl_destroy_stream) +#define EAVB_IOCTL_CREATE_STREAM_WITH_PATH \ + _IOWR(EAVB_IOCTL_MAGIC, 11, struct eavb_ioctl_create_stream_with_path) +#define EAVB_IOCTL_GET_CRF_TS \ + _IOWR(EAVB_IOCTL_MAGIC, 12, __u64) + +/* default value */ +#define STATION_ADDR_SIZE 8 +#define IF_NAMESIZE 16 +#define MAX_CONFIG_FILE_PATH 512 + +/* Invalid value for config file */ +#define AVB_INVALID_ADDR (0xFF) +#define AVB_INVALID_INTEGER (-1) +#define AVB_INVALID_UINT (-1) + + +enum avb_role { + AVB_ROLE_TALKER = 0, + AVB_ROLE_LISTENER, + AVB_ROLE_CRF_TALKER, + AVB_ROLE_INVALID = -1 +}; + +enum stream_mapping_type { + NONE = 0, + PCM, + H264, + MPEG2TS, + MJPEG, + CRF, + MAPPING_TYPE_INVALID = -1 +}; + +enum ring_buffer_mode { + /* Return error when new data doesn't fit in ring buffer */ + RING_BUFFER_MODE_FILL = 0, + /* Drop oldest samples to make room for new data */ + RING_BUFFER_MODE_DROP_OLD = 1, + /* Drop oldest samples to make room for new data */ + RING_BUFFER_MODE_INVALID = -1 +}; + +enum avb_class { + CLASS_A = 0, + CLASS_B = 1, + CLASS_AAF = 2, + CLASS_INVALID = -1 +}; + +enum data_endianness { + ENDIAN_BIG = 0, + ENDIAN_LITTLE = 1, + ENDIAN_INVALID = -1 +}; + +enum avb_ieee1722_version { + QAVB_IEEE_1722_ver_2010 = 0, + QAVB_IEEE_1722_ver_2016, + QAVB_IEEE_1722_ver_INVALID = -1, +}; + +enum avtp_crf_mode { + /* CRF disabled */ + QAVB_AVTP_CRF_MODE_DISABLED = 0, + /* CRF talker */ + QAVB_AVTP_CRF_MODE_TALKER, + /* CRF listener, PPM = local - remote */ + QAVB_AVTP_CRF_MODE_LISTENER, + /* CRF listener, PPM = nominal - remote */ + QAVB_AVTP_CRF_MODE_LISTENER_NOMINAL, + QAVB_AVTP_CRF_MODE_MAX +}; + +enum avtp_crf_type { + /* user specified */ + QAVB_AVTP_CRF_TYPE_USER = 0, + /* audio ample timestamp */ + QAVB_AVTP_CRF_TYPE_AUDIO_SAMPLE, + /* video frame sync timestamp */ + QAVB_AVTP_CRF_TYPE_VIDEO_FRAME, + /* video line timestamp */ + QAVB_AVTP_CRF_TYPE_VIDEO_LINE, + /* machine cycle timestamp */ + QAVB_AVTP_CRF_TYPE_MACHINE_CYCLE, + QAVB_AVTP_CRF_TYPE_MAX +}; + +enum avtp_crf_pull { + /* Multiply base_frequency field by 1.0 */ + QAVB_AVTP_CRF_PULL_1_DIV_1_0 = 0, + /* Multiply base_frequency field by 1/1.1001 */ + QAVB_AVTP_CRF_PULL_1_DIV_1_DOT_1001, + /* Multiply base_frequency field by 1.1001 */ + QAVB_AVTP_CRF_PULL_1_DOT_1001, + /* Multiply base_frequency field by 24/25 */ + QAVB_AVTP_CRF_PULL_24_DIV_25, + /* Multiply base_frequency field by 25/24 */ + QAVB_AVTP_CRF_PULL_25_DIV_24, + /* Multiply base_frequency field by 8 */ + QAVB_AVTP_CRF_PULL_8, + QAVB_AVTP_CRF_PULL_MAX +}; + +struct eavb_ioctl_hdr { + __u64 streamCtx; +}; + + +/* + * EAVB_IOCTL_CREATE_STREAM + */ + +/* DEPRECATED Do not use this in new code */ +struct eavb_ioctl_stream_config { + __u16 stream_id; + char eth_interface[IF_NAMESIZE]; + __u16 vlan_id; + __u16 ring_buffer_elem_count; + enum ring_buffer_mode ring_buffer_mode; + /* talker = 0 or listener = 1 */ + enum avb_role avb_role; + __u8 dest_macaddr[STATION_ADDR_SIZE]; + __u8 stream_addr[STATION_ADDR_SIZE]; + /* "crf_macaddr" or "crf_dest_macaddr" */ + __u8 crf_dest_macaddr[STATION_ADDR_SIZE]; + __u8 crf_stream_addr[STATION_ADDR_SIZE]; + enum stream_mapping_type mapping_type; + int wakeup_interval; /* "wakeup_interval" or "tx_interval" */ + int tx_pkts_per_sec; /* if not set, do default */ + int max_stale_ms; /* int max_stale_ns = max_stale_ms*1000; */ + int presentation_time_ms; /* if not set, do default */ + int enforce_presentation_time; + int sr_class_type; /* A = 0 B = 1 AAF = 2 */ + /* sets number of items to se sent on each tx / rx */ + int packing_factor; + int bandwidth; + + /* H.264 */ + int max_payload; /* "max_payload" or "max_video_payload" */ + + int mrp_enabled; + + /* Audio Specific */ + int pcm_bit_depth; + int pcm_channels; + int sample_rate; /* in hz */ + unsigned char endianness; /* 0 = big 1 = little */ + + int ieee1722_standard; + + /* Thread priority in QNX side */ + int talker_priority; + int listener_priority; + int crf_priority; + + /* CRF */ + + /* 0 - disabled + * 1 - CRF talker (listener drives reference) + * 2 - CRF with talker reference (talker has CRF talker) + */ + int crf_mode; + + /* 0 - custom + * 1- audio + * 2- video frame + * 3 - video line + * 4 - machine cycle + */ + int crf_type; + + /* time interval after how many events timestamp is to be produced + * (base_frequency * pull) / timestamp_interval = + * # of timestamps per second + */ + int crf_timestamping_interval; + int crf_timestamping_interval_remote; + int crf_timestamping_interval_local; + /* enables/disables dynamic IPG adjustments */ + int crf_allow_dynamic_tx_adjust; + /* indicates how many CRF timestamps per each CRF packet */ + int crf_num_timestamps_per_pkt; + + __s64 crf_mcr_adjust_min_ppm; + __s64 crf_mcr_adjust_max_ppm; + + __u16 crf_stream_id; /* CRF stream ID */ + __s32 crf_base_frequency; /* CRF base frequency */ + + __s32 crf_listener_ts_smoothing; + __s32 crf_talker_ts_smoothing; + + /* multiplier for the base frequency */ + int crf_pull; + /* indicates how often to issue MCR callback events + * how many packets will generate one callback. + */ + int crf_event_callback_interval; + /* Indicates how often to update IPG */ + int crf_dynamic_tx_adjust_interval; + + /* stats */ + __s32 enable_stats_reporting; + __s32 stats_reporting_interval; + __s32 stats_reporting_samples; + + /* packet tracking */ + __s32 enable_packet_tracking; + __s32 packet_tracking_interval; + int blocking_write_enabled; + double blocking_write_fill_level; + int app_tx_block_enabled; + int stream_interleaving_enabled; + int create_talker_thread; + int create_crf_threads; + int listener_bpf_pkts_per_buff; +} __packed; + +/* DEPRECATED Do not use this in new code */ +struct eavb_ioctl_create_stream { + struct eavb_ioctl_stream_config config; /* IN */ + struct eavb_ioctl_hdr hdr; /* OUT */ +}; + +struct eavb_ioctl_create_stream_with_path { + char path[MAX_CONFIG_FILE_PATH]; /* IN */ + struct eavb_ioctl_hdr hdr; /* OUT */ +}; + +/* + * EAVB_IOCTL_GET_STREAM_INFO + */ + +struct eavb_ioctl_stream_info { + enum avb_role role; + enum stream_mapping_type mapping_type; + /* Max packet payload size */ + unsigned int max_payload; + /* Number of packets sent per wake */ + unsigned int pkts_per_wake; + /* Time to sleep between wakes */ + unsigned int wakeup_period_us; + + /* Audio Specific */ + /* Audio bit depth 8/16/24/32 */ + int pcm_bit_depth; + /* Audio channels 1/2 */ + int num_pcm_channels; + /* Audio sample rate in hz */ + int sample_rate; + /* Audio sample endianness 0(big)/1(little) */ + unsigned char endianness; + + /* Max buffer size (Bytes) allowed */ + unsigned int max_buffer_size; +} __packed; + +struct eavb_ioctl_get_stream_info { + struct eavb_ioctl_hdr hdr; /* IN */ + struct eavb_ioctl_stream_info info; /* OUT */ +}; + + +/* + * EAVB_IOCTL_CONNECT_STREAM + */ + +struct eavb_ioctl_connect_stream { + struct eavb_ioctl_hdr hdr; /* IN */ +}; + + +/* + * EAVB_IOCTL_RECEIVE + */ + +struct eavb_ioctl_buf_hdr { + /* This flag is used for H.264 and MJPEG streams: + * 1. H.264: Set for the very last packet of an access unit. + * 2. MJPEG Set the last packet of a video frame. + */ + __u32 flag_end_of_frame:1; + /* This flag is used in file transfer only: + * Set for the last packet in the file + */ + __u32 flag_end_of_file:1; + __u32 flag_reserved:30; + /* Audio event Layout D3scription Valid Channels + * event value + * 0 Static layout Based on config + * 1 Mono 0 + * 2 Stereo 0, 1 + * 3 5.1 0,1,2,3,4,5 + * 4 7.1 0,1,2,3,4,5,6,7 + * 5-15 Custom Defined by System + * Integrator + */ + __u32 event; + __u32 reserved; + /* Size of the payload (bytes) */ + __u32 payload_size; +} __packed; + +struct eavb_ioctl_buf_data { + struct eavb_ioctl_buf_hdr hdr; + /* virtual address of buffer */ + __u64 pbuf; +} __packed; + +struct eavb_ioctl_receive { + struct eavb_ioctl_hdr hdr; /* IN */ + struct eavb_ioctl_buf_data data; /* IN/OUT */ + __s32 received; /* OUT */ +}; + + +/* + * EAVB_IOCTL_RECV_DONE + */ + +struct eavb_ioctl_recv_done { + struct eavb_ioctl_hdr hdr; /* IN */ + struct eavb_ioctl_buf_data data; /* IN */ +}; + + +/* + * EAVB_IOCTL_TRANSMIT + */ + +struct eavb_ioctl_transmit { + struct eavb_ioctl_hdr hdr; /* IN */ + struct eavb_ioctl_buf_data data; /* IN/OUT */ + __s32 written; +}; + + +/* + * EAVB_IOCTL_DISCONNECT_STREAM + */ + +struct eavb_ioctl_disconnect_stream { + struct eavb_ioctl_hdr hdr; /* IN */ +}; + + +/* + * EAVB_IOCTL_DESTROY_STREAM + */ + +struct eavb_ioctl_destroy_stream { + struct eavb_ioctl_hdr hdr; /* IN */ +}; + +#endif /*__EAVB_SHARED_H__*/