cregit-Linux how code gets into the kernel

Release 4.16 samples/vfio-mdev/mtty.c

/*
 * Mediated virtual PCI serial host device driver
 *
 * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
 *     Author: Neo Jia <cjia@nvidia.com>
 *             Kirti Wankhede <kwankhede@nvidia.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Sample driver that creates mdev device that simulates serial port over PCI
 * card.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uuid.h>
#include <linux/vfio.h>
#include <linux/iommu.h>
#include <linux/sysfs.h>
#include <linux/ctype.h>
#include <linux/file.h>
#include <linux/mdev.h>
#include <linux/pci.h>
#include <linux/serial.h>
#include <uapi/linux/serial_reg.h>
#include <linux/eventfd.h>
/*
 * #defines
 */


#define VERSION_STRING  "0.1"

#define DRIVER_AUTHOR   "NVIDIA Corporation"


#define MTTY_CLASS_NAME "mtty"


#define MTTY_NAME       "mtty"


#define MTTY_STRING_LEN		16


#define MTTY_CONFIG_SPACE_SIZE  0xff

#define MTTY_IO_BAR_SIZE        0x8

#define MTTY_MMIO_BAR_SIZE      0x100000


#define STORE_LE16(addr, val)   (*(u16 *)addr = val)

#define STORE_LE32(addr, val)   (*(u32 *)addr = val)


#define MAX_FIFO_SIZE   16


#define CIRCULAR_BUF_INC_IDX(idx)    (idx = (idx + 1) & (MAX_FIFO_SIZE - 1))


#define MTTY_VFIO_PCI_OFFSET_SHIFT   40


#define MTTY_VFIO_PCI_OFFSET_TO_INDEX(off)   (off >> MTTY_VFIO_PCI_OFFSET_SHIFT)

#define MTTY_VFIO_PCI_INDEX_TO_OFFSET(index) \
				((u64)(index) << MTTY_VFIO_PCI_OFFSET_SHIFT)

#define MTTY_VFIO_PCI_OFFSET_MASK    \
				(((u64)(1) << MTTY_VFIO_PCI_OFFSET_SHIFT) - 1)

#define MAX_MTTYS	24

/*
 * Global Structures
 */


struct mtty_dev {
	
dev_t		vd_devt;
	
struct class	*vd_class;
	
struct cdev	vd_cdev;
	
struct idr	vd_idr;
	
struct device	dev;

} mtty_dev;


struct mdev_region_info {
	
u64 start;
	
u64 phys_start;
	
u32 size;
	
u64 vfio_offset;
};

#if defined(DEBUG_REGS)

const char *wr_reg[] = {
	"TX",
	"IER",
	"FCR",
	"LCR",
	"MCR",
	"LSR",
	"MSR",
	"SCR"
};


const char *rd_reg[] = {
	"RX",
	"IER",
	"IIR",
	"LCR",
	"MCR",
	"LSR",
	"MSR",
	"SCR"
};
#endif

/* loop back buffer */

struct rxtx {
	
u8 fifo[MAX_FIFO_SIZE];
	

u8 head, tail;
	
u8 count;
};


struct serial_port {
	
u8 uart_reg[8];         /* 8 registers */
	
struct rxtx rxtx;       /* loop back buffer */
	
bool dlab;
	
bool overrun;
	
u16 divisor;
	
u8 fcr;                 /* FIFO control register */
	
u8 max_fifo_size;
	
u8 intr_trigger_level;  /* interrupt trigger level */
};

/* State of each mdev device */

struct mdev_state {
	
int irq_fd;
	
struct eventfd_ctx *intx_evtfd;
	
struct eventfd_ctx *msi_evtfd;
	
int irq_index;
	
u8 *vconfig;
	
struct mutex ops_lock;
	
struct mdev_device *mdev;
	
struct mdev_region_info region_info[VFIO_PCI_NUM_REGIONS];
	
u32 bar_mask[VFIO_PCI_NUM_REGIONS];
	
struct list_head next;
	
struct serial_port s[2];
	
struct mutex rxtx_lock;
	
struct vfio_device_info dev_info;
	
int nr_ports;
};


struct mutex mdev_list_lock;

struct list_head mdev_devices_list;


static const struct file_operations vd_fops = {
	.owner          = THIS_MODULE,
};

/* function prototypes */

static int mtty_trigger_interrupt(uuid_le uuid);

/* Helper functions */

static struct mdev_state *find_mdev_state_by_uuid(uuid_le uuid) { struct mdev_state *mds; list_for_each_entry(mds, &mdev_devices_list, next) { if (uuid_le_cmp(mdev_uuid(mds->mdev), uuid) == 0) return mds; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Kirti Wankhede4593.75%150.00%
Alex Williamson36.25%150.00%
Total48100.00%2100.00%


void dump_buffer(char *buf, uint32_t count) { #if defined(DEBUG) int i; pr_info("Buffer:\n"); for (i = 0; i < count; i++) { pr_info("%2x ", *(buf + i)); if ((i + 1) % 16 == 0) pr_info("\n"); } #endif }

Contributors

PersonTokensPropCommitsCommitProp
Kirti Wankhede72100.00%1100.00%
Total72100.00%1100.00%


static void mtty_create_config_space(struct mdev_state *mdev_state) { /* PCI dev ID */ STORE_LE32((u32 *) &mdev_state->vconfig[0x0], 0x32534348); /* Control: I/O+, Mem-, BusMaster- */ STORE_LE16((u16 *) &mdev_state->vconfig[0x4], 0x0001); /* Status: capabilities list absent */ STORE_LE16((u16 *) &mdev_state->vconfig[0x6], 0x0200); /* Rev ID */ mdev_state->vconfig[0x8] = 0x10; /* programming interface class : 16550-compatible serial controller */ mdev_state->vconfig[0x9] = 0x02; /* Sub class : 00 */ mdev_state->vconfig[0xa] = 0x00; /* Base class : Simple Communication controllers */ mdev_state->vconfig[0xb] = 0x07; /* base address registers */ /* BAR0: IO space */ STORE_LE32((u32 *) &mdev_state->vconfig[0x10], 0x000001); mdev_state->bar_mask[0] = ~(MTTY_IO_BAR_SIZE) + 1; if (mdev_state->nr_ports == 2) { /* BAR1: IO space */ STORE_LE32((u32 *) &mdev_state->vconfig[0x14], 0x000001); mdev_state->bar_mask[1] = ~(MTTY_IO_BAR_SIZE) + 1; } /* Subsystem ID */ STORE_LE32((u32 *) &mdev_state->vconfig[0x2c], 0x32534348); mdev_state->vconfig[0x34] = 0x00; /* Cap Ptr */ mdev_state->vconfig[0x3d] = 0x01; /* interrupt pin (INTA#) */ /* Vendor specific data */ mdev_state->vconfig[0x40] = 0x23; mdev_state->vconfig[0x43] = 0x80; mdev_state->vconfig[0x44] = 0x23; mdev_state->vconfig[0x48] = 0x23; mdev_state->vconfig[0x4c] = 0x23; mdev_state->vconfig[0x60] = 0x50; mdev_state->vconfig[0x61] = 0x43; mdev_state->vconfig[0x62] = 0x49; mdev_state->vconfig[0x63] = 0x20; mdev_state->vconfig[0x64] = 0x53; mdev_state->vconfig[0x65] = 0x65; mdev_state->vconfig[0x66] = 0x72; mdev_state->vconfig[0x67] = 0x69; mdev_state->vconfig[0x68] = 0x61; mdev_state->vconfig[0x69] = 0x6c; mdev_state->vconfig[0x6a] = 0x2f; mdev_state->vconfig[0x6b] = 0x55; mdev_state->vconfig[0x6c] = 0x41; mdev_state->vconfig[0x6d] = 0x52; mdev_state->vconfig[0x6e] = 0x54; }

Contributors

PersonTokensPropCommitsCommitProp
Kirti Wankhede399100.00%1100.00%
Total399100.00%1100.00%


static void handle_pci_cfg_write(struct mdev_state *mdev_state, u16 offset, char *buf, u32 count) { u32 cfg_addr, bar_mask, bar_index = 0; switch (offset) { case 0x04: /* device control */ case 0x06: /* device status */ /* do nothing */ break; case 0x3c: /* interrupt line */ mdev_state->vconfig[0x3c] = buf[0]; break; case 0x3d: /* * Interrupt Pin is hardwired to INTA. * This field is write protected by hardware */ break; case 0x10: /* BAR0 */ case 0x14: /* BAR1 */ if (offset == 0x10) bar_index = 0; else if (offset == 0x14) bar_index = 1; if ((mdev_state->nr_ports == 1) && (bar_index == 1)) { STORE_LE32(&mdev_state->vconfig[offset], 0); break; } cfg_addr = *(u32 *)buf; pr_info("BAR%d addr 0x%x\n", bar_index, cfg_addr); if (cfg_addr == 0xffffffff) { bar_mask = mdev_state->bar_mask[bar_index]; cfg_addr = (cfg_addr & bar_mask); } cfg_addr |= (mdev_state->vconfig[offset] & 0x3ul); STORE_LE32(&mdev_state->vconfig[offset], cfg_addr); break; case 0x18: /* BAR2 */ case 0x1c: /* BAR3 */ case 0x20: /* BAR4 */ STORE_LE32(&mdev_state->vconfig[offset], 0); break; default: pr_info("PCI config write @0x%x of %d bytes not handled\n", offset, count); break; } }

Contributors

PersonTokensPropCommitsCommitProp
Kirti Wankhede236100.00%1100.00%
Total236100.00%1100.00%


static void handle_bar_write(unsigned int index, struct mdev_state *mdev_state, u16 offset, char *buf, u32 count) { u8 data = *buf; /* Handle data written by guest */ switch (offset) { case UART_TX: /* if DLAB set, data is LSB of divisor */ if (mdev_state->s[index].dlab) { mdev_state->s[index].divisor |= data; break; } mutex_lock(&mdev_state->rxtx_lock); /* save in TX buffer */ if (mdev_state->s[index].rxtx.count < mdev_state->s[index].max_fifo_size) { mdev_state->s[index].rxtx.fifo[ mdev_state->s[index].rxtx.head] = data; mdev_state->s[index].rxtx.count++; CIRCULAR_BUF_INC_IDX(mdev_state->s[index].rxtx.head); mdev_state->s[index].overrun = false; /* * Trigger interrupt if receive data interrupt is * enabled and fifo reached trigger level */ if ((mdev_state->s[index].uart_reg[UART_IER] & UART_IER_RDI) && (mdev_state->s[index].rxtx.count == mdev_state->s[index].intr_trigger_level)) { /* trigger interrupt */ #if defined(DEBUG_INTR) pr_err("Serial port %d: Fifo level trigger\n", index); #endif mtty_trigger_interrupt( mdev_uuid(mdev_state->mdev)); } } else { #if defined(DEBUG_INTR) pr_err("Serial port %d: Buffer Overflow\n", index); #endif mdev_state->s[index].overrun = true; /* * Trigger interrupt if receiver line status interrupt * is enabled */ if (mdev_state->s[index].uart_reg[UART_IER] & UART_IER_RLSI) mtty_trigger_interrupt( mdev_uuid(mdev_state->mdev)); } mutex_unlock(&mdev_state->rxtx_lock); break; case UART_IER: /* if DLAB set, data is MSB of divisor */ if (mdev_state->s[index].dlab) mdev_state->s[index].divisor |= (u16)data << 8; else { mdev_state->s[index].uart_reg[offset] = data; mutex_lock(&mdev_state->rxtx_lock); if ((data & UART_IER_THRI) && (mdev_state->s[index].rxtx.head == mdev_state->s[index].rxtx.tail)) { #if defined(DEBUG_INTR) pr_err("Serial port %d: IER_THRI write\n", index); #endif mtty_trigger_interrupt( mdev_uuid(mdev_state->mdev)); } mutex_unlock(&mdev_state->rxtx_lock); } break; case UART_FCR: mdev_state->s[index].fcr = data; mutex_lock(&mdev_state->rxtx_lock); if (data & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)) { /* clear loop back FIFO */ mdev_state->s[index].rxtx.count = 0; mdev_state->s[index].rxtx.head = 0; mdev_state->s[index].rxtx.tail = 0; } mutex_unlock(&mdev_state->rxtx_lock); switch (data & UART_FCR_TRIGGER_MASK) { case UART_FCR_TRIGGER_1: mdev_state->s[index].intr_trigger_level = 1; break; case UART_FCR_TRIGGER_4: mdev_state->s[index].intr_trigger_level = 4; break; case UART_FCR_TRIGGER_8: mdev_state->s[index].intr_trigger_level = 8; break; case UART_FCR_TRIGGER_14: mdev_state->s[index].intr_trigger_level = 14; break; } /* * Set trigger level to 1 otherwise or implement timer with * timeout of 4 characters and on expiring that timer set * Recevice data timeout in IIR register */ mdev_state->s[index].intr_trigger_level = 1; if (data & UART_FCR_ENABLE_FIFO) mdev_state->s[index].max_fifo_size = MAX_FIFO_SIZE; else { mdev_state->s[index].max_fifo_size = 1; mdev_state->s[index].intr_trigger_level = 1; } break; case UART_LCR: if (data & UART_LCR_DLAB) { mdev_state->s[index].dlab = true; mdev_state->s[index].divisor = 0; } else mdev_state->s[index].dlab = false; mdev_state->s[index].uart_reg[offset] = data; break; case UART_MCR: mdev_state->s[index].uart_reg[offset] = data; if ((mdev_state->s[index].uart_reg[UART_IER] & UART_IER_MSI) && (data & UART_MCR_OUT2)) { #if defined(DEBUG_INTR) pr_err("Serial port %d: MCR_OUT2 write\n", index); #endif mtty_trigger_interrupt(mdev_uuid(mdev_state->mdev)); } if ((mdev_state->s[index].uart_reg[UART_IER] & UART_IER_MSI) && (data & (UART_MCR_RTS | UART_MCR_DTR))) { #if defined(DEBUG_INTR) pr_err("Serial port %d: MCR RTS/DTR write\n", index); #endif mtty_trigger_interrupt(mdev_uuid(mdev_state->mdev)); } break; case UART_LSR: case UART_MSR: /* do nothing */ break; case UART_SCR: mdev_state->s[index].uart_reg[offset] = data; break; default: break; } }

Contributors

PersonTokensPropCommitsCommitProp
Kirti Wankhede82298.21%150.00%
Alex Williamson151.79%150.00%
Total837100.00%2100.00%


static void handle_bar_read(unsigned int index, struct mdev_state *mdev_state, u16 offset, char *buf, u32 count) { /* Handle read requests by guest */ switch (offset) { case UART_RX: /* if DLAB set, data is LSB of divisor */ if (mdev_state->s[index].dlab) { *buf = (u8)mdev_state->s[index].divisor; break; } mutex_lock(&mdev_state->rxtx_lock); /* return data in tx buffer */ if (mdev_state->s[index].rxtx.head != mdev_state->s[index].rxtx.tail) { *buf = mdev_state->s[index].rxtx.fifo[ mdev_state->s[index].rxtx.tail]; mdev_state->s[index].rxtx.count--; CIRCULAR_BUF_INC_IDX(mdev_state->s[index].rxtx.tail); } if (mdev_state->s[index].rxtx.head == mdev_state->s[index].rxtx.tail) { /* * Trigger interrupt if tx buffer empty interrupt is * enabled and fifo is empty */ #if defined(DEBUG_INTR) pr_err("Serial port %d: Buffer Empty\n", index); #endif if (mdev_state->s[index].uart_reg[UART_IER] & UART_IER_THRI) mtty_trigger_interrupt( mdev_uuid(mdev_state->mdev)); } mutex_unlock(&mdev_state->rxtx_lock); break; case UART_IER: if (mdev_state->s[index].dlab) { *buf = (u8)(mdev_state->s[index].divisor >> 8); break; } *buf = mdev_state->s[index].uart_reg[offset] & 0x0f; break; case UART_IIR: { u8 ier = mdev_state->s[index].uart_reg[UART_IER]; *buf = 0; mutex_lock(&mdev_state->rxtx_lock); /* Interrupt priority 1: Parity, overrun, framing or break */ if ((ier & UART_IER_RLSI) && mdev_state->s[index].overrun) *buf |= UART_IIR_RLSI; /* Interrupt priority 2: Fifo trigger level reached */ if ((ier & UART_IER_RDI) && (mdev_state->s[index].rxtx.count == mdev_state->s[index].intr_trigger_level)) *buf |= UART_IIR_RDI; /* Interrupt priotiry 3: transmitter holding register empty */ if ((ier & UART_IER_THRI) && (mdev_state->s[index].rxtx.head == mdev_state->s[index].rxtx.tail)) *buf |= UART_IIR_THRI; /* Interrupt priotiry 4: Modem status: CTS, DSR, RI or DCD */ if ((ier & UART_IER_MSI) && (mdev_state->s[index].uart_reg[UART_MCR] & (UART_MCR_RTS | UART_MCR_DTR))) *buf |= UART_IIR_MSI; /* bit0: 0=> interrupt pending, 1=> no interrupt is pending */ if (*buf == 0) *buf = UART_IIR_NO_INT; /* set bit 6 & 7 to be 16550 compatible */ *buf |= 0xC0; mutex_unlock(&mdev_state->rxtx_lock); } break; case UART_LCR: case UART_MCR: *buf = mdev_state->s[index].uart_reg[offset]; break; case UART_LSR: { u8 lsr = 0; mutex_lock(&mdev_state->rxtx_lock); /* atleast one char in FIFO */ if (mdev_state->s[index].rxtx.head != mdev_state->s[index].rxtx.tail) lsr |= UART_LSR_DR; /* if FIFO overrun */ if (mdev_state->s[index].overrun) lsr |= UART_LSR_OE; /* transmit FIFO empty and tramsitter empty */ if (mdev_state->s[index].rxtx.head == mdev_state->s[index].rxtx.tail) lsr |= UART_LSR_TEMT | UART_LSR_THRE; mutex_unlock(&mdev_state->rxtx_lock); *buf = lsr; break; } case UART_MSR: *buf = UART_MSR_DSR | UART_MSR_DDSR | UART_MSR_DCD; mutex_lock(&mdev_state->rxtx_lock);