Linux-Kernel

不使用 mmap 寫入 /dev/mem

  • January 26, 2022

不使用 mmap 可以在 /dev/mem 上寫入嗎?

我在 LKM 內的 Raspberry Pi 上啟用了上拉電阻,但該功能void *mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset)不存在。

我嘗試使用open(稍後將其轉換為filp_open),但它什麼也沒做:

#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <errno.h>

// From https://github.com/RPi-Distro/raspi-gpio/blob/master/raspi-gpio.c


#define PULL_UNSET  -1
#define PULL_NONE   0
#define PULL_DOWN   1
#define PULL_UP     2

#define GPIO_BASE_OFFSET 0x00200000
#define GPPUD       37
#define GPPUDCLK0   38
#define BASE_READ   0x1000
#define BASE_SIZE   (BASE_READ/sizeof(uint32_t))

uint32_t getGpioRegBase(void) {
   const char *revision_file = "/proc/device-tree/system/linux,revision";
   uint8_t revision[4] = { 0 };
   uint32_t cpu = 0;
   FILE *fd;

   if ((fd = fopen(revision_file, "rb")) == NULL) {
       printf("Can't open '%s'\n", revision_file);
       exit(EXIT_FAILURE);
   }
   else {
       if (fread(revision, 1, sizeof(revision), fd) == 4) cpu = (revision[2] >> 4) & 0xf;
       else {
           printf("Revision data too short\n");
           exit(EXIT_FAILURE);
       }

       fclose(fd);
   }

   printf("CPU: %d\n", cpu);
   switch (cpu) {
       case 0: // BCM2835 [Pi 1 A; Pi 1 B; Pi 1 B+; Pi Zero; Pi Zero W]
           //chip = &gpio_chip_2835;
           return 0x20000000 + GPIO_BASE_OFFSET;
       case 1: // BCM2836 [Pi 2 B]
       case 2: // BCM2837 [Pi 3 B; Pi 3 B+; Pi 3 A+]
           //chip = &gpio_chip_2835;
           return 0x3f000000 + GPIO_BASE_OFFSET;
       case 3: // BCM2711 [Pi 4 B]
           //chip = &gpio_chip_2711;
           return 0xfe000000 + GPIO_BASE_OFFSET;
       default:
           printf("Unrecognised revision code\n");
           exit(1);
   }
}

int writeBase(uint32_t reg_base, uint32_t offset, uint32_t data) {
   int fd;
   if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0) return -1;
   
   if (lseek(fd, reg_base+offset, SEEK_SET) == -1) return -2;
   if (write(fd, (void*)&data, sizeof(uint32_t)) != sizeof(uint32_t)) return -3;
   if (close(fd) == -1) return -4;
   return 0;
}

int setPull(unsigned int gpio, int pull) {
   int r;
   int clkreg = GPPUDCLK0 + (gpio / 32);
   int clkbit = 1 << (gpio % 32);
   uint32_t reg_base = getGpioRegBase();

   r = writeBase(reg_base, GPPUD, pull); // base[GPPUD] = pull
   if (r < 0) return r;
   usleep(10);
   r = writeBase(reg_base, clkreg, clkbit); // base[clkreg] = clkbit
   if (r < 0) return r;
   usleep(10);
   r = writeBase(reg_base, GPPUD, 0); // base[GPPUD] = 0
   if (r < 0) return r;
   usleep(10);
   r = writeBase(reg_base, clkreg, 0); // base[clkreg] = 0
   usleep(10);
   return r;
}

int main(int argc, char *argv[]) {
   int gpio, r;
   
   if (argc!=2) {
       printf("GPIO pin needed!\n");
       return 1;
   }
   
   gpio = atoi(argv[1]);
   printf("Enabling pull-up on GPIO%d...\n", gpio);
   r = setPull(gpio, PULL_UP);
   printf("Return value: %d\n", r);
   if (r != 0) printf("%s\n", strerror(errno));
   return r;
}

raspi-gpio這是我想要的一個片段:

#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>

// From https://github.com/RPi-Distro/raspi-gpio/blob/master/raspi-gpio.c


#define PULL_UNSET  -1
#define PULL_NONE    0
#define PULL_DOWN    1
#define PULL_UP      2

#define GPIO_BASE_OFFSET 0x00200000
#define GPPUD        37
#define GPPUDCLK0    38

uint32_t getGpioRegBase(void) {
   const char *revision_file = "/proc/device-tree/system/linux,revision";
   uint8_t revision[4] = { 0 };
   uint32_t cpu = 0;
   FILE *fd;

   if ((fd = fopen(revision_file, "rb")) == NULL)
   {
       printf("Can't open '%s'\n", revision_file);
   }
   else
   {
       if (fread(revision, 1, sizeof(revision), fd) == 4)
           cpu = (revision[2] >> 4) & 0xf;
       else
           printf("Revision data too short\n");

       fclose(fd);
   }

   printf("CPU: %d\n", cpu);
   switch (cpu) {
       case 0: // BCM2835 [Pi 1 A; Pi 1 B; Pi 1 B+; Pi Zero; Pi Zero W]
           return 0x20000000 + GPIO_BASE_OFFSET;
       case 1: // BCM2836 [Pi 2 B]
       case 2: // BCM2837 [Pi 3 B; Pi 3 B+; Pi 3 A+]
           return 0x3f000000 + GPIO_BASE_OFFSET;
       case 3: // BCM2711 [Pi 4 B]
           return 0xfe000000 + GPIO_BASE_OFFSET;
       default:
           printf("Unrecognised revision code\n");
           exit(1);
   }
}

volatile uint32_t *getBase(uint32_t reg_base) {
   int fd;
   if ((fd = open ("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0) return NULL;
   return (uint32_t *)mmap(0, /*chip->reg_size*/ 0x1000,
                                 PROT_READ|PROT_WRITE, MAP_SHARED,
                                 fd, reg_base);
}

void setPull(volatile uint32_t *base, unsigned int gpio, int pull) {
   int clkreg = GPPUDCLK0 + (gpio / 32);
   int clkbit = 1 << (gpio % 32);

   base[GPPUD] = pull;
   usleep(10);
   base[clkreg] = clkbit;
   usleep(10);
   base[GPPUD] = 0;
   usleep(10);
   base[clkreg] = 0;
   usleep(10);
}

int main(int argc, char *argv[]) {
   if (argc!=2) {
       printf("GPIO pin needed!\n");
       return 1;
   }

   uint32_t reg_base = getGpioRegBase();
   volatile uint32_t *base = getBase(reg_base);
   if (base == NULL || base == (uint32_t *)-1) {
       printf("Base error");
       return 1;
   }
   printf("Base: %p\n", base);
   setPull(base, atoi(argv[1]), PULL_UP);
   return 0;
}

這是啟用上拉的 KML 片段(我需要刪除該mmap部分):

#include <linux/types.h>    // uint_32
#include <linux/fs.h>       // filp_open/filp_close
#include <linux/delay.h>    // udelay

#define PULL_DOWN    1
#define PULL_UP      2

#define GPIO_BASE_OFFSET 0x00200000
#define GPPUD        37
#define GPPUDCLK0    38

static uint32_t getGpioRegBase(bool *error) {
   uint8_t revision[4] = { 0 };
   uint32_t cpu = 0;
   struct file *fd;
   ssize_t rc = 0;

   if (IS_ERR(( fd = filp_open("/proc/device-tree/system/linux,revision", O_RDONLY | O_SYNC | O_CLOEXEC, 0) ))) {
       *error = true;
       return 0;
   }
   
   if ((rc = kernel_read(fd, revision, sizeof(revision), 0)) == 4) cpu = (revision[2] >> 4) & 0xf;
   else {
       *error = true;
       return 0;
   }

   filp_close(fd, NULL);

   *error = false;
   switch (cpu) {
       case 0: // BCM2835 [Pi 1 A; Pi 1 B; Pi 1 B+; Pi Zero; Pi Zero W]
           return 0x20000000 + GPIO_BASE_OFFSET;
       case 1: // BCM2836 [Pi 2 B]
       case 2: // BCM2837 [Pi 3 B; Pi 3 B+; Pi 3 A+]
           return 0x3f000000 + GPIO_BASE_OFFSET;
       case 3: // BCM2711 [Pi 4 B]
           return 0xfe000000 + GPIO_BASE_OFFSET;
       default:
           *error = true;
           return 0;
   }
}

static volatile uint32_t *getBase(uint32_t reg_base) {
   struct file *fd;
   volatile uint32_t *r;
   
   if (IS_ERR(( fd = filp_open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC, 0) ))) return NULL;
   r = (uint32_t*)mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, reg_base);
   filp_close(fd, NULL); // TODO the original didn't have this
   
   return r;
}

static void setPull(volatile uint32_t *base, uint32_t gpio, int pull) {
   int clkreg = GPPUDCLK0 + (gpio / 32);
   int clkbit = 1 << (gpio % 32);

   base[GPPUD] = pull;
   udelay(10);
   base[clkreg] = clkbit;
   udelay(10);
   base[GPPUD] = 0;
   udelay(10);
   base[clkreg] = 0;
   udelay(10);
}

/**
* Equivalent to 'raspi-gpio set <gpio> <pu/pd>'
* @param gpio Valid GPIO pin
* @param pull PULL_DOWN/PULL_UP
*/
static int setGpioPull(uint32_t gpio, int pull) {
   bool error;
   uint32_t reg_base;
   volatile uint32_t *base;
   
   reg_base = getGpioRegBase(&error);
   if (error) return -1;
   base = getBase(reg_base);
   if (base == NULL || base == (uint32_t*)-1) return -1;
   setPull(base, gpio, pull);
   
   return 0;
}```

/proc和設備節點/dev用於使用者空間;核心不需要它們,核心模組也不應該使用它們。

相反,要訪問 GPIO,您應該使用ioremap各種ioread...``iowrite...功能:獲取ioremap您所追求的物理地址相對應的地址,以及執行 IO 的其他功能。

我不知道如何立即檢索您從中獲得的設備樹資訊/proc,但應該有核心函式可以這樣做。

也許另一種方式:

在我的 RPi 3B+/bullseye 上:

$ ls -l /dev | grep mem
crw-rw---- 1 root gpio    247,   0 Jan 26 00:06 gpiomem
crw-r----- 1 root kmem      1,   1 Jan 26 00:06 mem

該組的成員gpio可以訪問gpiomem,並且RPi OS (née raspbian) 中的預設(通常是唯一的)使用者是該組的成員。pi

引用自:https://unix.stackexchange.com/questions/671689