平台总线专题4 -作业

利用互联网的思想,通过编写客户端和服务端,开发板上运行服务端,可以直接控制led灯,并且等待客户端端的连接,客户端通过
socket连接到服务端,通过发送指令来远程控制服务端的led灯

device设备代码 exynos4412_led_dev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define GPIO_BASE_ADDRESS 0x11000000
#define GPX1_CON 0x0C20
#define GPX1_SIZE 24
#define GPX2_CON 0x0C40
#define GPX2_SIZE 24
#define GPF3_CON 0x01E0
#define GPX3_SIZE 24

struct resource	led_resource[] = {
	[0] = {
		.start = GPIO_BASE_ADDRESS + GPX2_CON,
		.end = GPIO_BASE_ADDRESS + GPX2_CON + GPX2_SIZE - 1,
		.name = "exynos4412_dev_led2",
		.flags = IORESOURCE_MEM,
	},

	[1] = {
		.start = GPIO_BASE_ADDRESS + GPX1_CON,
		.end = GPIO_BASE_ADDRESS + GPX1_CON + GPX1_SIZE - 1,
		.name = "exynos4412_dev_led3",
		.flags = IORESOURCE_MEM,
	},

	//中断资源描述,这里只是举例,在实际led驱动中并没有使用到该中断
	[2] = {
		.start = 73,	//irq中断号 
		.end = 73,
		.name = "exynos4412_dev_irq0",
		.flags = IORESOURCE_IRQ,
	},

};


struct platform_device dev_led = {
	.name = "exynos4412_dev_led",
	.id = -1,
	.num_resources = ARRAY_SIZE(led_resource),
	.resource = led_resource,
};

static int __init led_dev_init(void)
{
	return platform_device_register(&dev_led);	
}

static void __exit led_dev_exit(void)
{
	platform_device_unregister(&dev_led);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

driver驱动代码 exynos4412_led_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/slab.h>


struct led_drv_info {
	unsigned int major;
	struct class* pdrv_class;
	struct device * pdrv_device;
	struct resource * pdrv_resource;
	void* reg_base;	//表示物理地址映射之后的虚拟地址
};

struct led_drv_info* pdrvinfo;


int fops_open (struct inode * inode, struct file * filp)
{
	printk("--------%s----------\n",__FUNCTION__);
	return 0;
}
int fops_release (struct inode * inode, struct file * filp)
{
	printk("--------%s----------\n",__FUNCTION__);
	return 0;
}

ssize_t fops_read (struct file * filp, char __user * buf, size_t count, loff_t * fpos)
{
	printk("--------%s----------\n",__FUNCTION__);
	return count;
}

ssize_t fops_write (struct file * filp, const char __user * buf, size_t count, loff_t * fpos)
{
	int buf_write,ret;
	
	printk("--------%s----------\n",__FUNCTION__);

	ret = copy_from_user(&buf_write, buf, count);

	if(0 != ret)
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	else
	{
		if(0 == buf_write)
		{
			writel(readl(pdrvinfo->reg_base + 4) & ~(0x1<<7) , pdrvinfo->reg_base + 4);
		}
		else
		{
			writel(readl(pdrvinfo->reg_base + 4) | (0x1<<7) , pdrvinfo->reg_base + 4);
		}
	}
	return count;
}

struct file_operations fops = {
	.read = fops_read,
	.write = fops_write,
	.open = fops_open,
	.release = fops_release,
};


int exynos4412_led_drv_probe(struct platform_device *pdev) //匹配成功之后被调用的函数
{
	/*
		注册完毕,同时如果和pdev匹配成功,自动调用probe方法:
				probe方法: 对硬件进行操作
						a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口
						b, 创建设备节点
						c, 初始化硬件
									ioremap(地址);  //地址从pdev需要获取
									readl/writle();
						d,实现各种io接口: xxx_open, xxx_read, ..
	
	*/
	int irqno;
	printk("--------%s----------\n",__FUNCTION__);

	pdrvinfo = (struct led_drv_info*)kzalloc(sizeof(struct led_drv_info), GFP_KERNEL);

	if(NULL == pdrvinfo)
	{
		printk("kzalloc Error\n");
		return -ENOMEM;
	}

	pdrvinfo->major =  register_chrdev(0, "led_drv", &fops);

	pdrvinfo->pdrv_class = class_create(THIS_MODULE, "my_led_cls");	

	pdrvinfo->pdrv_device = device_create(pdrvinfo->pdrv_class, NULL,  MKDEV(pdrvinfo->major,0), NULL, "LED0");

	//获取资源
	//参数1:从哪哥pdev中获取资源
	//参数2:资源类型
	//参数3:表示获取同种资源的第几个(从0开始计数)种类一般分IORESOURCE_MEM 和 IORESOURCE_IRQ 可能还有其他种类 本实验没有涉及到
	pdrvinfo->pdrv_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);	//获取第0位的IORESOURCE_MEM

	//pdrvinfo->pdrv_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1);  //获取第1位的IORESOURCE_MEM

	//pdrvinfo->pdrv_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  //获取第0位的IORESOURCE_IRQ

	irqno = platform_get_irq(pdev, 0);  //获取第0位的IORESOURCE_IRQ 和上面的写法等同

	printk("---------irqno = %d------------\n",irqno);
	
	pdrvinfo->reg_base = ioremap(pdrvinfo->pdrv_resource->start,resource_size(pdrvinfo->pdrv_resource));
	//	resource_size 等效于 pdrvinfo->pdrv_resource->end - pdrvinfo->pdrv_resource->start + 1

	//对寄存器进行配置
	writel((readl((unsigned int*)pdrvinfo->reg_base) & ~(0xf<<28)) | (0x1<<28),pdrvinfo->reg_base);
	
	return 0;
}

int exynos4412_led_drv_remove(struct platform_device *pdev)//device移除的时候调用的函数
{
	printk("--------%s----------\n",__FUNCTION__);

	iounmap(pdrvinfo->reg_base);

	device_destroy(pdrvinfo->pdrv_class, MKDEV(pdrvinfo->major,0));

	class_destroy(pdrvinfo->pdrv_class);

	unregister_chrdev(pdrvinfo->major, "led_drv");

	kfree((void*)pdrvinfo);
	
	return 0;
}

struct platform_device_id id_table[] = {	//id列表 将驱动支持的设备都列出来
	[0] = {
		.name = "exynos4412_dev_led",	//用于匹配dev的名字
		.driver_data = 0x1111,
	},
	
	[1] = {
		.name = "exynos2410_dev_led",
		.driver_data = 0x2222,
	},
	
	[2] = {
		.name = "exynos6410_dev_led",
		.driver_data = 0x3333,
	},
	
	[3] = {
		.name = "exynos6412_dev_led",
		.driver_data = 0x4444,
	},
};

struct platform_driver pdrv = {
	.probe = exynos4412_led_drv_probe,
	.remove = exynos4412_led_drv_remove,
	.driver = {
			.name = "SAMSUNG_drv_led", 	//这个名字是在sys/bus/platform/drivers下显示的驱动名 
										//也可用于匹配dev 但是优先级低于下面的id_table
		},
	.id_table = id_table,	
};

int __init exynos4412_led_drv_init(void)
{
	printk("--------%s----------\n",__FUNCTION__);
	
	return platform_driver_register(&pdrv);
}

void __exit exynos4412_led_drv_exit(void)
{
	printk("--------%s----------\n",__FUNCTION__);
	
	platform_driver_unregister(&pdrv);
}

module_init(exynos4412_led_drv_init);
module_exit(exynos4412_led_drv_exit);
MODULE_LICENSE("GPL");

server.c 开发板端 应用层服务器代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>

#define SERV_PORT 5080  //端口号 根据需要可自行修改
#define SERV_IP_ADDR "192.168.1.160"    //IP地址 根据需要可自行修改

void* func_recall(void* arg);
void handler(int signo) {   //自动回收子进程 防止僵尸进程
    
    if (signo == SIGCHLD)
    {
        printf("Recall SIGINT\n");
        waitpid(-1, NULL, WNOHANG);
    }
}

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen = sizeof(clientscokaddr);

    static pthread_t nptd;

    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    /*优化4: 允许绑定地址快速重用 */
    int b_reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));

    /*2. 绑定 */
    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号
    mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*2.2 绑定 */
    result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("bind");
        return -1;
    }

    /*3. 调用listen()把主动套接字变成被动套接字 */
    result = listen(fd, 5);
    if (result != 0) {
        perror("listen");
        return -1;
    }

    /*4. 阻塞等待客户端连接请求 */

    /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/
    int fd_client;

    pid_t pid;
    signal(SIGCHLD, handler);
    while (1) {
        bzero((void*)&clientscokaddr, sizeof(clientscokaddr));
        fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        if (-1 == fd_client) {
            perror("accpet");
            return 0;
        }
        char ipv4_addr[16];
        if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) {
            perror("inet_ntop");
            return -1;
        }

        printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port));
        pid = fork();
        if (pid < 0) {
            perror("fork");
            return -1;
        }
        else if (pid > 0) { //父进程
            close(fd_client);
        }
        else {  //子进程
            close(fd);
            func_recall((void*)&fd_client); //调用读写函数
            return 0;
        }
    }

    close(fd);
    close(fd_client);
    return 0;
}

void* func_recall(void* arg) {
    int result = 0;
    int newfd = *(int*)arg;
	int fd,botton;
	
    printf("handler thread: newfd =%d\n", newfd);
    //..和newfd进行数据读写
    char buf[BUFSIZ];

	fd = open("/dev/LED0",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		return;
	}
	
    while (1) {
        bzero(buf, BUFSIZ);

        do {
            result = read(newfd, buf, BUFSIZ - 1);
        } while (result < 0 && EINTR == errno);

        if (result < 0) {
            perror("read");
            break;
        }

        if (!result)
            break;

		//收到开启信息
		if (!strcmp(buf, "open"))
		{
			botton = 1;
			write(fd, &botton, 4);
		}
		//收到关闭信息
		else if(!strcmp(buf, "close"))
		{
			botton = 0;
			write(fd, &botton, 4);
		}
		//收到其他信息
		else
		{
			printf("other message :%s\n", buf);
		}	
    }
    close(newfd);
	close(fd);
    return 0;
}

client.c linux远端 应用层 客户端代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>


#define SERV_PORT 5080
#define SERV_IP_ADDR "192.168.1.160"

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    char MESSAGE[32] = {0};

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);
#if 0    
    mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif

    result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("connect");
        return -1;
    }
    printf("Client staring...OK!\n");

    while (1) {
		scanf("%s",MESSAGE);
        do {
            result = write(fd, (void*)MESSAGE, sizeof(MESSAGE));
        } while (result < 0 && EINTR == errno);         
        
    }

    close(fd);
    return 0;
}

makefile代码

ROOTFS_DIR = /home/linux/Level11

APP_NAME = server

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean
		rm $(APP_NAME) ./1/$(APP_NAME)
		clear

install:
		cp -raf  *.ko  $(APP_NAME)   ./1/
		#cp -raf  *.ko  ./1/
		
else
obj-m += exynos4412_led_dev.o
obj-m += exynos4412_led_drv.o

endif

效果展示

linux远端 控制台信息:

linux@ubuntu:/mnt/hgfs/myproject/day15homework$ ./client
Client staring...OK!
close
open 
close
open
233333

开发板 控制台信息:

[root@farsight ]# insmod exynos4412_led_dev.ko
[   46.239501] exynos4412_led_dev: loading out-of-tree module taints kernel.
[root@farsight ]# insmod exynos4412_led_drv.ko
[  177.116264] --------exynos4412_led_drv_init----------
[  177.120229] --------exynos4412_led_drv_probe----------
[  177.125658] ---------irqno = 73------------
[root@farsight ]# ./server
Clinet(192.168.1.158:56826) is connected!
handler thread: [  183.952933] --------fops_open----------
newfd =4
[  193.186926] --------fops_write----------
[  194.157127] random: crng init done
[  199.872891] --------fops_write----------
[  204.207945] --------fops_write----------
[  207.134812] --------fops_write----------
other message :233333

综述:通过linux远端控制台输入open ,close 分别实现打开和关闭开发板的led灯,输入其他信息会打印在开发板的控制台上。