Commit 9db373ba authored by Alessandro Rubini's avatar Alessandro Rubini

Merge branch 'sdb-for-all'

parents 32dd97e9 42e0449f
......@@ -204,10 +204,11 @@ config UART_SW
and diagnostics run on the hardware UART if available.
config SDB_EEPROM
depends on DEVELOPER && W1
default y
boolean "Use SDB to manage EEPROM (instead of legacy code)"
help
New experimental code to unify W1 and EEPROM storage
Use SDB to manage eeproms, both W1 and I2C. If not, legacy code
will be selected.
config LEGACY_EEPROM
boolean
......
......@@ -6,7 +6,7 @@ obj-$(CONFIG_WR_NODE) += \
dev/pps_gen.o \
dev/syscon.o \
dev/sfp.o \
dev/sdb.o \
dev/devicelist.o \
dev/rxts_calibrator.o
obj-$(CONFIG_WR_SWITCH) += dev/timer-wrs.o dev/ad9516.o
......
/*
* This work is part of the White Rabbit project
*
* Copyright (C) 2014 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released according to the GNU GPL, version 2 or any later version.
*/
#include <wrc.h>
#define SDBFS_BIG_ENDIAN
#include <libsdbfs.h>
/* The following pointers are exported */
unsigned char *BASE_MINIC;
unsigned char *BASE_EP;
unsigned char *BASE_SOFTPLL;
unsigned char *BASE_PPS_GEN;
unsigned char *BASE_SYSCON;
unsigned char *BASE_UART;
unsigned char *BASE_ONEWIRE;
unsigned char *BASE_ETHERBONE_CFG;
/* The sdb filesystem itself */
static struct sdbfs wrc_fpga_sdb = {
.name = "fpga-area",
.blocksize = 1, /* Not currently used */
.entrypoint = SDB_ADDRESS,
.data = 0,
.flags = SDBFS_F_ZEROBASED,
};
/* called by outside, at boot time */
void sdb_print_devices(void)
{
struct sdb_device *d;
int new = 1;
while ( (d = sdbfs_scan(&wrc_fpga_sdb, new)) != NULL) {
/*
* "%.19s" is not working for XINT printf, and zeroing
* d->sdb_component.product.record_type won't work, as
* the device is read straight from fpga ROM registers
*/
const int namesize = sizeof(d->sdb_component.product.name);
char name[namesize + 1];
memcpy(name, d->sdb_component.product.name, sizeof(name));
name[namesize] = '\0';
pp_printf("dev 0x%08lx @ %06lx, %s\n",
(long)(d->sdb_component.product.device_id),
wrc_fpga_sdb.f_offset, name);
new = 0;
}
}
/* To save a little size, we enumerate our vendors */
#define VID_CERN 0x0000ce42LL
#define VID_GSI 0x00000651LL
struct wrc_device {
unsigned char **base;
uint64_t vid;
uint32_t did;
};
struct wrc_device devs[] = {
{&BASE_MINIC, VID_CERN, 0xab28633a},
{&BASE_EP, VID_CERN, 0x650c2d4f},
{&BASE_SOFTPLL, VID_CERN, 0x65158dc0},
{&BASE_PPS_GEN, VID_CERN, 0xde0d8ced},
{&BASE_SYSCON, VID_CERN, 0xff07fc47},
{&BASE_UART, VID_CERN, 0xe2d13d04},
{&BASE_ONEWIRE, VID_CERN, 0x779c5443},
{&BASE_ETHERBONE_CFG, VID_GSI, 0x68202b22},
};
void sdb_find_devices(void)
{
struct wrc_device *d;
static int done;
int i;
if (!done) {
sdbfs_dev_create(&wrc_fpga_sdb);
done++;
}
for (d = devs, i = 0; i < ARRAY_SIZE(devs); d++, i++) {
*(d->base) = (void *)sdbfs_find_id(&wrc_fpga_sdb,
d->vid, d->did);
}
}
......@@ -12,8 +12,8 @@
#include <w1.h>
#include <eeprom.h>
//#include "types.h"
//#include "i2c.h"
#include "types.h"
#include "i2c.h"
//#include "eeprom.h"
//#include "board.h"
//#include "syscon.h"
......@@ -43,8 +43,72 @@ static int sdb_w1_write(struct sdbfs *fs, int offset, void *buf, int count)
return w1_write_eeprom_bus(fs->drvdata, offset, buf, count);
}
/* The methods for I2C access -- FIXME */
/*
* I2C code.
* The functions in ./eeprom.c (legacy) are replicated here with the sdb
* calling convention. So we miss the low-level and high-level layer splitting
*/
struct i2c_params {
int ifnum;
int addr;
};
static struct i2c_params i2c_params;
static int sdb_i2c_read(struct sdbfs *fs, int offset, void *buf, int count)
{
int i;
struct i2c_params *p = fs->drvdata;
unsigned char *cb = buf;
mi2c_start(p->ifnum);
if (mi2c_put_byte(p->ifnum, p->addr << 1) < 0) {
mi2c_stop(p->ifnum);
return -1;
}
mi2c_put_byte(p->ifnum, (offset >> 8) & 0xff);
mi2c_put_byte(p->ifnum, offset & 0xff);
mi2c_repeat_start(p->ifnum);
mi2c_put_byte(p->ifnum, (p->addr << 1) | 1);
for (i = 0; i < count - 1; ++i) {
mi2c_get_byte(p->ifnum, cb, 0);
cb++;
}
mi2c_get_byte(p->ifnum, cb, 1);
cb++;
mi2c_stop(p->ifnum);
return count;
}
static int sdb_i2c_write(struct sdbfs *fs, int offset, void *buf, int count)
{
int i, busy;
struct i2c_params *p = fs->drvdata;
unsigned char *cb = buf;
for (i = 0; i < count; i++) {
mi2c_start(p->ifnum);
if (mi2c_put_byte(p->ifnum, p->addr << 1) < 0) {
mi2c_stop(p->ifnum);
return -1;
}
mi2c_put_byte(p->ifnum, (offset >> 8) & 0xff);
mi2c_put_byte(p->ifnum, offset & 0xff);
mi2c_put_byte(p->ifnum, *cb++);
offset++;
mi2c_stop(p->ifnum);
do { /* wait until the chip becomes ready */
mi2c_start(p->ifnum);
busy = mi2c_put_byte(p->ifnum, p->addr << 1);
mi2c_stop(p->ifnum);
} while (busy);
}
return count;
}
/*
* A trivial dumper, just to show what's up in there
......@@ -62,11 +126,13 @@ static void eeprom_sdb_list(struct sdbfs *fs)
new = 0;
}
}
/* The sdb filesystem itself */
/* The sdb filesystem itself, build-time initialized for i2c */
static struct sdbfs wrc_sdb = {
.name = "wrpc-storage",
.name = "eeprom",
.blocksize = 1, /* Not currently used */
/* .read and .write according to device type */
.drvdata = &i2c_params,
.read = sdb_i2c_read,
.write = sdb_i2c_write,
};
uint8_t has_eeprom = 0; /* modified at init time */
......@@ -82,7 +148,7 @@ uint8_t eeprom_present(uint8_t i2cif, uint8_t i2c_addr)
static unsigned entry_points[] = {0, 64, 128, 256, 512, 1024};
int i, ret;
/* Look for w1 first: if there is no eeprom if fails fast */
/* Look for w1 first: if there is no eeprom it fails fast */
for (i = 0; i < ARRAY_SIZE(entry_points); i++) {
ret = w1_read_eeprom_bus(&wrpc_w1_bus, entry_points[i],
(void *)&magic, sizeof(magic));
......@@ -93,21 +159,40 @@ uint8_t eeprom_present(uint8_t i2cif, uint8_t i2c_addr)
}
if (magic == SDB_MAGIC) {
pp_printf("sdbfs: found at %i in W1\n", entry_points[i]);
/* override default i2c settings with w1 ones */
wrc_sdb.drvdata = &wrpc_w1_bus;
wrc_sdb.read = sdb_w1_read;
wrc_sdb.write = sdb_w1_write;
has_eeprom = 1;
eeprom_sdb_list(&wrc_sdb);
return 0;
goto found_exit;
}
/*
* If w1 failed, look for i2c: start from high offsets by now.
* FIXME: this is a hack, until we have subdirectory support
* If w1 failed, look for i2c: start from low offsets.
*/
for (i = ARRAY_SIZE(entry_points) - i; i >= 0; i--) {
/* FIXME: i2c */
if (!mi2c_devprobe(i2cif, i2c_addr))
return 0;
i2c_params.ifnum = i2cif;
i2c_params.addr = i2c_addr;
/* While looking for the magic number, use sdb-based read function */
for (i = 0; i < ARRAY_SIZE(entry_points); i++) {
uint32_t magic;
sdb_i2c_read(&wrc_sdb, entry_points[i], (void *)&magic,
sizeof(magic));
if (magic == SDB_MAGIC)
break;
}
if (i == ARRAY_SIZE(entry_points)) {
pp_printf("No SDB filesystem in i2c eeprom\n");
return 0;
}
found_exit:
/* found: register the filesystem */
has_eeprom = 1;
sdbfs_dev_create(&wrc_sdb);
eeprom_sdb_list(&wrc_sdb);
return 0;
}
......
/*
* This work is part of the White Rabbit project
*
* Copyright (C) 2012 GSI (www.gsi.de)
* Author: Wesley W. Terpstra <w.terpstra@gsi.de>
*
* Released according to the GNU GPL, version 2 or any later version.
*/
#include <string.h>
#include <wrc.h>
#include "hw/memlayout.h"
unsigned char *BASE_MINIC;
unsigned char *BASE_EP;
unsigned char *BASE_SOFTPLL;
unsigned char *BASE_PPS_GEN;
unsigned char *BASE_SYSCON;
unsigned char *BASE_UART;
unsigned char *BASE_ONEWIRE;
unsigned char *BASE_ETHERBONE_CFG;
#define SDB_INTERCONNET 0x00
#define SDB_DEVICE 0x01
#define SDB_BRIDGE 0x02
#define SDB_EMPTY 0xFF
typedef struct pair64 {
uint32_t high;
uint32_t low;
} pair64_t;
struct sdb_empty {
int8_t reserved[63];
uint8_t record_type;
};
struct sdb_product {
pair64_t vendor_id;
uint32_t device_id;
uint32_t version;
uint32_t date;
int8_t name[19];
uint8_t record_type;
};
struct sdb_component {
pair64_t addr_first;
pair64_t addr_last;
struct sdb_product product;
};
struct sdb_device {
uint16_t abi_class;
uint8_t abi_ver_major;
uint8_t abi_ver_minor;
uint32_t bus_specific;
struct sdb_component sdb_component;
};
struct sdb_bridge {
pair64_t sdb_child;
struct sdb_component sdb_component;
};
struct sdb_interconnect {
uint32_t sdb_magic;
uint16_t sdb_records;
uint8_t sdb_version;
uint8_t sdb_bus_type;
struct sdb_component sdb_component;
};
typedef union sdb_record {
struct sdb_empty empty;
struct sdb_device device;
struct sdb_bridge bridge;
struct sdb_interconnect interconnect;
} sdb_record_t;
static unsigned char *find_device_deep(unsigned int base, unsigned int sdb,
unsigned int devid)
{
sdb_record_t *record = (sdb_record_t *) sdb;
int records = record->interconnect.sdb_records;
int i;
for (i = 0; i < records; ++i, ++record) {
if (record->empty.record_type == SDB_BRIDGE) {
unsigned char *out =
find_device_deep(base +
record->bridge.sdb_component.
addr_first.low,
base +
record->bridge.sdb_child.low,
devid);
if (out)
return out;
}
if (record->empty.record_type == SDB_DEVICE &&
record->device.sdb_component.product.device_id == devid) {
break;
}
}
if (i == records)
return 0;
return (unsigned char *)(base +
record->device.sdb_component.addr_first.low);
}
static void print_devices_deep(unsigned int base, unsigned int sdb)
{
sdb_record_t *record = (sdb_record_t *) sdb;
int records = record->interconnect.sdb_records;
int i;
char buf[20];
for (i = 0; i < records; ++i, ++record) {
if (record->empty.record_type == SDB_BRIDGE)
print_devices_deep(base +
record->bridge.sdb_component.
addr_first.low,
base +
record->bridge.sdb_child.low);
if (record->empty.record_type != SDB_DEVICE)
continue;
memcpy(buf, record->device.sdb_component.product.name, 19);
buf[19] = 0;
mprintf("%8x:%8x 0x%8x %s\n",
record->device.sdb_component.product.vendor_id.low,
record->device.sdb_component.product.device_id,
base + record->device.sdb_component.addr_first.low,
buf);
}
}
static unsigned char *find_device(unsigned int devid)
{
return find_device_deep(0, SDB_ADDRESS, devid);
}
void sdb_print_devices(void)
{
mprintf("SDB memory map:\n");
print_devices_deep(0, SDB_ADDRESS);
mprintf("---\n");
}
void sdb_find_devices(void)
{
BASE_MINIC = find_device(0xab28633a);
BASE_EP = find_device(0x650c2d4f);
BASE_SOFTPLL = find_device(0x65158dc0);
BASE_PPS_GEN = find_device(0xde0d8ced);
BASE_SYSCON = find_device(0xff07fc47);
BASE_UART = find_device(0xe2d13d04);
BASE_ONEWIRE = find_device(0x779c5443);
BASE_ETHERBONE_CFG = find_device(0x68202b22);
}
......@@ -21,7 +21,7 @@ int sdbfs_fstat(struct sdbfs *fs, struct sdb_device *record_return)
int sdbfs_fread(struct sdbfs *fs, int offset, void *buf, int count)
{
int ret = count;
int ret;
if (!fs->currentp)
return -ENOENT;
......@@ -29,6 +29,7 @@ int sdbfs_fread(struct sdbfs *fs, int offset, void *buf, int count)
offset = fs->read_offset;
if (offset + count > fs->f_len)
count = fs->f_len - offset;
ret = count;
if (fs->data)
memcpy(buf, fs->data + fs->f_offset + offset, count);
else
......@@ -40,7 +41,7 @@ int sdbfs_fread(struct sdbfs *fs, int offset, void *buf, int count)
int sdbfs_fwrite(struct sdbfs *fs, int offset, void *buf, int count)
{
int ret = count;
int ret;
if (!fs->currentp)
return -ENOENT;
......@@ -48,6 +49,7 @@ int sdbfs_fwrite(struct sdbfs *fs, int offset, void *buf, int count)
offset = fs->read_offset;
if (offset + count > fs->f_len)
count = fs->f_len - offset;
ret = count;
if (fs->data)
memcpy(buf, fs->data + fs->f_offset + offset, count);
else
......
/*
* Copyright (C) 2012 CERN (www.cern.ch)
* Copyright (C) 2012,2014 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released according to the GNU GPL, version 2 or any later version.
......@@ -14,21 +14,24 @@
static struct sdbfs *sdbfs_list;
/* All fields unused by the caller are expected to be zeroed */
int sdbfs_dev_create(struct sdbfs *fs, int verbose)
int sdbfs_dev_create(struct sdbfs *fs)
{
unsigned int magic;
/* First, check we have the magic */
if (fs->data)
if (fs->data || (fs->flags & SDBFS_F_ZEROBASED))
magic = *(unsigned int *)(fs->data + fs->entrypoint);
else
fs->read(fs, fs->entrypoint, &magic, sizeof(magic));
if (htonl(magic) != SDB_MAGIC)
if (magic == SDB_MAGIC) {
/* Uh! If we are little-endian, we must convert */
if (ntohl(1) != 1)
fs->flags |= SDBFS_F_CONVERT32;
} else if (htonl(magic) == SDB_MAGIC) {
/* ok, don't convert */
} else {
return -ENOTDIR;
if (verbose)
fs->flags |= SDBFS_F_VERBOSE;
}
fs->next = sdbfs_list;
sdbfs_list = fs;
......@@ -70,47 +73,120 @@ static struct sdb_device *sdbfs_readentry(struct sdbfs *fs,
/*
* This function reads an entry from a known good offset. It
* returns the pointer to the entry, which may be stored in
* the fs structure itself. Only touches fs->current_record
* the fs structure itself. Only touches fs->current_record.
*/
if (fs->data)
if (fs->data || (fs->flags & SDBFS_F_ZEROBASED)) {
if (!(fs->flags & SDBFS_F_CONVERT32))
return (struct sdb_device *)(fs->data + offset);
/* copy to local storage for conversion */
memcpy(&fs->current_record, fs->data + offset,
sizeof(fs->current_record));
} else {
if (!fs->read)
return NULL;
fs->read(fs, offset, &fs->current_record, sizeof(fs->current_record));
fs->read(fs, offset, &fs->current_record,
sizeof(fs->current_record));
}
if (fs->flags & SDBFS_F_CONVERT32) {
uint32_t *p = (void *)&fs->current_record;
int i;
for (i = 0; i < sizeof(fs->current_record) / sizeof(*p); i++)
p[i] = ntohl(p[i]);
}
return &fs->current_record;
}
/* Helper for scanning: we enter a new directory, and we must validate */
static struct sdb_device *scan_newdir(struct sdbfs *fs, int depth)
{
struct sdb_device *dev;
struct sdb_interconnect *intercon;
dev = fs->currentp = sdbfs_readentry(fs, fs->this[depth]);
if (dev->sdb_component.product.record_type != sdb_type_interconnect)
return NULL;
intercon = (typeof(intercon))dev;
if (ntohl(intercon->sdb_magic) != SDB_MAGIC)
return NULL;
fs->nleft[depth] = ntohs(intercon->sdb_records) - 1;
fs->this[depth] += sizeof(*intercon);
fs->depth = depth;
return dev;
}
struct sdb_device *sdbfs_scan(struct sdbfs *fs, int newscan)
{
/*
* This returns a pointer to the next sdb record, or a new one.
* Subdirectories are not supported. Uses all internal fields
* This returns a pointer to the next sdb record, or the first one.
* Subdirectories (bridges) are returned before their contents.
* It only uses internal fields.
*/
struct sdb_device *ret;
struct sdb_interconnect *i;
struct sdb_device *dev;
struct sdb_bridge *bridge;
int depth, type, newdir = 0; /* check there's the magic */
if (newscan) {
fs->f_offset = fs->entrypoint;
} else {
fs->f_offset += sizeof(struct sdb_device);
if (!fs->nleft)
return NULL;
fs->base[0] = 0;
fs->this[0] = fs->entrypoint;
depth = fs->depth = 0;
newdir = 1;
goto scan;
}
ret = sdbfs_readentry(fs, fs->f_offset);
if (newscan) {
i = (typeof(i))ret;
fs->nleft = ntohs(i->sdb_records) - 1;
} else {
fs->nleft--;
/* If we already returned a bridge, go inside it (check type) */
depth = fs->depth;
type = fs->currentp->sdb_component.product.record_type;
if (type == sdb_type_bridge && depth + 1 < SDBFS_DEPTH) {
bridge = (typeof(bridge))fs->currentp;
fs->this[depth + 1] = fs->base[depth]
+ ntohll(bridge->sdb_child);
fs->base[depth + 1] = fs->base[depth]
+ ntohll(bridge->sdb_component.addr_first);
depth++;
newdir++;
}
scan:
/* If entering a new directory, verify magic and set nleft */
if (newdir) {
dev = scan_newdir(fs, depth);
if (dev)
goto out;
/* Otherwise the directory is not there: no intercon */
if (!depth)
return NULL; /* no entries at all */
depth--;
}
return ret;
while (fs->nleft[depth] == 0) {
/* No more at this level, "cd .." if possible */
if (!depth)
return NULL;
fs->depth = --depth;
}
/* so, read the next entry */
dev = fs->currentp = sdbfs_readentry(fs, fs->this[depth]);
fs->this[depth] += sizeof(*dev);
fs->nleft[depth]--;
out:
fs->f_offset = fs->base[fs->depth]
+ htonll(fs->currentp->sdb_component.addr_first);
return dev;
}
static void __open(struct sdbfs *fs)
{
fs->f_offset = htonll(fs->currentp->sdb_component.addr_first);
fs->f_offset = fs->base[fs->depth]
+ htonll(fs->currentp->sdb_component.addr_first);
fs->f_len = htonll(fs->currentp->sdb_component.addr_last)
+ 1 - fs->f_offset;
+ 1 - htonll(fs->currentp->sdb_component.addr_first);
fs->read_offset = 0;
}
......@@ -157,3 +233,31 @@ int sdbfs_close(struct sdbfs *fs)
return 0;
}
/* to "find" a device, open it, get the current offset, then close */
unsigned long sdbfs_find_name(struct sdbfs *fs, const char *name)
{
unsigned long offset;
int ret;
ret = sdbfs_open_name(fs, name);
if (ret < 0)
return (unsigned long)ret;
offset = fs->f_offset;
sdbfs_close(fs);
return offset;
}
unsigned long sdbfs_find_id(struct sdbfs *fs, uint64_t vid, uint32_t did)
{
unsigned long offset;
int ret;
ret = sdbfs_open_id(fs, vid, did);
if (ret < 0)
return (unsigned long)ret;
offset = fs->f_offset;
sdbfs_close(fs);
return offset;
}
/*
* This supports both the Linux kernel and barebox, that is similar
* by design, and defines __KERNEL__ too.
*/
#ifdef __BAREBOX__
# include <errno.h>
#else /* really linux */
# include <linux/errno.h>
#endif
#include <linux/types.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <asm/byteorder.h>
/*
......@@ -11,8 +19,12 @@
* So, check if we got the information we need before strange errors happen.
* The DECLARE_BITMAP macro is in <linux/types.h> since the epoch, but it
* is not installed in /usr/include/linux/types.h, so use it to check.
*
* If building for barebox, we miss the macro, but we are sure that
* we are picking the correct header, because the library is only built
* within the barebox source tree.
*/
#ifndef DECLARE_BITMAP
#if !defined(DECLARE_BITMAP) && !defined(__BAREBOX__)
# error "Please point LINUX to a source tree if you define __KERNEL__"
#endif
......@@ -20,5 +32,4 @@
#define SDB_USER 0
#define SDB_FREESTAND 0
#define sdb_print(format, ...) printk(format, __VA_ARGS__)
#ifndef __LIBSDBFS_H__
#define __LIBSDBFS_H__
/* The library can work in three different environments */
/* The library can work in different environments, take care of them */
#ifdef __KERNEL__
# include "libsdbfs-kernel.h"
#elif defined(__unix__)
......@@ -12,6 +12,7 @@
#include <sdb.h> /* Please point your "-I" to some sensible place */
#define SDBFS_DEPTH 4 /* Max number of subdirectory depth */
/*
* Data structures: please not that the library intself doesn't use
* malloc, so it's the caller who must deal withallocation/removal.
......@@ -24,8 +25,9 @@ struct sdbfs {
/* Some fields are informative */
char *name; /* may be null */
void *drvdata; /* driver may need some detail.. */
int blocksize;
unsigned long blocksize;
unsigned long entrypoint;
unsigned long flags;
/* The "driver" must offer some methods */
void *data; /* Use this if directly mapped */
......@@ -35,23 +37,30 @@ struct sdbfs {
int (*erase)(struct sdbfs *fs, int offset, int count);
/* The following fields are library-private */
struct sdb_device current_record;
struct sdb_device *currentp;
int nleft;
unsigned long f_offset;
struct sdb_device current_record;
unsigned long f_len;
unsigned long read_offset;
unsigned long flags;
unsigned long f_offset; /* start of file */
unsigned long read_offset; /* current location */
struct sdbfs *next;
/* The following ones are directory-aware */
unsigned long base[SDBFS_DEPTH]; /* for relative addresses */
unsigned long this[SDBFS_DEPTH]; /* current sdb record */
int nleft[SDBFS_DEPTH];
int depth;
};
#define SDBFS_F_VERBOSE 0x0001
/* Some flags are set by the user, some (convert32) by the library */
#define SDBFS_F_VERBOSE 0x0001 /* not really used yet */
#define SDBFS_F_CONVERT32 0x0002 /* swap SDB words as they are read */
#define SDBFS_F_ZEROBASED 0x0004 /* zero is a valid data pointer */
/* Defined in glue.c */
int sdbfs_dev_create(struct sdbfs *fs, int verbose);
int sdbfs_dev_create(struct sdbfs *fs);
int sdbfs_dev_destroy(struct sdbfs *fs);
struct sdbfs *sdbfs_dev_find(const char *name);
unsigned long sdbfs_find_name(struct sdbfs *fs, const char *name);
unsigned long sdbfs_find_id(struct sdbfs *fs, uint64_t vid, uint32_t did);
int sdbfs_open_name(struct sdbfs *fs, const char *name);
int sdbfs_open_id(struct sdbfs *fs, uint64_t vid, uint32_t did);
int sdbfs_close(struct sdbfs *fs);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment