Search code examples
chargtklinux-device-driverfwriteuserspace

Data cannot be written to device file from userspace program


I am developing a kernel module for custom device that, in fact, a 4*8-bit i-o ports attached to ISA bus with addresses 0x0120 - 0x0123. This driver is based on "scull" by Alessandro Rubini and Jonathan Corbet. My OS is Ubuntu 10.04, kernel is 2.6.32-74 generic, I use built-in console-oriented compiler gcc. Now module is loading and unloading successfully, read and write functions are implemented. Reading from device file I implemented this way:

gboolean DataRead (GtkWidget *widget, gpointer data)
{
GtkWidget *statusbar = GTK_WIDGET(data);
g_assert(statusbar != NULL);
gchar *StatusText = "Reading data from ADC F-4226";
gtk_statusbar_remove (GTK_STATUSBAR(statusbar), StatusBarContext, MessageId);
StatusBarContext = gtk_statusbar_get_context_id (GTK_STATUSBAR(statusbar), "ET3201-read");
MessageId = gtk_statusbar_push (GTK_STATUSBAR(statusbar), StatusBarContext, StatusText);
ERR = NULL; 
gchar *contents;
gsize *length = NULL;
gboolean ReadOK = FALSE;

ReadOK = g_file_get_contents (DevFile, &contents, length, &ERR);
if (ReadOK) StatusText = contents;
    else 
    {
        g_warning ("Can't open ET3201: %s", ERR->message);
        StatusText = g_strdup_printf("Error!!! %s: %d, %s", g_quark_to_string (ERR->domain), ERR->code, ERR->message);
        g_error_free(ERR);
    }
    gtk_statusbar_remove (GTK_STATUSBAR(statusbar), StatusBarContext, MessageId);
    StatusBarContext = gtk_statusbar_get_context_id (GTK_STATUSBAR(statusbar), "ET3201-read");
    MessageId = gtk_statusbar_push (GTK_STATUSBAR(statusbar), StatusBarContext, StatusText);
    g_free(contents);
}
return ReadOK;

}

  • and everything works, statusbar displays a string that read from device. But when I tried to write device file - the problem arise. My function that write device file always reports an error.

    GError * on_BnStop_clicked (GtkButton *button, gpointer data)
    {
    char Data = 255;
    FILE * ET3201 = NULL;
    char buf;
    ERR = NULL;
    GtkWidget *label = GTK_WIDGET(data);
    g_assert(label != NULL);
    if ( Started ) Started = FALSE; 
    if ( ! Started ) Data = 0;              /* FOR TESTING ONLY!!!*/
    gchar *LabelText = "STOP";
    gtk_label_set_text(GTK_LABEL(label), LabelText);
    errno = 0;
    ET3201=fopen(DevFile,"w");/* Opening the device ET3201 */
    ErrorCode = errno;
    if (ET3201 == NULL)
    {
        ERR = g_error_new ( G_FILE_ERROR, G_FILE_ERROR_NXIO, "Can't
    open port ET3201!" );
        goto fail;
    }   
    setvbuf(ET3201,&buf,_IONBF,1); /* We remove the buffer from the
    file i/o */
    fwrite(&Data,1,1,ET3201);       /* Writing to device*/
    sleep(1);
    fclose(ET3201);    
    /* g_free(&buf);  - leading to "Program aborted" */
    fail: 
    return ERR; 
    }
    

-just like ET3201 == NULL constantly. At this point kernel module is loaded and reading from it is OK. After debugging I found that "Write" function in kernel module is never invoked since it doesn't print corresponding "printk" message in kernel log. In desperation I tried to do it this way:

GError * on_BnStart_clicked (GtkButton *button, gpointer data)
{
    gchar *contents;
    gssize length = 0;
    ERR = NULL;
    GtkWidget *label = GTK_WIDGET(data);
    g_assert(label != NULL);
    if (! Started ) Started = TRUE; 
    gchar *LabelText = "RUN";
    gtk_label_set_text(GTK_LABEL(label), LabelText);

    contents = g_strdup_printf("/255");   /*FOR TESTING PURPOSES!!!*/

    length = sizeof(contents);

    g_file_set_contents (DevFile, contents, length, &ERR);

    return ERR; 
}

and I get an error message in statusbar: Error: g-file-error-quark: 2, Failed to create file '/dev/ET32010.****8X': Permission denied I think that's because g_file_set_contents writes to a temporary file and then tries to rename it to device file getting "Permission denied". This is maybe a wrong way to write device files, please help me to find the right one...

My questions: Why my function on_BnStop_clicked doesn't work properly? How to make a correct write to char device file from userspace program?

I have checked for device access rights...

master@master-desktop:~$ ls -l /dev/ET3201*
lrwxrwxrwx 1 root root       7 2015-11-23 14:34 /dev/ET3201 -> ET32010
crw-rw-r-- 1 root staff 250, 0 2015-11-23 14:34 /dev/ET32010
crw-rw-r-- 1 root staff 250, 1 2015-11-23 14:34 /dev/ET32011
crw-rw-r-- 1 root staff 250, 2 2015-11-23 14:34 /dev/ET32012
crw-rw-r-- 1 root staff 250, 3 2015-11-23 14:34 /dev/ET32013

then changed mode in kernel module init script to 666

lrwxrwxrwx 1 root root       7 2015-11-23 22:27 /dev/ET3201 -> ET32010
crw-rw-rw- 1 root staff 250, 0 2015-11-23 22:27 /dev/ET32010
crw-rw-rw- 1 root staff 250, 1 2015-11-23 22:27 /dev/ET32011
crw-rw-rw- 1 root staff 250, 2 2015-11-23 22:27 /dev/ET32012
crw-rw-rw- 1 root staff 250, 3 2015-11-23 22:27 /dev/ET32013

and on_BnStart_clicked function returned "Permission denied" as it was before, but the on_BnStop_clicked has invoked the "Write" method of kernel module and then completed successfully with ERR == NULL. After all an error reporting function has crashed with "Segmentation fault" (reporting ERR == NULL) but I already fixed this bug.


Solution

  • Documentation of g_file_set_contents says:

    This write is atomic in the sense that it is first written to a temporary file which is then renamed to the final name.

    So, file /dev/ET32010.****8X is actually temporal. And, because creating files under /dev is usually allowed only for root, creation of temporary file fails with "Permission denied".

    Even if writting into /dev would be allowed, g_file_set_contents will replace device file with regular one, which is not what you want.

    You need to use lower-level way of writting into device file. E.g., using fopen + fwrite.