/* * Samsung EXYNOS FIMC-IS (Imaging Subsystem) driver * * Copyright (C) 2015 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include "fimc-is-binary.h" #include "exynos-fimc-is-sensor.h" /* the version storage of each library binary */ struct lib_ver { char v[LIBRARY_VER_LEN + 1]; }; static struct lib_ver lib_ver_s[IS_BIN_LIB_HINT_RTA + 1]; /* the version stroage of each sensor setfile by position */ struct set_ver { char v[SETFILE_VER_LEN + 1]; }; static struct set_ver set_ver_s[SENSOR_POSITION_MAX]; static const char * const bin_names[] = { "DDK", "RTA", "SETFILE" }; static char *library_get_buf(const struct is_bin_ver_info *info, unsigned int hint) { struct lib_ver *s = (struct lib_ver *)info->s; return s[hint].v; } static unsigned int library_get_name_idx(unsigned int hint) { if (hint == IS_BIN_LIB_HINT_RTA) return 1; /* RTA */ return 0; /* DDK */ } static char *setfile_get_buf(const struct is_bin_ver_info *info, unsigned int hint) { struct set_ver *s = (struct set_ver *)info->s; return s[hint].v; } static unsigned int setfile_get_name_idx(unsigned int hint) { return 2; /* SETFILE */ } static const struct is_bin_ver_info bin_ver_info[] = { { .type = IS_BIN_LIBRARY, .maxhint = IS_BIN_LIB_HINT_RTA + 1, .offset = LIBRARY_VER_OFS, .length = LIBRARY_VER_LEN, .s = &lib_ver_s, .get_buf = library_get_buf, .get_name_idx = library_get_name_idx, }, { .type = IS_BIN_SETFILE, .maxhint = SENSOR_POSITION_MAX, .offset = SETFILE_VER_OFS, .length = SETFILE_VER_LEN, .s = &set_ver_s, .get_buf = setfile_get_buf, .get_name_idx = setfile_get_name_idx, }, {}, }; static noinline_for_stack long get_file_size(struct file *file) { struct kstat st; u32 request_mask = (STATX_MODE | STATX_SIZE); if (vfs_getattr(&file->f_path, &st, request_mask, KSTAT_QUERY_FLAGS)) return -1; if (!S_ISREG(st.mode)) return -1; if (st.size != (long)st.size) return -1; return st.size; } static int read_file_contents(struct file *fp, struct fimc_is_binary *bin) { long size; char *buf; int ret; loff_t pos = 0; size = get_file_size(fp); if (size <= 0) return -EBADF; /* if a buffer for binary is already allocated */ if (bin->data) { if (!bin->size) return -EINVAL; buf = bin->data; /* shrink read size to fit the size of a given buffer */ if (size > bin->size) { pr_crit("%s: will read only %ld bytes from a file (%ld)", __func__, bin->size, size); size = bin->size; } } else { buf = bin->alloc(size); if (!buf) return -ENOMEM; } ret = kernel_read(fp, buf, size, &pos); if (ret != size) { if (!bin->data) bin->free(buf); return ret; } bin->data = buf; bin->size = size; return 0; } static int write_file_contents(struct file *fp, struct fimc_is_binary *bin) { ssize_t ret; ssize_t count; loff_t pos = 0; char *buf = (char *)bin->data; do { count = min_t(ssize_t, PAGE_SIZE, bin->size - pos); ret = kernel_write(fp, buf, count, &pos); if (ret > 0) { pos += ret; buf += ret; } else { break; } } while (pos < bin->size); return 0; } /** * get_filesystem_binary: copy a binary in userland to a given buffer * @filename: filenam of binary file including full path * @bin: pointer to fimc_is_binary structure **/ int get_filesystem_binary(const char *filename, struct fimc_is_binary *bin) { struct file *fp; int ret = 0; fp = filp_open(filename, O_RDONLY, 0); if (!IS_ERR_OR_NULL(fp)) { ret = read_file_contents(fp, bin); fput(fp); } else { ret = PTR_ERR(fp); } return ret; } /** * put_filesystem_binary: copy a given buffer to a file in userland * @filename: filenam of binary file including full path * @bin: pointer to fimc_is_binary structure **/ int put_filesystem_binary(const char *filename, struct fimc_is_binary *bin, u32 flags) { struct file *fp; int ret = 0; fp = filp_open(filename, flags, 0666); if (!IS_ERR_OR_NULL(fp)) { ret = write_file_contents(fp, bin); fput(fp); } else { ret = PTR_ERR(fp); } return ret; } /** * setup_binary_loader: customize an instance of the binary loader * @bin: pointer to fimc_is_binary structure * @retry_cnt: retry count of request_firmware * @retry_err: specific error number for retry of request_firmware * @alloc: custom allocation function for file system loading * @free: custom free function for file system loading **/ void setup_binary_loader(struct fimc_is_binary *bin, unsigned int retry_cnt, int retry_err, void *(*alloc)(unsigned long size), void (*free)(const void *buf)) { bin->retry_cnt = retry_cnt; bin->retry_err = retry_err; if (alloc && free) { bin->alloc = alloc; bin->free = free; } else { /* set vmalloc/vfree as a default */ bin->alloc = &vmalloc; bin->free = &vfree; } bin->customized = (unsigned long)bin; } /** * request_binary: send loading request to the loader * @bin: pointer to fimc_is_binary structure * @path: path of binary file * @name: name of binary file * @device: device for which binary is being loaded **/ int request_binary(struct fimc_is_binary *bin, const char *path, const char *name, struct device *device) { char *filename; unsigned int retry_cnt = 0; int retry_err = 0; int ret; bin->data = NULL; bin->size = 0; bin->fw = NULL; /* whether the loader is customized or not */ if (bin->customized != (unsigned long)bin) { bin->alloc = &vmalloc; bin->free = &vfree; } else { retry_cnt = bin->retry_cnt; retry_err = bin->retry_err; } /* read the requested binary from file system directly */ if (path) { filename = __getname(); if (unlikely(!filename)) return -ENOMEM; snprintf(filename, PATH_MAX, "%s%s", path, name); ret = get_filesystem_binary(filename, bin); __putname(filename); /* read successfully or don't want to go further more */ if (!ret || !device) return ret; } /* ask to 'request_firmware' */ do { ret = request_firmware(&bin->fw, name, device); if (!ret && bin->fw) { bin->data = (void *)bin->fw->data; bin->size = bin->fw->size; break; } /* * if no specific error for retry is given; * whatever any error is occurred, we should retry */ if (!bin->retry_err) retry_err = ret; } while (retry_cnt-- && (retry_err == ret)); return ret; } /** * release_firmware: release the resource related to a binary * @bin: binary resource to release **/ void release_binary(struct fimc_is_binary *bin) { if (bin->fw) release_firmware(bin->fw); else if (bin->data) bin->free(bin->data); } /** * was_loaded_by: how was a binary loaded * @bin: pointer to fimc_is_binary structure **/ int was_loaded_by(struct fimc_is_binary *bin) { if (bin->fw) return 1; /* by request_firmware */ else if (bin->data) return 0; /* from file system */ else return -EINVAL; } /* * carve_binary_version: update the version of a binary to use in the future * @type: a binary type for IS * @hint: a hint to distinguish between same type binaries * @data: buffer of a loaded binary * @size: size of a loaded binary */ /* FIXME: void carve_binary_version(enum is_bin_type type, int hint, struct fimc_is_binary *bin) */ int carve_binary_version(enum is_bin_type type, unsigned int hint, void *data, size_t size) { int ofs, len; char *buf; if (type >= ARRAY_SIZE(bin_ver_info)) { pr_err("unknown type binary(%d)\n", type); return -EINVAL; } if (hint >= bin_ver_info[type].maxhint) { pr_warn("invalid hint(%d) to carved-out\n", hint); return -EINVAL; } ofs = size - bin_ver_info[type].offset; if (ofs <= 0) { pr_warn("out of range offset(size: %d <= offset: %d)\n", size, bin_ver_info[type].offset); return -EINVAL; } len = bin_ver_info[type].length; if ((ofs + len) > size) { pr_warn("too long version length (binary: %d < version: %d)\n", size, (ofs + len)); len -= ((ofs + len) - size); } buf = bin_ver_info[type].get_buf(&bin_ver_info[type], hint); memcpy(buf, &data[ofs], len); buf[len] = '\0'; info("%s version: %s\n", bin_names[bin_ver_info[type].get_name_idx(hint)], buf); return 0; } /* * get_binary_version: retrun the version string of a binary * @type: a binary type for IS * @hint: a hint to distinguish between same type binaries * library: DDK(0), RTA(1) * setfile: sensor position */ char *get_binary_version(enum is_bin_type type, unsigned int hint) { if (type >= ARRAY_SIZE(bin_ver_info)) { pr_err("unknown type binary(%d)\n", type); return NULL; } if (hint >= bin_ver_info[type].maxhint) { pr_warn("invalid binary hint(%d)\n", hint); return NULL; } return bin_ver_info[type].get_buf(&bin_ver_info[type], hint); }