IT_Study/Embedded System

Embedded System (5) : Memory Map, LPAE, H/W 제어, Module Parameter

__Vivacé__ 2023. 4. 24. 16:38

Memory Map 분석

 

Raspberry Pi Datasheet 다운로드

Google에 "bcm2711 pdf" 검색 후 다운로드하면 된다

 

 

 

Rpi의 메모리 맵은 아래와 같이 크게 3종류가 있다

 - Legacy : 공식문서 default 메모리 주소
 - Full 35 bit Address : high performance 용 주소
 - Low peripheral : 현재 메모리 주소

Rpi 메모리 맵 / BCM2711 pdf 그림

공식 Document에 있는 주소는 Legacy (32-bit) 기준으로 서술되어있다.

 

하지만, Rpi4는 LPAE 기능으로 Full 35-bit Address 주소 체계를 쓰고 있다.

LPAE( Large Physical Address Extention)
ARM 칩셋에서 제공하는 기능

 - 32bit system 에선 4GB 까지의 메모리 사용가능 (0x0000 0000 ~ 0xFFFF FFFF)
 - Raspbian OS는 32 bit system
 - LPAE는 가상 주소 공간을 이용해 더 넓게(35 bit) 사용 --> 최대 32GB 메모리 사용 가능

 

GPIO 핀에 연결된 H/W 제어를 할 것이다 --> Main Peripherals 주소에 코드를 짜야 한다.

 

32bit의 시스템에서, 포인터의 크기는 4byte -> 32bit까지 접근 가능

현재 Rpi4는 32 bit 시스템 / Main Peripherals의 주소는 3 bit가 더 있다.

--> C언어의 포인터가 표현을 할 수 없다.

--> 그래서 main pheripheral을 (중) -> (우) 로 옮김

 

그래서 우리는 가장 오른쪽 map address를 보면서 개발을 해야 한다.

작업을 하면 OS가 알아서 (중)으로 옮겨 준다.

 

 

왜 이렇게 메모리 관리를 할 때, 가상 메모리 주소를 이용할까?

더보기

PC에 꽂힌 삼성 사의 D램이 3개 있다.

그런데, 각각 메모리주소를 다르게 갖고 있다면, 개발하면서 굉장히 힘들다

그런데, 이 메모리를 개발하는 회사도 여러 회사가 있고, 메모리를 갖고 개발을 시작하는 SoC업체도 제각각이다.

실제로도 물리적 메모리에 커널이 실행되는 물리 메모리 주소가 다르다. 업체별로

가상 메모리 주소를 쓰면, 개발자가 물리 메모리 주소가 어떻든 신경 쓸 게 없다


LPAE 확인

$ cat /proc/cpuinfo

lpae 사용 중임을 확인할 수 있음

 

 

 

Legacy를 봤을 때,

 Main Pheripheral : 0x7C00 0000 이라 나와 있고, 위에 GPIO base address : 0x7E20 0000라고 나와있으므로,

 두 메모리 주소의 간격은 0x0220 0000

 

개발용 (Low pheripheral mode)을 봤을 때,

 Main Peripheral : 0xFC00 0000 이므로, GPIO base address : 0xFE20 0000 임을 알 수 있다.

 

이런 식으로 계산해서 메모리 값을 변경해서 H/W 제어 예정


H/W 제어

 

 

1. device file 생성

$ sudo mknod /dev/nobrand c 100 0
$ sudo chmod 666 /dev/nobrand

 

2. Rpi4에 LED 연결

 - GPIO 18, 23에 각각 LED 연결

 

아래 글 참고해서 연결

https://lg960214.tistory.com/70

 

Embedded System (3) : PWM, Motor 및 Raspberry Pi 연동

PWM(Pulse Width Modulation) 펄스 폭을 변조하여 아날로그 신호를 디지털로 표현하는 기술 임베디드 보드(디지털 장치)가 아날로그 형식으로 신호를 표현하기 위해서는 PWM을 이용해야 한다. PWM 펄스 폭

lg960214.tistory.com

 


 

3. GPIO Setup

GPIO 핀이 OUTPUT / INPUT 모드인 지 세팅 --> 해당 핀에 입력장치 or 출력장치를 연결하고 나서

개발자가 직접 쓰는 것

 

GPIO PIN Setup 순서

1. GPFSELx : GPIO Input 또는 Output 결정

2. GPSETx : GPIO set - 해당 GPIO 핀에 HIGH 신호를 보내줄 때 사용

3. GPCLRx : GPIO Clear 해당 GPIO 핀에 LOW 신호를 보내줄 때 사용

 

찾아야 하는 Address --> DataSheet 활용

1. GPFSEL에서 18번 PIN

2. GPSET에서 18번 PIN

3. GPCLR에서 18번 PIN

 

 

GPFSEL

GPFSEL1 register 주소 : 0xFE20 0000 + 0x0000 0004(offset) = 0xFE20 0004

따라서 0xFE20 0004 ~ 0xFE20 0007 까지 (1 byte x 4 bit = 32 bit) 값을 넣을 수 있음

 

해당 주소의 26:24 bit에 비트연산으로 0b001을 넣어주면, output으로 설정 가능하다.

 

 

GPSET

GPSET0 register 주소 : 0xFE20 0000 + 0x0000 001C(offset)

n번 비트를 set하면, GPIO n번 PIN이 set 된다 --> LED가 켜짐

 

GPCLR

GPCLR0 register 주소 : 0xFE20 0000 + 0x00 0028(offset)

n번 비트를 set하면 , GPIO n번 PIN이 clear된다 --> LED가 꺼짐

 

 

gpiozero 라이브러리의 경우, wrapping되어 있어 이런 setting이 따로 필요 없음


4. Device Driver 및 App Setting

"test" 디렉토리 내에 다음 코드 생성 후 명령어 실행

 

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main()
{
    int fd = open("/dev/nobrand", O_RDWR);
    if (fd < 0)
    {	
        printf("ERROR\n");
        exit(1);
    }
    
    printf("LED ON!\n");
    ioctl(fd, _IO(0,3), 0);
    usleep(500*1000);

    printf("LED OFF\n");
    ioctl(fd, _IO(0,4), 0);

    close(fd);
    return 0;
}

ioctl() 에 관련된 내용

 

 

nobrand.c

#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>

#define NOD_MAJOR 100
#define NOD_NAME "nobrand"

MODULE_LICENSE("GPL");

static volatile uint32_t *BASE;
static volatile uint32_t *GPFSEL1;
static volatile uint32_t *GPSET0;
static volatile uint32_t *GPCLR0;

static int nobrand_open(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "welcome\n");
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp)
{
    printk(KERN_INFO "release\n");
    return 0;
}

static void ledon(void){
	printk(KERN_INFO "LED ON");
	*GPSET0 = (1<<18);
}

static void ledoff(void){
	printk(KERN_INFO "LED OFF");
	*GPCLR0 = (1<<18);
}

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
	switch(cmd){
		case _IO(0,3):
			ledon();
			break;
		case _IO(0,4):
			ledoff();
			break;
	}
	return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .unlocked_ioctl = nobrand_ioctl,
};

static int nobrand_init(void)
{
	BASE = (uint32_t *)ioremap(0xFE200000, 256);
	GPFSEL1 = BASE + (0x04 / 4);
	GPSET0 = BASE + (0x1C / 4);
	GPCLR0 = BASE + (0x28 / 4);
	
	//GPIO 18 PINOUT
	*GPFSEL1 &= ~(0x7 << 24);
	*GPFSEL1 |= (1 << 24);

    if (register_chrdev(NOD_MAJOR, NOD_NAME, &fops) < 0)
    {
        printk("INIT FALE\n");
    }


    printk(KERN_INFO "hi\n");
    return 0;
}

static void nobrand_exit(void)
{
	*GPCLR0 = (1 << 18);
	iounmap(BASE);

	unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk(KERN_INFO "bye\n");
}

module_init(nobrand_init);
module_exit(nobrand_exit);

 

alias.sh

alias sd='sudo rmmod nobrand'
alias sc='sudo insmod nobrand.ko'

 

Makefile

KERNEL_HEADERS=/lib/modules/$(shell uname -r)/build
CC = gcc

TARGET := app
obj-m := nobrand.o

all: driver app

driver:
	make -C $(KERNEL_HEADERS) M=$(PWD) modules

app:
	$(CC) -o $@ $@.c

clean:
	make -C $(KERNEL_HEADERS) M=$(PWD) clean
	rm -f *.o $(TARGET)

 

 

명령어 실행

$ source alias.sh

$ sc
$./app
# LED ON!
# LED OFF!
$ sd

모듈 파라미터

커널 모듈의 동작을 사용자가 변경 가능하지만, 보안에 취약해진다.

#include <linux/moduleparam.h>

module_param(name, type, permission)

- name : 파라미터 이름
- type : 파라미터 data type
- permision : 파라미터가 변경 가능한 권한 ( S_IRUGO : 읽기 전용 , S_IWUSR : 쓰기 가능 )
    - S_IRUGO / S_IWUSR : 파일 접근 허가 상수


<linux / gpio.h>

 

GPIO는 리눅스에서 표준화되어 있다.

커널에서 쓸 수 있는 API가 지원

#include <linux/gpio.h>

// API 사용법
gpio_request(핀번호, 라벨) : gpio 핀 사용

gpio_free(핀번호) : gpio 핀 사용 해제

gpio_direction_input(핀번호) : gpio 핀 입력 설정

gpio_direction_output(핀번호, 0) : gpio 핀 출력 설정

gpio_set_value(핀번호, 0 또는 1) : gpio 핀 출력 값 설정

gpio_get_value(핀번호) : gpio 핀 입력 값 읽기

gpio_to_irq(핀번호) : gpio 핀에 대한 IRQ 번호 읽기