da99969eb7
base and size parameters to ofs and len (resp). Add a new busdma_sync() that makes the entire MD coherent.
559 lines
12 KiB
C
559 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2015 Marcel Moolenaar
|
|
* 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 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>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "busdma.h"
|
|
|
|
#include "../../sys/dev/proto/proto_dev.h"
|
|
|
|
struct obj {
|
|
int oid;
|
|
u_int type;
|
|
#define OBJ_TYPE_NONE 0
|
|
#define OBJ_TYPE_TAG 1
|
|
#define OBJ_TYPE_MD 2
|
|
#define OBJ_TYPE_SEG 3
|
|
u_int refcnt;
|
|
int fd;
|
|
struct obj *parent;
|
|
u_long key;
|
|
union {
|
|
struct {
|
|
unsigned long align;
|
|
unsigned long bndry;
|
|
unsigned long maxaddr;
|
|
unsigned long maxsz;
|
|
unsigned long maxsegsz;
|
|
unsigned long nsegs;
|
|
unsigned long datarate;
|
|
} tag;
|
|
struct {
|
|
struct obj *seg[3];
|
|
int nsegs[3];
|
|
#define BUSDMA_MD_BUS 0
|
|
#define BUSDMA_MD_PHYS 1
|
|
#define BUSDMA_MD_VIRT 2
|
|
} md;
|
|
struct {
|
|
struct obj *next;
|
|
unsigned long address;
|
|
unsigned long size;
|
|
} seg;
|
|
} u;
|
|
};
|
|
|
|
static struct obj **oidtbl = NULL;
|
|
static int noids = 0;
|
|
|
|
static struct obj *
|
|
obj_alloc(u_int type)
|
|
{
|
|
struct obj **newtbl, *obj;
|
|
int oid;
|
|
|
|
obj = calloc(1, sizeof(struct obj));
|
|
obj->type = type;
|
|
|
|
for (oid = 0; oid < noids; oid++) {
|
|
if (oidtbl[oid] == 0)
|
|
break;
|
|
}
|
|
if (oid == noids) {
|
|
newtbl = realloc(oidtbl, sizeof(struct obj *) * (noids + 1));
|
|
if (newtbl == NULL) {
|
|
free(obj);
|
|
return (NULL);
|
|
}
|
|
oidtbl = newtbl;
|
|
noids++;
|
|
}
|
|
oidtbl[oid] = obj;
|
|
obj->oid = oid;
|
|
return (obj);
|
|
}
|
|
|
|
static int
|
|
obj_free(struct obj *obj)
|
|
{
|
|
|
|
oidtbl[obj->oid] = NULL;
|
|
free(obj);
|
|
return (0);
|
|
}
|
|
|
|
static struct obj *
|
|
obj_lookup(int oid, u_int type)
|
|
{
|
|
struct obj *obj;
|
|
|
|
if (oid < 0 || oid >= noids) {
|
|
errno = EINVAL;
|
|
return (NULL);
|
|
}
|
|
obj = oidtbl[oid];
|
|
if (obj->refcnt == 0) {
|
|
errno = ENXIO;
|
|
return (NULL);
|
|
}
|
|
if (type != OBJ_TYPE_NONE && obj->type != type) {
|
|
errno = ENODEV;
|
|
return (NULL);
|
|
}
|
|
return (obj);
|
|
}
|
|
|
|
static struct obj *
|
|
bd_tag_new(struct obj *ptag, int fd, u_long align, u_long bndry,
|
|
u_long maxaddr, u_long maxsz, u_int nsegs, u_long maxsegsz,
|
|
u_int datarate, u_int flags)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *tag;
|
|
|
|
tag = obj_alloc(OBJ_TYPE_TAG);
|
|
if (tag == NULL)
|
|
return (NULL);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = (ptag != NULL) ? PROTO_IOC_BUSDMA_TAG_DERIVE :
|
|
PROTO_IOC_BUSDMA_TAG_CREATE;
|
|
ioc.key = (ptag != NULL) ? ptag->key : 0;
|
|
ioc.u.tag.align = align;
|
|
ioc.u.tag.bndry = bndry;
|
|
ioc.u.tag.maxaddr = maxaddr;
|
|
ioc.u.tag.maxsz = maxsz;
|
|
ioc.u.tag.nsegs = nsegs;
|
|
ioc.u.tag.maxsegsz = maxsegsz;
|
|
ioc.u.tag.datarate = datarate;
|
|
ioc.u.tag.flags = flags;
|
|
if (ioctl(fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
|
|
obj_free(tag);
|
|
return (NULL);
|
|
}
|
|
tag->refcnt = 1;
|
|
tag->fd = fd;
|
|
tag->parent = ptag;
|
|
tag->key = ioc.result;
|
|
tag->u.tag.align = ioc.u.tag.align;
|
|
tag->u.tag.bndry = ioc.u.tag.bndry;
|
|
tag->u.tag.maxaddr = ioc.u.tag.maxaddr;
|
|
tag->u.tag.maxsz = ioc.u.tag.maxsz;
|
|
tag->u.tag.maxsegsz = ioc.u.tag.maxsegsz;
|
|
tag->u.tag.nsegs = ioc.u.tag.nsegs;
|
|
tag->u.tag.datarate = ioc.u.tag.datarate;
|
|
return (tag);
|
|
}
|
|
|
|
int
|
|
bd_tag_create(const char *dev, u_long align, u_long bndry, u_long maxaddr,
|
|
u_long maxsz, u_int nsegs, u_long maxsegsz, u_int datarate, u_int flags)
|
|
{
|
|
char path[PATH_MAX];
|
|
struct obj *tag;
|
|
int fd, len;
|
|
|
|
len = snprintf(path, PATH_MAX, "/dev/proto/%s/busdma", dev);
|
|
if (len >= PATH_MAX) {
|
|
errno = EINVAL;
|
|
return (-1);
|
|
}
|
|
fd = open(path, O_RDWR);
|
|
if (fd == -1)
|
|
return (-1);
|
|
|
|
tag = bd_tag_new(NULL, fd, align, bndry, maxaddr, maxsz, nsegs,
|
|
maxsegsz, datarate, flags);
|
|
if (tag == NULL) {
|
|
close(fd);
|
|
return (-1);
|
|
}
|
|
return (tag->oid);
|
|
}
|
|
|
|
int
|
|
bd_tag_derive(int ptid, u_long align, u_long bndry, u_long maxaddr,
|
|
u_long maxsz, u_int nsegs, u_long maxsegsz, u_int datarate, u_int flags)
|
|
{
|
|
struct obj *ptag, *tag;
|
|
|
|
ptag = obj_lookup(ptid, OBJ_TYPE_TAG);
|
|
if (ptag == NULL)
|
|
return (-1);
|
|
|
|
tag = bd_tag_new(ptag, ptag->fd, align, bndry, maxaddr, maxsz, nsegs,
|
|
maxsegsz, datarate, flags);
|
|
if (tag == NULL)
|
|
return (-1);
|
|
ptag->refcnt++;
|
|
return (tag->oid);
|
|
}
|
|
|
|
int
|
|
bd_tag_destroy(int tid)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *ptag, *tag;
|
|
|
|
tag = obj_lookup(tid, OBJ_TYPE_TAG);
|
|
if (tag == NULL)
|
|
return (errno);
|
|
if (tag->refcnt > 1)
|
|
return (EBUSY);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_TAG_DESTROY;
|
|
ioc.key = tag->key;
|
|
if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
if (tag->parent != NULL)
|
|
tag->parent->refcnt--;
|
|
else
|
|
close(tag->fd);
|
|
obj_free(tag);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bd_md_add_seg(struct obj *md, int type, u_long addr, u_long size)
|
|
{
|
|
struct obj *seg;
|
|
|
|
seg = obj_alloc(OBJ_TYPE_SEG);
|
|
if (seg == NULL)
|
|
return (errno);
|
|
seg->refcnt = 1;
|
|
seg->parent = md;
|
|
seg->u.seg.address = addr;
|
|
seg->u.seg.size = size;
|
|
|
|
md->u.md.seg[type] = seg;
|
|
md->u.md.nsegs[type] = 1;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bd_md_del_segs(struct obj *md, int type, int unmap)
|
|
{
|
|
struct obj *seg, *seg0;
|
|
|
|
for (seg = md->u.md.seg[type]; seg != NULL; seg = seg0) {
|
|
if (unmap)
|
|
munmap((void *)seg->u.seg.address, seg->u.seg.size);
|
|
seg0 = seg->u.seg.next;
|
|
obj_free(seg);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_md_create(int tid, u_int flags)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md, *tag;
|
|
|
|
tag = obj_lookup(tid, OBJ_TYPE_TAG);
|
|
if (tag == NULL)
|
|
return (-1);
|
|
|
|
md = obj_alloc(OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (-1);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MD_CREATE;
|
|
ioc.u.md.tag = tag->key;
|
|
ioc.u.md.flags = flags;
|
|
if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
|
|
obj_free(md);
|
|
return (-1);
|
|
}
|
|
|
|
md->refcnt = 1;
|
|
md->fd = tag->fd;
|
|
md->parent = tag;
|
|
tag->refcnt++;
|
|
md->key = ioc.result;
|
|
return (md->oid);
|
|
}
|
|
|
|
int
|
|
bd_md_destroy(int mdid)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (errno);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MD_DESTROY;
|
|
ioc.key = md->key;
|
|
if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
md->parent->refcnt--;
|
|
obj_free(md);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_md_load(int mdid, void *buf, u_long len, u_int flags)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md;
|
|
int error;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (errno);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MD_LOAD;
|
|
ioc.key = md->key;
|
|
ioc.u.md.flags = flags;
|
|
ioc.u.md.virt_addr = (uintptr_t)buf;
|
|
ioc.u.md.virt_size = len;
|
|
if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
error = bd_md_add_seg(md, BUSDMA_MD_VIRT, ioc.u.md.virt_addr, len);
|
|
error = bd_md_add_seg(md, BUSDMA_MD_PHYS, ioc.u.md.phys_addr, len);
|
|
error = bd_md_add_seg(md, BUSDMA_MD_BUS, ioc.u.md.bus_addr, len);
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
bd_md_unload(int mdid)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md;
|
|
int error;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (errno);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MD_UNLOAD;
|
|
ioc.key = md->key;
|
|
if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
bd_md_del_segs(md, BUSDMA_MD_VIRT, 0);
|
|
bd_md_del_segs(md, BUSDMA_MD_PHYS, 0);
|
|
bd_md_del_segs(md, BUSDMA_MD_BUS, 0);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_mem_alloc(int tid, u_int flags)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md, *tag;
|
|
uintptr_t addr;
|
|
int error;
|
|
|
|
tag = obj_lookup(tid, OBJ_TYPE_TAG);
|
|
if (tag == NULL)
|
|
return (-1);
|
|
|
|
md = obj_alloc(OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (-1);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MEM_ALLOC;
|
|
ioc.u.md.tag = tag->key;
|
|
ioc.u.md.flags = flags;
|
|
if (ioctl(tag->fd, PROTO_IOC_BUSDMA, &ioc) == -1) {
|
|
obj_free(md);
|
|
return (-1);
|
|
}
|
|
|
|
md->refcnt = 1;
|
|
md->fd = tag->fd;
|
|
md->parent = tag;
|
|
tag->refcnt++;
|
|
md->key = ioc.result;
|
|
|
|
/* XXX we need to support multiple segments */
|
|
assert(ioc.u.md.phys_nsegs == 1);
|
|
assert(ioc.u.md.bus_nsegs == 1);
|
|
error = bd_md_add_seg(md, BUSDMA_MD_PHYS, ioc.u.md.phys_addr,
|
|
tag->u.tag.maxsz);
|
|
error = bd_md_add_seg(md, BUSDMA_MD_BUS, ioc.u.md.bus_addr,
|
|
tag->u.tag.maxsz);
|
|
|
|
addr = (uintptr_t)mmap(NULL, tag->u.tag.maxsz, PROT_READ | PROT_WRITE,
|
|
MAP_NOCORE | MAP_SHARED, md->fd, ioc.u.md.phys_addr);
|
|
if (addr == (uintptr_t)MAP_FAILED)
|
|
goto fail;
|
|
error = bd_md_add_seg(md, BUSDMA_MD_VIRT, addr, tag->u.tag.maxsz);
|
|
|
|
return (md->oid);
|
|
|
|
fail:
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MEM_FREE;
|
|
ioc.key = md->key;
|
|
ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc);
|
|
md->parent->refcnt--;
|
|
obj_free(md);
|
|
return (-1);
|
|
}
|
|
|
|
int
|
|
bd_mem_free(int mdid)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (errno);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_MEM_FREE;
|
|
ioc.key = md->key;
|
|
if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
bd_md_del_segs(md, BUSDMA_MD_VIRT, 1);
|
|
bd_md_del_segs(md, BUSDMA_MD_PHYS, 0);
|
|
bd_md_del_segs(md, BUSDMA_MD_BUS, 0);
|
|
md->parent->refcnt--;
|
|
obj_free(md);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_md_first_seg(int mdid, int space)
|
|
{
|
|
struct obj *md, *seg;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (-1);
|
|
|
|
if (space != BUSDMA_MD_BUS && space != BUSDMA_MD_PHYS &&
|
|
space != BUSDMA_MD_VIRT) {
|
|
errno = EINVAL;
|
|
return (-1);
|
|
}
|
|
seg = md->u.md.seg[space];
|
|
if (seg == NULL) {
|
|
errno = ENXIO;
|
|
return (-1);
|
|
}
|
|
return (seg->oid);
|
|
}
|
|
|
|
int
|
|
bd_md_next_seg(int mdid, int sid)
|
|
{
|
|
struct obj *seg;
|
|
|
|
seg = obj_lookup(sid, OBJ_TYPE_SEG);
|
|
if (seg == NULL)
|
|
return (-1);
|
|
|
|
seg = seg->u.seg.next;
|
|
if (seg == NULL) {
|
|
errno = ENXIO;
|
|
return (-1);
|
|
}
|
|
return (seg->oid);
|
|
}
|
|
|
|
int
|
|
bd_seg_get_addr(int sid, u_long *addr_p)
|
|
{
|
|
struct obj *seg;
|
|
|
|
if (addr_p == NULL)
|
|
return (EINVAL);
|
|
|
|
seg = obj_lookup(sid, OBJ_TYPE_SEG);
|
|
if (seg == NULL)
|
|
return (errno);
|
|
|
|
*addr_p = seg->u.seg.address;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_seg_get_size(int sid, u_long *size_p)
|
|
{
|
|
struct obj *seg;
|
|
|
|
if (size_p == NULL)
|
|
return (EINVAL);
|
|
|
|
seg = obj_lookup(sid, OBJ_TYPE_SEG);
|
|
if (seg == NULL)
|
|
return (errno);
|
|
|
|
*size_p = seg->u.seg.size;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
bd_sync(int mdid, u_int op, u_long ofs, u_long len)
|
|
{
|
|
struct proto_ioc_busdma ioc;
|
|
struct obj *md;
|
|
|
|
md = obj_lookup(mdid, OBJ_TYPE_MD);
|
|
if (md == NULL)
|
|
return (errno);
|
|
|
|
memset(&ioc, 0, sizeof(ioc));
|
|
ioc.request = PROTO_IOC_BUSDMA_SYNC;
|
|
ioc.key = md->key;
|
|
ioc.u.sync.op = op;
|
|
ioc.u.sync.base = ofs;
|
|
ioc.u.sync.size = len;
|
|
if (ioctl(md->fd, PROTO_IOC_BUSDMA, &ioc) == -1)
|
|
return (errno);
|
|
|
|
return (0);
|
|
}
|