CAM, FreeBSD, mmccam

Implementing a MMC/SD/SDIO stack using CAM framework(Part 1)

In this blog post we will have a detailed look at all the files added/changed while implementing the MMC/SD/SDIO stack via CAM framework. We will basically discuss the commit  https://github.com/freebsd/freebsd/commit/a87c7a85be3e3727b6b08b19741b4282f942400d which aims at adding MMC/SD/SDIO stack to FreeBSD’s already existing CAM framework.

So, lets start one by one with each file:

mmc.h

This file presents the basic structure of the SD-Card/MMC peripheral. This structure is further used in CAM_EDT i.e CAM Existing Device Table which stores the info. of all the present devices in the CAM bus along with the queue of pending CCBs. CAM EDT is the major structure describing the CAM bus.

cam_ccb.h

cam_ccb.h is the main header file consisting of major CCB definitions and structure. Have a look at ccb_mmcio stucture, it is the mmc specific CCB that will be used by MMC CAM transport layer. Then, look at ccb_trans_settings_mmc structure, it contains mmc transfer settings like 4/8 bit data bus support, hi speed transfer mode supported or not.

Look at the complete file  , There you will notice a number of enumerations, structures, flags describing the CCB. A large part of the CCB is common to all devices, however some part is device specific. For example in our case, struct ccb_mmcio  makes up this device(mmc) speciifc part of CCB. Then there’s a declaration and defination of the function cam_fill_mmcio which according to my interpretation initializes the CCB.

cam_periph.c

This function just inserts the device mmc in the list of known peripheral modules. It further allocates memory in CCB and do some basic CCB initialization.

cam_xpt.c

This function lays the basics of CAM transport layer whose main function is to take an I/O request along with device path, make a CCB, convert I/O command to SCSI commands, store back those commands in CCB and finally calls the SIM specific routine handling over the CCB along with SCSI command. Notice that the part where information is parred on to the SIM, there is mutual exclusion by mutex, which prevents multiple threads to access SIM concurrently.

mmc_da.c

Now, this is one of the most important file. It provides all device specific functions that takes a CCB, extracts SCSI command and convert it into hardware specific commands. It provides functions to decode the command into expected SD card/ MMC register contents.


Before moving forward, lets have a look at different registers of sd card/mmc:

There are total 8 sd card registers out of which only 6 are common with mmc. Refer https://www.kingston.com/datasheets/SDCIT-specsheet-64gb_en.pdf and this for detailed overview.

First 6 registers are common with MMC.

OCR register describes operating voltage, card power up state and power supply status bits.

CID Card Identification Register, contains vendor specific information about the sd card/mmc. It contains manufacturer id, product name, product serial number etc.

CSD(Card specific data register) contains all card related technical details like maximum data transfer rate, Data read access time, write speed factor etc. It is configurable.

RCA Register- The writable 16bit relative card address register carries the card address in SD Card mode.
SCR (SD Card Configuration Register) provides information on the SD memory card’s special features.
DSR (Driver Stage Register) an be optionally used to improve the bus performance for extended operating conditions (depending on parameters like bus length, transfer rate or number of cards). The CSD register carries the information about the DSR register usage. The default value of the DSR register is 0x404.

Moving back towards understanding mmc_da.c, first structure to encounter is sdda_softc it contains various hardware specific parameters like sector count ,media size etc. Then there is get_rca function which retrieves the RCA register content. Notice the path carefully(periph->path->device->mmc_ident_data.card_rca)!

Then there is, mmc_decode_csd_sd function that takes raw csd register data, decode it and fill it in mmc_csd structure which is passed on to this function as it’s second argument. Have a look at page 88 of this it explains various csd version register structures. Version 0 corresponds to standard capacity sd cards while version 1 corresponds to high capacity sd cards. Version 2 and 3 are still reserved for upcoming versions. Similarly, we have mmc_decode_cid_mmc function which decodes cid register data and stores it in a structure. Function mmc_format_card_id_string formats the SD-card/MMC id which will be used for its identification.

Then we have, sddaschedule which calls xpt_schedule() if device is already in the queue.

xpt_schedule schedules/prepares the peripheral module to recieve the CAM control block (CCB). It then calls xpt_run_dev_allocq which allocates and constructs the CCB. We will look at it in detail, in a later section.

And now we have our star function, sddastrategy

This function is the first one to be called for handling the I/O request by OS. It makes up the CAM peripheral module layer. So, the first thing it does is, bioq_disksort which places the I/O request in the queue. Maintaining a queue is very necessary as to avoid overloading the SIM with requests. Queue can have only a fixed number of CCBs and each executes in an order. It then calls sddaschedule which in turn calls xpt_schedule.

Then, next function we see is sddainit which does some rudimentary initialization before sddastrategy. Then we have  sddadiskgonecb ; After sending the CCB to SIM program execution is returned to the caller of sddastrategy but before that sddacleanup is used to cleanup all redundant  data and then sddadiskgonecb is called.

Then we have sddaasync function that handles all asynchronous interrupts from CAM bus. Following is the types of asynchronous calls:

and in the function sddaasync notice the switch case statement that handles all these events:

Notice that in case of hot plug/unplug event there will be an async. interrupt that will be handeled by bove function. Then we have mmc_set_timming() which sets the clock frequency of sd-card/mmc. It also determines whether a device supports high speed mode or not and sets the clock frequency accordingly.

sdda_start_init_task allocates and initializes a new CCB block with the received context (I/O request) this will be called after xpt_schedule.

sdda_set_bus_width as its name suggests sets the bus width.
sdda_start_init initializes the new CCB block made by sdda_start_init_task. It performs CID and CSD register check and extract all relevant data which is stored in CCB.

Then we have sddastart(), which is called after xpt_run_alloq_dev. It is the peripheral module start routine which takes a I/O command and convert it into SCSI command.

Notice the switch case statement that converts a command to a more specific SCSI command. For example, BIO_READ is a command to read data from sd/mmc, it after considering the amount of data to be read is converted into MMC_READ_MULTIPLE_BLOCK or MMC_READ_SINGLE_BLOCK. It further performs other functions like flush, delete etc.

sddadone and sddaerr functions will be called by SIM after it completes it’s requested operation. Status is stored and checked to raise an alert if unsuccessful.

Other then this file, there are other files that were also changed. W’ll look into then into my next post.

Tagged , ,

Leave a Reply

Your email address will not be published.

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