Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Broadcom VK device
#
config BCM_VK
tristate "Support for Broadcom VK Accelerators"
depends on PCI_MSI
help
Select this option to enable support for Broadcom
VK Accelerators. VK is used for performing
multiple specific offload processing tasks in parallel.
Such offload tasks assist in such operations as video
transcoding, compression, and crypto tasks.
This driver enables userspace programs to access these
accelerators via /dev/bcm-vk.N devices.
If unsure, say N.
config BCM_VK_TTY
bool "Enable tty ports on a Broadcom VK Accelerator device"
depends on TTY
depends on BCM_VK
help
Select this option to enable tty support to allow console
access to Broadcom VK Accelerator cards from host.
Device node will in the form /dev/bcm-vk.x_ttyVKy where:
x is the instance of the VK card
y is the tty device number on the VK card.
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for Broadcom VK driver
#
obj-$(CONFIG_BCM_VK) += bcm_vk.o
bcm_vk-objs := \
bcm_vk_dev.o \
bcm_vk_msg.o \
bcm_vk_sg.o
bcm_vk-$(CONFIG_BCM_VK_TTY) += bcm_vk_tty.o
+548
View File
@@ -0,0 +1,548 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2018-2020 Broadcom.
*/
#ifndef BCM_VK_H
#define BCM_VK_H
#include <linux/atomic.h>
#include <linux/firmware.h>
#include <linux/irq.h>
#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/poll.h>
#include <linux/sched/signal.h>
#include <linux/tty.h>
#include <linux/uaccess.h>
#include <uapi/linux/misc/bcm_vk.h>
#include "bcm_vk_msg.h"
#define DRV_MODULE_NAME "bcm-vk"
/*
* Load Image is completed in two stages:
*
* 1) When the VK device boot-up, M7 CPU runs and executes the BootROM.
* The Secure Boot Loader (SBL) as part of the BootROM will run
* to open up ITCM for host to push BOOT1 image.
* SBL will authenticate the image before jumping to BOOT1 image.
*
* 2) Because BOOT1 image is a secured image, we also called it the
* Secure Boot Image (SBI). At second stage, SBI will initialize DDR
* and wait for host to push BOOT2 image to DDR.
* SBI will authenticate the image before jumping to BOOT2 image.
*
*/
/* Location of registers of interest in BAR0 */
/* Request register for Secure Boot Loader (SBL) download */
#define BAR_CODEPUSH_SBL 0x400
/* Start of ITCM */
#define CODEPUSH_BOOT1_ENTRY 0x00400000
#define CODEPUSH_MASK 0xfffff000
#define CODEPUSH_BOOTSTART BIT(0)
/* Boot Status register */
#define BAR_BOOT_STATUS 0x404
#define SRAM_OPEN BIT(16)
#define DDR_OPEN BIT(17)
/* Firmware loader progress status definitions */
#define FW_LOADER_ACK_SEND_MORE_DATA BIT(18)
#define FW_LOADER_ACK_IN_PROGRESS BIT(19)
#define FW_LOADER_ACK_RCVD_ALL_DATA BIT(20)
/* Boot1/2 is running in standalone mode */
#define BOOT_STDALONE_RUNNING BIT(21)
/* definitions for boot status register */
#define BOOT_STATE_MASK (0xffffffff & \
~(FW_LOADER_ACK_SEND_MORE_DATA | \
FW_LOADER_ACK_IN_PROGRESS | \
BOOT_STDALONE_RUNNING))
#define BOOT_ERR_SHIFT 4
#define BOOT_ERR_MASK (0xf << BOOT_ERR_SHIFT)
#define BOOT_PROG_MASK 0xf
#define BROM_STATUS_NOT_RUN 0x2
#define BROM_NOT_RUN (SRAM_OPEN | BROM_STATUS_NOT_RUN)
#define BROM_STATUS_COMPLETE 0x6
#define BROM_RUNNING (SRAM_OPEN | BROM_STATUS_COMPLETE)
#define BOOT1_STATUS_COMPLETE 0x6
#define BOOT1_RUNNING (DDR_OPEN | BOOT1_STATUS_COMPLETE)
#define BOOT2_STATUS_COMPLETE 0x6
#define BOOT2_RUNNING (FW_LOADER_ACK_RCVD_ALL_DATA | \
BOOT2_STATUS_COMPLETE)
/* Boot request for Secure Boot Image (SBI) */
#define BAR_CODEPUSH_SBI 0x408
/* 64M mapped to BAR2 */
#define CODEPUSH_BOOT2_ENTRY 0x60000000
#define BAR_CARD_STATUS 0x410
/* CARD_STATUS definitions */
#define CARD_STATUS_TTYVK0_READY BIT(0)
#define CARD_STATUS_TTYVK1_READY BIT(1)
#define BAR_BOOT1_STDALONE_PROGRESS 0x420
#define BOOT1_STDALONE_SUCCESS (BIT(13) | BIT(14))
#define BOOT1_STDALONE_PROGRESS_MASK BOOT1_STDALONE_SUCCESS
#define BAR_METADATA_VERSION 0x440
#define BAR_OS_UPTIME 0x444
#define BAR_CHIP_ID 0x448
#define MAJOR_SOC_REV(_chip_id) (((_chip_id) >> 20) & 0xf)
#define BAR_CARD_TEMPERATURE 0x45c
/* defines for all temperature sensor */
#define BCM_VK_TEMP_FIELD_MASK 0xff
#define BCM_VK_CPU_TEMP_SHIFT 0
#define BCM_VK_DDR0_TEMP_SHIFT 8
#define BCM_VK_DDR1_TEMP_SHIFT 16
#define BAR_CARD_VOLTAGE 0x460
/* defines for voltage rail conversion */
#define BCM_VK_VOLT_RAIL_MASK 0xffff
#define BCM_VK_3P3_VOLT_REG_SHIFT 16
#define BAR_CARD_ERR_LOG 0x464
/* Error log register bit definition - register for error alerts */
#define ERR_LOG_UECC BIT(0)
#define ERR_LOG_SSIM_BUSY BIT(1)
#define ERR_LOG_AFBC_BUSY BIT(2)
#define ERR_LOG_HIGH_TEMP_ERR BIT(3)
#define ERR_LOG_WDOG_TIMEOUT BIT(4)
#define ERR_LOG_SYS_FAULT BIT(5)
#define ERR_LOG_RAMDUMP BIT(6)
#define ERR_LOG_COP_WDOG_TIMEOUT BIT(7)
/* warnings */
#define ERR_LOG_MEM_ALLOC_FAIL BIT(8)
#define ERR_LOG_LOW_TEMP_WARN BIT(9)
#define ERR_LOG_ECC BIT(10)
#define ERR_LOG_IPC_DWN BIT(11)
/* Alert bit definitions detectd on host */
#define ERR_LOG_HOST_INTF_V_FAIL BIT(13)
#define ERR_LOG_HOST_HB_FAIL BIT(14)
#define ERR_LOG_HOST_PCIE_DWN BIT(15)
#define BAR_CARD_ERR_MEM 0x468
/* defines for mem err, all fields have same width */
#define BCM_VK_MEM_ERR_FIELD_MASK 0xff
#define BCM_VK_ECC_MEM_ERR_SHIFT 0
#define BCM_VK_UECC_MEM_ERR_SHIFT 8
/* threshold of event occurrence and logs start to come out */
#define BCM_VK_ECC_THRESHOLD 10
#define BCM_VK_UECC_THRESHOLD 1
#define BAR_CARD_PWR_AND_THRE 0x46c
/* defines for power and temp threshold, all fields have same width */
#define BCM_VK_PWR_AND_THRE_FIELD_MASK 0xff
#define BCM_VK_LOW_TEMP_THRE_SHIFT 0
#define BCM_VK_HIGH_TEMP_THRE_SHIFT 8
#define BCM_VK_PWR_STATE_SHIFT 16
#define BAR_CARD_STATIC_INFO 0x470
#define BAR_INTF_VER 0x47c
#define BAR_INTF_VER_MAJOR_SHIFT 16
#define BAR_INTF_VER_MASK 0xffff
/*
* major and minor semantic version numbers supported
* Please update as required on interface changes
*/
#define SEMANTIC_MAJOR 1
#define SEMANTIC_MINOR 0
/*
* first door bell reg, ie for queue = 0. Only need the first one, as
* we will use the queue number to derive the others
*/
#define VK_BAR0_REGSEG_DB_BASE 0x484
#define VK_BAR0_REGSEG_DB_REG_GAP 8 /*
* DB register gap,
* DB1 at 0x48c and DB2 at 0x494
*/
/* reset register and specific values */
#define VK_BAR0_RESET_DB_NUM 3
#define VK_BAR0_RESET_DB_SOFT 0xffffffff
#define VK_BAR0_RESET_DB_HARD 0xfffffffd
#define VK_BAR0_RESET_RAMPDUMP 0xa0000000
#define VK_BAR0_Q_DB_BASE(q_num) (VK_BAR0_REGSEG_DB_BASE + \
((q_num) * VK_BAR0_REGSEG_DB_REG_GAP))
#define VK_BAR0_RESET_DB_BASE (VK_BAR0_REGSEG_DB_BASE + \
(VK_BAR0_RESET_DB_NUM * VK_BAR0_REGSEG_DB_REG_GAP))
#define BAR_BOOTSRC_SELECT 0xc78
/* BOOTSRC definitions */
#define BOOTSRC_SOFT_ENABLE BIT(14)
/* Card OS Firmware version size */
#define BAR_FIRMWARE_TAG_SIZE 50
#define FIRMWARE_STATUS_PRE_INIT_DONE 0x1f
/* VK MSG_ID defines */
#define VK_MSG_ID_BITMAP_SIZE 4096
#define VK_MSG_ID_BITMAP_MASK (VK_MSG_ID_BITMAP_SIZE - 1)
#define VK_MSG_ID_OVERFLOW 0xffff
/*
* BAR1
*/
/* BAR1 message q definition */
/* indicate if msgq ctrl in BAR1 is populated */
#define VK_BAR1_MSGQ_DEF_RDY 0x60c0
/* ready marker value for the above location, normal boot2 */
#define VK_BAR1_MSGQ_RDY_MARKER 0xbeefcafe
/* ready marker value for the above location, normal boot2 */
#define VK_BAR1_DIAG_RDY_MARKER 0xdeadcafe
/* number of msgqs in BAR1 */
#define VK_BAR1_MSGQ_NR 0x60c4
/* BAR1 queue control structure offset */
#define VK_BAR1_MSGQ_CTRL_OFF 0x60c8
/* BAR1 ucode and boot1 version tag */
#define VK_BAR1_UCODE_VER_TAG 0x6170
#define VK_BAR1_BOOT1_VER_TAG 0x61b0
#define VK_BAR1_VER_TAG_SIZE 64
/* Memory to hold the DMA buffer memory address allocated for boot2 download */
#define VK_BAR1_DMA_BUF_OFF_HI 0x61e0
#define VK_BAR1_DMA_BUF_OFF_LO (VK_BAR1_DMA_BUF_OFF_HI + 4)
#define VK_BAR1_DMA_BUF_SZ (VK_BAR1_DMA_BUF_OFF_HI + 8)
/* Scratch memory allocated on host for VK */
#define VK_BAR1_SCRATCH_OFF_HI 0x61f0
#define VK_BAR1_SCRATCH_OFF_LO (VK_BAR1_SCRATCH_OFF_HI + 4)
#define VK_BAR1_SCRATCH_SZ_ADDR (VK_BAR1_SCRATCH_OFF_HI + 8)
#define VK_BAR1_SCRATCH_DEF_NR_PAGES 32
/* BAR1 DAUTH info */
#define VK_BAR1_DAUTH_BASE_ADDR 0x6200
#define VK_BAR1_DAUTH_STORE_SIZE 0x48
#define VK_BAR1_DAUTH_VALID_SIZE 0x8
#define VK_BAR1_DAUTH_MAX 4
#define VK_BAR1_DAUTH_STORE_ADDR(x) \
(VK_BAR1_DAUTH_BASE_ADDR + \
(x) * (VK_BAR1_DAUTH_STORE_SIZE + VK_BAR1_DAUTH_VALID_SIZE))
#define VK_BAR1_DAUTH_VALID_ADDR(x) \
(VK_BAR1_DAUTH_STORE_ADDR(x) + VK_BAR1_DAUTH_STORE_SIZE)
/* BAR1 SOTP AUTH and REVID info */
#define VK_BAR1_SOTP_REVID_BASE_ADDR 0x6340
#define VK_BAR1_SOTP_REVID_SIZE 0x10
#define VK_BAR1_SOTP_REVID_MAX 2
#define VK_BAR1_SOTP_REVID_ADDR(x) \
(VK_BAR1_SOTP_REVID_BASE_ADDR + (x) * VK_BAR1_SOTP_REVID_SIZE)
/* VK device supports a maximum of 3 bars */
#define MAX_BAR 3
/* default number of msg blk for inband SGL */
#define BCM_VK_DEF_IB_SGL_BLK_LEN 16
#define BCM_VK_IB_SGL_BLK_MAX 24
enum pci_barno {
BAR_0 = 0,
BAR_1,
BAR_2
};
#ifdef CONFIG_BCM_VK_TTY
#define BCM_VK_NUM_TTY 2
#else
#define BCM_VK_NUM_TTY 0
#endif
struct bcm_vk_tty {
struct tty_port port;
u32 to_offset; /* bar offset to use */
u32 to_size; /* to VK buffer size */
u32 wr; /* write offset shadow */
u32 from_offset; /* bar offset to use */
u32 from_size; /* from VK buffer size */
u32 rd; /* read offset shadow */
pid_t pid;
bool irq_enabled;
bool is_opened; /* tracks tty open/close */
};
/* VK device max power state, supports 3, full, reduced and low */
#define MAX_OPP 3
#define MAX_CARD_INFO_TAG_SIZE 64
struct bcm_vk_card_info {
u32 version;
char os_tag[MAX_CARD_INFO_TAG_SIZE];
char cmpt_tag[MAX_CARD_INFO_TAG_SIZE];
u32 cpu_freq_mhz;
u32 cpu_scale[MAX_OPP];
u32 ddr_freq_mhz;
u32 ddr_size_MB;
u32 video_core_freq_mhz;
};
/* DAUTH related info */
struct bcm_vk_dauth_key {
char store[VK_BAR1_DAUTH_STORE_SIZE];
char valid[VK_BAR1_DAUTH_VALID_SIZE];
};
struct bcm_vk_dauth_info {
struct bcm_vk_dauth_key keys[VK_BAR1_DAUTH_MAX];
};
/*
* Control structure of logging messages from the card. This
* buffer is for logmsg that comes from vk
*/
struct bcm_vk_peer_log {
u32 rd_idx;
u32 wr_idx;
u32 buf_size;
u32 mask;
};
/* max buf size allowed */
#define BCM_VK_PEER_LOG_BUF_MAX SZ_16K
/* max size per line of peer log */
#define BCM_VK_PEER_LOG_LINE_MAX 256
/*
* single entry for processing type + utilization
*/
#define BCM_VK_PROC_TYPE_TAG_LEN 8
struct bcm_vk_proc_mon_entry_t {
char tag[BCM_VK_PROC_TYPE_TAG_LEN];
u32 used;
u32 max; /**< max capacity */
};
/**
* Structure for run time utilization
*/
#define BCM_VK_PROC_MON_MAX 8 /* max entries supported */
struct bcm_vk_proc_mon_info {
u32 num; /**< no of entries */
u32 entry_size; /**< per entry size */
struct bcm_vk_proc_mon_entry_t entries[BCM_VK_PROC_MON_MAX];
};
struct bcm_vk_hb_ctrl {
struct delayed_work work;
u32 last_uptime;
u32 lost_cnt;
};
struct bcm_vk_alert {
u16 flags;
u16 notfs;
};
/* some alert counters that the driver will keep track */
struct bcm_vk_alert_cnts {
u16 ecc;
u16 uecc;
};
struct bcm_vk {
struct pci_dev *pdev;
void __iomem *bar[MAX_BAR];
int num_irqs;
struct bcm_vk_card_info card_info;
struct bcm_vk_proc_mon_info proc_mon_info;
struct bcm_vk_dauth_info dauth_info;
/* mutex to protect the ioctls */
struct mutex mutex;
struct miscdevice miscdev;
int devid; /* dev id allocated */
#ifdef CONFIG_BCM_VK_TTY
struct tty_driver *tty_drv;
struct timer_list serial_timer;
struct bcm_vk_tty tty[BCM_VK_NUM_TTY];
struct workqueue_struct *tty_wq_thread;
struct work_struct tty_wq_work;
#endif
/* Reference-counting to handle file operations */
struct kref kref;
spinlock_t msg_id_lock; /* Spinlock for msg_id */
u16 msg_id;
DECLARE_BITMAP(bmap, VK_MSG_ID_BITMAP_SIZE);
spinlock_t ctx_lock; /* Spinlock for component context */
struct bcm_vk_ctx ctx[VK_CMPT_CTX_MAX];
struct bcm_vk_ht_entry pid_ht[VK_PID_HT_SZ];
pid_t reset_pid; /* process that issue reset */
atomic_t msgq_inited; /* indicate if info has been synced with vk */
struct bcm_vk_msg_chan to_v_msg_chan;
struct bcm_vk_msg_chan to_h_msg_chan;
struct workqueue_struct *wq_thread;
struct work_struct wq_work; /* work queue for deferred job */
unsigned long wq_offload[1]; /* various flags on wq requested */
void *tdma_vaddr; /* test dma segment virtual addr */
dma_addr_t tdma_addr; /* test dma segment bus addr */
struct notifier_block panic_nb;
u32 ib_sgl_size; /* size allocated for inband sgl insertion */
/* heart beat mechanism control structure */
struct bcm_vk_hb_ctrl hb_ctrl;
/* house-keeping variable of error logs */
spinlock_t host_alert_lock; /* protection to access host_alert struct */
struct bcm_vk_alert host_alert;
struct bcm_vk_alert peer_alert; /* bits set by the card */
struct bcm_vk_alert_cnts alert_cnts;
/* offset of the peer log control in BAR2 */
u32 peerlog_off;
struct bcm_vk_peer_log peerlog_info; /* record of peer log info */
/* offset of processing monitoring info in BAR2 */
u32 proc_mon_off;
};
/* wq offload work items bits definitions */
enum bcm_vk_wq_offload_flags {
BCM_VK_WQ_DWNLD_PEND = 0,
BCM_VK_WQ_DWNLD_AUTO = 1,
BCM_VK_WQ_NOTF_PEND = 2,
};
/* a macro to get an individual field with mask and shift */
#define BCM_VK_EXTRACT_FIELD(_field, _reg, _mask, _shift) \
(_field = (((_reg) >> (_shift)) & (_mask)))
struct bcm_vk_entry {
const u32 mask;
const u32 exp_val;
const char *str;
};
/* alerts that could be generated from peer */
#define BCM_VK_PEER_ERR_NUM 12
extern struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM];
/* alerts detected by the host */
#define BCM_VK_HOST_ERR_NUM 3
extern struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM];
/*
* check if PCIe interface is down on read. Use it when it is
* certain that _val should never be all ones.
*/
#define BCM_VK_INTF_IS_DOWN(val) ((val) == 0xffffffff)
static inline u32 vkread32(struct bcm_vk *vk, enum pci_barno bar, u64 offset)
{
return readl(vk->bar[bar] + offset);
}
static inline void vkwrite32(struct bcm_vk *vk,
u32 value,
enum pci_barno bar,
u64 offset)
{
writel(value, vk->bar[bar] + offset);
}
static inline u8 vkread8(struct bcm_vk *vk, enum pci_barno bar, u64 offset)
{
return readb(vk->bar[bar] + offset);
}
static inline void vkwrite8(struct bcm_vk *vk,
u8 value,
enum pci_barno bar,
u64 offset)
{
writeb(value, vk->bar[bar] + offset);
}
static inline bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk)
{
u32 rdy_marker = 0;
u32 fw_status;
fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
if ((fw_status & VK_FWSTS_READY) == VK_FWSTS_READY)
rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
return (rdy_marker == VK_BAR1_MSGQ_RDY_MARKER);
}
int bcm_vk_open(struct inode *inode, struct file *p_file);
ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
loff_t *f_pos);
ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
size_t count, loff_t *f_pos);
__poll_t bcm_vk_poll(struct file *p_file, struct poll_table_struct *wait);
int bcm_vk_release(struct inode *inode, struct file *p_file);
void bcm_vk_release_data(struct kref *kref);
irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id);
irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id);
irqreturn_t bcm_vk_tty_irqhandler(int irq, void *dev_id);
int bcm_vk_msg_init(struct bcm_vk *vk);
void bcm_vk_msg_remove(struct bcm_vk *vk);
void bcm_vk_drain_msg_on_reset(struct bcm_vk *vk);
int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync);
void bcm_vk_blk_drv_access(struct bcm_vk *vk);
s32 bcm_to_h_msg_dequeue(struct bcm_vk *vk);
int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, u32 shut_type,
const pid_t pid, const u32 q_num);
void bcm_to_v_q_doorbell(struct bcm_vk *vk, u32 q_num, u32 db_val);
int bcm_vk_auto_load_all_images(struct bcm_vk *vk);
void bcm_vk_hb_init(struct bcm_vk *vk);
void bcm_vk_hb_deinit(struct bcm_vk *vk);
void bcm_vk_handle_notf(struct bcm_vk *vk);
bool bcm_vk_drv_access_ok(struct bcm_vk *vk);
void bcm_vk_set_host_alert(struct bcm_vk *vk, u32 bit_mask);
#ifdef CONFIG_BCM_VK_TTY
int bcm_vk_tty_init(struct bcm_vk *vk, char *name);
void bcm_vk_tty_exit(struct bcm_vk *vk);
void bcm_vk_tty_terminate_tty_user(struct bcm_vk *vk);
void bcm_vk_tty_wq_exit(struct bcm_vk *vk);
static inline void bcm_vk_tty_set_irq_enabled(struct bcm_vk *vk, int index)
{
vk->tty[index].irq_enabled = true;
}
#else
static inline int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
{
return 0;
}
static inline void bcm_vk_tty_exit(struct bcm_vk *vk)
{
}
static inline void bcm_vk_tty_terminate_tty_user(struct bcm_vk *vk)
{
}
static inline void bcm_vk_tty_wq_exit(struct bcm_vk *vk)
{
}
static inline void bcm_vk_tty_set_irq_enabled(struct bcm_vk *vk, int index)
{
}
#endif /* CONFIG_BCM_VK_TTY */
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,163 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2018-2020 Broadcom.
*/
#ifndef BCM_VK_MSG_H
#define BCM_VK_MSG_H
#include <uapi/linux/misc/bcm_vk.h>
#include "bcm_vk_sg.h"
/* Single message queue control structure */
struct bcm_vk_msgq {
u16 type; /* queue type */
u16 num; /* queue number */
u32 start; /* offset in BAR1 where the queue memory starts */
u32 rd_idx; /* read idx */
u32 wr_idx; /* write idx */
u32 size; /*
* size, which is in number of 16byte blocks,
* to align with the message data structure.
*/
u32 nxt; /*
* nxt offset to the next msg queue struct.
* This is to provide flexibity for alignment purposes.
*/
/* Least significant 16 bits in below field hold doorbell register offset */
#define DB_SHIFT 16
u32 db_offset; /* queue doorbell register offset in BAR0 */
u32 rsvd;
};
/*
* Structure to record static info from the msgq sync. We keep local copy
* for some of these variables for both performance + checking purpose.
*/
struct bcm_vk_sync_qinfo {
void __iomem *q_start;
u32 q_size;
u32 q_mask;
u32 q_low;
u32 q_db_offset;
};
#define VK_MSGQ_MAX_NR 4 /* Maximum number of message queues */
/*
* message block - basic unit in the message where a message's size is always
* N x sizeof(basic_block)
*/
struct vk_msg_blk {
u8 function_id;
#define VK_FID_TRANS_BUF 5
#define VK_FID_SHUTDOWN 8
#define VK_FID_INIT 9
u8 size; /* size of the message in number of vk_msg_blk's */
u16 trans_id; /* transport id, queue & msg_id */
u32 context_id;
#define VK_NEW_CTX 0
u32 cmd;
#define VK_CMD_PLANES_MASK 0x000f /* number of planes to up/download */
#define VK_CMD_UPLOAD 0x0400 /* memory transfer to vk */
#define VK_CMD_DOWNLOAD 0x0500 /* memory transfer from vk */
#define VK_CMD_MASK 0x0f00 /* command mask */
u32 arg;
};
/* vk_msg_blk is 16 bytes fixed */
#define VK_MSGQ_BLK_SIZE (sizeof(struct vk_msg_blk))
/* shift for fast division of basic msg blk size */
#define VK_MSGQ_BLK_SZ_SHIFT 4
/* use msg_id 0 for any simplex host2vk communication */
#define VK_SIMPLEX_MSG_ID 0
/* context per session opening of sysfs */
struct bcm_vk_ctx {
struct list_head node; /* use for linkage in Hash Table */
unsigned int idx;
bool in_use;
pid_t pid;
u32 hash_idx;
u32 q_num; /* queue number used by the stream */
struct miscdevice *miscdev;
atomic_t pend_cnt; /* number of items pending to be read from host */
atomic_t dma_cnt; /* any dma transaction outstanding */
wait_queue_head_t rd_wq;
};
/* pid hash table entry */
struct bcm_vk_ht_entry {
struct list_head head;
};
#define VK_DMA_MAX_ADDRS 4 /* Max 4 DMA Addresses */
/* structure for house keeping a single work entry */
struct bcm_vk_wkent {
struct list_head node; /* for linking purpose */
struct bcm_vk_ctx *ctx;
/* Store up to 4 dma pointers */
struct bcm_vk_dma dma[VK_DMA_MAX_ADDRS];
u32 to_h_blks; /* response */
struct vk_msg_blk *to_h_msg;
/*
* put the to_v_msg at the end so that we could simply append to_v msg
* to the end of the allocated block
*/
u32 usr_msg_id;
u32 to_v_blks;
u32 seq_num;
struct vk_msg_blk to_v_msg[] __counted_by(to_v_blks);
};
/* queue stats counters */
struct bcm_vk_qs_cnts {
u32 cnt; /* general counter, used to limit output */
u32 acc_sum;
u32 max_occ; /* max during a sampling period */
u32 max_abs; /* the abs max since reset */
};
/* control channel structure for either to_v or to_h communication */
struct bcm_vk_msg_chan {
u32 q_nr;
/* Mutex to access msgq */
struct mutex msgq_mutex;
/* pointing to BAR locations */
struct bcm_vk_msgq __iomem *msgq[VK_MSGQ_MAX_NR];
/* Spinlock to access pending queue */
spinlock_t pendq_lock;
/* for temporary storing pending items, one for each queue */
struct list_head pendq[VK_MSGQ_MAX_NR];
/* static queue info from the sync */
struct bcm_vk_sync_qinfo sync_qinfo[VK_MSGQ_MAX_NR];
};
/* totol number of message q allowed by the driver */
#define VK_MSGQ_PER_CHAN_MAX 3
#define VK_MSGQ_NUM_DEFAULT (VK_MSGQ_PER_CHAN_MAX - 1)
/* total number of supported ctx, 32 ctx each for 5 components */
#define VK_CMPT_CTX_MAX (32 * 5)
/* hash table defines to store the opened FDs */
#define VK_PID_HT_SHIFT_BIT 7 /* 128 */
#define VK_PID_HT_SZ BIT(VK_PID_HT_SHIFT_BIT)
/* The following are offsets of DDR info provided by the vk card */
#define VK_BAR0_SEG_SIZE (4 * SZ_1K) /* segment size for BAR0 */
/* shutdown types supported */
#define VK_SHUTDOWN_PID 1
#define VK_SHUTDOWN_GRACEFUL 2
#endif
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018-2020 Broadcom.
*/
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/pgtable.h>
#include <linux/vmalloc.h>
#include <asm/page.h>
#include <linux/unaligned.h>
#include <uapi/linux/misc/bcm_vk.h>
#include "bcm_vk.h"
#include "bcm_vk_msg.h"
#include "bcm_vk_sg.h"
/*
* Valkyrie has a hardware limitation of 16M transfer size.
* So limit the SGL chunks to 16M.
*/
#define BCM_VK_MAX_SGL_CHUNK SZ_16M
static int bcm_vk_dma_alloc(struct device *dev,
struct bcm_vk_dma *dma,
int dir,
struct _vk_data *vkdata);
static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
/* Uncomment to dump SGLIST */
/* #define BCM_VK_DUMP_SGLIST */
static int bcm_vk_dma_alloc(struct device *dev,
struct bcm_vk_dma *dma,
int direction,
struct _vk_data *vkdata)
{
dma_addr_t addr, sg_addr;
int err;
int i;
int offset;
u32 size;
u32 remaining_size;
u32 transfer_size;
u64 data;
unsigned long first, last;
struct _vk_data *sgdata;
/* Get 64-bit user address */
data = get_unaligned(&vkdata->address);
/* offset into first page */
offset = offset_in_page(data);
/* Calculate number of pages */
first = (data & PAGE_MASK) >> PAGE_SHIFT;
last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
dma->nr_pages = last - first + 1;
/* Allocate DMA pages */
dma->pages = kmalloc_objs(struct page *, dma->nr_pages);
if (!dma->pages)
return -ENOMEM;
dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
data, vkdata->size, dma->nr_pages);
dma->direction = direction;
/* Get user pages into memory */
err = get_user_pages_fast(data & PAGE_MASK,
dma->nr_pages,
direction == DMA_FROM_DEVICE,
dma->pages);
if (err != dma->nr_pages) {
dma->nr_pages = (err >= 0) ? err : 0;
dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
err, dma->nr_pages);
return err < 0 ? err : -EINVAL;
}
/* Max size of sg list is 1 per mapped page + fields at start */
dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
(sizeof(u32) * SGLIST_VKDATA_START);
/* Allocate sglist */
dma->sglist = dma_alloc_coherent(dev,
dma->sglen,
&dma->handle,
GFP_KERNEL);
if (!dma->sglist)
return -ENOMEM;
dma->sglist[SGLIST_NUM_SG] = 0;
dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
remaining_size = vkdata->size;
sgdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
/* Map all pages into DMA */
size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
remaining_size -= size;
sg_addr = dma_map_page(dev,
dma->pages[0],
offset,
size,
dma->direction);
transfer_size = size;
if (unlikely(dma_mapping_error(dev, sg_addr))) {
__free_page(dma->pages[0]);
return -EIO;
}
for (i = 1; i < dma->nr_pages; i++) {
size = min_t(size_t, PAGE_SIZE, remaining_size);
remaining_size -= size;
addr = dma_map_page(dev,
dma->pages[i],
0,
size,
dma->direction);
if (unlikely(dma_mapping_error(dev, addr))) {
__free_page(dma->pages[i]);
return -EIO;
}
/*
* Compress SG list entry when pages are contiguous
* and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
*/
if ((addr == (sg_addr + transfer_size)) &&
((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
/* pages are contiguous, add to same sg entry */
transfer_size += size;
} else {
/* pages are not contiguous, write sg entry */
sgdata->size = transfer_size;
put_unaligned(sg_addr, (u64 *)&sgdata->address);
dma->sglist[SGLIST_NUM_SG]++;
/* start new sg entry */
sgdata++;
sg_addr = addr;
transfer_size = size;
}
}
/* Write last sg list entry */
sgdata->size = transfer_size;
put_unaligned(sg_addr, (u64 *)&sgdata->address);
dma->sglist[SGLIST_NUM_SG]++;
/* Update pointers and size field to point to sglist */
put_unaligned((u64)dma->handle, &vkdata->address);
vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
(sizeof(u32) * SGLIST_VKDATA_START);
#ifdef BCM_VK_DUMP_SGLIST
dev_dbg(dev,
"sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
(u64)dma->sglist,
dma->handle,
dma->sglen,
vkdata->size);
for (i = 0; i < vkdata->size / sizeof(u32); i++)
dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
#endif
return 0;
}
int bcm_vk_sg_alloc(struct device *dev,
struct bcm_vk_dma *dma,
int dir,
struct _vk_data *vkdata,
int num)
{
int i;
int rc = -EINVAL;
/* Convert user addresses to DMA SG List */
for (i = 0; i < num; i++) {
if (vkdata[i].size && vkdata[i].address) {
/*
* If both size and address are non-zero
* then DMA alloc.
*/
rc = bcm_vk_dma_alloc(dev,
&dma[i],
dir,
&vkdata[i]);
} else if (vkdata[i].size ||
vkdata[i].address) {
/*
* If one of size and address are zero
* there is a problem.
*/
dev_err(dev,
"Invalid vkdata %x 0x%x 0x%llx\n",
i, vkdata[i].size, vkdata[i].address);
rc = -EINVAL;
} else {
/*
* If size and address are both zero
* don't convert, but return success.
*/
rc = 0;
}
if (rc)
goto fail_alloc;
}
return rc;
fail_alloc:
while (i > 0) {
i--;
if (dma[i].sglist)
bcm_vk_dma_free(dev, &dma[i]);
}
return rc;
}
static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
{
dma_addr_t addr;
int i;
int num_sg;
u32 size;
struct _vk_data *vkdata;
dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);
/* Unmap all pages in the sglist */
num_sg = dma->sglist[SGLIST_NUM_SG];
vkdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
for (i = 0; i < num_sg; i++) {
size = vkdata[i].size;
addr = get_unaligned(&vkdata[i].address);
dma_unmap_page(dev, addr, size, dma->direction);
}
/* Free allocated sglist */
dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
/* Release lock on all pages */
for (i = 0; i < dma->nr_pages; i++)
put_page(dma->pages[i]);
/* Free allocated dma pages */
kfree(dma->pages);
dma->sglist = NULL;
return 0;
}
int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num,
int *proc_cnt)
{
int i;
*proc_cnt = 0;
/* Unmap and free all pages and sglists */
for (i = 0; i < num; i++) {
if (dma[i].sglist) {
bcm_vk_dma_free(dev, &dma[i]);
*proc_cnt += 1;
}
}
return 0;
}
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2018-2020 Broadcom.
*/
#ifndef BCM_VK_SG_H
#define BCM_VK_SG_H
#include <linux/dma-mapping.h>
struct bcm_vk_dma {
/* for userland buffer */
struct page **pages;
int nr_pages;
/* common */
dma_addr_t handle;
/*
* sglist is of the following LE format
* [U32] num_sg = number of sg addresses (N)
* [U32] totalsize = totalsize of data being transferred in sglist
* [U32] size[0] = size of data in address0
* [U32] addr_l[0] = lower 32-bits of address0
* [U32] addr_h[0] = higher 32-bits of address0
* ..
* [U32] size[N-1] = size of data in addressN-1
* [U32] addr_l[N-1] = lower 32-bits of addressN-1
* [U32] addr_h[N-1] = higher 32-bits of addressN-1
*/
u32 *sglist;
#define SGLIST_NUM_SG 0
#define SGLIST_TOTALSIZE 1
#define SGLIST_VKDATA_START 2
int sglen; /* Length (bytes) of sglist */
int direction;
};
struct _vk_data {
u32 size; /* data size in bytes */
u64 address; /* Pointer to data */
} __packed;
/*
* Scatter-gather DMA buffer API.
*
* These functions provide a simple way to create a page list and a
* scatter-gather list from userspace address and map the memory
* for DMA operation.
*/
int bcm_vk_sg_alloc(struct device *dev,
struct bcm_vk_dma *dma,
int dir,
struct _vk_data *vkdata,
int num);
int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num,
int *proc_cnt);
#endif
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018-2020 Broadcom.
*/
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include "bcm_vk.h"
/* TTYVK base offset is 0x30000 into BAR1 */
#define BAR1_TTYVK_BASE_OFFSET 0x300000
/* Each TTYVK channel (TO or FROM) is 0x10000 */
#define BAR1_TTYVK_CHAN_OFFSET 0x100000
/* Each TTYVK channel has TO and FROM, hence the * 2 */
#define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
((index) * BAR1_TTYVK_CHAN_OFFSET * 2))
/* TO TTYVK channel base comes before FROM for each index */
#define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
#define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
BAR1_TTYVK_CHAN_OFFSET)
struct bcm_vk_tty_chan {
u32 reserved;
u32 size;
u32 wr;
u32 rd;
u32 *data;
};
#define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
+ offsetof(struct bcm_vk_tty_chan, e))
#define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
#define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
#define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
#define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + (off))
#define VK_BAR0_REGSEG_TTY_DB_OFFSET 0x86c
/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
#define SERIAL_TIMER_VALUE (HZ / 10)
static void bcm_vk_tty_poll(struct timer_list *t)
{
struct bcm_vk *vk = timer_container_of(vk, t, serial_timer);
queue_work(vk->tty_wq_thread, &vk->tty_wq_work);
mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
}
irqreturn_t bcm_vk_tty_irqhandler(int irq, void *dev_id)
{
struct bcm_vk *vk = dev_id;
queue_work(vk->tty_wq_thread, &vk->tty_wq_work);
return IRQ_HANDLED;
}
static void bcm_vk_tty_wq_handler(struct work_struct *work)
{
struct bcm_vk *vk = container_of(work, struct bcm_vk, tty_wq_work);
struct bcm_vk_tty *vktty;
int card_status;
int count;
int i;
int wr;
u8 c;
card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
if (BCM_VK_INTF_IS_DOWN(card_status))
return;
for (i = 0; i < BCM_VK_NUM_TTY; i++) {
count = 0;
/* Check the card status that the tty channel is ready */
if ((card_status & BIT(i)) == 0)
continue;
vktty = &vk->tty[i];
/* Don't increment read index if tty app is closed */
if (!vktty->is_opened)
continue;
/* Fetch the wr offset in buffer from VK */
wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from));
/* safe to ignore until bar read gives proper size */
if (vktty->from_size == 0)
continue;
if (wr >= vktty->from_size) {
dev_err(&vk->pdev->dev,
"ERROR: wq handler ttyVK%d wr:0x%x > 0x%x\n",
i, wr, vktty->from_size);
/* Need to signal and close device in this case */
continue;
}
/*
* Simple read of circular buffer and
* insert into tty flip buffer
*/
while (vk->tty[i].rd != wr) {
c = vkread8(vk, BAR_1,
VK_BAR_CHAN_DATA(vktty, from, vktty->rd));
vktty->rd++;
if (vktty->rd >= vktty->from_size)
vktty->rd = 0;
tty_insert_flip_char(&vktty->port, c, TTY_NORMAL);
count++;
}
if (count) {
tty_flip_buffer_push(&vktty->port);
/* Update read offset from shadow register to card */
vkwrite32(vk, vktty->rd, BAR_1,
VK_BAR_CHAN_RD(vktty, from));
}
}
}
static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file)
{
int card_status;
struct bcm_vk *vk;
struct bcm_vk_tty *vktty;
int index;
/* initialize the pointer in case something fails */
tty->driver_data = NULL;
vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
index = tty->index;
if (index >= BCM_VK_NUM_TTY)
return -EINVAL;
vktty = &vk->tty[index];
vktty->pid = task_pid_nr(current);
vktty->to_offset = TO_TTYK_BASE(index);
vktty->from_offset = FROM_TTYK_BASE(index);
/* Do not allow tty device to be opened if tty on card not ready */
card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
if (BCM_VK_INTF_IS_DOWN(card_status) || ((card_status & BIT(index)) == 0))
return -EBUSY;
/*
* Get shadow registers of the buffer sizes and the "to" write offset
* and "from" read offset
*/
vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to));
vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to));
vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from));
vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from));
vktty->is_opened = true;
if (tty->count == 1 && !vktty->irq_enabled) {
timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0);
mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
}
return 0;
}
static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file)
{
struct bcm_vk *vk = dev_get_drvdata(tty->dev);
if (tty->index >= BCM_VK_NUM_TTY)
return;
vk->tty[tty->index].is_opened = false;
if (tty->count == 1)
timer_delete_sync(&vk->serial_timer);
}
static void bcm_vk_tty_doorbell(struct bcm_vk *vk, u32 db_val)
{
vkwrite32(vk, db_val, BAR_0,
VK_BAR0_REGSEG_DB_BASE + VK_BAR0_REGSEG_TTY_DB_OFFSET);
}
static ssize_t bcm_vk_tty_write(struct tty_struct *tty, const u8 *buffer,
size_t count)
{
int index;
struct bcm_vk *vk;
struct bcm_vk_tty *vktty;
size_t i;
index = tty->index;
vk = dev_get_drvdata(tty->dev);
vktty = &vk->tty[index];
/* Simple write each byte to circular buffer */
for (i = 0; i < count; i++) {
vkwrite8(vk, buffer[i], BAR_1,
VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
vktty->wr++;
if (vktty->wr >= vktty->to_size)
vktty->wr = 0;
}
/* Update write offset from shadow register to card */
vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to));
bcm_vk_tty_doorbell(vk, 0);
return count;
}
static unsigned int bcm_vk_tty_write_room(struct tty_struct *tty)
{
struct bcm_vk *vk = dev_get_drvdata(tty->dev);
return vk->tty[tty->index].to_size - 1;
}
static const struct tty_operations serial_ops = {
.open = bcm_vk_tty_open,
.close = bcm_vk_tty_close,
.write = bcm_vk_tty_write,
.write_room = bcm_vk_tty_write_room,
};
int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
{
int i;
int err;
struct tty_driver *tty_drv;
struct device *dev = &vk->pdev->dev;
tty_drv = tty_alloc_driver
(BCM_VK_NUM_TTY,
TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(tty_drv))
return PTR_ERR(tty_drv);
/* Save struct tty_driver for uninstalling the device */
vk->tty_drv = tty_drv;
/* initialize the tty driver */
tty_drv->driver_name = KBUILD_MODNAME;
tty_drv->name = kstrdup(name, GFP_KERNEL);
if (!tty_drv->name) {
err = -ENOMEM;
goto err_tty_driver_kref_put;
}
tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
tty_drv->subtype = SERIAL_TYPE_NORMAL;
tty_drv->init_termios = tty_std_termios;
tty_set_operations(tty_drv, &serial_ops);
/* register the tty driver */
err = tty_register_driver(tty_drv);
if (err) {
dev_err(dev, "tty_register_driver failed\n");
goto err_kfree_tty_name;
}
for (i = 0; i < BCM_VK_NUM_TTY; i++) {
struct device *tty_dev;
tty_port_init(&vk->tty[i].port);
tty_dev = tty_port_register_device_attr(&vk->tty[i].port,
tty_drv, i, dev, vk,
NULL);
if (IS_ERR(tty_dev)) {
err = PTR_ERR(tty_dev);
goto unwind;
}
vk->tty[i].is_opened = false;
}
INIT_WORK(&vk->tty_wq_work, bcm_vk_tty_wq_handler);
vk->tty_wq_thread = create_singlethread_workqueue("tty");
if (!vk->tty_wq_thread) {
dev_err(dev, "Fail to create tty workqueue thread\n");
err = -ENOMEM;
goto unwind;
}
return 0;
unwind:
while (--i >= 0)
tty_port_unregister_device(&vk->tty[i].port, tty_drv, i);
tty_unregister_driver(tty_drv);
err_kfree_tty_name:
kfree(tty_drv->name);
tty_drv->name = NULL;
err_tty_driver_kref_put:
tty_driver_kref_put(tty_drv);
return err;
}
void bcm_vk_tty_exit(struct bcm_vk *vk)
{
int i;
timer_delete_sync(&vk->serial_timer);
for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
tty_port_unregister_device(&vk->tty[i].port,
vk->tty_drv,
i);
tty_port_destroy(&vk->tty[i].port);
}
tty_unregister_driver(vk->tty_drv);
kfree(vk->tty_drv->name);
vk->tty_drv->name = NULL;
tty_driver_kref_put(vk->tty_drv);
}
void bcm_vk_tty_terminate_tty_user(struct bcm_vk *vk)
{
struct bcm_vk_tty *vktty;
int i;
for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
vktty = &vk->tty[i];
if (vktty->pid)
kill_pid(find_vpid(vktty->pid), SIGKILL, 1);
}
}
void bcm_vk_tty_wq_exit(struct bcm_vk *vk)
{
cancel_work_sync(&vk->tty_wq_work);
destroy_workqueue(vk->tty_wq_thread);
}