aboutsummaryrefslogtreecommitdiffstats
path: root/roms/SLOF/lib/libvirtio/virtio.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/SLOF/lib/libvirtio/virtio.c')
-rw-r--r--roms/SLOF/lib/libvirtio/virtio.c645
1 files changed, 645 insertions, 0 deletions
diff --git a/roms/SLOF/lib/libvirtio/virtio.c b/roms/SLOF/lib/libvirtio/virtio.c
new file mode 100644
index 000000000..9dfc93cb2
--- /dev/null
+++ b/roms/SLOF/lib/libvirtio/virtio.c
@@ -0,0 +1,645 @@
+/******************************************************************************
+ * Copyright (c) 2011 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ * IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <cpu.h>
+#include <cache.h>
+#include <byteorder.h>
+#include "virtio.h"
+#include "helpers.h"
+#include "virtio-internal.h"
+
+/* PCI virtio header offsets */
+#define VIRTIOHDR_DEVICE_FEATURES 0
+#define VIRTIOHDR_GUEST_FEATURES 4
+#define VIRTIOHDR_QUEUE_ADDRESS 8
+#define VIRTIOHDR_QUEUE_SIZE 12
+#define VIRTIOHDR_QUEUE_SELECT 14
+#define VIRTIOHDR_QUEUE_NOTIFY 16
+#define VIRTIOHDR_DEVICE_STATUS 18
+#define VIRTIOHDR_ISR_STATUS 19
+#define VIRTIOHDR_DEVICE_CONFIG 20
+
+/* PCI defines */
+#define PCI_BASE_ADDR_SPACE_IO 0x01
+#define PCI_BASE_ADDR_SPACE_64BIT 0x04
+#define PCI_BASE_ADDR_MEM_MASK (~0x0fUL)
+#define PCI_BASE_ADDR_IO_MASK (~0x03UL)
+
+#define PCI_BASE_ADDR_REG_0 0x10
+#define PCI_CONFIG_CAP_REG 0x34
+
+#define PCI_CAP_ID_VNDR 0x9
+
+/* Common configuration */
+#define VIRTIO_PCI_CAP_COMMON_CFG 1
+/* Notifications */
+#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
+/* ISR access */
+#define VIRTIO_PCI_CAP_ISR_CFG 3
+/* Device specific configuration */
+#define VIRTIO_PCI_CAP_DEVICE_CFG 4
+/* PCI configuration access */
+#define VIRTIO_PCI_CAP_PCI_CFG 5
+
+#define VIRTIO_PCI_CAP_VNDR 0 /* Generic PCI field: PCI_CAP_ID_VNDR */
+#define VIRTIO_PCI_CAP_NEXT 1 /* Generic PCI field: next ptr. */
+#define VIRTIO_PCI_CAP_LEN 2 /* Generic PCI field: capability length */
+#define VIRTIO_PCI_CAP_CFG_TYPE 3 /* Identifies the structure. */
+#define VIRTIO_PCI_CAP_BAR 4 /* Where to find it. */
+#define VIRTIO_PCI_CAP_OFFSET 8 /* Offset within bar. */
+#define VIRTIO_PCI_CAP_LENGTH 12 /* Length of the structure, in bytes. */
+
+struct virtio_dev_common {
+ le32 dev_features_sel;
+ le32 dev_features;
+ le32 drv_features_sel;
+ le32 drv_features;
+ le16 msix_config;
+ le16 num_queues;
+ uint8_t dev_status;
+ uint8_t cfg_generation;
+
+ le16 q_select;
+ le16 q_size;
+ le16 q_msix_vec;
+ le16 q_enable;
+ le16 q_notify_off;
+ le64 q_desc;
+ le64 q_avail;
+ le64 q_used;
+} __attribute__ ((packed));
+
+/* virtio 1.0 Spec: 4.1.3 PCI Device Layout
+ *
+ * Fields of different sizes are present in the device configuration regions.
+ * All 64-bit, 32-bit and 16-bit fields are little-endian. 64-bit fields are to
+ * be treated as two 32-bit fields, with low 32 bit part followed by the high 32
+ * bit part.
+ */
+static void virtio_pci_write64(void *addr, uint64_t val)
+{
+ uint32_t hi = (val >> 32) & 0xFFFFFFFF;
+ uint32_t lo = val & 0xFFFFFFFF;
+
+ ci_write_32(addr, cpu_to_le32(lo));
+ ci_write_32(addr + 4, cpu_to_le32(hi));
+}
+
+static void virtio_cap_set_base_addr(struct virtio_cap *cap, uint32_t offset)
+{
+ uint64_t addr;
+
+ addr = SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * cap->bar);
+ if (addr & PCI_BASE_ADDR_SPACE_IO) {
+ addr = addr & PCI_BASE_ADDR_IO_MASK;
+ cap->is_io = 1;
+ } else {
+ if (addr & PCI_BASE_ADDR_SPACE_64BIT)
+ addr |= SLOF_pci_config_read32(PCI_BASE_ADDR_REG_0 + 4 * (cap->bar + 1)) << 32;
+ addr = addr & PCI_BASE_ADDR_MEM_MASK;
+ cap->is_io = 0;
+ }
+ addr = (uint64_t)SLOF_translate_my_address((void *)addr);
+ cap->addr = (void *)addr + offset;
+}
+
+static void virtio_process_cap(struct virtio_device *dev, uint8_t cap_ptr)
+{
+ struct virtio_cap *cap;
+ uint8_t cfg_type, bar;
+ uint32_t offset;
+
+ cfg_type = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_CFG_TYPE);
+ bar = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_BAR);
+ offset = SLOF_pci_config_read32(cap_ptr + VIRTIO_PCI_CAP_OFFSET);
+
+ switch(cfg_type) {
+ case VIRTIO_PCI_CAP_COMMON_CFG:
+ cap = &dev->common;
+ break;
+ case VIRTIO_PCI_CAP_NOTIFY_CFG:
+ cap = &dev->notify;
+ dev->notify_off_mul = SLOF_pci_config_read32(cap_ptr + sizeof(struct virtio_cap));
+ break;
+ case VIRTIO_PCI_CAP_ISR_CFG:
+ cap = &dev->isr;
+ break;
+ case VIRTIO_PCI_CAP_DEVICE_CFG:
+ cap = &dev->device;
+ break;
+ default:
+ return;
+ }
+
+ cap->bar = bar;
+ virtio_cap_set_base_addr(cap, offset);
+ cap->cap_id = cfg_type;
+}
+
+/**
+ * Reads the virtio device capabilities, gets called from SLOF routines The
+ * function determines legacy or modern device and sets up driver registers
+ */
+struct virtio_device *virtio_setup_vd(void)
+{
+ uint8_t cap_ptr, cap_vndr;
+ struct virtio_device *dev;
+
+ dev = SLOF_alloc_mem(sizeof(struct virtio_device));
+ if (!dev) {
+ printf("Failed to allocate memory");
+ return NULL;
+ }
+
+ cap_ptr = SLOF_pci_config_read8(PCI_CONFIG_CAP_REG);
+ while (cap_ptr != 0) {
+ cap_vndr = SLOF_pci_config_read8(cap_ptr + VIRTIO_PCI_CAP_VNDR);
+ if (cap_vndr == PCI_CAP_ID_VNDR)
+ virtio_process_cap(dev, cap_ptr);
+ cap_ptr = SLOF_pci_config_read8(cap_ptr+VIRTIO_PCI_CAP_NEXT);
+ }
+
+ if (dev->common.cap_id && dev->notify.cap_id &&
+ dev->isr.cap_id && dev->device.cap_id) {
+ dev->features = VIRTIO_F_VERSION_1;
+ } else {
+ dev->features = 0;
+ dev->legacy.cap_id = 0;
+ dev->legacy.bar = 0;
+ virtio_cap_set_base_addr(&dev->legacy, 0);
+ }
+ return dev;
+}
+
+/**
+ * Calculate ring size according to queue size number
+ */
+unsigned long virtio_vring_size(unsigned int qsize)
+{
+ return VQ_ALIGN(sizeof(struct vring_desc) * qsize +
+ sizeof(struct vring_avail) + sizeof(uint16_t) * qsize) +
+ VQ_ALIGN(sizeof(struct vring_used) +
+ sizeof(struct vring_used_elem) * qsize);
+}
+
+
+/**
+ * Get number of elements in a vring
+ * @param dev pointer to virtio device information
+ * @param queue virtio queue number
+ * @return number of elements
+ */
+unsigned int virtio_get_qsize(struct virtio_device *dev, int queue)
+{
+ unsigned int size = 0;
+
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ void *addr = dev->common.addr + offset_of(struct virtio_dev_common, q_select);
+ ci_write_16(addr, cpu_to_le16(queue));
+ eieio();
+ addr = dev->common.addr + offset_of(struct virtio_dev_common, q_size);
+ size = le16_to_cpu(ci_read_16(addr));
+ }
+ else {
+ ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT,
+ cpu_to_le16(queue));
+ eieio();
+ size = le16_to_cpu(ci_read_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SIZE));
+ }
+
+ return size;
+}
+
+
+/**
+ * Get address of descriptor vring
+ * @param dev pointer to virtio device information
+ * @param queue virtio queue number
+ * @return pointer to the descriptor ring
+ */
+struct vring_desc *virtio_get_vring_desc(struct virtio_device *dev, int queue)
+{
+ return dev->vq[queue].desc;
+}
+
+
+/**
+ * Get address of "available" vring
+ * @param dev pointer to virtio device information
+ * @param queue virtio queue number
+ * @return pointer to the "available" ring
+ */
+struct vring_avail *virtio_get_vring_avail(struct virtio_device *dev, int queue)
+{
+ return dev->vq[queue].avail;
+}
+
+
+/**
+ * Get address of "used" vring
+ * @param dev pointer to virtio device information
+ * @param queue virtio queue number
+ * @return pointer to the "used" ring
+ */
+struct vring_used *virtio_get_vring_used(struct virtio_device *dev, int queue)
+{
+ return dev->vq[queue].used;
+}
+
+/**
+ * Fill the virtio ring descriptor depending on the legacy mode or virtio 1.0
+ */
+void virtio_fill_desc(struct vqs *vq, int id, uint64_t features,
+ uint64_t addr, uint32_t len,
+ uint16_t flags, uint16_t next)
+{
+ struct vring_desc *desc;
+
+ id %= vq->size;
+ desc = &vq->desc[id];
+ next %= vq->size;
+
+ if (features & VIRTIO_F_VERSION_1) {
+ if (features & VIRTIO_F_IOMMU_PLATFORM) {
+ void *gpa = (void *) addr;
+
+ if (!vq->desc_gpas) {
+ fprintf(stderr, "IOMMU setup has not been done!\n");
+ return;
+ }
+
+ addr = SLOF_dma_map_in(gpa, len, 0);
+ vq->desc_gpas[id] = gpa;
+ }
+ desc->addr = cpu_to_le64(addr);
+ desc->len = cpu_to_le32(len);
+ desc->flags = cpu_to_le16(flags);
+ desc->next = cpu_to_le16(next);
+ } else {
+ desc->addr = addr;
+ desc->len = len;
+ desc->flags = flags;
+ desc->next = next;
+ }
+}
+
+void virtio_free_desc(struct vqs *vq, int id, uint64_t features)
+{
+ struct vring_desc *desc;
+
+ id %= vq->size;
+ desc = &vq->desc[id];
+
+ if (!(features & VIRTIO_F_VERSION_1) ||
+ !(features & VIRTIO_F_IOMMU_PLATFORM))
+ return;
+
+ if (!vq->desc_gpas[id])
+ return;
+
+ SLOF_dma_map_out(le64_to_cpu(desc->addr), 0, le32_to_cpu(desc->len));
+ vq->desc_gpas[id] = NULL;
+}
+
+void *virtio_desc_addr(struct virtio_device *vdev, int queue, int id)
+{
+ struct vqs *vq = &vdev->vq[queue];
+
+ if (vq->desc_gpas)
+ return vq->desc_gpas[id];
+
+ return (void *) virtio_modern64_to_cpu(vdev, vq->desc[id].addr);
+}
+
+/**
+ * Reset virtio device
+ */
+void virtio_reset_device(struct virtio_device *dev)
+{
+ virtio_set_status(dev, 0);
+}
+
+
+/**
+ * Notify hypervisor about queue update
+ */
+void virtio_queue_notify(struct virtio_device *dev, int queue)
+{
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ void *q_sel = dev->common.addr + offset_of(struct virtio_dev_common, q_select);
+ void *q_ntfy = dev->common.addr + offset_of(struct virtio_dev_common, q_notify_off);
+ void *addr;
+ uint16_t q_notify_off;
+
+ ci_write_16(q_sel, cpu_to_le16(queue));
+ eieio();
+ q_notify_off = le16_to_cpu(ci_read_16(q_ntfy));
+ addr = dev->notify.addr + q_notify_off * dev->notify_off_mul;
+ ci_write_16(addr, cpu_to_le16(queue));
+ } else {
+ ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_NOTIFY, cpu_to_le16(queue));
+ }
+}
+
+/**
+ * Set queue address
+ */
+static void virtio_set_qaddr(struct virtio_device *dev, int queue, unsigned long qaddr)
+{
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ uint64_t q_desc = qaddr;
+ uint64_t q_avail;
+ uint64_t q_used;
+ uint32_t q_size = virtio_get_qsize(dev, queue);
+
+ if (dev->features & VIRTIO_F_IOMMU_PLATFORM) {
+ unsigned long cb;
+
+ cb = q_size * sizeof(struct vring_desc);
+ cb += sizeof(struct vring_avail) +
+ sizeof(uint16_t) * q_size;
+ cb = VQ_ALIGN(cb);
+ cb += sizeof(struct vring_used) +
+ sizeof(struct vring_used_elem) * q_size;
+ cb = VQ_ALIGN(cb);
+ q_desc = SLOF_dma_map_in((void *)q_desc, cb, 0);
+
+ dev->vq[queue].bus_desc = q_desc;
+ }
+
+ virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_desc), q_desc);
+ q_avail = q_desc + q_size * sizeof(struct vring_desc);
+ virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_avail), q_avail);
+ q_used = VQ_ALIGN(q_avail + sizeof(struct vring_avail) + sizeof(uint16_t) * q_size);
+ virtio_pci_write64(dev->common.addr + offset_of(struct virtio_dev_common, q_used), q_used);
+ ci_write_16(dev->common.addr + offset_of(struct virtio_dev_common, q_enable), cpu_to_le16(1));
+ } else {
+ uint32_t val = qaddr;
+ val = val >> 12;
+ ci_write_16(dev->legacy.addr+VIRTIOHDR_QUEUE_SELECT,
+ cpu_to_le16(queue));
+ eieio();
+ ci_write_32(dev->legacy.addr+VIRTIOHDR_QUEUE_ADDRESS,
+ cpu_to_le32(val));
+ }
+}
+
+struct vqs *virtio_queue_init_vq(struct virtio_device *dev, unsigned int id)
+{
+ struct vqs *vq;
+
+ if (id >= sizeof(dev->vq)/sizeof(dev->vq[0])) {
+ printf("Queue index is too big!\n");
+ return NULL;
+ }
+ vq = &dev->vq[id];
+
+ memset(vq, 0, sizeof(*vq));
+
+ vq->size = virtio_get_qsize(dev, id);
+ vq->desc = SLOF_alloc_mem_aligned(virtio_vring_size(vq->size), 4096);
+ if (!vq->desc) {
+ printf("memory allocation failed!\n");
+ return NULL;
+ }
+
+ vq->avail = (void *) vq->desc + vq->size * sizeof(struct vring_desc);
+ vq->used = (void *) VQ_ALIGN((unsigned long) vq->avail +
+ sizeof(struct vring_avail) +
+ sizeof(uint16_t) * vq->size);
+
+ memset(vq->desc, 0, virtio_vring_size(vq->size));
+ virtio_set_qaddr(dev, id, (unsigned long)vq->desc);
+
+ vq->avail->flags = virtio_cpu_to_modern16(dev, VRING_AVAIL_F_NO_INTERRUPT);
+ vq->avail->idx = 0;
+ if (dev->features & VIRTIO_F_IOMMU_PLATFORM)
+ vq->desc_gpas = SLOF_alloc_mem_aligned(
+ vq->size * sizeof(vq->desc_gpas[0]), 4096);
+
+ return vq;
+}
+
+void virtio_queue_term_vq(struct virtio_device *dev, struct vqs *vq, unsigned int id)
+{
+ if (vq->desc_gpas) {
+ unsigned i;
+
+ for (i = 0; i < vq->size; ++i)
+ virtio_free_desc(vq, i, dev->features);
+
+ SLOF_free_mem(vq->desc_gpas,
+ vq->size * sizeof(vq->desc_gpas[0]));
+ }
+ if (vq->desc) {
+ if (dev->features & VIRTIO_F_IOMMU_PLATFORM) {
+ unsigned long cb;
+ uint32_t q_size = virtio_get_qsize(dev, id);
+
+ cb = q_size * sizeof(struct vring_desc);
+ cb += sizeof(struct vring_avail) +
+ sizeof(uint16_t) * q_size;
+ cb = VQ_ALIGN(cb);
+ cb += sizeof(struct vring_used) +
+ sizeof(struct vring_used_elem) * q_size;
+ cb = VQ_ALIGN(cb);
+
+ SLOF_dma_map_out(vq->bus_desc, 0, cb);
+ }
+
+ SLOF_free_mem(vq->desc, virtio_vring_size(vq->size));
+ }
+ memset(vq, 0, sizeof(*vq));
+}
+
+/**
+ * Set device status bits
+ */
+void virtio_set_status(struct virtio_device *dev, int status)
+{
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ ci_write_8(dev->common.addr +
+ offset_of(struct virtio_dev_common, dev_status), status);
+ } else {
+ ci_write_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS, status);
+ }
+}
+
+/**
+ * Get device status bits
+ */
+void virtio_get_status(struct virtio_device *dev, int *status)
+{
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ *status = ci_read_8(dev->common.addr +
+ offset_of(struct virtio_dev_common, dev_status));
+ } else {
+ *status = ci_read_8(dev->legacy.addr+VIRTIOHDR_DEVICE_STATUS);
+ }
+}
+
+/**
+ * Set guest feature bits
+ */
+void virtio_set_guest_features(struct virtio_device *dev, uint64_t features)
+
+{
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ uint32_t f1 = (features >> 32) & 0xFFFFFFFF;
+ uint32_t f0 = features & 0xFFFFFFFF;
+ void *addr = dev->common.addr;
+
+ ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel),
+ cpu_to_le32(1));
+ ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features),
+ cpu_to_le32(f1));
+
+ ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features_sel),
+ cpu_to_le32(0));
+ ci_write_32(addr + offset_of(struct virtio_dev_common, drv_features),
+ cpu_to_le32(f0));
+ } else {
+ ci_write_32(dev->legacy.addr+VIRTIOHDR_GUEST_FEATURES, cpu_to_le32(features));
+ }
+}
+
+/**
+ * Get host feature bits
+ */
+uint64_t virtio_get_host_features(struct virtio_device *dev)
+
+{
+ uint64_t features = 0;
+ if (dev->features & VIRTIO_F_VERSION_1) {
+ uint32_t f0 = 0, f1 = 0;
+ void *addr = dev->common.addr;
+
+ ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel),
+ cpu_to_le32(1));
+ f1 = ci_read_32(addr +
+ offset_of(struct virtio_dev_common, dev_features));
+ ci_write_32(addr + offset_of(struct virtio_dev_common, dev_features_sel),
+ cpu_to_le32(0));
+ f0 = ci_read_32(addr +
+ offset_of(struct virtio_dev_common, dev_features));
+
+ features = ((uint64_t)le32_to_cpu(f1) << 32) | le32_to_cpu(f0);
+ } else {
+ features = le32_to_cpu(ci_read_32(dev->legacy.addr+VIRTIOHDR_DEVICE_FEATURES));
+ }
+ return features;
+}
+
+int virtio_negotiate_guest_features(struct virtio_device *dev, uint64_t features)
+{
+ uint64_t host_features = 0;
+ int status;
+
+ /* Negotiate features */
+ host_features = virtio_get_host_features(dev);
+ if (!(host_features & VIRTIO_F_VERSION_1)) {
+ fprintf(stderr, "Device does not support virtio 1.0 %llx\n", host_features);
+ return -1;
+ }
+
+ if (host_features & VIRTIO_F_IOMMU_PLATFORM)
+ features |= VIRTIO_F_IOMMU_PLATFORM;
+
+ virtio_set_guest_features(dev, features);
+ host_features = virtio_get_host_features(dev);
+ if ((host_features & features) != features) {
+ fprintf(stderr, "Features error %llx\n", features);
+ return -1;
+ }
+
+ virtio_get_status(dev, &status);
+ status |= VIRTIO_STAT_FEATURES_OK;
+ virtio_set_status(dev, status);
+
+ /* Read back to verify the FEATURES_OK bit */
+ virtio_get_status(dev, &status);
+ if ((status & VIRTIO_STAT_FEATURES_OK) != VIRTIO_STAT_FEATURES_OK)
+ return -1;
+
+ dev->features = features;
+
+ return 0;
+}
+
+/**
+ * Get additional config values
+ */
+uint64_t virtio_get_config(struct virtio_device *dev, int offset, int size)
+{
+ uint64_t val = ~0ULL;
+ uint32_t hi, lo;
+ void *confbase;
+
+ if (dev->features & VIRTIO_F_VERSION_1)
+ confbase = dev->device.addr;
+ else
+ confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG;
+
+ switch (size) {
+ case 1:
+ val = ci_read_8(confbase+offset);
+ break;
+ case 2:
+ val = ci_read_16(confbase+offset);
+ if (dev->features & VIRTIO_F_VERSION_1)
+ val = le16_to_cpu(val);
+ break;
+ case 4:
+ val = ci_read_32(confbase+offset);
+ if (dev->features & VIRTIO_F_VERSION_1)
+ val = le32_to_cpu(val);
+ break;
+ case 8:
+ /* We don't support 8 bytes PIO accesses
+ * in qemu and this is all PIO
+ */
+ lo = ci_read_32(confbase+offset);
+ hi = ci_read_32(confbase+offset+4);
+ if (dev->features & VIRTIO_F_VERSION_1)
+ val = (uint64_t)le32_to_cpu(hi) << 32 | le32_to_cpu(lo);
+ else
+ val = (uint64_t)hi << 32 | lo;
+ break;
+ }
+
+ return val;
+}
+
+/**
+ * Get config blob
+ */
+int __virtio_read_config(struct virtio_device *dev, void *dst,
+ int offset, int len)
+{
+ void *confbase;
+ unsigned char *buf = dst;
+ int i;
+
+ if (dev->features & VIRTIO_F_VERSION_1)
+ confbase = dev->device.addr;
+ else
+ confbase = dev->legacy.addr+VIRTIOHDR_DEVICE_CONFIG;
+
+ for (i = 0; i < len; i++)
+ buf[i] = ci_read_8(confbase + offset + i);
+
+ return len;
+}