When we send MMC commads that contain data the controller provides no description of the order of events and we need to be able to send data from and to the controller "when needed". Changed the code to react on buffer read and buffer write ready based on interrupts. Change-Id: I60c9140bf0e45b74be6475054564d4e1bd89f21e
668 lines
19 KiB
C
668 lines
19 KiB
C
/*
|
|
* Block driver for Multi Media Cards (MMC).
|
|
*/
|
|
/* kernel headers */
|
|
#include <minix/syslib.h>
|
|
#include <minix/driver.h>
|
|
#include <minix/blockdriver.h>
|
|
#include <minix/drvlib.h>
|
|
#include <minix/log.h>
|
|
#include <minix/minlib.h>
|
|
|
|
/* system headers */
|
|
#include <sys/ioc_disk.h> /* disk IOCTL's */
|
|
|
|
/* usr headers */
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <signal.h>
|
|
|
|
/* local headers */
|
|
#include "mmchost.h"
|
|
|
|
/* used for logging */
|
|
static struct log log = {
|
|
.name = "mmc_block",
|
|
.log_level = LEVEL_INFO,
|
|
.log_func = default_log
|
|
};
|
|
|
|
/* holding the current host controller */
|
|
static struct mmc_host host;
|
|
|
|
#define SUB_PER_DRIVE (NR_PARTITIONS * NR_PARTITIONS)
|
|
#define NR_SUBDEVS (MAX_DRIVES * SUB_PER_DRIVE)
|
|
|
|
/* When passing data over a grant one needs to pass
|
|
* a buffer to sys_safecopy copybuff is used for that*/
|
|
#define COPYBUFF_SIZE 0x1000 /* 4k buff */
|
|
static unsigned char copybuff[COPYBUFF_SIZE];
|
|
|
|
static struct sd_slot *get_slot(dev_t minor);
|
|
|
|
/* Prototypes for the block device */
|
|
static int block_open(dev_t minor, int access);
|
|
static int block_close(dev_t minor);
|
|
static int block_transfer(dev_t minor,
|
|
int do_write,
|
|
u64_t position,
|
|
endpoint_t endpt, iovec_t * iov, unsigned int nr_req, int flags);
|
|
|
|
static int block_ioctl(dev_t minor,
|
|
unsigned int request, endpoint_t endpt, cp_grant_id_t grant);
|
|
static struct device *block_part(dev_t minor);
|
|
|
|
/* System even handling */
|
|
static void sef_local_startup();
|
|
static int block_system_event_cb(int type, sef_init_info_t * info);
|
|
static void block_signal_handler_cb(int signo);
|
|
|
|
void
|
|
bdr_alarm(clock_t stamp)
|
|
{
|
|
log_debug(&log, "alarm %d\n", stamp);
|
|
}
|
|
|
|
static int apply_env();
|
|
static void hw_intr(unsigned int irqs);
|
|
|
|
/* set the global logging level */
|
|
static void set_log_level(int level);
|
|
|
|
/* Entry points for the BLOCK driver. */
|
|
static struct blockdriver mmc_driver = {
|
|
BLOCKDRIVER_TYPE_DISK, /* handle partition requests */
|
|
block_open, /* open or mount */
|
|
block_close, /* on a close */
|
|
block_transfer, /* does the I/O */
|
|
block_ioctl, /* ioclt's */
|
|
NULL, /* no need to clean up (yet) */
|
|
block_part, /* return partition information */
|
|
NULL, /* no geometry */
|
|
hw_intr, /* left over interrupts */
|
|
bdr_alarm, /* no alarm processing */
|
|
NULL, /* no processing of other messages */
|
|
NULL /* no threading support */
|
|
};
|
|
|
|
static void
|
|
hw_intr(unsigned int irqs)
|
|
{
|
|
log_debug(&log, "Hardware inter left over\n");
|
|
host.hw_intr(irqs);
|
|
}
|
|
|
|
static int
|
|
apply_env()
|
|
{
|
|
long v;
|
|
/* apply the env setting passed to this driver parameters accepted
|
|
* log_level=[0-4] (NONE,WARN,INFO,DEBUG,TRACE) instance=[0-3]
|
|
* instance/bus number to use for this driver Passing these arguments
|
|
* is done when starting the driver using the service command in the
|
|
* following way service up /sbin/mmc -args "log_level=2 instance=1
|
|
* driver=dummy" -dev /dev/c2d0 */
|
|
char driver[16];
|
|
memset(driver, '\0', 16);
|
|
(void) env_get_param("driver", driver, 16);
|
|
if (strlen(driver) == 0
|
|
|| strncmp(driver, "mmchs", strlen("mmchs") + 1) == 0) {
|
|
/* early init of host mmc host controller. This code should
|
|
* depend on knowing the hardware that is running bellow. */
|
|
#ifdef __arm__
|
|
host_initialize_host_structure_mmchs(&host);
|
|
#endif
|
|
} else if (strncmp(driver, "dummy", strlen("dummy") + 1) == 0) {
|
|
host_initialize_host_structure_dummy(&host);
|
|
} else {
|
|
log_warn(&log, "Unknown driver %s\n", driver);
|
|
}
|
|
/* Initialize the verbosity level. */
|
|
v = 0;
|
|
if (env_parse("log_level", "d", 0, &v, LEVEL_NONE,
|
|
LEVEL_TRACE) == EP_SET) {
|
|
set_log_level(v);
|
|
}
|
|
|
|
/* Find out which driver instance we are. */
|
|
v = 0;
|
|
env_parse("instance", "d", 0, &v, 0, 3);
|
|
if (host.host_set_instance(&host, v)) {
|
|
log_warn(&log, "Failed to set mmc instance to %d\n", v);
|
|
return -1; /* NOT OK */
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
;
|
|
|
|
/*===========================================================================*
|
|
* block_open *
|
|
*===========================================================================*/
|
|
static int
|
|
block_open(dev_t minor, int access)
|
|
{
|
|
struct sd_slot *slot;
|
|
slot = get_slot(minor);
|
|
int i, j;
|
|
int part_count, sub_part_count;
|
|
|
|
i = j = part_count = sub_part_count = 0;
|
|
|
|
if (!slot) {
|
|
log_debug(&log, "Not handling open on non existing slot\n");
|
|
return EIO;
|
|
}
|
|
|
|
assert(slot->host != NULL);
|
|
|
|
if (!slot->host->card_detect(slot)) {
|
|
log_debug(&log, "No card inserted in the SD slot\n");
|
|
return EIO;
|
|
}
|
|
|
|
/* If we are already open just increase the open count and return */
|
|
if (slot->card.state == SD_MODE_DATA_TRANSFER_MODE) {
|
|
assert(slot->card.open_ct >= 0);
|
|
slot->card.open_ct++;
|
|
log_trace(&log, "increased open count to %d\n",
|
|
slot->card.open_ct);
|
|
return OK;
|
|
}
|
|
|
|
/* We did not have an sd-card inserted so we are going to probe for it
|
|
*/
|
|
log_debug(&log, "First open on (%d)\n", minor);
|
|
if (!host.card_initialize(slot)) {
|
|
// * TODO: set card state to INVALID until removed? */
|
|
return EIO;
|
|
}
|
|
|
|
partition(&mmc_driver, 0 /* first card on bus */ , P_PRIMARY,
|
|
0 /* atapi device?? */ );
|
|
|
|
log_trace(&log, "descr \toffset(bytes) size(bytes)\n", minor);
|
|
|
|
log_trace(&log, "disk %d\t0x%016llx 0x%016llx\n", i,
|
|
slot->card.part[0].dv_base, slot->card.part[0].dv_size);
|
|
for (i = 1; i < 5; i++) {
|
|
if (slot->card.part[i].dv_size == 0)
|
|
continue;
|
|
part_count++;
|
|
log_trace(&log, "part %d\t0x%016llx 0x%016llx\n", i,
|
|
slot->card.part[i].dv_base, slot->card.part[i].dv_size);
|
|
for (j = 0; j < 4; j++) {
|
|
if (slot->card.subpart[(i - 1) * 4 + j].dv_size == 0)
|
|
continue;
|
|
sub_part_count++;
|
|
log_trace(&log,
|
|
" sub %d/%d\t0x%016llx 0x%016llx\n", i, j,
|
|
slot->card.subpart[(i - 1) * 4 + j].dv_base,
|
|
slot->card.subpart[(i - 1) * 4 + j].dv_size);
|
|
}
|
|
}
|
|
log_debug(&log, "Found %d partitions and %d sub partitions\n",
|
|
part_count, sub_part_count);
|
|
slot->card.open_ct++;
|
|
assert(slot->card.open_ct == 1);
|
|
return OK;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_close *
|
|
*===========================================================================*/
|
|
static int
|
|
block_close(dev_t minor)
|
|
{
|
|
struct sd_slot *slot;
|
|
|
|
slot = get_slot(minor);
|
|
if (!slot) {
|
|
log_debug(&log, "Not handling open on non existing slot\n");
|
|
return EIO;
|
|
}
|
|
|
|
/* if we arrived here we expect a card to be present, we will need do
|
|
* deal with removal later */
|
|
assert(slot->host != NULL);
|
|
assert(slot->card.open_ct >= 1);
|
|
|
|
/* If this is not the last open count simply decrease the counter and
|
|
* return */
|
|
if (slot->card.open_ct > 1) {
|
|
slot->card.open_ct--;
|
|
log_trace(&log, "decreased open count to %d\n",
|
|
slot->card.open_ct);
|
|
return OK;
|
|
}
|
|
|
|
assert(slot->card.open_ct == 1);
|
|
log_debug(&log, "freeing the block device as it is no longer used\n");
|
|
|
|
/* release the card as check the open_ct should be 0 */
|
|
slot->host->card_release(&slot->card);
|
|
assert(slot->card.open_ct == 0);
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
copyto(endpoint_t dst_e,
|
|
cp_grant_id_t gr_id, vir_bytes offset, vir_bytes address, size_t bytes)
|
|
{
|
|
/* Helper function that used memcpy to copy data when the endpoint ==
|
|
* SELF */
|
|
if (dst_e == SELF) {
|
|
memcpy((char *) gr_id + offset, (char *) address, bytes);
|
|
return OK;
|
|
} else {
|
|
/* Read io_size bytes from our data at the correct * offset
|
|
* and write it to the output buffer at 0 */
|
|
return sys_safecopyto(dst_e, gr_id, offset, address, bytes);
|
|
}
|
|
}
|
|
|
|
static int
|
|
copyfrom(endpoint_t src_e,
|
|
cp_grant_id_t gr_id, vir_bytes offset, vir_bytes address, size_t bytes)
|
|
{
|
|
/* Helper function that used memcpy to copy data when the endpoint ==
|
|
* SELF */
|
|
if (src_e == SELF) {
|
|
memcpy((char *) address, (char *) gr_id + offset, bytes);
|
|
return OK;
|
|
} else {
|
|
return sys_safecopyfrom(src_e, gr_id, offset, address, bytes);
|
|
}
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_transfer *
|
|
*===========================================================================*/
|
|
static int
|
|
block_transfer(dev_t minor, /* minor device number */
|
|
int do_write, /* read or write? */
|
|
u64_t position, /* offset on device to read or write */
|
|
endpoint_t endpt, /* process doing the request */
|
|
iovec_t * iov, /* pointer to read or write request vector */
|
|
unsigned int nr_req, /* length of request vector */
|
|
int flags /* transfer flags */
|
|
)
|
|
{
|
|
unsigned long counter;
|
|
iovec_t *ciov; /* Current IO Vector */
|
|
struct device *dev; /* The device used */
|
|
struct sd_slot *slot; /* The sd slot the requests is pointed to */
|
|
vir_bytes io_size; /* Size to read/write to/from the iov */
|
|
vir_bytes io_offset; /* Size to read/write to/from the iov */
|
|
vir_bytes bytes_written;
|
|
|
|
int r, blk_size, i;
|
|
|
|
/* Get the current "device" geometry */
|
|
dev = block_part(minor);
|
|
if (dev == NULL) {
|
|
log_warn(&log,
|
|
"Transfer requested on unknown device minor(%d)\n", minor);
|
|
/* Unknown device */
|
|
return ENXIO;
|
|
}
|
|
log_trace(&log, "I/O on minor(%d) %s at 0x%016llx\n", minor,
|
|
(do_write) ? "Write" : "Read", position);
|
|
|
|
slot = get_slot(minor);
|
|
assert(slot);
|
|
|
|
if (slot->card.blk_size == 0) {
|
|
log_warn(&log, "Request on a card with block size of 0\n");
|
|
return EINVAL;
|
|
}
|
|
if (slot->card.blk_size > COPYBUFF_SIZE) {
|
|
log_warn(&log,
|
|
"Card block size (%d) exceeds internal buffer size %d\n",
|
|
slot->card.blk_size, COPYBUFF_SIZE);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* It is fully up to the driver to decide on restrictions for the
|
|
* parameters of transfers, in those cases we return EINVAL */
|
|
if (position % slot->card.blk_size != 0) {
|
|
/* Starting at a block boundary */
|
|
log_warn(&log,
|
|
"Requests must start at a block boundary"
|
|
"(start,block size)=(%016llx,%08x)\n", position,
|
|
slot->card.blk_size);
|
|
return EINVAL;
|
|
}
|
|
|
|
blk_size = slot->card.blk_size;
|
|
|
|
bytes_written = 0;
|
|
|
|
/* Are we trying to start reading past the end */
|
|
if (position >= dev->dv_size) {
|
|
log_warn(&log, "start reading past drive size\n");
|
|
return 0;
|
|
};
|
|
|
|
ciov = iov;
|
|
/* do some more validation */
|
|
for (counter = 0; counter < nr_req; counter++) {
|
|
assert(ciov != NULL);
|
|
if (ciov->iov_size % blk_size != 0) {
|
|
/* transfer a multiple of blk_size */
|
|
log_warn(&log,
|
|
"Requests must start at a block boundary "
|
|
"(start,block size)=(%016llx,%08x)\n", position,
|
|
slot->card.blk_size);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (ciov->iov_size <= 0) {
|
|
log_warn(&log,
|
|
"Invalid iov size for iov %d of %d size\n",
|
|
counter, nr_req, ciov->iov_size);
|
|
return EINVAL;
|
|
}
|
|
ciov++;
|
|
}
|
|
|
|
ciov = iov;
|
|
for (counter = 0; counter < nr_req; counter++) {
|
|
/* Assume we are to transfer the amount of data given in the
|
|
* input/output vector but ensure we are not doing i/o past
|
|
* our own boundaries */
|
|
io_size = ciov->iov_size;
|
|
io_offset = position + bytes_written;
|
|
|
|
/* Check we are not reading/writing past the end */
|
|
if (position + bytes_written + io_size > dev->dv_size) {
|
|
io_size = dev->dv_size - (position + bytes_written);
|
|
};
|
|
|
|
log_trace(&log,
|
|
"I/O %s request(%d/%d) iov(grant,size,iosize,"
|
|
"offset)=(%d,%d,%d,%d)\n",
|
|
(do_write) ? "write" : "read", counter + 1, nr_req,
|
|
ciov->iov_addr, ciov->iov_size, io_size, io_offset);
|
|
/* transfer max one block at the time */
|
|
for (i = 0; i < io_size / blk_size; i++) {
|
|
if (do_write) {
|
|
/* Read io_size bytes from i/o vector starting
|
|
* at 0 and write it to out buffer at the
|
|
* correct offset */
|
|
r = copyfrom(endpt, ciov->iov_addr,
|
|
i * blk_size, (vir_bytes) copybuff,
|
|
blk_size);
|
|
if (r != OK) {
|
|
log_warn(&log,
|
|
"I/O write error: %s iov(base,size)=(%d,%d)"
|
|
" at offset=%d\n",
|
|
strerror(_SIGN r), ciov->iov_addr,
|
|
ciov->iov_size, io_offset);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* write a single block */
|
|
slot->host->write(&slot->card,
|
|
(dev->dv_base / blk_size) +
|
|
(io_offset / blk_size) + i, 1, copybuff);
|
|
bytes_written += blk_size;
|
|
} else {
|
|
/* read a single block info copybuff */
|
|
slot->host->read(&slot->card,
|
|
(dev->dv_base / blk_size) +
|
|
(io_offset / blk_size) + i, 1, copybuff);
|
|
/* Read io_size bytes from our data at the
|
|
* correct offset and write it to the output
|
|
* buffer at 0 */
|
|
r = copyto(endpt, ciov->iov_addr, i * blk_size,
|
|
(vir_bytes) copybuff, blk_size);
|
|
if (r != OK) {
|
|
log_warn(&log,
|
|
"I/O read error: %s iov(base,size)=(%d,%d)"
|
|
" at offset=%d\n",
|
|
strerror(_SIGN r), ciov->iov_addr,
|
|
ciov->iov_size, io_offset);
|
|
return EINVAL;
|
|
}
|
|
bytes_written += blk_size;
|
|
}
|
|
}
|
|
ciov++;
|
|
}
|
|
return bytes_written;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_ioctl *
|
|
*===========================================================================*/
|
|
static int
|
|
block_ioctl(dev_t minor,
|
|
unsigned int request, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
/* IOCTL handling */
|
|
struct sd_slot *slot;
|
|
log_trace(&log,
|
|
"enter (minor,request,endpoint,grant)=(%d,%lu,%d)\n", minor,
|
|
request, endpt, grant);
|
|
|
|
slot = get_slot(minor);
|
|
if (!slot) {
|
|
log_warn(&log,
|
|
"Doing ioctl on non existing block device(%d)\n", minor);
|
|
return EINVAL;
|
|
}
|
|
|
|
switch (request) {
|
|
case DIOCOPENCT:
|
|
// TODO: add a check for card validity */
|
|
log_trace(&log, "returning open count %d\n",
|
|
slot->card.open_ct);
|
|
/* return the current open count */
|
|
return sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes) & slot->card.open_ct,
|
|
sizeof(slot->card.open_ct));
|
|
case DIOCFLUSH:
|
|
/* No need to flush but some devices like movinands require
|
|
* 500 ms inactivity */
|
|
return OK;
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_part *
|
|
*===========================================================================*/
|
|
static struct device *
|
|
block_part(dev_t minor)
|
|
{
|
|
/*
|
|
* Reuse the existing MINIX major/minor partitioning scheme.
|
|
* - 8 drives
|
|
* - 5 devices per drive allowing direct access to the disk and up to 4
|
|
* partitions (IBM style partitioning without extended partitions)
|
|
* - 4 Minix style sub partitions per partitions
|
|
*/
|
|
struct device *dev;
|
|
struct sd_slot *slot;
|
|
|
|
dev = NULL;
|
|
slot = get_slot(minor);
|
|
if (!slot) {
|
|
log_warn(&log,
|
|
"Device information requested for non existing partition "
|
|
"minor(%d)\n", minor);
|
|
return NULL;
|
|
}
|
|
|
|
if (!slot->host->card_detect(slot)) {
|
|
log_warn(&log,
|
|
"Device information requested from empty slot(%d)\n",
|
|
minor);
|
|
return NULL;
|
|
}
|
|
|
|
if (minor < 5) {
|
|
/* we are talking about the first disk */
|
|
dev = &slot->card.part[minor];
|
|
log_trace(&log,
|
|
"returning partition(%d) (base,size)=(0x%016llx,0x%016llx)\n",
|
|
minor, dev->dv_base, dev->dv_size);
|
|
} else if (minor >= 128 && minor < 128 + 16) {
|
|
/* sub partitions of the first disk we don't care about the
|
|
* rest */
|
|
dev = &slot->card.subpart[minor - 128];
|
|
log_trace(&log,
|
|
"returning sub partition(%d) (base,size)=(0x%016llx,0x%016llx)\n",
|
|
minor - 128, dev->dv_base, dev->dv_size);
|
|
|
|
} else {
|
|
log_warn(&log,
|
|
"Device information requested for non existing "
|
|
"partition minor(%d)\n", minor);
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* sef_local_startup *
|
|
*===========================================================================*/
|
|
static void
|
|
sef_local_startup()
|
|
{
|
|
log_info(&log, "Initializing the MMC block device\n");
|
|
if (apply_env()) {
|
|
log_warn(&log, "Failed while applying environment settings\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (host.host_init(&host)) {
|
|
log_warn(&log, "Failed to initialize the host controller\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/*
|
|
* Register callbacks for fresh start, live update and restart.
|
|
* Use the same function for all event types
|
|
*/
|
|
sef_setcb_init_fresh(block_system_event_cb);
|
|
sef_setcb_init_lu(block_system_event_cb);
|
|
|
|
/* Register a signal handler */
|
|
sef_setcb_signal_handler(block_signal_handler_cb);
|
|
|
|
/* SEF startup */
|
|
sef_startup();
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_system_event_cb *
|
|
*===========================================================================*/
|
|
static int
|
|
block_system_event_cb(int type, sef_init_info_t * info)
|
|
{
|
|
/*
|
|
* Callbacks for the System event framework as registered in
|
|
* sef_local_startup */
|
|
switch (type) {
|
|
case SEF_INIT_FRESH:
|
|
log_info(&log, "System event framework fresh start\n");
|
|
break;
|
|
|
|
case SEF_INIT_LU:
|
|
/* Restore the state. post update */
|
|
log_info(&log, "System event framework live update\n");
|
|
break;
|
|
|
|
case SEF_INIT_RESTART:
|
|
log_info(&log, "System event framework post restart\n");
|
|
break;
|
|
}
|
|
blockdriver_announce(type);
|
|
return OK;
|
|
}
|
|
|
|
/*===========================================================================*
|
|
* block_signal_handler_cb *
|
|
*===========================================================================*/
|
|
static void
|
|
block_signal_handler_cb(int signo)
|
|
{
|
|
struct sd_slot *slot;
|
|
|
|
log_debug(&log, "System event framework signal handler sig(%d)\n",
|
|
signo);
|
|
/* Only check for termination signal, ignore anything else. */
|
|
if (signo != SIGTERM)
|
|
return;
|
|
|
|
/* we only have a single slot and need an open count idealy we should
|
|
* iterate over the card to determine the open count */
|
|
slot = get_slot(0);
|
|
assert(slot);
|
|
if (slot->card.open_ct > 0) {
|
|
log_debug(&log, "Not responding to SIGTERM (open count=%d)\n",
|
|
slot->card.open_ct);
|
|
return;
|
|
}
|
|
|
|
log_info(&log, "MMC driver exit");
|
|
exit(0);
|
|
}
|
|
|
|
#define IS_MINIX_SUB_PARTITION_MINOR(minor) (minor >= MINOR_d0p0s0 )
|
|
|
|
static struct sd_slot *
|
|
get_slot(dev_t minor)
|
|
{
|
|
/*
|
|
* Get an sd_slot based on the minor number.
|
|
*
|
|
* This driver only supports a single card at at time. Also as
|
|
* we are following the major/minor scheme of other driver we
|
|
* must return a slot for all minors on disk 0 these are 0-5
|
|
* for the disk and 4 main partitions and
|
|
* number 128 till 144 for sub partitions.
|
|
*/
|
|
/* If this is a minor for the first disk (e.g. minor 0 till 5) */
|
|
if (minor / DEV_PER_DRIVE == 0) {
|
|
/* we are talking about the first disk and that is all we
|
|
* support */
|
|
return &host.slot[0];
|
|
} else if (IS_MINIX_SUB_PARTITION_MINOR(minor)
|
|
&& (((minor - MINOR_d0p0s0) / SUB_PER_DRIVE) == 0)) {
|
|
/* a minor from the first disk */
|
|
return &host.slot[0];
|
|
} else {
|
|
log_trace(&log,
|
|
"Device information requested for non existing partition "
|
|
"minor(%d)\n", minor);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_log_level(int level)
|
|
{
|
|
if (level < 0 || level >= 4) {
|
|
return;
|
|
}
|
|
log_info(&log, "Setting verbosity level to %d\n", level);
|
|
log.log_level = level;
|
|
if (host.set_log_level) {
|
|
host.set_log_level(level);
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
|
|
/* Set and apply the environment */
|
|
env_setargs(argc, argv);
|
|
sef_local_startup();
|
|
blockdriver_task(&mmc_driver);
|
|
return EXIT_SUCCESS;
|
|
}
|