/* * Block driver for Multi Media Cards (MMC). */ /* kernel headers */ #include #include #include #include #include #include /* system headers */ #include /* disk IOCTL's */ /* usr headers */ #include #include #include #include #include /* 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 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(devminor_t minor); /* Prototypes for the block device */ static int block_open(devminor_t minor, int access); static int block_close(devminor_t minor); static int block_transfer(devminor_t minor, int do_write, u64_t position, endpoint_t endpt, iovec_t * iov, unsigned int nr_req, int flags); static int block_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt, cp_grant_id_t grant, endpoint_t user_endpt); static struct device *block_part(devminor_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 = { .bdr_type = BLOCKDRIVER_TYPE_DISK,/* handle partition requests */ .bdr_open = block_open, /* device open */ .bdr_close = block_close, /* on a close */ .bdr_transfer = block_transfer, /* does the I/O */ .bdr_ioctl = block_ioctl, /* ioctls */ .bdr_part = block_part, /* get partition information */ .bdr_intr = hw_intr, /* left over interrupts */ .bdr_alarm = bdr_alarm /* no alarm processing */ }; 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 /service/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(devminor_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(devminor_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( devminor_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(devminor_t minor, unsigned long request, endpoint_t endpt, cp_grant_id_t grant, endpoint_t UNUSED(user_endpt)) { /* 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 ENOTTY; } /*===========================================================================* * block_part * *===========================================================================*/ static struct device * block_part(devminor_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(devminor_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 >= 0 && 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; }