Re: PCI device driver file operations question



I realized what happened. My init function was not working. I read
through LDD 3rd Ed to see how to make the init function for a char
device. Now this is how my read function looks like:

ssize_t test_fopsread(struct file *file, char *buf, size_t count,
loff_t *f_pos) {
u32 readData;
char* region0;
u32 bar0;
unsigned int barLen;

printk(KERN_ALERT "We are reading from fops\n");
printk(KERN_ALERT "This is the offset: 0x%x\n", *f_pos);

struct pci_dev *dev;
dev = pci_get_device(RPM_VENDORID, RPM_DEVICEID, NULL);
if (dev) {
pci_request_region(dev, 0, "testdriver");
bar0 = pci_resource_start(dev, 0);
barLen = pci_resource_len(dev, 0);
region0 = ioremap_nocache(bar0, barLen);

readData = readl(region0 + *f_pos);
printk(KERN_ALERT "We read %x\n", readData);

iounmap(region0);
pci_dev_put(dev);

if (copy_to_user(buf, &readData, sizeof(u32))) return -EFAULT;

return 0;
}
else {
printk(KERN_ALERT "Error in reading data at BA0\n");
return 1;
}
}

This is the code in userspace:
unsigned long offset;
int fd = open("/dev/test", O_RDWR);
if (fd == -1)
printf("Error in opening /dev/test\n");
return 1;
}

offset = lseek(fd, 0x0, SEEK_SET);
pread(fd, &readData, sizeof(unsigned long), offset);
printf("Result: %x\n", readData);

offset = lseek(fd, 0x08, SEEK_SET);
pread(fd, &readData, sizeof(unsigned long), offset);
printf("Result: %x\n", readData);

I have been successful in reading the data.

Two questions:
1) How does the read function look? Whenever I do multiple reads, it
sometimes throw me the following error: PCI: Unable to reserve mem
region ... Is there any other function that I am supposed to call in
the read to release the memory region?

2) Eventually, I want to be able to read/write to different base
addresses. Right now, I can only read/write to base address zero.
This is what I tried:
In the device driver, I got rid of pci_request_region,
pci_resouce_start, and pci_resource_len. I ioremap_nocache to (*fops,
0x04).
In userspace, instead of lseek to an offset, I lseek to the base
address plus offset. I send that in to pread.
This did not work. What is the correct method to read/write to
different base addresses?


In a previous entry, I wrote about how I used a userspace C program to
read and write 32-bit binary data from/to custom PCI device memory by
opening /dev/mem and mmaping the memory range that I want. This program
worked well.

To try something similar, I wrote a PCI device driver and a userspace
program. The PCI device driver contains file operations calls. The PCI
device driver contains the following file operation functions:

int test_fopsopen(struct inode *inode, struct file *file){
return 0;
}

int test_fopsrelease(struct inode *inode, struct file *file){
return 0;
}

ssize_t test_fopsread(struct file *file, char *buf, size_t count,
loff_t *ppos) {
u32 readData;
char* region0;
int offset = 0;
u32 bar0;
unsigned int barLen;

struct pci_dev *dev;
dev = pci_get_device(TEST_VENDORID, TEST_DEVICEID, NULL);
if (dev) {
pci_request_region(dev, 0, "testdriver");
bar0 = pci_resource_start(dev, 0);
barLen = pci_resource_len(dev, 0);
region0 = ioremap_nocache(bar0, barLen);

readData = readl(region0+offset);
iounmap(region0);
pci_dev_put(dev);

if (copy_to_user(buf, &readData, sizeof(u32))) return -EFAULT;

return 0;
}
else {
printk(KERN_ALERT "Error in reading data at BA0\n");
return 1;
}
}

ssize_t test_fopswrite(struct file *file, char *buf, size_t count,
loff_t *ppos) {
u32 writeData;
char* region0;
int offset = 0;
u32 bar0;
unsigned int barLen;

struct pci_dev *dev;
dev = pci_get_device(TEST_VENDORID, TEST_DEVICEID, NULL);
if (dev) {
pci_request_region(dev, 0, "testdriver");
bar0 = pci_resource_start(dev, 0);
barLen = pci_resource_len(dev, 0);
region0 = ioremap_nocache(bar0, barLen);

if (copy_from_user(&writeData, buf, sizeof(writeData))) return
-EFAULT;
writel(wroteData, region0+offset);
iounmap(region0);
pci_dev_put(dev);
return 0;
}
else {
printk(KERN_ALERT "ERROR in writing data at BA0\n");
return 1;
}
}

where TEST_VENDORID and TEST_DEVICEID are defined by the Vendor ID and
Device ID of the PCI device. My user program is a simple read test:

int main(){
int fd, len;
char c[32];

fd = open("/dev/test", O_RDWR);
if (fd == -1) {
printf("Error in opening /dev/test\n");
return 1;
}

len = read(fd, c, 32);
printf("Result: %s", c);

close(fd);
}

Both the user program and the device driver compiled with no errors.

Some questions:

1) I went ahead and "mknod /dev/test c XXX 0" where XXX is the PCI
device major number. I also "chmod 666 /dev/test" so that anyone can
access it. After I insmod the kernel module, I went ahead to run the
user program. The user program gave me the following error: Error in
opening /dev/test. What caused the error to occur?

2) How does the functions in the device driver and the user program
look? Are there any improvements that can be made?

3) Eventually, I want to be able to read and write to different offsets
from base address 0. How can I send the offset number that I want from
userspace?

4) If I want the device driver to load automatically from bootup, how
do I do that? What script do I need to edit?

.



Relevant Pages