2

Background:

I am currently writing a device driver for Edu device in qemu (RISC-V). From this question, I find that there is already a device driver for such device.

Outline:

I would like to write an 8-byte value to the memory mapped IO register (of Edu device) at address 0x80, and then read the same address to see if we can get the same value.

  • Edu device introduction
  • The driver code I wrote (for read() and write())
  • User mode code for testing read() and write()
  • Problem I met

Edu device introduction

According to this line of the documentation of Edu device, it says size == 4 or size == 8 accesses are allowed for addresses >= 0x80. Such constraint is shown here for edu_mmio_read() and here for edu_mmio_write() in Edu device source code.

The driver code I wrote

In the driver code, for both read() and write(), it seems that it only handles reading/writing values of size 4 bytes, not 8 bytes. So I added something new to those two functions to support 8-byte values read/write:

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;
    u32 kbuf32;
    u64 kbuf64;

    if (*off % 4 || len == 0) {
        ret = 0;
    } else {
        switch (len)
        {
        case 4:
            kbuf32 = ioread32(mmio + *off);
            if (copy_to_user(buf, (void *)&kbuf32, len)) {
                ret = -EFAULT;
            } else {
                ret = 4;
                (*off)++;
            }
            break;
        
        case 8:
            kbuf64 = ioread64(mmio + *off);
            if (copy_to_user(buf, (void *)&kbuf64, len)) {
                ret = -EFAULT;
            } else {
                ret = 8;
                (*off)++;
            }
            break;

        default:
            ret = -EFAULT;
            break;
        }
    }
    return ret;
}


static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;
    u32 kbuf32; /* for size == 4 */
    u64 kbuf64; /* for size == 8 */
    
    ret = len;

    if (!(*off % 4)) {
        switch (len) {
        case 4:
            /* copy buf to kbuf32 */
            if (copy_from_user((void *)&kbuf32, buf, len)) {
                ret = -EFAULT;
            } else {
                iowrite32(kbuf32, mmio + *off);
            }
            break;
        
        case 8:
            if (copy_from_user((void *)&kbuf64, buf, len)) {
                ret = -EFAULT;
            } else {
                iowrite64(kbuf64, mmio + *off);
            }
            break;
        
        default:
            ret = -EFAULT;
            break;
        }
    }
    return ret;
}

User mode code for testing

In my user mode testing code, I did the following:

// open the device - succeed
// fd2 - the file descriptor representing the opened edu device

uint64_t val64 = 0x8b320000; // a random 64-bit value
unsigned long ret = -1; // retval

// write val64 to 0x80
lseek(fd2, 0x80, SEEK_SET); // seek to address 0x80 - dma.src in edu device source code (line 281)
ret = write(fd2, &val64, sizeof(uint64_t));
if(ret == -1) printf("write to dma src failed\n");
else printf("written %llx to dma src\n", val64);

// reset val64
val64 = 0;

// read what we have just written (sanity check)
lseek(fd2, 0x80, SEEK_SET);
ret = read(fd2, &ret64, sizeof(uint64_t));
if(ret == -1) printf("read from dma src failed\n");
else printf("sanity check: read dma src and we get - %llx\n", ret64);

Problem I met

However, when I tested the user mode code, it did write the value to address 0x80, but failed to read it. I added some printf statements and noticed that the error occurred in read(), corresponding to this line of code:

kbuf64 = ioread64(mmio + *off);

I found this page talking about the differences between IO access functions, and replaced ioread64() with readq(), but still did not solve the problem. The code stopped executing when kbuf64 = ioread64(mmio + *off); is being executed. I also tried to add #define CONFIG_64BIT, but still did not solve the error.

After that, I hit Ctrl+C but was unable to stop the user mode code. All I could do was to Ctrl+A then X to stop QEMU and restart again.

Note that for 4-byte values, read or write works well with ioread32() and iowrite32().

May I know what I did wrong that caused the error of ioread64()?

Ethan L.
  • 395
  • 2
  • 8
  • 1
    (a) Is unaligned access permitted? The **read()** driver code only checks for 4-byte address alignment. (b) `(*off)++;` for both cases of `len` in **read()** can't be correct. You're assuming that the size of loff_t is both 4 and 8? – sawdust Feb 01 '22 at 02:00
  • @sawdust (a) For "read() driver code only checks for 4-byte address alignment" did you mean ```*off % 4``` in the if-statement? If I understand correctly, I need to add ```*off % 8``` as well. I added print statement in qemu device code, and from the output I got, ```ioread64()``` was called since it calls ```edu_mmio_read()``` in the device code, but it never returns. (b) Great catch, I overlooked such part. – Ethan L. Feb 01 '22 at 02:26
  • @sawdust I am not sure if ioread64() can be used in this case. I think the answer is yes since I built 64bit riscv qemu, and iowrite64() does work. But from the [kernel source](https://elixir.bootlin.com/linux/latest/A/ident/ioread64), ioread64() is not that widely used. So I am not sure if I need to use ```ioread32_rep()``` instead. – Ethan L. Feb 01 '22 at 02:33

0 Answers0