xref: /minix/minix/drivers/storage/mmc/mmcblk.c (revision 7f5f010b)
1 /*
2  * Block driver for Multi Media Cards (MMC).
3  */
4 /* kernel headers */
5 #include <minix/syslib.h>
6 #include <minix/driver.h>
7 #include <minix/blockdriver.h>
8 #include <minix/drvlib.h>
9 #include <minix/log.h>
10 #include <minix/minlib.h>
11 
12 /* system headers */
13 #include <sys/ioc_disk.h>	/* disk IOCTL's */
14 
15 /* usr headers */
16 #include <assert.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <signal.h>
21 
22 /* local headers */
23 #include "mmchost.h"
24 
25 /* used for logging */
26 static struct log log = {
27 	.name = "mmc_block",
28 	.log_level = LEVEL_INFO,
29 	.log_func = default_log
30 };
31 
32 /* holding the current host controller */
33 static struct mmc_host host;
34 
35 #define NR_SUBDEVS              (MAX_DRIVES * SUB_PER_DRIVE)
36 
37 /* When passing data over a grant one needs to pass
38  * a buffer to sys_safecopy copybuff is used for that*/
39 #define COPYBUFF_SIZE 0x1000	/* 4k buff */
40 static unsigned char copybuff[COPYBUFF_SIZE];
41 
42 static struct sd_slot *get_slot(devminor_t minor);
43 
44 /* Prototypes for the block device */
45 static int block_open(devminor_t minor, int access);
46 static int block_close(devminor_t minor);
47 static int block_transfer(devminor_t minor,
48     int do_write,
49     u64_t position,
50     endpoint_t endpt, iovec_t * iov, unsigned int nr_req, int flags);
51 
52 static int block_ioctl(devminor_t minor, unsigned long request,
53 	endpoint_t endpt, cp_grant_id_t grant, endpoint_t user_endpt);
54 static struct device *block_part(devminor_t minor);
55 
56 /* System even handling */
57 static void sef_local_startup();
58 static int block_system_event_cb(int type, sef_init_info_t * info);
59 static void block_signal_handler_cb(int signo);
60 
61 void
62 bdr_alarm(clock_t stamp)
63 {
64 	log_debug(&log, "alarm %d\n", stamp);
65 }
66 
67 static int apply_env();
68 static void hw_intr(unsigned int irqs);
69 
70 /* set the global logging level */
71 static void set_log_level(int level);
72 
73 /* Entry points for the BLOCK driver. */
74 static struct blockdriver mmc_driver = {
75 	.bdr_type 	= BLOCKDRIVER_TYPE_DISK,/* handle partition requests */
76 	.bdr_open	= block_open,		/* device open */
77 	.bdr_close	= block_close,		/* on a close */
78 	.bdr_transfer	= block_transfer,	/* does the I/O */
79 	.bdr_ioctl	= block_ioctl,		/* ioctls */
80 	.bdr_part	= block_part,		/* get partition information */
81 	.bdr_intr	= hw_intr,		/* left over interrupts */
82 	.bdr_alarm	= bdr_alarm		/* no alarm processing */
83 };
84 
85 static void
86 hw_intr(unsigned int irqs)
87 {
88 	log_debug(&log, "Hardware inter left over\n");
89 	host.hw_intr(irqs);
90 }
91 
92 static int
93 apply_env()
94 {
95 	long v;
96 	/* apply the env setting passed to this driver parameters accepted
97 	 * log_level=[0-4] (NONE,WARN,INFO,DEBUG,TRACE) instance=[0-3]
98 	 * instance/bus number to use for this driver Passing these arguments
99 	 * is done when starting the driver using the service command in the
100 	 * following way service up /service/mmc -args "log_level=2 instance=1
101 	 * driver=dummy" -dev /dev/c2d0 */
102 	char driver[16];
103 	memset(driver, '\0', 16);
104 	(void) env_get_param("driver", driver, 16);
105 	if (strlen(driver) == 0
106 	    || strncmp(driver, "mmchs", strlen("mmchs") + 1) == 0) {
107 		/* early init of host mmc host controller. This code should
108 		 * depend on knowing the hardware that is running bellow. */
109 #ifdef __arm__
110 		host_initialize_host_structure_mmchs(&host);
111 #endif
112 	} else if (strncmp(driver, "dummy", strlen("dummy") + 1) == 0) {
113 		host_initialize_host_structure_dummy(&host);
114 	} else {
115 		log_warn(&log, "Unknown driver %s\n", driver);
116 	}
117 	/* Initialize the verbosity level. */
118 	v = 0;
119 	if (env_parse("log_level", "d", 0, &v, LEVEL_NONE,
120 		LEVEL_TRACE) == EP_SET) {
121 		set_log_level(v);
122 	}
123 
124 	/* Find out which driver instance we are. */
125 	v = 0;
126 	env_parse("instance", "d", 0, &v, 0, 3);
127 	if (host.host_set_instance(&host, v)) {
128 		log_warn(&log, "Failed to set mmc instance to  %d\n", v);
129 		return -1;	/* NOT OK */
130 	}
131 	return OK;
132 }
133 
134 ;
135 
136 /*===========================================================================*
137  *                    block_open                                             *
138  *===========================================================================*/
139 static int
140 block_open(devminor_t minor, int access)
141 {
142 	struct sd_slot *slot;
143 	slot = get_slot(minor);
144 	int i, j;
145 	int part_count, sub_part_count;
146 
147 	i = j = part_count = sub_part_count = 0;
148 
149 	if (!slot) {
150 		log_debug(&log, "Not handling open on non existing slot\n");
151 		return EIO;
152 	}
153 
154 	assert(slot->host != NULL);
155 
156 	if (!slot->host->card_detect(slot)) {
157 		log_debug(&log, "No card inserted in the SD slot\n");
158 		return EIO;
159 	}
160 
161 	/* If we are already open just increase the open count and return */
162 	if (slot->card.state == SD_MODE_DATA_TRANSFER_MODE) {
163 		assert(slot->card.open_ct >= 0);
164 		slot->card.open_ct++;
165 		log_trace(&log, "increased open count to %d\n",
166 		    slot->card.open_ct);
167 		return OK;
168 	}
169 
170 	/* We did not have an sd-card inserted so we are going to probe for it
171 	 */
172 	log_debug(&log, "First open on (%d)\n", minor);
173 	if (!host.card_initialize(slot)) {
174 		// * TODO: set card state to INVALID until removed? */
175 		return EIO;
176 	}
177 
178 	partition(&mmc_driver, 0 /* first card on bus */ , P_PRIMARY,
179 	    0 /* atapi device?? */ );
180 
181 	log_trace(&log, "descr \toffset(bytes)      size(bytes)\n", minor);
182 
183 	log_trace(&log, "disk %d\t0x%016llx 0x%016llx\n", i,
184 	    slot->card.part[0].dv_base, slot->card.part[0].dv_size);
185 	for (i = 1; i < 5; i++) {
186 		if (slot->card.part[i].dv_size == 0)
187 			continue;
188 		part_count++;
189 		log_trace(&log, "part %d\t0x%016llx 0x%016llx\n", i,
190 		    slot->card.part[i].dv_base, slot->card.part[i].dv_size);
191 		for (j = 0; j < 4; j++) {
192 			if (slot->card.subpart[(i - 1) * 4 + j].dv_size == 0)
193 				continue;
194 			sub_part_count++;
195 			log_trace(&log,
196 			    " sub %d/%d\t0x%016llx 0x%016llx\n", i, j,
197 			    slot->card.subpart[(i - 1) * 4 + j].dv_base,
198 			    slot->card.subpart[(i - 1) * 4 + j].dv_size);
199 		}
200 	}
201 	log_debug(&log, "Found %d partitions and %d sub partitions\n",
202 	    part_count, sub_part_count);
203 	slot->card.open_ct++;
204 	assert(slot->card.open_ct == 1);
205 	return OK;
206 }
207 
208 /*===========================================================================*
209  *                    block_close                                            *
210  *===========================================================================*/
211 static int
212 block_close(devminor_t minor)
213 {
214 	struct sd_slot *slot;
215 
216 	slot = get_slot(minor);
217 	if (!slot) {
218 		log_debug(&log, "Not handling open on non existing slot\n");
219 		return EIO;
220 	}
221 
222 	/* if we arrived here we expect a card to be present, we will need do
223 	 * deal with removal later */
224 	assert(slot->host != NULL);
225 	assert(slot->card.open_ct >= 1);
226 
227 	/* If this is not the last open count simply decrease the counter and
228 	 * return */
229 	if (slot->card.open_ct > 1) {
230 		slot->card.open_ct--;
231 		log_trace(&log, "decreased open count to %d\n",
232 		    slot->card.open_ct);
233 		return OK;
234 	}
235 
236 	assert(slot->card.open_ct == 1);
237 	log_debug(&log, "freeing the block device as it is no longer used\n");
238 
239 	/* release the card as check the open_ct should be 0 */
240 	slot->host->card_release(&slot->card);
241 	assert(slot->card.open_ct == 0);
242 	return OK;
243 }
244 
245 static int
246 copyto(endpoint_t dst_e,
247     cp_grant_id_t gr_id, vir_bytes offset, vir_bytes address, size_t bytes)
248 {
249 	/* Helper function that used memcpy to copy data when the endpoint ==
250 	 * SELF */
251 	if (dst_e == SELF) {
252 		memcpy((char *) gr_id + offset, (char *) address, bytes);
253 		return OK;
254 	} else {
255 		/* Read io_size bytes from our data at the correct * offset
256 		 * and write it to the output buffer at 0 */
257 		return sys_safecopyto(dst_e, gr_id, offset, address, bytes);
258 	}
259 }
260 
261 static int
262 copyfrom(endpoint_t src_e,
263     cp_grant_id_t gr_id, vir_bytes offset, vir_bytes address, size_t bytes)
264 {
265 	/* Helper function that used memcpy to copy data when the endpoint ==
266 	 * SELF */
267 	if (src_e == SELF) {
268 		memcpy((char *) address, (char *) gr_id + offset, bytes);
269 		return OK;
270 	} else {
271 		return sys_safecopyfrom(src_e, gr_id, offset, address, bytes);
272 	}
273 }
274 
275 /*===========================================================================*
276  *                    block_transfer                                         *
277  *===========================================================================*/
278 static int
279 block_transfer(
280     devminor_t minor,		/* minor device number */
281     int do_write,		/* read or write? */
282     u64_t position,		/* offset on device to read or write */
283     endpoint_t endpt,		/* process doing the request */
284     iovec_t * iov,		/* pointer to read or write request vector */
285     unsigned int nr_req,	/* length of request vector */
286     int flags			/* transfer flags */
287     )
288 {
289 	unsigned long counter;
290 	iovec_t *ciov;		/* Current IO Vector */
291 	struct device *dev;	/* The device used */
292 	struct sd_slot *slot;	/* The sd slot the requests is pointed to */
293 	vir_bytes io_size;	/* Size to read/write to/from the iov */
294 	vir_bytes io_offset;	/* Size to read/write to/from the iov */
295 	vir_bytes bytes_written;
296 
297 	int r, blk_size, i;
298 
299 	/* Get the current "device" geometry */
300 	dev = block_part(minor);
301 	if (dev == NULL) {
302 		log_warn(&log,
303 		    "Transfer requested on unknown device minor(%d)\n", minor);
304 		/* Unknown device */
305 		return ENXIO;
306 	}
307 	log_trace(&log, "I/O on minor(%d) %s at 0x%016llx\n", minor,
308 	    (do_write) ? "Write" : "Read", position);
309 
310 	slot = get_slot(minor);
311 	assert(slot);
312 
313 	if (slot->card.blk_size == 0) {
314 		log_warn(&log, "Request on a card with block size of 0\n");
315 		return EINVAL;
316 	}
317 	if (slot->card.blk_size > COPYBUFF_SIZE) {
318 		log_warn(&log,
319 		    "Card block size (%d) exceeds internal buffer size %d\n",
320 		    slot->card.blk_size, COPYBUFF_SIZE);
321 		return EINVAL;
322 	}
323 
324 	/* It is fully up to the driver to decide on restrictions for the
325 	 * parameters of transfers, in those cases we return EINVAL */
326 	if (position % slot->card.blk_size != 0) {
327 		/* Starting at a block boundary */
328 		log_warn(&log,
329 		    "Requests must start at a block boundary"
330 		    "(start,block size)=(%016llx,%08x)\n", position,
331 		    slot->card.blk_size);
332 		return EINVAL;
333 	}
334 
335 	blk_size = slot->card.blk_size;
336 
337 	bytes_written = 0;
338 
339 	/* Are we trying to start reading past the end */
340 	if (position >= dev->dv_size) {
341 		log_warn(&log, "start reading past drive size\n");
342 		return 0;
343 	};
344 
345 	ciov = iov;
346 	/* do some more validation */
347 	for (counter = 0; counter < nr_req; counter++) {
348 		assert(ciov != NULL);
349 		if (ciov->iov_size % blk_size != 0) {
350 			/* transfer a multiple of blk_size */
351 			log_warn(&log,
352 			    "Requests must start at a block boundary "
353 			    "(start,block size)=(%016llx,%08x)\n", position,
354 			    slot->card.blk_size);
355 			return EINVAL;
356 		}
357 
358 		if (ciov->iov_size <= 0) {
359 			log_warn(&log,
360 			    "Invalid iov size for iov %d of %d size\n",
361 			    counter, nr_req, ciov->iov_size);
362 			return EINVAL;
363 		}
364 		ciov++;
365 	}
366 
367 	ciov = iov;
368 	for (counter = 0; counter < nr_req; counter++) {
369 		/* Assume we are to transfer the amount of data given in the
370 		 * input/output vector but ensure we are not doing i/o past
371 		 * our own boundaries */
372 		io_size = ciov->iov_size;
373 		io_offset = position + bytes_written;
374 
375 		/* Check we are not reading/writing past the end */
376 		if (position + bytes_written + io_size > dev->dv_size) {
377 			io_size = dev->dv_size - (position + bytes_written);
378 		};
379 
380 		log_trace(&log,
381 		    "I/O %s request(%d/%d) iov(grant,size,iosize,"
382 		    "offset)=(%d,%d,%d,%d)\n",
383 		    (do_write) ? "write" : "read", counter + 1, nr_req,
384 		    ciov->iov_addr, ciov->iov_size, io_size, io_offset);
385 		/* transfer max one block at the time */
386 		for (i = 0; i < io_size / blk_size; i++) {
387 			if (do_write) {
388 				/* Read io_size bytes from i/o vector starting
389 				 * at 0 and write it to out buffer at the
390 				 * correct offset */
391 				r = copyfrom(endpt, ciov->iov_addr,
392 				    i * blk_size, (vir_bytes) copybuff,
393 				    blk_size);
394 				if (r != OK) {
395 					log_warn(&log,
396 					    "I/O write error: %s iov(base,size)=(%d,%d)"
397 					    " at offset=%d\n",
398 					    strerror(_SIGN r), ciov->iov_addr,
399 					    ciov->iov_size, io_offset);
400 					return EINVAL;
401 				}
402 
403 				/* write a single block */
404 				slot->host->write(&slot->card,
405 				    (dev->dv_base / blk_size) +
406 				    (io_offset / blk_size) + i, 1, copybuff);
407 				bytes_written += blk_size;
408 			} else {
409 				/* read a single block info copybuff */
410 				slot->host->read(&slot->card,
411 				    (dev->dv_base / blk_size) +
412 				    (io_offset / blk_size) + i, 1, copybuff);
413 				/* Read io_size bytes from our data at the
414 				 * correct offset and write it to the output
415 				 * buffer at 0 */
416 				r = copyto(endpt, ciov->iov_addr, i * blk_size,
417 				    (vir_bytes) copybuff, blk_size);
418 				if (r != OK) {
419 					log_warn(&log,
420 					    "I/O read error: %s iov(base,size)=(%d,%d)"
421 					    " at offset=%d\n",
422 					    strerror(_SIGN r), ciov->iov_addr,
423 					    ciov->iov_size, io_offset);
424 					return EINVAL;
425 				}
426 				bytes_written += blk_size;
427 			}
428 		}
429 		ciov++;
430 	}
431 	return bytes_written;
432 }
433 
434 /*===========================================================================*
435  *				block_ioctl		                     *
436  *===========================================================================*/
437 static int
438 block_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt,
439     cp_grant_id_t grant, endpoint_t UNUSED(user_endpt))
440 {
441 	/* IOCTL handling */
442 	struct sd_slot *slot;
443 	log_trace(&log,
444 	    "enter (minor,request,endpoint,grant)=(%d,%lu,%d)\n", minor,
445 	    request, endpt, grant);
446 
447 	slot = get_slot(minor);
448 	if (!slot) {
449 		log_warn(&log,
450 		    "Doing ioctl on non existing block device(%d)\n", minor);
451 		return EINVAL;
452 	}
453 
454 	switch (request) {
455 	case DIOCOPENCT:
456 		// TODO: add a check for card validity */
457 		log_trace(&log, "returning open count %d\n",
458 		    slot->card.open_ct);
459 		/* return the current open count */
460 		return sys_safecopyto(endpt, grant, 0,
461 		    (vir_bytes) & slot->card.open_ct,
462 		    sizeof(slot->card.open_ct));
463 	case DIOCFLUSH:
464 		/* No need to flush but some devices like movinands require
465 		 * 500 ms inactivity */
466 		return OK;
467 	}
468 
469 	return ENOTTY;
470 }
471 
472 /*===========================================================================*
473  *                    block_part                                             *
474  *===========================================================================*/
475 static struct device *
476 block_part(devminor_t minor)
477 {
478 	/*
479 	 * Reuse the existing MINIX major/minor partitioning scheme.
480 	 * - 8 drives
481 	 * - 5 devices per drive allowing direct access to the disk and up to 4
482 	 *   partitions (IBM style partitioning without extended partitions)
483 	 * - 4 Minix style sub partitions per partitions
484 	 */
485 	struct device *dev;
486 	struct sd_slot *slot;
487 
488 	dev = NULL;
489 	slot = get_slot(minor);
490 	if (!slot) {
491 		log_warn(&log,
492 		    "Device information requested for non existing partition "
493 		    "minor(%d)\n", minor);
494 		return NULL;
495 	}
496 
497 	if (!slot->host->card_detect(slot)) {
498 		log_warn(&log,
499 		    "Device information requested from empty slot(%d)\n",
500 		    minor);
501 		return NULL;
502 	}
503 
504 	if (minor < 5) {
505 		/* we are talking about the first disk */
506 		dev = &slot->card.part[minor];
507 		log_trace(&log,
508 		    "returning partition(%d) (base,size)=(0x%016llx,0x%016llx)\n",
509 		    minor, dev->dv_base, dev->dv_size);
510 	} else if (minor >= 128 && minor < 128 + 16) {
511 		/* sub partitions of the first disk we don't care about the
512 		 * rest */
513 		dev = &slot->card.subpart[minor - 128];
514 		log_trace(&log,
515 		    "returning sub partition(%d) (base,size)=(0x%016llx,0x%016llx)\n",
516 		    minor - 128, dev->dv_base, dev->dv_size);
517 
518 	} else {
519 		log_warn(&log,
520 		    "Device information requested for non existing "
521 		    "partition minor(%d)\n", minor);
522 	}
523 	return dev;
524 }
525 
526 /*===========================================================================*
527  *                         sef_local_startup                                 *
528  *===========================================================================*/
529 static void
530 sef_local_startup()
531 {
532 	log_info(&log, "Initializing the MMC block device\n");
533 	if (apply_env()) {
534 		log_warn(&log, "Failed while applying environment settings\n");
535 		exit(EXIT_FAILURE);
536 	}
537 
538 	if (host.host_init(&host)) {
539 		log_warn(&log, "Failed to initialize the host controller\n");
540 		exit(EXIT_FAILURE);
541 	}
542 	/*
543 	 * Register callbacks for fresh start, live update and restart.
544 	 *  Use the same function for all event types
545 	 */
546 	sef_setcb_init_fresh(block_system_event_cb);
547 	sef_setcb_init_lu(block_system_event_cb);
548 
549 	/* Register a signal handler */
550 	sef_setcb_signal_handler(block_signal_handler_cb);
551 
552 	/* SEF startup */
553 	sef_startup();
554 }
555 
556 /*===========================================================================*
557  *                         block_system_event_cb                             *
558  *===========================================================================*/
559 static int
560 block_system_event_cb(int type, sef_init_info_t * info)
561 {
562 	/*
563 	 * Callbacks for the System event framework as registered in
564 	 * sef_local_startup */
565 	switch (type) {
566 	case SEF_INIT_FRESH:
567 		log_info(&log, "System event framework fresh start\n");
568 		break;
569 
570 	case SEF_INIT_LU:
571 		/* Restore the state. post update */
572 		log_info(&log, "System event framework live update\n");
573 		break;
574 
575 	case SEF_INIT_RESTART:
576 		log_info(&log, "System event framework post restart\n");
577 		break;
578 	}
579 	blockdriver_announce(type);
580 	return OK;
581 }
582 
583 /*===========================================================================*
584  *                         block_signal_handler_cb                           *
585  *===========================================================================*/
586 static void
587 block_signal_handler_cb(int signo)
588 {
589 	struct sd_slot *slot;
590 
591 	log_debug(&log, "System event framework signal handler sig(%d)\n",
592 	    signo);
593 	/* Only check for termination signal, ignore anything else. */
594 	if (signo != SIGTERM)
595 		return;
596 
597 	/* we only have a single slot and need an open count idealy we should
598 	 * iterate over the card to determine the open count */
599 	slot = get_slot(0);
600 	assert(slot);
601 	if (slot->card.open_ct > 0) {
602 		log_debug(&log, "Not responding to SIGTERM (open count=%d)\n",
603 		    slot->card.open_ct);
604 		return;
605 	}
606 
607 	log_info(&log, "MMC driver exit");
608 	exit(0);
609 }
610 
611 #define IS_MINIX_SUB_PARTITION_MINOR(minor) (minor >= MINOR_d0p0s0 )
612 
613 static struct sd_slot *
614 get_slot(devminor_t minor)
615 {
616 	/*
617 	 * Get an sd_slot based on the minor number.
618 	 *
619 	 * This driver only supports a single card at at time. Also as
620 	 * we are following the major/minor scheme of other driver we
621 	 * must return a slot for all minors on disk 0 these are  0-5
622 	 * for the disk and 4 main partitions and
623 	 * number 128 till 144 for sub partitions.
624 	 */
625 	/* If this is a minor for the first disk (e.g. minor 0 till 5) */
626 	if (minor >= 0 && minor / DEV_PER_DRIVE == 0) {
627 		/* we are talking about the first disk and that is all we
628 		 * support */
629 		return &host.slot[0];
630 	} else if (IS_MINIX_SUB_PARTITION_MINOR(minor)
631 	    && (((minor - MINOR_d0p0s0) / SUB_PER_DRIVE) == 0)) {
632 		/* a minor from the first disk */
633 		return &host.slot[0];
634 	} else {
635 		log_trace(&log,
636 		    "Device information requested for non existing partition "
637 		    "minor(%d)\n", minor);
638 		return NULL;
639 	}
640 }
641 
642 static void
643 set_log_level(int level)
644 {
645 	if (level < 0 || level >= 4) {
646 		return;
647 	}
648 	log_info(&log, "Setting verbosity level to %d\n", level);
649 	log.log_level = level;
650 	if (host.set_log_level) {
651 		host.set_log_level(level);
652 	}
653 }
654 
655 int
656 main(int argc, char **argv)
657 {
658 
659 	/* Set and apply the environment */
660 	env_setargs(argc, argv);
661 	sef_local_startup();
662 	blockdriver_task(&mmc_driver);
663 	return EXIT_SUCCESS;
664 }
665