Re: [PATCH] memstick: add support for legacy memorysticks

From: Andrew Morton
Date: Wed Jul 17 2013 - 16:35:36 EST


On Mon, 8 Jul 2013 23:54:55 +0300 Maxim Levitsky <maximlevitsky@xxxxxxxxx> wrote:

> Based partially on MS standard spec quotes from Alex Dubov.
>
> As any code that works with user data this driver isn't
> recommended to use to write cards that contain valuable data.
>
> It tries its best though to avoid data corruption
> and possible damage to the card.
>
> Tested on MS DUO 64 MB card on Ricoh R592 card reader.
>
> ...
>
> +config MS_BLOCK
> + tristate "MemoryStick Standard device driver"
> + depends on BLOCK
> + help
> + Say Y here to enable the MemoryStick Standard device driver
> + support. This provides a block device driver, which you can use
> + to mount the filesystem.
> + This driver works with old (bulky) MemoryStick and MemoryStick Duo
> + but not PRO. Say Y if you have such card.
> + Driver is new and not yet well tested, thus it can damage your card
> + (even permanently)

Yikes. How true is this?

>
> ...
>
> +static size_t msb_sg_copy(struct scatterlist *sg_from,
> + struct scatterlist *sg_to, int to_nents, size_t offset, size_t len)
> +{
> + size_t copied = 0;
> +
> + while (offset > 0) {
> + if (offset >= sg_from->length) {
> + if (sg_is_last(sg_from))
> + return 0;
> +
> + offset -= sg_from->length;
> + sg_from = sg_next(sg_from);
> + continue;
> + }
> +
> + copied = min(len, sg_from->length - offset);
> + sg_set_page(sg_to, sg_page(sg_from),
> + copied, sg_from->offset + offset);
> +
> + len -= copied;
> + offset = 0;
> +
> + if (sg_is_last(sg_from) || !len)
> + goto out;
> +
> + sg_to = sg_next(sg_to);
> + to_nents--;
> + sg_from = sg_next(sg_from);
> + }
> +
> + while (len > sg_from->length && to_nents--) {
> + len -= sg_from->length;
> + copied += sg_from->length;
> +
> + sg_set_page(sg_to, sg_page(sg_from),
> + sg_from->length, sg_from->offset);
> +
> + if (sg_is_last(sg_from) || !len)
> + goto out;
> +
> + sg_from = sg_next(sg_from);
> + sg_to = sg_next(sg_to);
> + }
> +
> + if (len && to_nents) {
> + sg_set_page(sg_to, sg_page(sg_from), len, sg_from->offset);
> + copied += len;
> + }
> +out:
> + sg_mark_end(sg_to);
> + return copied;
> +}

There's nothing memstick-specific about this. Did you consider placing
it in lib/?


> +/*
> + * Compares section of 'sg' starting from offset 'offset' and with length 'len'
> + * to linear buffer of length 'len' at address 'buffer'
> + * Returns 0 if equal and -1 otherwice
> + */
> +static int msb_sg_compare_to_buffer(struct scatterlist *sg,
> + size_t offset, u8 *buffer, size_t len)
> +{
> + int retval = 0, cmplen;
> + struct sg_mapping_iter miter;
> +
> + sg_miter_start(&miter, sg, sg_nents(sg),
> + SG_MITER_ATOMIC | SG_MITER_FROM_SG);
> +
> + while (sg_miter_next(&miter) && len > 0) {
> + if (offset >= miter.length) {
> + offset -= miter.length;
> + continue;
> + }
> +
> + cmplen = min(miter.length - offset, len);
> + retval = memcmp(miter.addr + offset, buffer, cmplen) ? -1 : 0;
> + if (retval)
> + break;
> +
> + buffer += cmplen;
> + len -= cmplen;
> + offset = 0;
> + }
> +
> + if (!retval && len)
> + retval = -1;
> +
> + sg_miter_stop(&miter);
> + return retval;
> +}

And this, perhaps.

>
> ...
>
> +static void msb_mark_block_used(struct msb_data *msb, int pba)
> +{
> + int zone = msb_get_zone_from_pba(pba);
> +
> + if (test_bit(pba, msb->used_blocks_bitmap)) {
> + pr_err(
> + "BUG: attempt to mark already used pba %d as used", pba);
> + msb->read_only = true;
> + return;
> + }
> +
> + if (msb_validate_used_block_bitmap(msb))
> + return;
> +
> + /* No races because all IO is single threaded */

What guarantees that all IO is single threaded? Big lock? All done by
a single kernel thread?

> + __set_bit(pba, msb->used_blocks_bitmap);
> + msb->free_block_count[zone]--;
> +}
> +
>
> ...
>
> +static int msb_read_int_reg(struct msb_data *msb, long timeout)
> +{
> + struct memstick_request *mrq = &msb->card->current_mrq;
> +
> + WARN_ON(msb->state == -1);
> +
> + if (!msb->int_polling) {
> + msb->int_timeout = jiffies +
> + msecs_to_jiffies(timeout == -1 ? 500 : timeout);

All callers pass timeout=-1, so there's some pointless code here.

> + msb->int_polling = true;
> + } else if (time_after(jiffies, msb->int_timeout)) {
> + mrq->data[0] = MEMSTICK_INT_CMDNAK;
> + return 0;
> + }
> +
> + if ((msb->caps & MEMSTICK_CAP_AUTO_GET_INT) &&
> + mrq->need_card_int && !mrq->error) {
> + mrq->data[0] = mrq->int_reg;
> + mrq->need_card_int = false;
> + return 0;
> + } else {
> + memstick_init_req(mrq, MS_TPC_GET_INT, NULL, 1);
> + return 1;
> + }
> +}

Generally speaking, one wya in whcih kernel code gets real confusing
real fast is when it uses a mixture of time units: jiffies and
milliseconds and seconds, etc. This code falls into that trap, a
little bit. One really good way to clarify things is to embed the
units within the variable names. So s/timeout/timeout_ms/ and
s/int_timeout/int_timeout_jifs/ would be good.

>
> ...
>
> +static int msb_switch_to_parallel(struct msb_data *msb)
> +{
> + int error;
> +
> + error = msb_run_state_machine(msb, h_msb_parallel_switch);
> + if (error) {
> + pr_err("Switch to parallel failed");

Might want to display the value of `error' here.

> + msb->regs.param.system &= ~MEMSTICK_SYS_PAM;
> + msb_reset(msb, true);
> + return -EFAULT;

EFAULT is just wrong, surely. Can't we propagate `error' here?

> + }
> +
> + msb->caps |= MEMSTICK_CAP_AUTO_GET_INT;
> + return 0;
> +}
> +
>
> ...
>
> +static u16 msb_get_free_block(struct msb_data *msb, int zone)
> +{
> + u16 pos;
> + int pba = zone * MS_BLOCKS_IN_ZONE;
> + int i;
> +
> + get_random_bytes(&pos, sizeof(pos));

This isn't good. get_random_bytes() is the high-quality random number
generator. If memstick calls this frequently it will degrade the
entropy pool, thus impacting the quality (ie: security) of key
generation, TCP sequence numbers, etc. It's also slow.

Do you really require cryptographic quality randomness here, or would
prandom_bytes() suffice?

> + if (!msb->free_block_count[zone]) {
> + pr_err("NO free blocks in the zone %d, to use for a write, (media is WORN out) switching to RO mode", zone);
> + msb->read_only = true;
> + return MS_BLOCK_INVALID;
> + }
> +
> + pos %= msb->free_block_count[zone];
> +
> + dbg_verbose("have %d choices for a free block, selected randomally: %d",
> + msb->free_block_count[zone], pos);
> +
> + pba = find_next_zero_bit(msb->used_blocks_bitmap,
> + msb->block_count, pba);
> + for (i = 0; i < pos; ++i)
> + pba = find_next_zero_bit(msb->used_blocks_bitmap,
> + msb->block_count, pba + 1);
> +
> + dbg_verbose("result of the free blocks scan: pba %d", pba);
> +
> + if (pba == msb->block_count || (msb_get_zone_from_pba(pba)) != zone) {
> + pr_err("BUG: cant get a free block");
> + msb->read_only = true;
> + return MS_BLOCK_INVALID;
> + }
> +
> + msb_mark_block_used(msb, pba);
> + return pba;
> +}
>
> ...
>
> +static int msb_cache_write(struct msb_data *msb, int lba,
> + int page, bool add_to_cache_only, struct scatterlist *sg, int offset)
> +{
> + int error;
> + struct scatterlist sg_tmp[10];

That's a lot of stack.

> +
> + if (msb->read_only)
> + return -EROFS;
> +
> + if (msb->cache_block_lba == MS_BLOCK_INVALID ||
> + lba != msb->cache_block_lba)
> + if (add_to_cache_only)
> + return 0;
> +
> + /* If we need to write different block */
> + if (msb->cache_block_lba != MS_BLOCK_INVALID &&
> + lba != msb->cache_block_lba) {
> + dbg_verbose("first flush the cache");
> + error = msb_cache_flush(msb);
> + if (error)
> + return error;
> + }
> +
> + if (msb->cache_block_lba == MS_BLOCK_INVALID) {
> + msb->cache_block_lba = lba;
> + mod_timer(&msb->cache_flush_timer,
> + jiffies + msecs_to_jiffies(cache_flush_timeout));
> + }
> +
> + dbg_verbose("Write of LBA %d page %d to cache ", lba, page);
> +
> + sg_init_table(sg_tmp, ARRAY_SIZE(sg_tmp));
> + msb_sg_copy(sg, sg_tmp, ARRAY_SIZE(sg_tmp), offset, msb->page_size);
> +
> + sg_copy_to_buffer(sg_tmp, sg_nents(sg_tmp),
> + msb->cache + page * msb->page_size, msb->page_size);
> +
> + set_bit(page, &msb->valid_cache_bitmap);
> + return 0;
> +}
>
> ...
>
> +static int msb_bd_open(struct block_device *bdev, fmode_t mode)
> +{
> + struct gendisk *disk = bdev->bd_disk;
> + struct msb_data *msb = disk->private_data;
> +
> + dbg_verbose("block device open");
> +
> + mutex_lock(&msb_disk_lock);
> +
> + if (msb && msb->card)
> + msb->usage_count++;

hm, what are we refcounting here? Unfortunately the rather important
msb_data is undocumented :( What is an msb_data?

> + mutex_unlock(&msb_disk_lock);
> + return 0;
> +}
> +
>
> ...
>

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/