xref: /haiku/src/add-ons/kernel/drivers/disk/mmc/mmc_disk.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
1 /*
2  * Copyright 2018-2021 Haiku, Inc. All rights reserved.
3  * Copyright 2020, Viveris Technologies.
4  * Distributed under the terms of the MIT License.
5  *
6  * Authors:
7  *		B Krishnan Iyer, krishnaniyer97@gmail.com
8  *		Adrien Destugues, pulkomandy@pulkomandy.tk
9  */
10 
11 #include <new>
12 
13 #include <ctype.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <string.h>
17 
18 #include "mmc_disk.h"
19 #include "mmc_icon.h"
20 #include "mmc.h"
21 
22 #include <drivers/device_manager.h>
23 #include <drivers/KernelExport.h>
24 #include <drivers/Drivers.h>
25 #include <kernel/OS.h>
26 #include <util/fs_trim_support.h>
27 
28 #include <AutoDeleter.h>
29 
30 
31 #define TRACE_MMC_DISK
32 #ifdef TRACE_MMC_DISK
33 #	define TRACE(x...) dprintf("\33[33mmmc_disk:\33[0m " x)
34 #else
35 #	define TRACE(x...) ;
36 #endif
37 #define ERROR(x...)			dprintf("\33[33mmmc_disk:\33[0m " x)
38 #define CALLED() 			TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
39 
40 #define MMC_DISK_DRIVER_MODULE_NAME "drivers/disk/mmc/mmc_disk/driver_v1"
41 #define MMC_DISK_DEVICE_MODULE_NAME "drivers/disk/mmc/mmc_disk/device_v1"
42 #define MMC_DEVICE_ID_GENERATOR "mmc/device_id"
43 
44 
45 static const uint32 kBlockSize = 512; // FIXME get it from the CSD
46 
47 static device_manager_info* sDeviceManager;
48 
49 
50 struct mmc_disk_csd {
51 	// The content of this register is described in Physical Layer Simplified
52 	// Specification Version 8.00, section 5.3
53 	uint64 bits[2];
54 
55 	uint8 structure_version() { return bits[1] >> 54; }
56 	uint8 read_bl_len() { return (bits[1] >> 8) & 0xF; }
57 	uint32 c_size()
58 	{
59 		if (structure_version() == 0)
60 			return ((bits[0] >> 54) & 0x3FF) | ((bits[1] & 0x3) << 10);
61 		if (structure_version() == 1)
62 			return (bits[0] >> 40) & 0x3FFFFF;
63 		return ((bits[0] >> 40) & 0xFFFFFF) | ((bits[1] & 0xF) << 24);
64 	}
65 
66 	uint8 c_size_mult()
67 	{
68 		if (structure_version() == 0)
69 			return (bits[0] >> 39) & 0x7;
70 		// In later versions this field is not present in the structure and a
71 		// fixed value is used.
72 		return 8;
73 	}
74 };
75 
76 
77 static float
78 mmc_disk_supports_device(device_node* parent)
79 {
80 	// Filter all devices that are not on an MMC bus
81 	const char* bus;
82 	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus,
83 			true) != B_OK)
84 		return -1;
85 
86 	if (strcmp(bus, "mmc") != 0)
87 		return 0.0;
88 
89 	CALLED();
90 
91 	// Filter all devices that are not of the known types
92 	uint8_t deviceType;
93 	if (sDeviceManager->get_attr_uint8(parent, kMmcTypeAttribute,
94 			&deviceType, true) != B_OK)
95 	{
96 		ERROR("Could not get device type\n");
97 		return -1;
98 	}
99 
100 	if (deviceType == CARD_TYPE_SD)
101 		TRACE("SD card found, parent: %p\n", parent);
102 	else if (deviceType == CARD_TYPE_SDHC)
103 		TRACE("SDHC card found, parent: %p\n", parent);
104 	else
105 		return 0.0;
106 
107 	return 0.8;
108 }
109 
110 
111 static status_t
112 mmc_disk_register_device(device_node* node)
113 {
114 	CALLED();
115 
116 	device_attr attrs[] = {
117 		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "SD Card" }},
118 		{ NULL }
119 	};
120 
121 	return sDeviceManager->register_node(node, MMC_DISK_DRIVER_MODULE_NAME,
122 		attrs, NULL, NULL);
123 }
124 
125 
126 static status_t
127 mmc_disk_execute_iorequest(void* data, IOOperation* operation)
128 {
129 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)data;
130 	status_t error;
131 
132 	uint8_t command;
133 	if (operation->IsWrite())
134 		command = SD_WRITE_MULTIPLE_BLOCKS;
135 	else
136 		command = SD_READ_MULTIPLE_BLOCKS;
137 	error = info->mmc->do_io(info->parent, info->parentCookie, info->rca,
138 		command, operation, (info->flags & kIoCommandOffsetAsSectors) != 0);
139 
140 	if (error != B_OK) {
141 		info->scheduler->OperationCompleted(operation, error, 0);
142 		return error;
143 	}
144 
145 	info->scheduler->OperationCompleted(operation, B_OK, operation->Length());
146 	return B_OK;
147 }
148 
149 
150 static status_t
151 mmc_block_get_geometry(mmc_disk_driver_info* info, device_geometry* geometry)
152 {
153 	struct mmc_disk_csd csd;
154 	TRACE("Get geometry\n");
155 	status_t error = info->mmc->execute_command(info->parent,
156 		info->parentCookie, 0, SD_SEND_CSD, info->rca << 16, (uint32_t*)&csd);
157 	if (error != B_OK) {
158 		TRACE("Could not get CSD! %s\n", strerror(error));
159 		return error;
160 	}
161 
162 	TRACE("CSD: %" PRIx64 " %" PRIx64 "\n", csd.bits[0], csd.bits[1]);
163 
164 	if (csd.structure_version() >= 3) {
165 		TRACE("unknown CSD version %d\n", csd.structure_version());
166 		return B_NOT_SUPPORTED;
167 	}
168 
169 	geometry->bytes_per_sector = 1 << csd.read_bl_len();
170 	geometry->sectors_per_track = csd.c_size() + 1;
171 	geometry->cylinder_count = 1 << (csd.c_size_mult() + 2);
172 	geometry->head_count = 1;
173 	geometry->device_type = B_DISK;
174 	geometry->removable = true; // TODO detect eMMC which isn't
175 	geometry->read_only = false; // TODO check write protect switch?
176 	geometry->write_once = false;
177 
178 	// This function will be called before all data transfers, so we use this
179 	// opportunity to switch the card to 4-bit data transfers (instead of the
180 	// default 1 bit mode)
181 	uint32_t cardStatus;
182 	const uint32 k4BitMode = 2;
183 	info->mmc->execute_command(info->parent, info->parentCookie, info->rca,
184 		SD_APP_CMD, info->rca << 16, &cardStatus);
185 	info->mmc->execute_command(info->parent, info->parentCookie, info->rca,
186 		SD_SET_BUS_WIDTH, k4BitMode, &cardStatus);
187 
188 	// From now on we use 4 bit mode
189 	info->mmc->set_bus_width(info->parent, info->parentCookie, 4);
190 
191 	return B_OK;
192 }
193 
194 
195 static status_t
196 mmc_disk_init_driver(device_node* node, void** cookie)
197 {
198 	CALLED();
199 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)malloc(
200 		sizeof(mmc_disk_driver_info));
201 
202 	if (info == NULL)
203 		return B_NO_MEMORY;
204 
205 	memset(info, 0, sizeof(*info));
206 
207 	void* unused2;
208 	info->node = node;
209 	info->parent = sDeviceManager->get_parent_node(info->node);
210 	sDeviceManager->get_driver(info->parent, (driver_module_info **)&info->mmc,
211 		&unused2);
212 
213 	// We need to grab the bus cookie as well
214 	// FIXME it would be easier if that was available from the get_driver call
215 	// above directly, but currently it isn't.
216 	device_node* busNode = sDeviceManager->get_parent_node(info->parent);
217 	driver_module_info* unused;
218 	sDeviceManager->get_driver(busNode, &unused, &info->parentCookie);
219 	sDeviceManager->put_node(busNode);
220 
221 	TRACE("MMC bus handle: %p %s\n", info->mmc, info->mmc->info.info.name);
222 
223 	if (sDeviceManager->get_attr_uint16(node, kMmcRcaAttribute, &info->rca,
224 			true) != B_OK) {
225 		TRACE("MMC card node has no RCA attribute\n");
226 		free(info);
227 		return B_BAD_DATA;
228 	}
229 
230 	uint8_t deviceType;
231 	if (sDeviceManager->get_attr_uint8(info->parent, kMmcTypeAttribute,
232 			&deviceType, true) != B_OK) {
233 		ERROR("Could not get device type\n");
234 		free(info);
235 		return B_BAD_DATA;
236 	}
237 
238 	// SD and MMC cards use byte offsets for IO commands, later ones (SDHC,
239 	// SDXC, ...) use sectors.
240 	if (deviceType == CARD_TYPE_SD || deviceType == CARD_TYPE_MMC)
241 		info->flags = 0;
242 	else
243 		info->flags = kIoCommandOffsetAsSectors;
244 
245 	status_t error;
246 
247 	static const uint32 kDMAResourceBufferCount			= 16;
248 	static const uint32 kDMAResourceBounceBufferCount	= 16;
249 
250 	info->dmaResource = new(std::nothrow) DMAResource;
251 	if (info->dmaResource == NULL) {
252 		TRACE("Failed to allocate DMA resource");
253 		free(info);
254 		return B_NO_MEMORY;
255 	}
256 
257 	error = info->dmaResource->Init(info->node, kBlockSize,
258 		kDMAResourceBufferCount, kDMAResourceBounceBufferCount);
259 	if (error != B_OK) {
260 		TRACE("Failed to init DMA resource");
261 		delete info->dmaResource;
262 		free(info);
263 		return error;
264 	}
265 
266 	info->scheduler = new(std::nothrow) IOSchedulerSimple(info->dmaResource);
267 	if (info->scheduler == NULL) {
268 		TRACE("Failed to allocate scheduler");
269 		delete info->dmaResource;
270 		free(info);
271 		return B_NO_MEMORY;
272 	}
273 
274 	error = info->scheduler->Init("mmc storage");
275 	if (error != B_OK) {
276 		TRACE("Failed to init scheduler");
277 		delete info->scheduler;
278 		delete info->dmaResource;
279 		free(info);
280 		return error;
281 	}
282 	info->scheduler->SetCallback(&mmc_disk_execute_iorequest, info);
283 
284 	memset(&info->geometry, 0, sizeof(info->geometry));
285 
286 	TRACE("MMC card device initialized for RCA %x\n", info->rca);
287 	*cookie = info;
288 	return B_OK;
289 }
290 
291 
292 static void
293 mmc_disk_uninit_driver(void* _cookie)
294 {
295 	CALLED();
296 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie;
297 	delete info->scheduler;
298 	delete info->dmaResource;
299 	sDeviceManager->put_node(info->parent);
300 	free(info);
301 }
302 
303 
304 static status_t
305 mmc_disk_register_child_devices(void* _cookie)
306 {
307 	CALLED();
308 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie;
309 	status_t status;
310 
311 	int32 id = sDeviceManager->create_id(MMC_DEVICE_ID_GENERATOR);
312 	if (id < 0)
313 		return id;
314 
315 	char name[64];
316 	snprintf(name, sizeof(name), "disk/mmc/%" B_PRId32 "/raw", id);
317 
318 	status = sDeviceManager->publish_device(info->node, name,
319 		MMC_DISK_DEVICE_MODULE_NAME);
320 
321 	return status;
322 }
323 
324 
325 //	#pragma mark - device module API
326 
327 
328 static status_t
329 mmc_block_init_device(void* _info, void** _cookie)
330 {
331 	CALLED();
332 
333 	// No additional context, so just reuse the same data as the disk device
334 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)_info;
335 	*_cookie = info;
336 
337 	// Note: it is not possible to execute commands here, because this is called
338 	// with the mmc_bus locked for enumeration (and still using slow clock).
339 
340 	return B_OK;
341 }
342 
343 
344 static void
345 mmc_block_uninit_device(void* _cookie)
346 {
347 	CALLED();
348 	//mmc_disk_driver_info* info = (mmc_disk_driver_info*)_cookie;
349 
350 	// TODO cleanup whatever is relevant
351 }
352 
353 
354 static status_t
355 mmc_block_open(void* _info, const char* path, int openMode, void** _cookie)
356 {
357 	CALLED();
358 	mmc_disk_driver_info* info = (mmc_disk_driver_info*)_info;
359 
360 	// allocate cookie
361 	mmc_disk_handle* handle = new(std::nothrow) mmc_disk_handle;
362 	*_cookie = handle;
363 	if (handle == NULL) {
364 		return B_NO_MEMORY;
365 	}
366 	handle->info = info;
367 
368 	return B_OK;
369 }
370 
371 
372 static status_t
373 mmc_block_close(void* cookie)
374 {
375 	//mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
376 	CALLED();
377 
378 	return B_OK;
379 }
380 
381 
382 static status_t
383 mmc_block_free(void* cookie)
384 {
385 	CALLED();
386 	mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
387 
388 	delete handle;
389 	return B_OK;
390 }
391 
392 
393 static status_t
394 mmc_block_read(void* cookie, off_t pos, void* buffer, size_t* _length)
395 {
396 	CALLED();
397 	mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
398 
399 	size_t length = *_length;
400 
401 	if (handle->info->geometry.bytes_per_sector == 0) {
402 		status_t error = mmc_block_get_geometry(handle->info,
403 			&handle->info->geometry);
404 		if (error != B_OK) {
405 			TRACE("Failed to get disk capacity");
406 			return error;
407 		}
408 	}
409 
410 	// Do not allow reading past device end
411 	if (pos >= handle->info->DeviceSize())
412 		return B_BAD_VALUE;
413 	if (pos + (off_t)length > handle->info->DeviceSize())
414 		length = handle->info->DeviceSize() - pos;
415 
416 	IORequest request;
417 	status_t status = request.Init(pos, (addr_t)buffer, length, false, 0);
418 	if (status != B_OK)
419 		return status;
420 
421 	status = handle->info->scheduler->ScheduleRequest(&request);
422 	if (status != B_OK)
423 		return status;
424 
425 	status = request.Wait(0, 0);
426 	if (status == B_OK)
427 		*_length = length;
428 	return status;
429 }
430 
431 
432 static status_t
433 mmc_block_write(void* cookie, off_t position, const void* buffer,
434 	size_t* _length)
435 {
436 	CALLED();
437 	mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
438 
439 	size_t length = *_length;
440 
441 	if (handle->info->geometry.bytes_per_sector == 0) {
442 		status_t error = mmc_block_get_geometry(handle->info,
443 			&handle->info->geometry);
444 		if (error != B_OK) {
445 			TRACE("Failed to get disk capacity");
446 			return error;
447 		}
448 	}
449 
450 	if (position >= handle->info->DeviceSize())
451 		return B_BAD_VALUE;
452 	if (position + (off_t)length > handle->info->DeviceSize())
453 		length = handle->info->DeviceSize() - position;
454 
455 	IORequest request;
456 	status_t status = request.Init(position, (addr_t)buffer, length, true, 0);
457 	if (status != B_OK)
458 		return status;
459 
460 	status = handle->info->scheduler->ScheduleRequest(&request);
461 	if (status != B_OK)
462 		return status;
463 
464 	status = request.Wait(0, 0);
465 	if (status == B_OK)
466 		*_length = length;
467 
468 	return status;
469 }
470 
471 
472 static status_t
473 mmc_block_io(void* cookie, io_request* request)
474 {
475 	CALLED();
476 	mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
477 
478 	return handle->info->scheduler->ScheduleRequest(request);
479 }
480 
481 
482 static status_t
483 mmc_block_trim(mmc_disk_driver_info* info, fs_trim_data* trimData)
484 {
485 	enum {
486 		kEraseModeErase = 0, // force to actually erase the data
487 		kEraseModeDiscard = 1,
488 			// just mark the data as unused for internal wear leveling
489 			// algorithms
490 		kEraseModeFullErase = 2, // erase the whole card
491 	};
492 	TRACE("trim_device()\n");
493 
494 	trimData->trimmed_size = 0;
495 
496 	const off_t deviceSize = info->DeviceSize(); // in bytes
497 	if (deviceSize < 0)
498 		return B_BAD_VALUE;
499 
500 	STATIC_ASSERT(sizeof(deviceSize) <= sizeof(uint64));
501 	ASSERT(deviceSize >= 0);
502 
503 	// Do not trim past device end
504 	for (uint32 i = 0; i < trimData->range_count; i++) {
505 		uint64 offset = trimData->ranges[i].offset;
506 		uint64& size = trimData->ranges[i].size;
507 
508 		if (offset >= (uint64)deviceSize)
509 			return B_BAD_VALUE;
510 		size = min_c(size, (uint64)deviceSize - offset);
511 	}
512 
513 	uint64 trimmedSize = 0;
514 	status_t result = B_OK;
515 	for (uint32 i = 0; i < trimData->range_count; i++) {
516 		uint64 offset = trimData->ranges[i].offset;
517 		uint64 length = trimData->ranges[i].size;
518 
519 		// Round up offset and length to multiple of the sector size
520 		// The offset is rounded up, so some space may be left
521 		// (not trimmed) at the start of the range.
522 		offset = ROUNDUP(offset, kBlockSize);
523 		// Adjust the length for the possibly skipped range
524 		length -= offset - trimData->ranges[i].offset;
525 		// The length is rounded down, so some space at the end may also
526 		// be left (not trimmed).
527 		length &= ~(kBlockSize - 1);
528 
529 		if (length == 0)
530 			continue;
531 
532 		TRACE("trim %" B_PRIu64 " bytes from %" B_PRIu64 "\n",
533 			length, offset);
534 
535 		ASSERT(offset % kBlockSize == 0);
536 		ASSERT(length % kBlockSize == 0);
537 
538 		if ((info->flags & kIoCommandOffsetAsSectors) != 0) {
539 			offset /= kBlockSize;
540 			length /= kBlockSize;
541 		}
542 
543 		// Parameter of execute_command is uint32_t
544 		if (offset > UINT32_MAX
545 			|| length > UINT32_MAX - offset) {
546 			result = B_BAD_VALUE;
547 			break;
548 		}
549 
550 		uint32_t response;
551 		result = info->mmc->execute_command(info->parent, info->parentCookie,
552 			info->rca, SD_ERASE_WR_BLK_START, offset, &response);
553 		if (result != B_OK)
554 			break;
555 		result = info->mmc->execute_command(info->parent, info->parentCookie,
556 			info->rca, SD_ERASE_WR_BLK_END, offset + length, &response);
557 		if (result != B_OK)
558 			break;
559 		result = info->mmc->execute_command(info->parent, info->parentCookie,
560 			info->rca, SD_ERASE, kEraseModeDiscard, &response);
561 		if (result != B_OK)
562 			break;
563 
564 		trimmedSize += (info->flags & kIoCommandOffsetAsSectors) != 0
565 			? length * kBlockSize : length;
566 	}
567 
568 	trimData->trimmed_size = trimmedSize;
569 
570 	return result;
571 }
572 
573 
574 static status_t
575 mmc_block_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
576 {
577 	mmc_disk_handle* handle = (mmc_disk_handle*)cookie;
578 	mmc_disk_driver_info* info = handle->info;
579 
580 	switch (op) {
581 		case B_GET_MEDIA_STATUS:
582 		{
583 			if (buffer == NULL || length < sizeof(status_t))
584 				return B_BAD_VALUE;
585 
586 			*(status_t *)buffer = B_OK;
587 			return B_OK;
588 			break;
589 		}
590 
591 		case B_GET_DEVICE_SIZE:
592 		{
593 			// Legacy ioctl, use B_GET_GEOMETRY
594 			if (info->geometry.bytes_per_sector == 0) {
595 				status_t error = mmc_block_get_geometry(info, &info->geometry);
596 				if (error != B_OK) {
597 					TRACE("Failed to get disk capacity");
598 					return error;
599 				}
600 			}
601 
602 			uint64_t size = info->DeviceSize();
603 			if (size > SIZE_MAX)
604 				return B_NOT_SUPPORTED;
605 			size_t size32 = size;
606 			return user_memcpy(buffer, &size32, sizeof(size_t));
607 		}
608 
609 		case B_GET_GEOMETRY:
610 		{
611 			if (buffer == NULL || length > sizeof(device_geometry))
612 				return B_BAD_VALUE;
613 
614 			if (info->geometry.bytes_per_sector == 0) {
615 				status_t error = mmc_block_get_geometry(info, &info->geometry);
616 				if (error != B_OK) {
617 					TRACE("Failed to get disk capacity");
618 					return error;
619 				}
620 			}
621 
622 			return user_memcpy(buffer, &info->geometry, length);
623 		}
624 
625 		case B_GET_ICON_NAME:
626 			return user_strlcpy((char*)buffer, "devices/drive-harddisk",
627 				B_FILE_NAME_LENGTH);
628 
629 		case B_GET_VECTOR_ICON:
630 		{
631 			// TODO: take device type into account!
632 			device_icon iconData;
633 			if (length != sizeof(device_icon))
634 				return B_BAD_VALUE;
635 			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
636 				return B_BAD_ADDRESS;
637 
638 			if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) {
639 				if (user_memcpy(iconData.icon_data, kDriveIcon,
640 						sizeof(kDriveIcon)) != B_OK)
641 					return B_BAD_ADDRESS;
642 			}
643 
644 			iconData.icon_size = sizeof(kDriveIcon);
645 			return user_memcpy(buffer, &iconData, sizeof(device_icon));
646 		}
647 
648 		case B_TRIM_DEVICE:
649 		{
650 			// We know the buffer is kernel-side because it has been
651 			// preprocessed in devfs
652 			return mmc_block_trim(info, (fs_trim_data*)buffer);
653 		}
654 
655 		/*case B_FLUSH_DRIVE_CACHE:
656 			return synchronize_cache(info);*/
657 	}
658 
659 	return B_DEV_INVALID_IOCTL;
660 }
661 
662 
663 module_dependency module_dependencies[] = {
664 	{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
665 	{}
666 };
667 
668 
669 // The "block device" associated with the device file. It can be open()
670 // multiple times, eash allocating an mmc_disk_handle. It does not interact
671 // with the hardware directly, instead it forwards all IO requests to the
672 // disk driver through the IO scheduler.
673 struct device_module_info sMMCBlockDevice = {
674 	{
675 		MMC_DISK_DEVICE_MODULE_NAME,
676 		0,
677 		NULL
678 	},
679 
680 	mmc_block_init_device,
681 	mmc_block_uninit_device,
682 	NULL, // remove,
683 
684 	mmc_block_open,
685 	mmc_block_close,
686 	mmc_block_free,
687 	mmc_block_read,
688 	mmc_block_write,
689 	mmc_block_io,
690 	mmc_block_ioctl,
691 
692 	NULL,	// select
693 	NULL,	// deselect
694 };
695 
696 
697 // Driver for the disk devices itself. This is paired with an
698 // mmc_disk_driver_info instanciated once per device. Handles the actual disk
699 // I/O operations
700 struct driver_module_info sMMCDiskDriver = {
701 	{
702 		MMC_DISK_DRIVER_MODULE_NAME,
703 		0,
704 		NULL
705 	},
706 	mmc_disk_supports_device,
707 	mmc_disk_register_device,
708 	mmc_disk_init_driver,
709 	mmc_disk_uninit_driver,
710 	mmc_disk_register_child_devices,
711 	NULL, // mmc_disk_rescan_child_devices,
712 	NULL,
713 };
714 
715 
716 module_info* modules[] = {
717 	(module_info*)&sMMCDiskDriver,
718 	(module_info*)&sMMCBlockDevice,
719 	NULL
720 };
721