mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2026-06-08 10:04:42 +00:00
dm: Reorganize ACRN DM directory.
The current dm, all non-pci and non-acpi related files are put into hw/platform directory. This is actually disturbed the meaning of *platform*. The platform devices are mean of board and SoC specific non-PCI devices, like usb devices, etc. This patch refines the ACRN dm directory architecture. For some common device logic files, likes block_if.c/uart_core.c or usb_core.c. They will move to hw/ directly. For platform architecture depended files, create arch/ under root dir. And create sub-dir arch/x86 for x86 architecture, will create more architectures in future. The pm.c will move to this new dir. The hw/acpi will be moved to hw/platform/acpi due to acpi also be considered as part of platform. Signed-off-by: Yu Wang <yu1.wang@intel.com>
This commit is contained in:
1031
devicemodel/hw/platform/acpi/acpi.c
Normal file
1031
devicemodel/hw/platform/acpi/acpi.c
Normal file
File diff suppressed because it is too large
Load Diff
387
devicemodel/hw/platform/acpi/acpi_pm.c
Normal file
387
devicemodel/hw/platform/acpi/acpi_pm.c
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Intel Corporation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "vmm.h"
|
||||
#include "vmmapi.h"
|
||||
#include "dm.h"
|
||||
#include "acpi.h"
|
||||
|
||||
static inline int get_vcpu_pm_info(struct vmctx *ctx, int vcpu_id,
|
||||
uint64_t pm_type, uint64_t *pm_info)
|
||||
{
|
||||
*pm_info = ((ctx->vmid << PMCMD_VMID_SHIFT) & PMCMD_VMID_MASK)
|
||||
| ((vcpu_id << PMCMD_VCPUID_SHIFT) & PMCMD_VCPUID_MASK)
|
||||
| (pm_type & PMCMD_TYPE_MASK);
|
||||
|
||||
return vm_get_cpu_state(ctx, pm_info);
|
||||
}
|
||||
|
||||
static inline uint8_t get_vcpu_px_cnt(struct vmctx *ctx, int vcpu_id)
|
||||
{
|
||||
uint64_t px_cnt;
|
||||
|
||||
if (get_vcpu_pm_info(ctx, vcpu_id, PMCMD_GET_PX_CNT, &px_cnt)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (uint8_t)px_cnt;
|
||||
}
|
||||
|
||||
uint8_t get_vcpu_cx_cnt(struct vmctx *ctx, int vcpu_id)
|
||||
{
|
||||
uint64_t cx_cnt;
|
||||
|
||||
if (get_vcpu_pm_info(ctx, vcpu_id, PMCMD_GET_CX_CNT, &cx_cnt)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (uint8_t)cx_cnt;
|
||||
}
|
||||
|
||||
static int get_vcpu_px_data(struct vmctx *ctx, int vcpu_id,
|
||||
int px_num, struct cpu_px_data *vcpu_px_data)
|
||||
{
|
||||
uint64_t *pm_ioctl_buf;
|
||||
enum pm_cmd_type cmd_type = PMCMD_GET_PX_DATA;
|
||||
|
||||
pm_ioctl_buf = malloc(sizeof(struct cpu_px_data));
|
||||
if (!pm_ioctl_buf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*pm_ioctl_buf = ((ctx->vmid << PMCMD_VMID_SHIFT) & PMCMD_VMID_MASK)
|
||||
| ((vcpu_id << PMCMD_VCPUID_SHIFT) & PMCMD_VCPUID_MASK)
|
||||
| ((px_num << PMCMD_STATE_NUM_SHIFT) & PMCMD_STATE_NUM_MASK)
|
||||
| cmd_type;
|
||||
|
||||
/* get and validate px data */
|
||||
if (vm_get_cpu_state(ctx, pm_ioctl_buf)) {
|
||||
free(pm_ioctl_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(vcpu_px_data, pm_ioctl_buf,
|
||||
sizeof(struct cpu_px_data));
|
||||
|
||||
free(pm_ioctl_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_vcpu_cx_data(struct vmctx *ctx, int vcpu_id,
|
||||
int cx_num, struct cpu_cx_data *vcpu_cx_data)
|
||||
{
|
||||
uint64_t *pm_ioctl_buf;
|
||||
enum pm_cmd_type cmd_type = PMCMD_GET_CX_DATA;
|
||||
|
||||
pm_ioctl_buf = malloc(sizeof(struct cpu_cx_data));
|
||||
if (!pm_ioctl_buf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*pm_ioctl_buf = ((ctx->vmid << PMCMD_VMID_SHIFT) & PMCMD_VMID_MASK)
|
||||
| ((vcpu_id << PMCMD_VCPUID_SHIFT) & PMCMD_VCPUID_MASK)
|
||||
| ((cx_num << PMCMD_STATE_NUM_SHIFT) & PMCMD_STATE_NUM_MASK)
|
||||
| cmd_type;
|
||||
|
||||
/* get and validate cx data */
|
||||
if (vm_get_cpu_state(ctx, pm_ioctl_buf)) {
|
||||
free(pm_ioctl_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(vcpu_cx_data, pm_ioctl_buf,
|
||||
sizeof(struct cpu_cx_data));
|
||||
|
||||
free(pm_ioctl_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *_asi_table[7] = { "SystemMemory",
|
||||
"SystemIO",
|
||||
"PCI_Config",
|
||||
"EmbeddedControl",
|
||||
"SMBus",
|
||||
"PCC",
|
||||
"FFixedHW"};
|
||||
|
||||
static char *get_asi_string(uint8_t space_id)
|
||||
{
|
||||
switch (space_id) {
|
||||
case SPACE_SYSTEM_MEMORY:
|
||||
return _asi_table[0];
|
||||
|
||||
case SPACE_SYSTEM_IO:
|
||||
return _asi_table[1];
|
||||
|
||||
case SPACE_PCI_CONFIG:
|
||||
return _asi_table[2];
|
||||
|
||||
case SPACE_Embedded_Control:
|
||||
return _asi_table[3];
|
||||
|
||||
case SPACE_SMBUS:
|
||||
return _asi_table[4];
|
||||
|
||||
case SPACE_PLATFORM_COMM:
|
||||
return _asi_table[5];
|
||||
|
||||
case SPACE_FFixedHW:
|
||||
return _asi_table[6];
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* _CST: C-States
|
||||
*/
|
||||
void dsdt_write_cst(struct vmctx *ctx, int vcpu_id)
|
||||
{
|
||||
int i;
|
||||
uint8_t vcpu_cx_cnt;
|
||||
char *cx_asi;
|
||||
struct acpi_generic_address cx_reg;
|
||||
struct cpu_cx_data *vcpu_cx_data;
|
||||
|
||||
vcpu_cx_cnt = get_vcpu_cx_cnt(ctx, vcpu_id);
|
||||
if (!vcpu_cx_cnt) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* vcpu_cx_data start from C1, cx_cnt is total Cx entry num. */
|
||||
vcpu_cx_data = malloc(vcpu_cx_cnt * sizeof(struct cpu_cx_data));
|
||||
if (!vcpu_cx_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy and validate cx data first */
|
||||
for (i = 1; i <= vcpu_cx_cnt; i++) {
|
||||
if (get_vcpu_cx_data(ctx, vcpu_id, i, vcpu_cx_data + i - 1)) {
|
||||
/* something must be wrong, so skip the write. */
|
||||
free(vcpu_cx_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line(" Method (_CST, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (Package (0x%02X)", vcpu_cx_cnt + 1);
|
||||
dsdt_line(" {");
|
||||
|
||||
dsdt_line(" 0x%02X,", vcpu_cx_cnt);
|
||||
|
||||
for (i = 0; i < vcpu_cx_cnt; i++) {
|
||||
|
||||
dsdt_line(" Package (0x04)");
|
||||
dsdt_line(" {");
|
||||
|
||||
cx_reg = (vcpu_cx_data + i)->cx_reg;
|
||||
cx_asi = get_asi_string(cx_reg.space_id);
|
||||
|
||||
dsdt_line(" ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Register (%s,", cx_asi);
|
||||
dsdt_line(" 0x%02x,", cx_reg.bit_width);
|
||||
dsdt_line(" 0x%02x,", cx_reg.bit_offset);
|
||||
dsdt_line(" 0x%016lx,", cx_reg.address);
|
||||
dsdt_line(" 0x%02x,", cx_reg.access_size);
|
||||
dsdt_line(" )");
|
||||
dsdt_line(" },");
|
||||
|
||||
dsdt_line(" 0x%04X,", (vcpu_cx_data + i)->type);
|
||||
dsdt_line(" 0x%04X,", (vcpu_cx_data + i)->latency);
|
||||
dsdt_line(" 0x%04X", (vcpu_cx_data + i)->power);
|
||||
|
||||
if (i == (vcpu_cx_cnt - 1)) {
|
||||
dsdt_line(" }");
|
||||
} else {
|
||||
dsdt_line(" },");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dsdt_line(" })");
|
||||
dsdt_line(" }");
|
||||
|
||||
free(vcpu_cx_data);
|
||||
}
|
||||
|
||||
/* _PPC: Performance Present Capabilities
|
||||
* hard code _PPC to 0, all states are available.
|
||||
*/
|
||||
static void dsdt_write_ppc(void)
|
||||
{
|
||||
dsdt_line(" Name (_PPC, Zero)");
|
||||
}
|
||||
|
||||
/* _PCT: Performance Control
|
||||
* Both Performance Control and Status Register are set to FFixedHW
|
||||
*/
|
||||
static void dsdt_write_pct(void)
|
||||
{
|
||||
dsdt_line(" Method (_PCT, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (Package (0x02)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Register (FFixedHW,");
|
||||
dsdt_line(" 0x00,");
|
||||
dsdt_line(" 0x00,");
|
||||
dsdt_line(" 0x0000000000000000,");
|
||||
dsdt_line(" ,)");
|
||||
dsdt_line(" },");
|
||||
dsdt_line("");
|
||||
dsdt_line(" ResourceTemplate ()");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Register (FFixedHW,");
|
||||
dsdt_line(" 0x00,");
|
||||
dsdt_line(" 0x00,");
|
||||
dsdt_line(" 0x0000000000000000,");
|
||||
dsdt_line(" ,)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line(" })");
|
||||
dsdt_line(" }");
|
||||
|
||||
}
|
||||
|
||||
/* _PSS: Performance Supported States
|
||||
*/
|
||||
static void dsdt_write_pss(struct vmctx *ctx, int vcpu_id)
|
||||
{
|
||||
uint8_t vcpu_px_cnt;
|
||||
int i;
|
||||
struct cpu_px_data *vcpu_px_data;
|
||||
|
||||
vcpu_px_cnt = get_vcpu_px_cnt(ctx, vcpu_id);
|
||||
if (!vcpu_px_cnt) {
|
||||
return;
|
||||
}
|
||||
|
||||
vcpu_px_data = malloc(vcpu_px_cnt * sizeof(struct cpu_px_data));
|
||||
if (!vcpu_px_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy and validate px data first */
|
||||
for (i = 0; i < vcpu_px_cnt; i++) {
|
||||
if (get_vcpu_px_data(ctx, vcpu_id, i, vcpu_px_data + i)) {
|
||||
/* something must be wrong, so skip the write. */
|
||||
free(vcpu_px_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dsdt_line("");
|
||||
dsdt_line(" Method (_PSS, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (Package (0x%02X)", vcpu_px_cnt);
|
||||
dsdt_line(" {");
|
||||
|
||||
for (i = 0; i < vcpu_px_cnt; i++) {
|
||||
|
||||
dsdt_line(" Package (0x%02X)", 6);
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" 0x%08X,",
|
||||
(vcpu_px_data + i)->core_frequency);
|
||||
dsdt_line(" 0x%08X,",
|
||||
(vcpu_px_data + i)->power);
|
||||
dsdt_line(" 0x%08X,",
|
||||
(vcpu_px_data + i)->transition_latency);
|
||||
dsdt_line(" 0x%08X,",
|
||||
(vcpu_px_data + i)->bus_master_latency);
|
||||
dsdt_line(" 0x%08X,",
|
||||
(vcpu_px_data + i)->control);
|
||||
dsdt_line(" 0x%08X",
|
||||
(vcpu_px_data + i)->status);
|
||||
|
||||
if (i == (vcpu_px_cnt - 1)) {
|
||||
dsdt_line(" }");
|
||||
} else {
|
||||
dsdt_line(" },");
|
||||
}
|
||||
}
|
||||
dsdt_line(" })");
|
||||
dsdt_line(" }");
|
||||
|
||||
free(vcpu_px_data);
|
||||
|
||||
}
|
||||
|
||||
void pm_write_dsdt(struct vmctx *ctx, int ncpu)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Scope (_PR) */
|
||||
dsdt_line("");
|
||||
dsdt_line(" Scope (_PR)");
|
||||
dsdt_line(" {");
|
||||
for (i = 0; i < ncpu; i++) {
|
||||
dsdt_line(" Processor (CPU%d, 0x%02X, 0x00000000, 0x00) {}",
|
||||
i, i);
|
||||
}
|
||||
dsdt_line(" }");
|
||||
dsdt_line("");
|
||||
|
||||
/* Scope (_PR.CPU(N)) */
|
||||
for (i = 0; i < ncpu; i++) {
|
||||
dsdt_line(" Scope (_PR.CPU%d)", i);
|
||||
dsdt_line(" {");
|
||||
dsdt_line("");
|
||||
|
||||
dsdt_write_pss(ctx, i);
|
||||
dsdt_write_cst(ctx, i);
|
||||
|
||||
/* hard code _PPC and _PCT for all vpu */
|
||||
if (i == 0) {
|
||||
dsdt_write_ppc();
|
||||
dsdt_write_pct();
|
||||
} else {
|
||||
dsdt_line(" Method (_PPC, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (^^CPU0._PPC)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line("");
|
||||
dsdt_line(" Method (_PCT, 0, NotSerialized)");
|
||||
dsdt_line(" {");
|
||||
dsdt_line(" Return (^^CPU0._PCT)");
|
||||
dsdt_line(" }");
|
||||
dsdt_line("");
|
||||
}
|
||||
|
||||
dsdt_line(" }");
|
||||
}
|
||||
}
|
||||
@@ -1,936 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2013 Peter Grehan <grehan@freebsd.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dm.h"
|
||||
#include "mevent.h"
|
||||
#include "block_if.h"
|
||||
#include "ahci.h"
|
||||
|
||||
/*
|
||||
* Notes:
|
||||
* The F_OFD_SETLK support is introduced in glibc 2.20.
|
||||
* The glibc version on target board is above 2.20.
|
||||
* The following code temporarily fixes up building issues on Ubuntu 14.04,
|
||||
* where the glibc version is 2.19 by default.
|
||||
* Theoretically we should use cross-compiling tool to compile applications.
|
||||
*/
|
||||
#ifndef F_OFD_SETLK
|
||||
#define F_OFD_SETLK 37
|
||||
#endif
|
||||
|
||||
#define BLOCKIF_SIG 0xb109b109
|
||||
|
||||
#define BLOCKIF_NUMTHR 8
|
||||
#define BLOCKIF_MAXREQ (64 + BLOCKIF_NUMTHR)
|
||||
|
||||
/*
|
||||
* Debug printf
|
||||
*/
|
||||
static int block_if_debug;
|
||||
#define DPRINTF(params) do { if (block_if_debug) printf params; } while (0)
|
||||
#define WPRINTF(params) (printf params)
|
||||
|
||||
enum blockop {
|
||||
BOP_READ,
|
||||
BOP_WRITE,
|
||||
BOP_FLUSH,
|
||||
BOP_DELETE
|
||||
};
|
||||
|
||||
enum blockstat {
|
||||
BST_FREE,
|
||||
BST_BLOCK,
|
||||
BST_PEND,
|
||||
BST_BUSY,
|
||||
BST_DONE
|
||||
};
|
||||
|
||||
struct blockif_elem {
|
||||
TAILQ_ENTRY(blockif_elem) link;
|
||||
struct blockif_req *req;
|
||||
enum blockop op;
|
||||
enum blockstat status;
|
||||
pthread_t tid;
|
||||
off_t block;
|
||||
};
|
||||
|
||||
struct blockif_ctxt {
|
||||
int magic;
|
||||
int fd;
|
||||
int isblk;
|
||||
int isgeom;
|
||||
int candelete;
|
||||
int rdonly;
|
||||
off_t size;
|
||||
int sub_file_assign;
|
||||
off_t sub_file_start_lba;
|
||||
struct flock fl;
|
||||
int sectsz;
|
||||
int psectsz;
|
||||
int psectoff;
|
||||
int closing;
|
||||
pthread_t btid[BLOCKIF_NUMTHR];
|
||||
pthread_mutex_t mtx;
|
||||
pthread_cond_t cond;
|
||||
|
||||
/* Request elements and free/pending/busy queues */
|
||||
TAILQ_HEAD(, blockif_elem) freeq;
|
||||
TAILQ_HEAD(, blockif_elem) pendq;
|
||||
TAILQ_HEAD(, blockif_elem) busyq;
|
||||
struct blockif_elem reqs[BLOCKIF_MAXREQ];
|
||||
};
|
||||
|
||||
static pthread_once_t blockif_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
struct blockif_sig_elem {
|
||||
pthread_mutex_t mtx;
|
||||
pthread_cond_t cond;
|
||||
int pending;
|
||||
struct blockif_sig_elem *next;
|
||||
};
|
||||
|
||||
static struct blockif_sig_elem *blockif_bse_head;
|
||||
|
||||
static int
|
||||
blockif_enqueue(struct blockif_ctxt *bc, struct blockif_req *breq,
|
||||
enum blockop op)
|
||||
{
|
||||
struct blockif_elem *be, *tbe;
|
||||
off_t off;
|
||||
int i;
|
||||
|
||||
be = TAILQ_FIRST(&bc->freeq);
|
||||
assert(be != NULL);
|
||||
assert(be->status == BST_FREE);
|
||||
TAILQ_REMOVE(&bc->freeq, be, link);
|
||||
be->req = breq;
|
||||
be->op = op;
|
||||
switch (op) {
|
||||
case BOP_READ:
|
||||
case BOP_WRITE:
|
||||
case BOP_DELETE:
|
||||
off = breq->offset;
|
||||
for (i = 0; i < breq->iovcnt; i++)
|
||||
off += breq->iov[i].iov_len;
|
||||
break;
|
||||
default:
|
||||
/* off = OFF_MAX; */
|
||||
off = 1 << (sizeof(off_t) - 1);
|
||||
}
|
||||
be->block = off;
|
||||
TAILQ_FOREACH(tbe, &bc->pendq, link) {
|
||||
if (tbe->block == breq->offset)
|
||||
break;
|
||||
}
|
||||
if (tbe == NULL) {
|
||||
TAILQ_FOREACH(tbe, &bc->busyq, link) {
|
||||
if (tbe->block == breq->offset)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tbe == NULL)
|
||||
be->status = BST_PEND;
|
||||
else
|
||||
be->status = BST_BLOCK;
|
||||
TAILQ_INSERT_TAIL(&bc->pendq, be, link);
|
||||
return (be->status == BST_PEND);
|
||||
}
|
||||
|
||||
static int
|
||||
blockif_dequeue(struct blockif_ctxt *bc, pthread_t t, struct blockif_elem **bep)
|
||||
{
|
||||
struct blockif_elem *be;
|
||||
|
||||
TAILQ_FOREACH(be, &bc->pendq, link) {
|
||||
if (be->status == BST_PEND)
|
||||
break;
|
||||
assert(be->status == BST_BLOCK);
|
||||
}
|
||||
if (be == NULL)
|
||||
return 0;
|
||||
TAILQ_REMOVE(&bc->pendq, be, link);
|
||||
be->status = BST_BUSY;
|
||||
be->tid = t;
|
||||
TAILQ_INSERT_TAIL(&bc->busyq, be, link);
|
||||
*bep = be;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
blockif_complete(struct blockif_ctxt *bc, struct blockif_elem *be)
|
||||
{
|
||||
struct blockif_elem *tbe;
|
||||
|
||||
if (be->status == BST_DONE || be->status == BST_BUSY)
|
||||
TAILQ_REMOVE(&bc->busyq, be, link);
|
||||
else
|
||||
TAILQ_REMOVE(&bc->pendq, be, link);
|
||||
TAILQ_FOREACH(tbe, &bc->pendq, link) {
|
||||
if (tbe->req->offset == be->block)
|
||||
tbe->status = BST_PEND;
|
||||
}
|
||||
be->tid = 0;
|
||||
be->status = BST_FREE;
|
||||
be->req = NULL;
|
||||
TAILQ_INSERT_TAIL(&bc->freeq, be, link);
|
||||
}
|
||||
|
||||
static void
|
||||
blockif_proc(struct blockif_ctxt *bc, struct blockif_elem *be, uint8_t *buf)
|
||||
{
|
||||
struct blockif_req *br;
|
||||
off_t arg[2];
|
||||
ssize_t clen, len, off, boff, voff;
|
||||
int i, err;
|
||||
|
||||
br = be->req;
|
||||
if (br->iovcnt <= 1)
|
||||
buf = NULL;
|
||||
err = 0;
|
||||
switch (be->op) {
|
||||
case BOP_READ:
|
||||
if (buf == NULL) {
|
||||
len = preadv(bc->fd, br->iov, br->iovcnt,
|
||||
br->offset + bc->sub_file_start_lba);
|
||||
if (len < 0)
|
||||
err = errno;
|
||||
else
|
||||
br->resid -= len;
|
||||
break;
|
||||
}
|
||||
i = 0;
|
||||
off = voff = 0;
|
||||
while (br->resid > 0) {
|
||||
len = MIN(br->resid, MAXPHYS);
|
||||
if (pread(bc->fd, buf, len, br->offset +
|
||||
off + bc->sub_file_start_lba) < 0) {
|
||||
err = errno;
|
||||
break;
|
||||
}
|
||||
boff = 0;
|
||||
do {
|
||||
clen = MIN(len - boff, br->iov[i].iov_len -
|
||||
voff);
|
||||
memcpy(br->iov[i].iov_base + voff,
|
||||
buf + boff, clen);
|
||||
if (clen < br->iov[i].iov_len - voff)
|
||||
voff += clen;
|
||||
else {
|
||||
i++;
|
||||
voff = 0;
|
||||
}
|
||||
boff += clen;
|
||||
} while (boff < len);
|
||||
off += len;
|
||||
br->resid -= len;
|
||||
}
|
||||
break;
|
||||
case BOP_WRITE:
|
||||
if (bc->rdonly) {
|
||||
err = EROFS;
|
||||
break;
|
||||
}
|
||||
if (buf == NULL) {
|
||||
len = pwritev(bc->fd, br->iov, br->iovcnt,
|
||||
br->offset + bc->sub_file_start_lba);
|
||||
if (len < 0)
|
||||
err = errno;
|
||||
else
|
||||
br->resid -= len;
|
||||
break;
|
||||
}
|
||||
i = 0;
|
||||
off = voff = 0;
|
||||
while (br->resid > 0) {
|
||||
len = MIN(br->resid, MAXPHYS);
|
||||
boff = 0;
|
||||
do {
|
||||
clen = MIN(len - boff, br->iov[i].iov_len -
|
||||
voff);
|
||||
memcpy(buf + boff,
|
||||
br->iov[i].iov_base + voff, clen);
|
||||
if (clen < br->iov[i].iov_len - voff)
|
||||
voff += clen;
|
||||
else {
|
||||
i++;
|
||||
voff = 0;
|
||||
}
|
||||
boff += clen;
|
||||
} while (boff < len);
|
||||
if (pwrite(bc->fd, buf, len, br->offset +
|
||||
off + bc->sub_file_start_lba) < 0) {
|
||||
err = errno;
|
||||
break;
|
||||
}
|
||||
off += len;
|
||||
br->resid -= len;
|
||||
}
|
||||
break;
|
||||
case BOP_FLUSH:
|
||||
if (fsync(bc->fd))
|
||||
err = errno;
|
||||
break;
|
||||
case BOP_DELETE:
|
||||
/* only used by AHCI */
|
||||
if (!bc->candelete)
|
||||
err = EOPNOTSUPP;
|
||||
else if (bc->rdonly)
|
||||
err = EROFS;
|
||||
else if (bc->isblk) {
|
||||
arg[0] = br->offset;
|
||||
arg[1] = br->resid;
|
||||
if (ioctl(bc->fd, BLKDISCARD, arg))
|
||||
err = errno;
|
||||
else
|
||||
br->resid = 0;
|
||||
}
|
||||
else
|
||||
err = EOPNOTSUPP;
|
||||
break;
|
||||
default:
|
||||
err = EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
be->status = BST_DONE;
|
||||
|
||||
(*br->callback)(br, err);
|
||||
}
|
||||
|
||||
static void *
|
||||
blockif_thr(void *arg)
|
||||
{
|
||||
struct blockif_ctxt *bc;
|
||||
struct blockif_elem *be;
|
||||
pthread_t t;
|
||||
uint8_t *buf;
|
||||
|
||||
bc = arg;
|
||||
if (bc->isgeom)
|
||||
buf = malloc(MAXPHYS);
|
||||
else
|
||||
buf = NULL;
|
||||
t = pthread_self();
|
||||
|
||||
pthread_mutex_lock(&bc->mtx);
|
||||
for (;;) {
|
||||
while (blockif_dequeue(bc, t, &be)) {
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
blockif_proc(bc, be, buf);
|
||||
pthread_mutex_lock(&bc->mtx);
|
||||
blockif_complete(bc, be);
|
||||
}
|
||||
/* Check ctxt status here to see if exit requested */
|
||||
if (bc->closing)
|
||||
break;
|
||||
pthread_cond_wait(&bc->cond, &bc->mtx);
|
||||
}
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
|
||||
if (buf)
|
||||
free(buf);
|
||||
pthread_exit(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
blockif_sigcont_handler(int signal)
|
||||
{
|
||||
struct blockif_sig_elem *bse;
|
||||
|
||||
WPRINTF(("block_if sigcont handler!\n"));
|
||||
|
||||
for (;;) {
|
||||
/*
|
||||
* Process the entire list even if not intended for
|
||||
* this thread.
|
||||
*/
|
||||
do {
|
||||
bse = blockif_bse_head;
|
||||
if (bse == NULL)
|
||||
return;
|
||||
} while (!__sync_bool_compare_and_swap(
|
||||
(uintptr_t *)&blockif_bse_head,
|
||||
(uintptr_t)bse,
|
||||
(uintptr_t)bse->next));
|
||||
|
||||
pthread_mutex_lock(&bse->mtx);
|
||||
bse->pending = 0;
|
||||
pthread_cond_signal(&bse->cond);
|
||||
pthread_mutex_unlock(&bse->mtx);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
blockif_init(void)
|
||||
{
|
||||
signal(SIGCONT, blockif_sigcont_handler);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function checks if the sub file range, specified by sub_start and
|
||||
* sub_size, has any overlap with other sub file ranges with write access.
|
||||
*/
|
||||
static int
|
||||
sub_file_validate(struct blockif_ctxt *bc, int fd, int read_only,
|
||||
off_t sub_start, off_t sub_size)
|
||||
{
|
||||
struct flock *fl = &bc->fl;
|
||||
|
||||
memset(fl, 0, sizeof(struct flock));
|
||||
fl->l_whence = SEEK_SET; /* offset base is start of file */
|
||||
if (read_only)
|
||||
fl->l_type = F_RDLCK;
|
||||
else
|
||||
fl->l_type = F_WRLCK;
|
||||
fl->l_start = sub_start;
|
||||
fl->l_len = sub_size;
|
||||
|
||||
/* use "open file description locks" to validate */
|
||||
if (fcntl(fd, F_OFD_SETLK, fl) == -1) {
|
||||
DPRINTF(("failed to lock subfile!\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Keep file lock on to prevent other sub files, until DM exits */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
sub_file_unlock(struct blockif_ctxt *bc)
|
||||
{
|
||||
struct flock *fl;
|
||||
|
||||
if (bc->sub_file_assign) {
|
||||
fl = &bc->fl;
|
||||
DPRINTF(("blockif: release file lock...\n"));
|
||||
fl->l_type = F_UNLCK;
|
||||
if (fcntl(bc->fd, F_OFD_SETLK, fl) == -1) {
|
||||
fprintf(stderr, "blockif: failed to unlock subfile!\n");
|
||||
exit(1);
|
||||
}
|
||||
DPRINTF(("blockif: release done\n"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct blockif_ctxt *
|
||||
blockif_open(const char *optstr, const char *ident)
|
||||
{
|
||||
char tname[MAXCOMLEN + 1];
|
||||
/* char name[MAXPATHLEN]; */
|
||||
char *nopt, *xopts, *cp;
|
||||
struct blockif_ctxt *bc;
|
||||
struct stat sbuf;
|
||||
/* struct diocgattr_arg arg; */
|
||||
off_t size, psectsz, psectoff;
|
||||
int extra, fd, i, sectsz;
|
||||
int nocache, sync, ro, candelete, geom, ssopt, pssopt;
|
||||
long sz;
|
||||
long long b;
|
||||
int err_code = -1;
|
||||
off_t sub_file_start_lba, sub_file_size;
|
||||
int sub_file_assign;
|
||||
|
||||
pthread_once(&blockif_once, blockif_init);
|
||||
|
||||
fd = -1;
|
||||
ssopt = 0;
|
||||
nocache = 0;
|
||||
sync = 0;
|
||||
ro = 0;
|
||||
sub_file_assign = 0;
|
||||
|
||||
/*
|
||||
* The first element in the optstring is always a pathname.
|
||||
* Optional elements follow
|
||||
*/
|
||||
nopt = xopts = strdup(optstr);
|
||||
if (!nopt) {
|
||||
WPRINTF(("block_if.c: strdup retruns NULL\n"));
|
||||
return NULL;
|
||||
}
|
||||
while (xopts != NULL) {
|
||||
cp = strsep(&xopts, ",");
|
||||
if (cp == nopt) /* file or device pathname */
|
||||
continue;
|
||||
else if (!strcmp(cp, "nocache"))
|
||||
nocache = 1;
|
||||
else if (!strcmp(cp, "sync") || !strcmp(cp, "direct"))
|
||||
sync = 1;
|
||||
else if (!strcmp(cp, "ro"))
|
||||
ro = 1;
|
||||
else if (sscanf(cp, "sectorsize=%d/%d", &ssopt, &pssopt) == 2)
|
||||
;
|
||||
else if (sscanf(cp, "sectorsize=%d", &ssopt) == 1)
|
||||
pssopt = ssopt;
|
||||
else if (sscanf(cp, "range=%ld/%ld", &sub_file_start_lba,
|
||||
&sub_file_size) == 2)
|
||||
sub_file_assign = 1;
|
||||
else {
|
||||
fprintf(stderr, "Invalid device option \"%s\"\n", cp);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* enforce a write-through policy by default */
|
||||
nocache = 1;
|
||||
sync = 1;
|
||||
|
||||
extra = 0;
|
||||
if (nocache)
|
||||
extra |= O_DIRECT;
|
||||
if (sync)
|
||||
extra |= O_SYNC;
|
||||
|
||||
fd = open(nopt, (ro ? O_RDONLY : O_RDWR) | extra);
|
||||
if (fd < 0 && !ro) {
|
||||
/* Attempt a r/w fail with a r/o open */
|
||||
fd = open(nopt, O_RDONLY | extra);
|
||||
ro = 1;
|
||||
}
|
||||
|
||||
if (fd < 0) {
|
||||
warn("Could not open backing file: %s", nopt);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (fstat(fd, &sbuf) < 0) {
|
||||
warn("Could not stat backing file %s", nopt);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deal with raw devices
|
||||
*/
|
||||
size = sbuf.st_size;
|
||||
sectsz = DEV_BSIZE;
|
||||
psectsz = psectoff = 0;
|
||||
candelete = geom = 0;
|
||||
|
||||
if (S_ISBLK(sbuf.st_mode)) {
|
||||
/* get size */
|
||||
err_code = ioctl(fd, BLKGETSIZE, &sz);
|
||||
if (err_code) {
|
||||
fprintf(stderr, "error %d getting block size!\n",
|
||||
err_code);
|
||||
size = sbuf.st_size; /* set default value */
|
||||
} else {
|
||||
size = sz * DEV_BSIZE; /* DEV_BSIZE is 512 on Linux */
|
||||
}
|
||||
if (!err_code || err_code == EFBIG) {
|
||||
err_code = ioctl(fd, BLKGETSIZE64, &b);
|
||||
if (err_code || b == 0 || b == sz)
|
||||
size = b * DEV_BSIZE;
|
||||
else
|
||||
size = b;
|
||||
}
|
||||
DPRINTF(("block partition size is 0x%lx\n", size));
|
||||
|
||||
/* get sector size, 512 on Linux */
|
||||
sectsz = DEV_BSIZE;
|
||||
DPRINTF(("block partition sector size is 0x%x\n", sectsz));
|
||||
|
||||
/* get physical sector size */
|
||||
err_code = ioctl(fd, BLKPBSZGET, &psectsz);
|
||||
if (err_code) {
|
||||
fprintf(stderr, "error %d getting physical sectsz!\n",
|
||||
err_code);
|
||||
psectsz = DEV_BSIZE; /* set default physical size */
|
||||
}
|
||||
DPRINTF(("block partition physical sector size is 0x%lx\n",
|
||||
psectsz));
|
||||
|
||||
} else
|
||||
psectsz = sbuf.st_blksize;
|
||||
|
||||
if (ssopt != 0) {
|
||||
if (!powerof2(ssopt) || !powerof2(pssopt) || ssopt < 512 ||
|
||||
ssopt > pssopt) {
|
||||
fprintf(stderr, "Invalid sector size %d/%d\n",
|
||||
ssopt, pssopt);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some backend drivers (e.g. cd0, ada0) require that the I/O
|
||||
* size be a multiple of the device's sector size.
|
||||
*
|
||||
* Validate that the emulated sector size complies with this
|
||||
* requirement.
|
||||
*/
|
||||
if (S_ISCHR(sbuf.st_mode)) {
|
||||
if (ssopt < sectsz || (ssopt % sectsz) != 0) {
|
||||
fprintf(stderr,
|
||||
"Sector size %d incompatible with underlying device sector size %d\n",
|
||||
ssopt, sectsz);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
sectsz = ssopt;
|
||||
psectsz = pssopt;
|
||||
psectoff = 0;
|
||||
}
|
||||
|
||||
bc = calloc(1, sizeof(struct blockif_ctxt));
|
||||
if (bc == NULL) {
|
||||
perror("calloc");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (sub_file_assign) {
|
||||
DPRINTF(("sector size is %d\n", sectsz));
|
||||
bc->sub_file_assign = 1;
|
||||
bc->sub_file_start_lba = sub_file_start_lba * sectsz;
|
||||
size = sub_file_size * sectsz;
|
||||
DPRINTF(("Validating sub file...\n"));
|
||||
err_code = sub_file_validate(bc, fd, ro, bc->sub_file_start_lba,
|
||||
size);
|
||||
if (err_code < 0) {
|
||||
fprintf(stderr, "subfile range specified not valid!\n");
|
||||
exit(1);
|
||||
}
|
||||
DPRINTF(("Validated done!\n"));
|
||||
} else {
|
||||
/* normal case */
|
||||
bc->sub_file_assign = 0;
|
||||
bc->sub_file_start_lba = 0;
|
||||
}
|
||||
|
||||
bc->magic = BLOCKIF_SIG;
|
||||
bc->fd = fd;
|
||||
bc->isblk = S_ISBLK(sbuf.st_mode);
|
||||
bc->isgeom = geom;
|
||||
bc->candelete = candelete;
|
||||
bc->rdonly = ro;
|
||||
bc->size = size;
|
||||
bc->sectsz = sectsz;
|
||||
bc->psectsz = psectsz;
|
||||
bc->psectoff = psectoff;
|
||||
pthread_mutex_init(&bc->mtx, NULL);
|
||||
pthread_cond_init(&bc->cond, NULL);
|
||||
TAILQ_INIT(&bc->freeq);
|
||||
TAILQ_INIT(&bc->pendq);
|
||||
TAILQ_INIT(&bc->busyq);
|
||||
for (i = 0; i < BLOCKIF_MAXREQ; i++) {
|
||||
bc->reqs[i].status = BST_FREE;
|
||||
TAILQ_INSERT_HEAD(&bc->freeq, &bc->reqs[i], link);
|
||||
}
|
||||
|
||||
for (i = 0; i < BLOCKIF_NUMTHR; i++) {
|
||||
pthread_create(&bc->btid[i], NULL, blockif_thr, bc);
|
||||
snprintf(tname, sizeof(tname), "blk-%s-%d", ident, i);
|
||||
pthread_setname_np(bc->btid[i], tname);
|
||||
}
|
||||
|
||||
return bc;
|
||||
err:
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
blockif_request(struct blockif_ctxt *bc, struct blockif_req *breq,
|
||||
enum blockop op)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = 0;
|
||||
|
||||
pthread_mutex_lock(&bc->mtx);
|
||||
if (!TAILQ_EMPTY(&bc->freeq)) {
|
||||
/*
|
||||
* Enqueue and inform the block i/o thread
|
||||
* that there is work available
|
||||
*/
|
||||
if (blockif_enqueue(bc, breq, op))
|
||||
pthread_cond_signal(&bc->cond);
|
||||
} else {
|
||||
/*
|
||||
* Callers are not allowed to enqueue more than
|
||||
* the specified blockif queue limit. Return an
|
||||
* error to indicate that the queue length has been
|
||||
* exceeded.
|
||||
*/
|
||||
err = E2BIG;
|
||||
}
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
blockif_read(struct blockif_ctxt *bc, struct blockif_req *breq)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return blockif_request(bc, breq, BOP_READ);
|
||||
}
|
||||
|
||||
int
|
||||
blockif_write(struct blockif_ctxt *bc, struct blockif_req *breq)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return blockif_request(bc, breq, BOP_WRITE);
|
||||
}
|
||||
|
||||
int
|
||||
blockif_flush(struct blockif_ctxt *bc, struct blockif_req *breq)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return blockif_request(bc, breq, BOP_FLUSH);
|
||||
}
|
||||
|
||||
int
|
||||
blockif_delete(struct blockif_ctxt *bc, struct blockif_req *breq)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return blockif_request(bc, breq, BOP_DELETE);
|
||||
}
|
||||
|
||||
int
|
||||
blockif_cancel(struct blockif_ctxt *bc, struct blockif_req *breq)
|
||||
{
|
||||
struct blockif_elem *be;
|
||||
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
|
||||
pthread_mutex_lock(&bc->mtx);
|
||||
/*
|
||||
* Check pending requests.
|
||||
*/
|
||||
TAILQ_FOREACH(be, &bc->pendq, link) {
|
||||
if (be->req == breq)
|
||||
break;
|
||||
}
|
||||
if (be != NULL) {
|
||||
/*
|
||||
* Found it.
|
||||
*/
|
||||
blockif_complete(bc, be);
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check in-flight requests.
|
||||
*/
|
||||
TAILQ_FOREACH(be, &bc->busyq, link) {
|
||||
if (be->req == breq)
|
||||
break;
|
||||
}
|
||||
if (be == NULL) {
|
||||
/*
|
||||
* Didn't find it.
|
||||
*/
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interrupt the processing thread to force it return
|
||||
* prematurely via it's normal callback path.
|
||||
*/
|
||||
while (be->status == BST_BUSY) {
|
||||
struct blockif_sig_elem bse, *old_head;
|
||||
|
||||
pthread_mutex_init(&bse.mtx, NULL);
|
||||
pthread_cond_init(&bse.cond, NULL);
|
||||
|
||||
bse.pending = 1;
|
||||
|
||||
do {
|
||||
old_head = blockif_bse_head;
|
||||
bse.next = old_head;
|
||||
} while (!__sync_bool_compare_and_swap((uintptr_t *)&
|
||||
blockif_bse_head,
|
||||
(uintptr_t)old_head,
|
||||
(uintptr_t)&bse));
|
||||
|
||||
pthread_kill(be->tid, SIGCONT);
|
||||
|
||||
pthread_mutex_lock(&bse.mtx);
|
||||
while (bse.pending)
|
||||
pthread_cond_wait(&bse.cond, &bse.mtx);
|
||||
pthread_mutex_unlock(&bse.mtx);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
|
||||
/*
|
||||
* The processing thread has been interrupted. Since it's not
|
||||
* clear if the callback has been invoked yet, return EBUSY.
|
||||
*/
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
int
|
||||
blockif_close(struct blockif_ctxt *bc)
|
||||
{
|
||||
void *jval;
|
||||
int i;
|
||||
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
sub_file_unlock(bc);
|
||||
|
||||
/*
|
||||
* Stop the block i/o thread
|
||||
*/
|
||||
pthread_mutex_lock(&bc->mtx);
|
||||
bc->closing = 1;
|
||||
pthread_mutex_unlock(&bc->mtx);
|
||||
pthread_cond_broadcast(&bc->cond);
|
||||
for (i = 0; i < BLOCKIF_NUMTHR; i++)
|
||||
pthread_join(bc->btid[i], &jval);
|
||||
|
||||
/* XXX Cancel queued i/o's ??? */
|
||||
|
||||
/*
|
||||
* Release resources
|
||||
*/
|
||||
bc->magic = 0;
|
||||
close(bc->fd);
|
||||
free(bc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return virtual C/H/S values for a given block. Use the algorithm
|
||||
* outlined in the VHD specification to calculate values.
|
||||
*/
|
||||
void
|
||||
blockif_chs(struct blockif_ctxt *bc, uint16_t *c, uint8_t *h, uint8_t *s)
|
||||
{
|
||||
off_t sectors; /* total sectors of the block dev */
|
||||
off_t hcyl; /* cylinders times heads */
|
||||
uint16_t secpt; /* sectors per track */
|
||||
uint8_t heads;
|
||||
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
|
||||
sectors = bc->size / bc->sectsz;
|
||||
|
||||
/* Clamp the size to the largest possible with CHS */
|
||||
if (sectors > 65535UL*16*255)
|
||||
sectors = 65535UL*16*255;
|
||||
|
||||
if (sectors >= 65536UL*16*63) {
|
||||
secpt = 255;
|
||||
heads = 16;
|
||||
hcyl = sectors / secpt;
|
||||
} else {
|
||||
secpt = 17;
|
||||
hcyl = sectors / secpt;
|
||||
heads = (hcyl + 1023) / 1024;
|
||||
|
||||
if (heads < 4)
|
||||
heads = 4;
|
||||
|
||||
if (hcyl >= (heads * 1024) || heads > 16) {
|
||||
secpt = 31;
|
||||
heads = 16;
|
||||
hcyl = sectors / secpt;
|
||||
}
|
||||
if (hcyl >= (heads * 1024)) {
|
||||
secpt = 63;
|
||||
heads = 16;
|
||||
hcyl = sectors / secpt;
|
||||
}
|
||||
}
|
||||
|
||||
*c = hcyl / heads;
|
||||
*h = heads;
|
||||
*s = secpt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
||||
off_t
|
||||
blockif_size(struct blockif_ctxt *bc)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return bc->size;
|
||||
}
|
||||
|
||||
int
|
||||
blockif_sectsz(struct blockif_ctxt *bc)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return bc->sectsz;
|
||||
}
|
||||
|
||||
void
|
||||
blockif_psectsz(struct blockif_ctxt *bc, int *size, int *off)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
*size = bc->psectsz;
|
||||
*off = bc->psectoff;
|
||||
}
|
||||
|
||||
int
|
||||
blockif_queuesz(struct blockif_ctxt *bc)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return (BLOCKIF_MAXREQ - 1);
|
||||
}
|
||||
|
||||
int
|
||||
blockif_is_ro(struct blockif_ctxt *bc)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return bc->rdonly;
|
||||
}
|
||||
|
||||
int
|
||||
blockif_candelete(struct blockif_ctxt *bc)
|
||||
{
|
||||
assert(bc->magic == BLOCKIF_SIG);
|
||||
return bc->candelete;
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2013 Hudson River Trading LLC
|
||||
* Written by: John H. Baldwin <jhb@FreeBSD.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "vmmapi.h"
|
||||
#include "vmm.h"
|
||||
#include "acpi.h"
|
||||
#include "inout.h"
|
||||
#include "mevent.h"
|
||||
#include "irq.h"
|
||||
#include "lpc.h"
|
||||
|
||||
static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct mevent *power_button;
|
||||
static sig_t old_power_handler;
|
||||
|
||||
/*
|
||||
* Reset Control register at I/O port 0xcf9. Bit 2 forces a system
|
||||
* reset when it transitions from 0 to 1. Bit 1 selects the type of
|
||||
* reset to attempt: 0 selects a "soft" reset, and 1 selects a "hard"
|
||||
* reset.
|
||||
*/
|
||||
static int
|
||||
reset_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
int error;
|
||||
|
||||
static uint8_t reset_control;
|
||||
|
||||
if (bytes != 1)
|
||||
return -1;
|
||||
if (in)
|
||||
*eax = reset_control;
|
||||
else {
|
||||
reset_control = *eax;
|
||||
|
||||
/* Treat hard and soft resets the same. */
|
||||
if (reset_control & 0x4) {
|
||||
error = vm_suspend(ctx, VM_SUSPEND_RESET);
|
||||
assert(error == 0 || errno == EALREADY);
|
||||
}
|
||||
|
||||
/* cold reset should clear the value in 0xcf9 */
|
||||
if (reset_control & 0x8) {
|
||||
reset_control = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler);
|
||||
|
||||
/*
|
||||
* ACPI's SCI is a level-triggered interrupt.
|
||||
*/
|
||||
static int sci_active;
|
||||
|
||||
static void
|
||||
sci_assert(struct vmctx *ctx)
|
||||
{
|
||||
if (sci_active)
|
||||
return;
|
||||
vm_isa_assert_irq(ctx, SCI_INT, SCI_INT);
|
||||
sci_active = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
sci_deassert(struct vmctx *ctx)
|
||||
{
|
||||
if (!sci_active)
|
||||
return;
|
||||
vm_isa_deassert_irq(ctx, SCI_INT, SCI_INT);
|
||||
sci_active = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Management 1 Event Registers
|
||||
*
|
||||
* The only power management event supported is a power button upon
|
||||
* receiving SIGTERM.
|
||||
*/
|
||||
static uint16_t pm1_enable, pm1_status;
|
||||
|
||||
#define PM1_TMR_STS 0x0001
|
||||
#define PM1_BM_STS 0x0010
|
||||
#define PM1_GBL_STS 0x0020
|
||||
#define PM1_PWRBTN_STS 0x0100
|
||||
#define PM1_SLPBTN_STS 0x0200
|
||||
#define PM1_RTC_STS 0x0400
|
||||
#define PM1_WAK_STS 0x8000
|
||||
|
||||
#define PM1_TMR_EN 0x0001
|
||||
#define PM1_GBL_EN 0x0020
|
||||
#define PM1_PWRBTN_EN 0x0100
|
||||
#define PM1_SLPBTN_EN 0x0200
|
||||
#define PM1_RTC_EN 0x0400
|
||||
|
||||
static void
|
||||
sci_update(struct vmctx *ctx)
|
||||
{
|
||||
int need_sci;
|
||||
|
||||
/* See if the SCI should be active or not. */
|
||||
need_sci = 0;
|
||||
if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS))
|
||||
need_sci = 1;
|
||||
if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS))
|
||||
need_sci = 1;
|
||||
if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS))
|
||||
need_sci = 1;
|
||||
if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS))
|
||||
need_sci = 1;
|
||||
if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS))
|
||||
need_sci = 1;
|
||||
if (need_sci)
|
||||
sci_assert(ctx);
|
||||
else
|
||||
sci_deassert(ctx);
|
||||
}
|
||||
|
||||
static int
|
||||
pm1_status_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
if (bytes != 2)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_lock(&pm_lock);
|
||||
if (in)
|
||||
*eax = pm1_status;
|
||||
else {
|
||||
/*
|
||||
* Writes are only permitted to clear certain bits by
|
||||
* writing 1 to those flags.
|
||||
*/
|
||||
pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS |
|
||||
PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS));
|
||||
sci_update(ctx);
|
||||
}
|
||||
pthread_mutex_unlock(&pm_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pm1_enable_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
if (bytes != 2)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_lock(&pm_lock);
|
||||
if (in)
|
||||
*eax = pm1_enable;
|
||||
else {
|
||||
/*
|
||||
* Only permit certain bits to be set. We never use
|
||||
* the global lock, but ACPI-CA whines profusely if it
|
||||
* can't set GBL_EN.
|
||||
*/
|
||||
pm1_enable = *eax & (PM1_PWRBTN_EN | PM1_GBL_EN);
|
||||
sci_update(ctx);
|
||||
}
|
||||
pthread_mutex_unlock(&pm_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler);
|
||||
INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler);
|
||||
|
||||
static void
|
||||
power_button_handler(int signal, enum ev_type type, void *arg)
|
||||
{
|
||||
struct vmctx *ctx;
|
||||
|
||||
ctx = arg;
|
||||
pthread_mutex_lock(&pm_lock);
|
||||
if (!(pm1_status & PM1_PWRBTN_STS)) {
|
||||
pm1_status |= PM1_PWRBTN_STS;
|
||||
sci_update(ctx);
|
||||
}
|
||||
pthread_mutex_unlock(&pm_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Management 1 Control Register
|
||||
*
|
||||
* This is mostly unimplemented except that we wish to handle writes that
|
||||
* set SPL_EN to handle S5 (soft power off).
|
||||
*/
|
||||
static uint16_t pm1_control;
|
||||
|
||||
#define PM1_SCI_EN 0x0001
|
||||
#define PM1_SLP_TYP 0x1c00
|
||||
#define PM1_SLP_EN 0x2000
|
||||
#define PM1_ALWAYS_ZERO 0xc003
|
||||
|
||||
static int
|
||||
pm1_control_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (bytes != 2)
|
||||
return -1;
|
||||
if (in)
|
||||
*eax = pm1_control;
|
||||
else {
|
||||
/*
|
||||
* Various bits are write-only or reserved, so force them
|
||||
* to zero in pm1_control. Always preserve SCI_EN as OSPM
|
||||
* can never change it.
|
||||
*/
|
||||
pm1_control = (pm1_control & PM1_SCI_EN) |
|
||||
(*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO));
|
||||
|
||||
/*
|
||||
* If SLP_EN is set, check for S5. ACRN-DM's _S5_ method
|
||||
* says that '5' should be stored in SLP_TYP for S5.
|
||||
*/
|
||||
if (*eax & PM1_SLP_EN) {
|
||||
if ((pm1_control & PM1_SLP_TYP) >> 10 == 5) {
|
||||
error = vm_suspend(ctx, VM_SUSPEND_POWEROFF);
|
||||
assert(error == 0 || errno == EALREADY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler);
|
||||
SYSRES_IO(PM1A_EVT_ADDR, 8);
|
||||
|
||||
/*
|
||||
* ACPI SMI Command Register
|
||||
*
|
||||
* This write-only register is used to enable and disable ACPI.
|
||||
*/
|
||||
static int
|
||||
smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
|
||||
uint32_t *eax, void *arg)
|
||||
{
|
||||
assert(!in);
|
||||
if (bytes != 1)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_lock(&pm_lock);
|
||||
switch (*eax) {
|
||||
case ACPI_ENABLE:
|
||||
pm1_control |= PM1_SCI_EN;
|
||||
if (power_button == NULL) {
|
||||
power_button = mevent_add(SIGTERM, EVF_SIGNAL,
|
||||
power_button_handler, ctx);
|
||||
old_power_handler = signal(SIGTERM, SIG_IGN);
|
||||
}
|
||||
break;
|
||||
case ACPI_DISABLE:
|
||||
pm1_control &= ~PM1_SCI_EN;
|
||||
if (power_button != NULL) {
|
||||
mevent_delete(power_button);
|
||||
power_button = NULL;
|
||||
signal(SIGTERM, old_power_handler);
|
||||
}
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&pm_lock);
|
||||
return 0;
|
||||
}
|
||||
INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler);
|
||||
SYSRES_IO(SMI_CMD, 1);
|
||||
|
||||
void
|
||||
sci_init(struct vmctx *ctx)
|
||||
{
|
||||
/*
|
||||
* Mark ACPI's SCI as level trigger and bump its use count
|
||||
* in the PIRQ router.
|
||||
*/
|
||||
pci_irq_use(SCI_INT);
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2012 NetApp, Inc.
|
||||
* Copyright (c) 2013 Neel Natu <neel@freebsd.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "mevent.h"
|
||||
#include "uart_core.h"
|
||||
#include "ns16550.h"
|
||||
#include "dm.h"
|
||||
|
||||
#define COM1_BASE 0x3F8
|
||||
#define COM1_IRQ 4
|
||||
#define COM2_BASE 0x2F8
|
||||
#define COM2_IRQ 3
|
||||
|
||||
#define DEFAULT_RCLK 1843200
|
||||
#define DEFAULT_BAUD 9600
|
||||
|
||||
#define FCR_RX_MASK 0xC0
|
||||
|
||||
#define MCR_OUT1 0x04
|
||||
#define MCR_OUT2 0x08
|
||||
|
||||
#define MSR_DELTA_MASK 0x0f
|
||||
|
||||
#ifndef REG_SCR
|
||||
#define REG_SCR com_scr
|
||||
#endif
|
||||
|
||||
#define FIFOSZ 256
|
||||
|
||||
static struct termios tio_stdio_orig;
|
||||
|
||||
static struct {
|
||||
int baseaddr;
|
||||
int irq;
|
||||
bool inuse;
|
||||
} uart_lres[] = {
|
||||
{ COM1_BASE, COM1_IRQ, false},
|
||||
{ COM2_BASE, COM2_IRQ, false},
|
||||
};
|
||||
|
||||
#define UART_NLDEVS (ARRAY_SIZE(uart_lres))
|
||||
|
||||
struct fifo {
|
||||
uint8_t buf[FIFOSZ];
|
||||
int rindex; /* index to read from */
|
||||
int windex; /* index to write to */
|
||||
int num; /* number of characters in the fifo */
|
||||
int size; /* size of the fifo */
|
||||
};
|
||||
|
||||
struct ttyfd {
|
||||
bool opened;
|
||||
int fd; /* tty device file descriptor */
|
||||
struct termios tio_orig, tio_new; /* I/O Terminals */
|
||||
};
|
||||
|
||||
struct uart_vdev {
|
||||
pthread_mutex_t mtx; /* protects all elements */
|
||||
uint8_t data; /* Data register (R/W) */
|
||||
uint8_t ier; /* Interrupt enable register (R/W) */
|
||||
uint8_t lcr; /* Line control register (R/W) */
|
||||
uint8_t mcr; /* Modem control register (R/W) */
|
||||
uint8_t lsr; /* Line status register (R/W) */
|
||||
uint8_t msr; /* Modem status register (R/W) */
|
||||
uint8_t fcr; /* FIFO control register (W) */
|
||||
uint8_t scr; /* Scratch register (R/W) */
|
||||
|
||||
uint8_t dll; /* Baudrate divisor latch LSB */
|
||||
uint8_t dlh; /* Baudrate divisor latch MSB */
|
||||
|
||||
struct fifo rxfifo;
|
||||
struct mevent *mev;
|
||||
|
||||
struct ttyfd tty;
|
||||
bool thre_int_pending; /* THRE interrupt pending */
|
||||
|
||||
void *arg;
|
||||
uart_intr_func_t intr_assert;
|
||||
uart_intr_func_t intr_deassert;
|
||||
};
|
||||
|
||||
static void uart_drain(int fd, enum ev_type ev, void *arg);
|
||||
|
||||
static void
|
||||
ttyclose(void)
|
||||
{
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &tio_stdio_orig);
|
||||
}
|
||||
|
||||
static void
|
||||
ttyopen(struct ttyfd *tf)
|
||||
{
|
||||
tcgetattr(tf->fd, &tf->tio_orig);
|
||||
|
||||
tf->tio_new = tf->tio_orig;
|
||||
cfmakeraw(&tf->tio_new);
|
||||
tf->tio_new.c_cflag |= CLOCAL;
|
||||
tcsetattr(tf->fd, TCSANOW, &tf->tio_new);
|
||||
|
||||
if (tf->fd == STDIN_FILENO) {
|
||||
tio_stdio_orig = tf->tio_orig;
|
||||
atexit(ttyclose);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ttyread(struct ttyfd *tf)
|
||||
{
|
||||
unsigned char rb;
|
||||
|
||||
if (read(tf->fd, &rb, 1) > 0)
|
||||
return rb;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
ttywrite(struct ttyfd *tf, unsigned char wb)
|
||||
{
|
||||
|
||||
if (write(tf->fd, &wb, 1) > 0)
|
||||
return 1;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
rxfifo_reset(struct uart_vdev *uart, int size)
|
||||
{
|
||||
char flushbuf[32];
|
||||
struct fifo *fifo;
|
||||
ssize_t nread;
|
||||
int error;
|
||||
|
||||
fifo = &uart->rxfifo;
|
||||
bzero(fifo, sizeof(struct fifo));
|
||||
fifo->size = size;
|
||||
|
||||
if (uart->tty.opened) {
|
||||
/*
|
||||
* Flush any unread input from the tty buffer.
|
||||
*/
|
||||
while (1) {
|
||||
nread = read(uart->tty.fd, flushbuf, sizeof(flushbuf));
|
||||
if (nread != sizeof(flushbuf))
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable mevent to trigger when new characters are available
|
||||
* on the tty fd.
|
||||
*/
|
||||
error = mevent_enable(uart->mev);
|
||||
assert(error == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
rxfifo_available(struct uart_vdev *uart)
|
||||
{
|
||||
struct fifo *fifo;
|
||||
|
||||
fifo = &uart->rxfifo;
|
||||
return (fifo->num < fifo->size);
|
||||
}
|
||||
|
||||
static int
|
||||
rxfifo_putchar(struct uart_vdev *uart, uint8_t ch)
|
||||
{
|
||||
struct fifo *fifo;
|
||||
int error;
|
||||
|
||||
fifo = &uart->rxfifo;
|
||||
|
||||
if (fifo->num < fifo->size) {
|
||||
fifo->buf[fifo->windex] = ch;
|
||||
fifo->windex = (fifo->windex + 1) % fifo->size;
|
||||
fifo->num++;
|
||||
if (!rxfifo_available(uart)) {
|
||||
if (uart->tty.opened) {
|
||||
/*
|
||||
* Disable mevent callback if the FIFO is full.
|
||||
*/
|
||||
error = mevent_disable(uart->mev);
|
||||
assert(error == 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
rxfifo_getchar(struct uart_vdev *uart)
|
||||
{
|
||||
struct fifo *fifo;
|
||||
int c, error, wasfull;
|
||||
|
||||
wasfull = 0;
|
||||
fifo = &uart->rxfifo;
|
||||
if (fifo->num > 0) {
|
||||
if (!rxfifo_available(uart))
|
||||
wasfull = 1;
|
||||
c = fifo->buf[fifo->rindex];
|
||||
fifo->rindex = (fifo->rindex + 1) % fifo->size;
|
||||
fifo->num--;
|
||||
if (wasfull) {
|
||||
if (uart->tty.opened) {
|
||||
error = mevent_enable(uart->mev);
|
||||
assert(error == 0);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
rxfifo_numchars(struct uart_vdev *uart)
|
||||
{
|
||||
struct fifo *fifo = &uart->rxfifo;
|
||||
|
||||
return fifo->num;
|
||||
}
|
||||
|
||||
static void
|
||||
uart_opentty(struct uart_vdev *uart)
|
||||
{
|
||||
ttyopen(&uart->tty);
|
||||
if (isatty(uart->tty.fd)) {
|
||||
uart->mev = mevent_add(uart->tty.fd, EVF_READ,
|
||||
uart_drain, uart);
|
||||
assert(uart->mev != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
uart_closetty(struct uart_vdev *uart)
|
||||
{
|
||||
if (uart->tty.fd != STDIN_FILENO)
|
||||
mevent_delete_close(uart->mev);
|
||||
else
|
||||
mevent_delete(uart->mev);
|
||||
|
||||
uart->mev = 0;
|
||||
ttyclose();
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
modem_status(uint8_t mcr)
|
||||
{
|
||||
uint8_t msr;
|
||||
|
||||
if (mcr & MCR_LOOPBACK) {
|
||||
/*
|
||||
* In the loopback mode certain bits from the MCR are
|
||||
* reflected back into MSR.
|
||||
*/
|
||||
msr = 0;
|
||||
if (mcr & MCR_RTS)
|
||||
msr |= MSR_CTS;
|
||||
if (mcr & MCR_DTR)
|
||||
msr |= MSR_DSR;
|
||||
if (mcr & MCR_OUT1)
|
||||
msr |= MSR_RI;
|
||||
if (mcr & MCR_OUT2)
|
||||
msr |= MSR_DCD;
|
||||
} else {
|
||||
/*
|
||||
* Always assert DCD and DSR so tty open doesn't block
|
||||
* even if CLOCAL is turned off.
|
||||
*/
|
||||
msr = MSR_DCD | MSR_DSR;
|
||||
}
|
||||
assert((msr & MSR_DELTA_MASK) == 0);
|
||||
|
||||
return msr;
|
||||
}
|
||||
|
||||
/*
|
||||
* The IIR returns a prioritized interrupt reason:
|
||||
* - receive data available
|
||||
* - transmit holding register empty
|
||||
* - modem status change
|
||||
*
|
||||
* Return an interrupt reason if one is available.
|
||||
*/
|
||||
static int
|
||||
uart_intr_reason(struct uart_vdev *uart)
|
||||
{
|
||||
if ((uart->lsr & LSR_OE) != 0 && (uart->ier & IER_ERLS) != 0)
|
||||
return IIR_RLS;
|
||||
else if (rxfifo_numchars(uart) > 0 && (uart->ier & IER_ERXRDY) != 0)
|
||||
return IIR_RXTOUT;
|
||||
else if (uart->thre_int_pending && (uart->ier & IER_ETXRDY) != 0)
|
||||
return IIR_TXRDY;
|
||||
else if ((uart->msr & MSR_DELTA_MASK) != 0 &&
|
||||
(uart->ier & IER_EMSC) != 0)
|
||||
return IIR_MLSC;
|
||||
else
|
||||
return IIR_NOPEND;
|
||||
}
|
||||
|
||||
static void
|
||||
uart_reset(struct uart_vdev *uart)
|
||||
{
|
||||
uint16_t divisor;
|
||||
|
||||
divisor = DEFAULT_RCLK / DEFAULT_BAUD / 16;
|
||||
uart->dll = divisor;
|
||||
uart->dlh = divisor >> 16;
|
||||
uart->msr = modem_status(uart->mcr);
|
||||
|
||||
rxfifo_reset(uart, 1); /* no fifo until enabled by software */
|
||||
}
|
||||
|
||||
/*
|
||||
* Toggle the COM port's intr pin depending on whether or not we have an
|
||||
* interrupt condition to report to the processor.
|
||||
*/
|
||||
static void
|
||||
uart_toggle_intr(struct uart_vdev *uart)
|
||||
{
|
||||
uint8_t intr_reason;
|
||||
|
||||
intr_reason = uart_intr_reason(uart);
|
||||
|
||||
if (intr_reason == IIR_NOPEND)
|
||||
(*uart->intr_deassert)(uart->arg);
|
||||
else
|
||||
(*uart->intr_assert)(uart->arg);
|
||||
}
|
||||
|
||||
static void
|
||||
uart_drain(int fd, enum ev_type ev, void *arg)
|
||||
{
|
||||
struct uart_vdev *uart;
|
||||
int ch;
|
||||
|
||||
uart = arg;
|
||||
|
||||
assert(fd == uart->tty.fd);
|
||||
assert(ev == EVF_READ);
|
||||
|
||||
/*
|
||||
* This routine is called in the context of the mevent thread
|
||||
* to take out the uart lock to protect against concurrent
|
||||
* access from a vCPU i/o exit
|
||||
*/
|
||||
pthread_mutex_lock(&uart->mtx);
|
||||
|
||||
if ((uart->mcr & MCR_LOOPBACK) != 0) {
|
||||
(void) ttyread(&uart->tty);
|
||||
} else {
|
||||
while ((ch = ttyread(&uart->tty)) != -1)
|
||||
rxfifo_putchar(uart, ch);
|
||||
|
||||
uart_toggle_intr(uart);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&uart->mtx);
|
||||
}
|
||||
|
||||
void
|
||||
uart_write(struct uart_vdev *uart, int offset, uint8_t value)
|
||||
{
|
||||
int fifosz;
|
||||
uint8_t msr;
|
||||
|
||||
pthread_mutex_lock(&uart->mtx);
|
||||
|
||||
/*
|
||||
* Take care of the special case DLAB accesses first
|
||||
*/
|
||||
if ((uart->lcr & LCR_DLAB) != 0) {
|
||||
if (offset == REG_DLL) {
|
||||
uart->dll = value;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (offset == REG_DLH) {
|
||||
uart->dlh = value;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case REG_DATA:
|
||||
if (uart->mcr & MCR_LOOPBACK) {
|
||||
if (rxfifo_putchar(uart, value) != 0)
|
||||
uart->lsr |= LSR_OE;
|
||||
} else if (uart->tty.opened) {
|
||||
ttywrite(&uart->tty, value);
|
||||
} /* else drop on floor */
|
||||
uart->thre_int_pending = true;
|
||||
break;
|
||||
case REG_IER:
|
||||
/*
|
||||
* Apply mask so that bits 4-7 are 0
|
||||
* Also enables bits 0-3 only if they're 1
|
||||
*/
|
||||
uart->ier = value & 0x0F;
|
||||
break;
|
||||
case REG_FCR:
|
||||
/*
|
||||
* When moving from FIFO and 16450 mode and vice versa,
|
||||
* the FIFO contents are reset.
|
||||
*/
|
||||
if ((uart->fcr & FCR_ENABLE) ^ (value & FCR_ENABLE)) {
|
||||
fifosz = (value & FCR_ENABLE) ? FIFOSZ : 1;
|
||||
rxfifo_reset(uart, fifosz);
|
||||
}
|
||||
|
||||
/*
|
||||
* The FCR_ENABLE bit must be '1' for the programming
|
||||
* of other FCR bits to be effective.
|
||||
*/
|
||||
if ((value & FCR_ENABLE) == 0) {
|
||||
uart->fcr = 0;
|
||||
} else {
|
||||
if ((value & FCR_RCV_RST) != 0)
|
||||
rxfifo_reset(uart, FIFOSZ);
|
||||
|
||||
uart->fcr = value &
|
||||
(FCR_ENABLE | FCR_DMA | FCR_RX_MASK);
|
||||
}
|
||||
break;
|
||||
case REG_LCR:
|
||||
uart->lcr = value;
|
||||
break;
|
||||
case REG_MCR:
|
||||
/* Apply mask so that bits 5-7 are 0 */
|
||||
uart->mcr = value & 0x1F;
|
||||
msr = modem_status(uart->mcr);
|
||||
|
||||
/*
|
||||
* Detect if there has been any change between the
|
||||
* previous and the new value of MSR. If there is
|
||||
* then assert the appropriate MSR delta bit.
|
||||
*/
|
||||
if ((msr & MSR_CTS) ^ (uart->msr & MSR_CTS))
|
||||
uart->msr |= MSR_DCTS;
|
||||
if ((msr & MSR_DSR) ^ (uart->msr & MSR_DSR))
|
||||
uart->msr |= MSR_DDSR;
|
||||
if ((msr & MSR_DCD) ^ (uart->msr & MSR_DCD))
|
||||
uart->msr |= MSR_DDCD;
|
||||
if ((uart->msr & MSR_RI) != 0 && (msr & MSR_RI) == 0)
|
||||
uart->msr |= MSR_TERI;
|
||||
|
||||
/*
|
||||
* Update the value of MSR while retaining the delta
|
||||
* bits.
|
||||
*/
|
||||
uart->msr &= MSR_DELTA_MASK;
|
||||
uart->msr |= msr;
|
||||
break;
|
||||
case REG_LSR:
|
||||
/*
|
||||
* Line status register is not meant to be written to
|
||||
* during normal operation.
|
||||
*/
|
||||
break;
|
||||
case REG_MSR:
|
||||
/*
|
||||
* As far as I can tell MSR is a read-only register.
|
||||
*/
|
||||
break;
|
||||
case REG_SCR:
|
||||
uart->scr = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
uart_toggle_intr(uart);
|
||||
pthread_mutex_unlock(&uart->mtx);
|
||||
}
|
||||
|
||||
uint8_t
|
||||
uart_read(struct uart_vdev *uart, int offset)
|
||||
{
|
||||
uint8_t iir, intr_reason, reg;
|
||||
|
||||
pthread_mutex_lock(&uart->mtx);
|
||||
|
||||
/*
|
||||
* Take care of the special case DLAB accesses first
|
||||
*/
|
||||
if ((uart->lcr & LCR_DLAB) != 0) {
|
||||
if (offset == REG_DLL) {
|
||||
reg = uart->dll;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (offset == REG_DLH) {
|
||||
reg = uart->dlh;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case REG_DATA:
|
||||
reg = rxfifo_getchar(uart);
|
||||
break;
|
||||
case REG_IER:
|
||||
reg = uart->ier;
|
||||
break;
|
||||
case REG_IIR:
|
||||
iir = (uart->fcr & FCR_ENABLE) ? IIR_FIFO_MASK : 0;
|
||||
|
||||
intr_reason = uart_intr_reason(uart);
|
||||
|
||||
/*
|
||||
* Deal with side effects of reading the IIR register
|
||||
*/
|
||||
if (intr_reason == IIR_TXRDY)
|
||||
uart->thre_int_pending = false;
|
||||
|
||||
iir |= intr_reason;
|
||||
|
||||
reg = iir;
|
||||
break;
|
||||
case REG_LCR:
|
||||
reg = uart->lcr;
|
||||
break;
|
||||
case REG_MCR:
|
||||
reg = uart->mcr;
|
||||
break;
|
||||
case REG_LSR:
|
||||
/* Transmitter is always ready for more data */
|
||||
uart->lsr |= LSR_TEMT | LSR_THRE;
|
||||
|
||||
/* Check for new receive data */
|
||||
if (rxfifo_numchars(uart) > 0)
|
||||
uart->lsr |= LSR_RXRDY;
|
||||
else
|
||||
uart->lsr &= ~LSR_RXRDY;
|
||||
|
||||
reg = uart->lsr;
|
||||
|
||||
/* The LSR_OE bit is cleared on LSR read */
|
||||
uart->lsr &= ~LSR_OE;
|
||||
break;
|
||||
case REG_MSR:
|
||||
/*
|
||||
* MSR delta bits are cleared on read
|
||||
*/
|
||||
reg = uart->msr;
|
||||
uart->msr &= ~MSR_DELTA_MASK;
|
||||
break;
|
||||
case REG_SCR:
|
||||
reg = uart->scr;
|
||||
break;
|
||||
default:
|
||||
reg = 0xFF;
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
uart_toggle_intr(uart);
|
||||
pthread_mutex_unlock(&uart->mtx);
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
int
|
||||
uart_legacy_alloc(int which, int *baseaddr, int *irq)
|
||||
{
|
||||
if (which < 0 || which >= UART_NLDEVS || uart_lres[which].inuse)
|
||||
return -1;
|
||||
|
||||
uart_lres[which].inuse = true;
|
||||
*baseaddr = uart_lres[which].baseaddr;
|
||||
*irq = uart_lres[which].irq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
uart_legacy_dealloc(int which)
|
||||
{
|
||||
uart_lres[which].inuse = false;
|
||||
}
|
||||
|
||||
struct uart_vdev *
|
||||
uart_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert,
|
||||
void *arg)
|
||||
{
|
||||
struct uart_vdev *uart;
|
||||
|
||||
uart = calloc(1, sizeof(struct uart_vdev));
|
||||
|
||||
assert(uart != NULL);
|
||||
|
||||
uart->arg = arg;
|
||||
uart->intr_assert = intr_assert;
|
||||
uart->intr_deassert = intr_deassert;
|
||||
|
||||
pthread_mutex_init(&uart->mtx, NULL);
|
||||
|
||||
uart_reset(uart);
|
||||
|
||||
return uart;
|
||||
}
|
||||
|
||||
void
|
||||
uart_deinit(struct uart_vdev *uart)
|
||||
{
|
||||
if (uart) {
|
||||
if (uart->tty.opened && uart->tty.fd == STDIN_FILENO) {
|
||||
ttyclose();
|
||||
stdio_in_use = false;
|
||||
}
|
||||
free(uart);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
uart_tty_backend(struct uart_vdev *uart, const char *opts)
|
||||
{
|
||||
int fd;
|
||||
int retval;
|
||||
|
||||
retval = -1;
|
||||
|
||||
fd = open(opts, O_RDWR | O_NONBLOCK);
|
||||
if (fd > 0 && isatty(fd)) {
|
||||
uart->tty.fd = fd;
|
||||
uart->tty.opened = true;
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
uart_set_backend(struct uart_vdev *uart, const char *opts)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = -1;
|
||||
|
||||
if (opts == NULL)
|
||||
return 0;
|
||||
|
||||
if (strcmp("stdio", opts) == 0) {
|
||||
if (!stdio_in_use) {
|
||||
uart->tty.fd = STDIN_FILENO;
|
||||
uart->tty.opened = true;
|
||||
stdio_in_use = true;
|
||||
retval = 0;
|
||||
}
|
||||
} else if (uart_tty_backend(uart, opts) == 0) {
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
/* Make the backend file descriptor non-blocking */
|
||||
if (retval == 0)
|
||||
retval = fcntl(uart->tty.fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
if (retval == 0)
|
||||
uart_opentty(uart);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
uart_release_backend(struct uart_vdev *uart, const char *opts)
|
||||
{
|
||||
if (opts == NULL)
|
||||
return;
|
||||
|
||||
uart_closetty(uart);
|
||||
if (strcmp("stdio", opts) == 0) {
|
||||
stdio_in_use = false;
|
||||
} else
|
||||
close(uart->tty.fd);
|
||||
|
||||
uart->tty.fd = 0;
|
||||
uart->tty.opened = false;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*-
|
||||
* Copyright (c) 2014 Nahanni Systems Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/queue.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "usb_core.h"
|
||||
|
||||
SET_DECLARE(usb_emu_set, struct usb_devemu);
|
||||
|
||||
struct usb_devemu *
|
||||
usb_emu_finddev(char *name)
|
||||
{
|
||||
struct usb_devemu **udpp, *udp;
|
||||
|
||||
SET_FOREACH(udpp, usb_emu_set) {
|
||||
udp = *udpp;
|
||||
if (!strcmp(udp->ue_emu, name))
|
||||
return udp;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct usb_data_xfer_block *
|
||||
usb_data_xfer_append(struct usb_data_xfer *xfer, void *buf, int blen,
|
||||
void *hci_data, int ccs)
|
||||
{
|
||||
struct usb_data_xfer_block *xb;
|
||||
|
||||
if (xfer->ndata >= USB_MAX_XFER_BLOCKS)
|
||||
return NULL;
|
||||
|
||||
xb = &xfer->data[xfer->tail];
|
||||
xb->buf = buf;
|
||||
xb->blen = blen;
|
||||
xb->hci_data = hci_data;
|
||||
xb->ccs = ccs;
|
||||
xb->processed = 0;
|
||||
xb->bdone = 0;
|
||||
xfer->ndata++;
|
||||
xfer->tail = (xfer->tail + 1) % USB_MAX_XFER_BLOCKS;
|
||||
return xb;
|
||||
}
|
||||
Reference in New Issue
Block a user