CAM, GSoC, mmccam, RTEMS, SDIO

RTEMS SDIO driver: Current progress

Hi, this post mainly concerns with the current progress of SDIO driver’s implementation on RTEMS. In a nutshell, driver is able to detect, initialize the type of card. However, the part concerned with registering the partitions of the card as RTEMS disks is still buggy. So, I’ll discuss some of the bugs which were previously resolved and the ones that are still left.

Starting with a very short introduction of how MMCCAM driver is being interfaced with SDHCI driver:

Part 1: Interfacing

Complete interfacing task is done mainly via these two files:

nexus_devices.h

#ifdef RTEMS_BSD_MODULE_MMCCAM
SYSINIT_MODULE_REFERENCE(cam);
SYSINIT_MODULE_REFERENCE(mmcprobe);
SYSINIT_MODULE_REFERENCE(sdda);
#endif /* RTEMS_BSD_MODULE_MMCCAM */

During initialization of the nexus bus following modules should be initialized:

  • cam : this initializes the CAM XPT layer along with the queues and does some initialization work for SIM’s.
  • mmcprobe : this initializes the SIM i.e MMCCAM stack and prepares the stack for handling interrupts from devices which will be added to the cam queue in future. Please note that any MMC/SD/SDIO interrupt occurring before the initialization of this module will be ignored. That’s why it’s being called via nexus-devices.h. Also, it is responsible for initializing the card and to do IO in case of SDIO cards.
  • sdda : this module is mainly concerned with handling and registering the partitions of the respective disk. It probably will not be used in case of SDIO where we just have to do I/O.

Moreover, please find the default-mmccam.ini file. It’s the buildset configuration file for turning mmccam on with this content:

[modules]
mmccam = on

sdhci.c

Head towards sys/dev/sdhci/sdhci.c file,

void
sdhci_start_slot(struct sdhci_slot *slot)
{
        if ((slot->devq = cam_simq_alloc(1)) == NULL) {
                goto fail;
        }

        mtx_init(&slot->sim_mtx, "sdhcisim", NULL, MTX_DEF);
        slot->sim = cam_sim_alloc(sdhci_cam_action, sdhci_cam_poll,
                                  "sdhci_slot", slot, device_get_unit(slot->bus),
                                  &slot->sim_mtx, 1, 1, slot->devq);

        if (slot->sim == NULL) {
                cam_simq_free(slot->devq);
                slot_printf(slot, "cannot allocate CAM SIM\n");
                goto fail;
        }

        mtx_lock(&slot->sim_mtx);
        if (xpt_bus_register(slot->sim, slot->bus, 0) != 0) {
                slot_printf(slot,
                              "cannot register SCSI pass-through bus\n");
                cam_sim_free(slot->sim, FALSE);
                cam_simq_free(slot->devq);
                mtx_unlock(&slot->sim_mtx);
                goto fail;
        }

        mtx_unlock(&slot->sim_mtx);
        /* End CAM-specific init */
	slot->card_present = 0;
	sdhci_card_task(slot, 0);
        return;

fail:
        if (slot->sim != NULL) {
                mtx_lock(&slot->sim_mtx);
                xpt_bus_deregister(cam_sim_path(slot->sim));
                cam_sim_free(slot->sim, FALSE);
                mtx_unlock(&slot->sim_mtx);
        }

        if (slot->devq != NULL)
                cam_simq_free(slot->devq);
}

sdhci_start_slot function has a separate definition in case of normal sdhci driver, which can be found here:https://github.com/madaari/rtems-libbsd/blob/master/freebsd/sys/dev/sdhci/sdhci.c#L921

After getting the interrupt like in the form sdhci_ti0: <TI MMCHS (SDHCI 2.0)> mem 0x48060000-0x48060fff irq 64 on simplebus0 sdhci_start_slot() is called with an appropriate slot id. In case of BBB, there were actually two of these interrupts. One for eMMC another for SDHCI.

sdhci_start_slot() calls the cam_sim_alloc() which registers the SIM on the CAM bus. cam_simq_alloc() checks the device queue. Then xpt_bus_register initializes a bus(a  list containing all the CCB’s related to the card) between the mmcprobe/sdda module and the SIM. Rest of the work is handled by the CAM initializing routines.

Part 2: Card Initialization

Main file responsible for initialization of the SD/MMC/SDIO card is sys/cam/mmc/mmc_xpt.c

Notice the below structure:

static struct xpt_xport_ops mmc_xport_ops = {
	.alloc_device = mmc_alloc_device,
	.action = mmc_action,
	.async = mmc_dev_async,
	.announce = mmc_announce_periph,
};

#define MMC_XPT_XPORT(x, X)				\
	static struct xpt_xport mmc_xport_ ## x = {	\
		.xport = XPORT_ ## X,			\
		.name = #x,				\
		.ops = &mmc_xport_ops,			\
	};						\
	CAM_XPT_XPORT(mmc_xport_ ## x);

MMC_XPT_XPORT(mmc, MMCSD);

here, all the functions mentioned in mmc_xport_ops struct act as an interface to the CAM module. Whenever CAM discovers a SD/MMC/SDIO card it first announces it’s presence using mmc_alloc_device. Similarly, whenever, a CCB is created mmc_action is called to server the request. mmc_dev_async is used to handle asynchronous events.

Here’s the order in which these functions are called:

mmc_alloc_device -> mmc_dev_async -> mmc_probe_{LUN,BUS,TGT} scan -> mmc_probe_find_card type

void
mmc_print_ident(struct mmc_params *ident_data)
{
        printf("Relative addr: %08x\n", ident_data->card_rca);
        printf("Card features: <");
        if (ident_data->card_features & CARD_FEATURE_MMC)
                printf("MMC ");
        if (ident_data->card_features & CARD_FEATURE_MEMORY)
                printf("Memory ");
        if (ident_data->card_features & CARD_FEATURE_SDHC)
                printf("High-Capacity ");
        if (ident_data->card_features & CARD_FEATURE_SD20)
                printf("SD2.0-Conditions ");
        if (ident_data->card_features & CARD_FEATURE_SDIO)
                printf("SDIO ");
        printf(">\n");

        if (ident_data->card_features & CARD_FEATURE_MEMORY)
                printf("Card memory OCR: %08x\n", ident_data->card_ocr);

        if (ident_data->card_features & CARD_FEATURE_SDIO) {
                printf("Card IO OCR: %08x\n", ident_data->io_ocr);
                printf("Number of funcitions: %u\n", ident_data->sdio_func_count);
        }
}

After selecting the card type, it is then initialized and the initialization process looks somewhat like:

sdhci_ti0-slot0: Clock => 0                                                                                        
sdhci_ti0-slot0: VDD => 18                                                                                         
sdhci_ti0-slot0: CS => 0                                                                                           
sdhci_ti0-slot0: Bus width => 0                                                                                    
sdhci_ti0-slot0: Power mode => 1                                                                                   
sdhci_ti0-slot0: Bus mode => 1                                                                                     
sdhci_ti0-slot0: sdhci_cam_update_ios: power_mode=1, clk=0, bus_width=0, timing=0

Card is then supplied with few SDIO related commands which if they didn’t respond to, confirms their card type .Here are all the commands supported by the mmcprobe module.

After initializing the card completely, sdda module is then called upon to determine and register the SDHC/MMC partitions as separate disks.

Part 3: Determine Partitions and register them as separate disks

sdda module is responsible for determining the partitions and registering them as separate disks. Corresponding file is sys/cam/mmc/mmc_da.c 

The bug lies here!! Theoretically, i have to replace the BIO/GEOM part concerned with disk attachment with the RTEMS media_server. In FreeBSD, BIO first calls the sddastrategy routine which further calls the sdda_schedule and then calls the sdda_start routine which converts the BIO requests into a CCB which is then passed to the CAM_XPT layer. For RTEMS, we have to do something like this only. In the sdda_add_part function which invokes the disk routines, rtems_media_server is called as:

rtems_status_code status_code = rtems_media_server_disk_attach(
        part->name, rtems_bsd_mmcsd_attach_worker, part);
    	BSD_ASSERT(status_code == RTEMS_SUCCESSFUL);
static int
rtems_bsd_mmcsd_disk_ioctl(rtems_disk_device *dd, uint32_t req, void *arg)
{


	if (req == RTEMS_BLKIO_REQUEST) {
		struct sdda_part *part = rtems_disk_get_driver_data(dd);
		rtems_blkdev_request *blkreq = arg;

		return rtems_bsd_mmcsd_disk_read_write(part, blkreq);
	} else if (req == RTEMS_BLKIO_CAPABILITIES) {
		*(uint32_t *) arg = RTEMS_BLKDEV_CAP_MULTISECTOR_CONT;
		return 0;
	} else {
		return rtems_blkdev_ioctl(dd, req, arg);
	}

}

static rtems_status_code
rtems_bsd_mmcsd_attach_worker(rtems_media_state state, const char *src, char **dest, void *arg)
{

	rtems_status_code status_code = RTEMS_SUCCESSFUL;
	struct sdda_part *part = arg;
	char *disk = NULL;

	if (state == RTEMS_MEDIA_STATE_READY) {
		struct sdda_softc *sc = part->sc;
		//device_t dev = sc->dev;
		uint32_t block_count = sc->sector_count;
		uint32_t block_size = 512;
		printk("Unit number is %u \n",sc->periph->unit_number);
		disk = rtems_media_create_path("/dev", src, sc->periph->unit_number);
		if (disk == NULL) {
			printf("OOPS: create path failed\n");
			goto error;
		}
/*
		MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev);

		status_code = rtems_bsd_mmcsd_set_block_size(dev, block_size);
		if (status_code != RTEMS_SUCCESSFUL) {
			printf("OOPS: set block size failed\n");
			goto error;
		}
*/

		status_code = rtems_blkdev_create(disk, block_size,
		    block_count, rtems_bsd_mmcsd_disk_ioctl, part);
		if (status_code != RTEMS_SUCCESSFUL) {
			goto error;
		}

		*dest = strdup(disk, M_RTEMS_HEAP);
	}

	return RTEMS_SUCCESSFUL;
error:
	free(disk, M_RTEMS_HEAP);

	return RTEMS_IO_ERROR;
}

After which it should successfully add the path:

media listener: event = DISK ATTACH, state = INQUIRY, src = sdda                
Unit number is 0                                                                
media listener: event = DISK ATTACH, state = SUCCESS, src = **xpt_done

After attaching the disk, RTEMS media server will try to read the partition table and will thus invoke rtems_bsd_sdda_disk_read_write:

static int
rtems_bsd_sdda_disk_read_write(struct sdda_part *part, rtems_blkdev_request *blkreq)
{

	rtems_status_code status_code = RTEMS_SUCCESSFUL;
	struct sdda_softc *sc = part->sc;
	//device_t dev = sc->dev;
	struct cam_periph *periph = &sc->periph;
	struct mmc_params *mmcp = &periph->path->device->mmc_ident_data;
	int shift = (mmcp->card_features & CARD_FEATURE_SDHC) ? 0 : 9;
	int rca = get_rca(sc->periph);
	uint32_t buffer_count = blkreq->bufnum;
	uint32_t transfer_bytes = blkreq->bufs[0].length;
	uint32_t block_count = transfer_bytes / MMC_SECTOR_SIZE;
	uint32_t opcode;
	uint32_t data_flags;
	uint32_t i;
	uint32_t prio = min(periph->scheduled_priority,periph->immediate_priority);

	if (blkreq->req == RTEMS_BLKDEV_REQ_WRITE) {
		if (block_count > 1) {
			opcode = MMC_WRITE_MULTIPLE_BLOCK;
			printk("RTEMS multiple block write req \n");
		} else {
			opcode = MMC_WRITE_BLOCK;
			printk("RTEMS single block write req \n");
		}

		data_flags = MMC_DATA_WRITE;
	} else {
		BSD_ASSERT(blkreq->req == RTEMS_BLKDEV_REQ_READ);

		if (block_count > 1) {
			opcode = MMC_READ_MULTIPLE_BLOCK;
			printk("RTEMS multiple block read req \n");
		} else {
			opcode = MMC_READ_SINGLE_BLOCK;
			printk("RTEMS single block read req \n");
		}

		data_flags = MMC_DATA_READ;
	}

	for (i = 0; i < buffer_count; ++i) {
		sg = &blkreq->bufs [i];

Till here, we have converted the rtems_blkdev_request into bare information which we can use to form the CCB, In my view it is this part which is faulty, CCB being created here is probably wrong and that’s why the requested IO fails, and thus media server fails to add it as a disk.

CCB is being created as:

struct ccb_mmcio *mmcio;
		uint64_t blockno = sg->block;
		uint16_t count = 1; //not sure, should i divide by 512?
		uint16_t opcode = MMC_READ_SINGLE_BLOCK;
		printf("blockno=%u, count=%u \n",blockno,(unsigned int)count);

		start_ccb->ccb_h.func_code = XPT_MMC_IO;
		start_ccb->ccb_h.flags = CAM_DIR_IN;
		start_ccb->ccb_h.retry_count = 0;
		start_ccb->ccb_h.timeout = 15 * 1000;
		start_ccb->ccb_h.cbfcnp = sddadone;

		mmcio = &start_ccb->mmcio;
		mmcio->cmd.opcode = opcode;
		mmcio->cmd.arg = blockno;
		//if (!(mmcp->card_features & CARD_FEATURE_SDHC))
		//	mmcio->cmd.arg <<= 9;

		mmcio->cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
		mmcio->cmd.data = softc->mmcdata;
		mmcio->cmd.data->data = sg->buffer;
		mmcio->cmd.data->len = sg->length;
		mmcio->cmd.data->flags = MMC_DATA_READ;
		/* Direct h/w to issue CMD12 upon completion 
		if (count > 1) {
			mmcio->cmd.data->flags |= MMC_DATA_MULTI;
			mmcio->stop.opcode = MMC_STOP_TRANSMISSION;
			mmcio->stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
			mmcio->stop.arg = 0;
		}*/
		break;
	}

 

Tagged , ,

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.