// ...
// Здесь небольшая таблица инициализации пинов
// Несмотря на то, что коммент ниже уговаривает нас не тратить время зря,
// код не выглядит нерабочим, поэтому добавим инициализацию нужных нам пинов
/* We are disabling this code for now. */
#define GPIO_INIT_PIN_MODE(_gpio, _is_input, _value) { .gpio_nr = _gpio, .is_input = _is_input, .value = _value, }
static struct gpio_init_pin_info init_gpio_mode_grouper_common[] = {
GPIO_INIT_PIN_MODE(TEGRA_GPIO_PDD7, false, 0),
GPIO_INIT_PIN_MODE(TEGRA_GPIO_PCC6, false, 0),
GPIO_INIT_PIN_MODE(TEGRA_GPIO_PR0, false, 0),
// вот тут наши пины. Почему так - написано в таблице ниже :)
GPIO_INIT_PIN_MODE(TEGRA_GPIO_PBB0, true, 0),
GPIO_INIT_PIN_MODE(TEGRA_GPIO_PBB5, false, 0),
};
//
static __initdata struct tegra_pingroup_config grouper_pinmux_common[] = {
// ...
/*
На найденном ранее расположении пинов на схеме мы узнали, что искомые пины имеют имена
GPIO_PBB0 и GPIO_PBB5. Oни здесь, в блоке для камеры, которой нет. Немного поменяем их конфиг
*/
/* CAMERA */
DEFAULT_PINMUX(CAM_MCLK, VI_ALT2, PULL_DOWN, NORMAL, INPUT),
DEFAULT_PINMUX(GPIO_PCC1, RSVD1, NORMAL, NORMAL, INPUT),
// было
//DEFAULT_PINMUX(GPIO_PBB0, RSVD1, NORMAL, NORMAL, INPUT),
// стало: пин для прерывания ставим на вход и подтягиваем вверх, так как у нас nIRQ
DEFAULT_PINMUX(GPIO_PBB0, RSVD1, PULL_UP, NORMAL, INPUT),
DEFAULT_PINMUX(GPIO_PBB3, VGP3, NORMAL, NORMAL, INPUT),
//DEFAULT_PINMUX(GPIO_PBB5, VGP5, NORMAL, NORMAL, INPUT), // было
// стало: пин для управления питанием оставляем на выход и притягиваем вниз, чтобы сенсор был выключен по умолчанию
DEFAULT_PINMUX(GPIO_PBB5, VGP5, PULL_DOWN, NORMAL, OUTPUT),
// ...
};
// ...
// Эта функция инициализации вызывается из следующей и применяет
// таблицу поменьше, что выше в этой вырезке кода
static void __init grouper_gpio_init_configure(void)
{
int len;
int i;
struct gpio_init_pin_info *pins_info;
u32 project_info = grouper_get_project_id();
if (project_info == GROUPER_PROJECT_NAKASI_3G) {
len = ARRAY_SIZE(init_gpio_mode_grouper3g);
pins_info = init_gpio_mode_grouper3g;
} else {
// вот это оно - проект у нас не 3g, так как в планшете этом 3G нету
len = ARRAY_SIZE(init_gpio_mode_grouper_common);
pins_info = init_gpio_mode_grouper_common;
}
for (i = 0; i < len; ++i) {
tegra_gpio_init_configure(pins_info->gpio_nr,
pins_info->is_input, pins_info->value);
pins_info++;
}
}
// Это одна из функций инициализации ядра, где наши pinmux`ы уйдут в закрытую часть
// кода nVidia
int __init grouper_pinmux_init(void)
{
struct board_info board_info;
u32 project_info = grouper_get_project_id();
tegra_get_board_info(&board_info);
BUG_ON(board_info.board_id != BOARD_E1565);
grouper_gpio_init_configure();
// вот тут
tegra_pinmux_config_table(grouper_pinmux_common, ARRAY_SIZE(grouper_pinmux_common));
tegra_drive_pinmux_config_table(grouper_drive_pinmux,
ARRAY_SIZE(grouper_drive_pinmux));
if (project_info == GROUPER_PROJECT_NAKASI_3G) {
tegra_pinmux_config_table(pinmux_grouper3g,
ARRAY_SIZE(pinmux_grouper3g));
}
tegra_pinmux_config_table(unused_pins_lowpower,
ARRAY_SIZE(unused_pins_lowpower));
grouper_pinmux_audio_init();
return 0;
}
// ...
// ...
// Вот эту структуру получит драйвер после загрузки и будет уже
// иметь абстрагированное от конкретного номера GPIO значение nIRQ
// По честноку, управление питанием надо бы реализовать в этом же файле
// и передать указатели на функции, но лень, поэтому оно является
// частью драйвера, который знает номер GPIO для PWRD
static const struct i2c_board_info tricky_sensor_board_info[] = {
{
I2C_BOARD_INFO("tricky",0x55),
.irq = TEGRA_GPIO_TO_IRQ(TEGRA_GPIO_PBB0)
},
};
// вот тут мы настраиваем 2 наших GPIO (отдельно, для порядка).
// ХЗ как тамреализована работа в закрытой части кода, поэтому
// скажем какими хотим видеть наши пины более привычным и уже
// абстрагированным через linux/gpio путём
static int grouper_tricky_init(void)
{
// хотя и тут не обходится без магии вызова функций tegra_gpio_enable,
// так как иначе пины работать не будут
int ret = 0;
ret = gpio_request(TEGRA_GPIO_PBB5, "tricky_npwd");
if (ret < 0) {
pr_err("Tricky: Error: cannot register GPIO_PWR_DOWN\n");
}
else {
ret = gpio_direction_output(TEGRA_GPIO_PBB5, true);
if (ret < 0) {
pr_err("Tricky: Error: cannot set GPIO_PWR_DOWN as output\n");
}
else {
tegra_gpio_enable(TEGRA_GPIO_PBB5);
}
}
ret = gpio_request(TEGRA_GPIO_PBB0, "tricky_nirq");
if (ret < 0) {
pr_err("Tricky: Error: cannot register GPIO_NIRQ\n");
return ret;
}
ret = gpio_direction_input(TEGRA_GPIO_PBB0);
if (ret < 0) {
gpio_free(TEGRA_GPIO_PBB0);
pr_err("Tricky: Error: cannot set GPIO_NIRQ as input\n");
}
else {
tegra_gpio_enable(TEGRA_GPIO_PBB0);
}
printk("%s: Tricky OK", __FUNCTION__);
return ret;
}
// ...
// А вот это уже функция инициализации всех сенсоров планшета,
// где мы вызываем настройку своих пинов и регистрируем устройство
int __init grouper_sensors_init(void)
{
int err;
grouper_camera_init();
#ifdef CONFIG_VIDEO_OV2710
i2c_register_board_info(2, grouper_i2c2_board_info,
ARRAY_SIZE(grouper_i2c2_board_info));
#endif
/* Front Camera mi1040 + */
pr_info("mi1040 i2c_register_board_info");
i2c_register_board_info(2, front_sensor_i2c2_board_info,
ARRAY_SIZE(front_sensor_i2c2_board_info));
err = grouper_nct1008_init();
if (err)
printk("[Error] Thermal: Configure GPIO_PCC2 as an irq fail!");
i2c_register_board_info(4, grouper_i2c4_nct1008_board_info,
ARRAY_SIZE(grouper_i2c4_nct1008_board_info));
mpuirq_init();
i2c_register_board_info(2, cardhu_i2c1_board_info_al3010,
ARRAY_SIZE(cardhu_i2c1_board_info_al3010));
if (GROUPER_PROJECT_BACH == grouper_get_project_id()) {
i2c_register_board_info(2, cap1106_i2c1_board_info,
ARRAY_SIZE(cap1106_i2c1_board_info));
}
// вот тут наша инициализация
grouper_tricky_init();
i2c_register_board_info(2/*это номер I2C шины, на которой сидит сенсор*/, tricky_sensor_board_info,
ARRAY_SIZE(tricky_sensor_board_info));
return 0;
}
// TBD (показать регистрацию устройства)
#include <linux/init.h> // Macros used to mark up functions e.g. __init __exit
#include <linux/module.h> // Core header for loading LKMs into the kernel
#include <linux/device.h> // Header to support the kernel Driver Model
#include <linux/kernel.h> // Contains types, macros, functions for the kernel
#include <linux/fs.h> // Header for the Linux file system support
#include <linux/i2c.h> // main sensor communication protocol
#include <linux/gpio.h> // sensor`s wake/sleep and new data interrupts are processed via two pins
#include <linux/interrupt.h> // Support GPIO IRQ handler
#include <asm/uaccess.h> // copy_to_user and copy_from_user functions
#include <asm/io.h> // Access to memset()
#include <linux/workqueue.h> // Make IRQ event into deferred handler task
#include <linux/mutex.h> // Sync data buffer usage between IRQ-work and outer read requests
#include <linux/delay.h> // Access to mdelay
// Устройство будет доступно в ядре как /dev/tricky_temperature
#define DEVICE_NAME "tricky_temperature"
// Имя символьного устройства
#define CLASS_NAME "tricky"
// ... всякие разные дефайны ...
// описание модуля
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pavel Akimov");
MODULE_DESCRIPTION("Test Linux driver for tricky sensor"); ///< Описание доступно через команду modinfo
MODULE_VERSION("0.1");
// номер устройства и указатели на структуры, необходимые для регистрации драйвера
static int majorNumber;
static struct class* trickyClass = NULL;
static struct device* trickyDevice = NULL;
// ... всякие разные конфиги и команды для сенсора ...
// массив для последних считанных данных
static u8 sensor_data_buffer[I2C_DATA_SIZE] = { 0 };
// Через указатель на I2C клиент осуществляется доступ к коммуникации
struct i2c_client *tricky_i2c_client = NULL;
// Объявление файлового интерфейса драйвера
static int dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param);
// Объявление I2C интерфейса для подключения драйвера
static int tricky_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int tricky_i2c_remove(struct i2c_client *i2c_client);
// Установка питания через пин
static int set_sensor_power(u8 enabled);
// Чтение данных через I2C
static int read_raw_temperatures(void);
// Обработчик прерывания о поступлении новых данных
static irq_handler_t tricky_data_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs);
// Поточная функция для выполнения задачи при возникновении прерывания
static void read_data_work_handler(struct work_struct *w);
// Работа с фоновым потоком для чтения данных в ядре
static struct workqueue_struct *wq = NULL;
static DECLARE_DELAYED_WORK(read_data_work, read_data_work_handler);
static struct mutex read_data_mutex;
// файловый интерфейс драйвера
static struct file_operations fops =
{
.open = dev_open,
.read = dev_read,
.unlocked_ioctl = dev_ioctl
};
// таблица устройств
static const struct i2c_device_id tricky_i2c_id[] = {
{ CLASS_NAME, 0 },
{ }, // должна заканчиваться пустой записью, по которой ядро определит конец текущей таблицы
};
MODULE_DEVICE_TABLE(i2c, tricky_i2c_id);
// описание драйвера, который будет добавлен при инициализации модуля
static struct i2c_driver tricky_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = CLASS_NAME,
},
.id_table = tricky_i2c_id,
.probe = tricky_i2c_probe,
.remove = tricky_i2c_remove
};
// после добавления i2c драйвера здесь мы получим указатель на i2c клиент, если мы зарегистрированы в списке железа
static int tricky_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
tricky_i2c_client = client;
return 0;
}
//
static int tricky_i2c_remove(struct i2c_client *i2c_client)
{
if (tricky_i2c_client != NULL) {
i2c_unregister_device(tricky_i2c_client);
tricky_i2c_client = NULL;
}
return 0;
}
// инициализации драйвера вызывается при загрузке ядра
static int __init tricky_temperature_init(void) {
int err;
// Try to dynamically allocate a major number for the device -- more difficult but worth it
majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
if (majorNumber<0){
pr_err(KERN_ALERT "Tricky failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO "Tricky: registered correctly with major number %d\n", majorNumber);
// Register the device class
trickyClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(trickyClass)){ // Check for error and clean up if there is
pr_err(KERN_ALERT "Failed to register device class\n");
err = PTR_ERR(trickyClass); // Correct way to return an error on a pointer
goto err_char_dev;
}
printk(KERN_INFO "Tricky: device class registered correctly\n");
// Register the device driver
trickyDevice = device_create(trickyClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(trickyDevice)){ // Clean up if there is an error
pr_err(KERN_ALERT "Failed to create the device\n");
err = PTR_ERR(trickyDevice);
goto err_class;
}
printk(KERN_INFO "Tricky: device class created correctly\n"); // Made it! device was initialized
// добавляем новый I2С драйвер, после чего мы найдем устройство в списке
// зарегистрированных устройств (тот, что в board grouper sensors) и вызовем probe
err = i2c_add_driver(&tricky_i2c_driver);
if (err < 0) {
pr_err("Tricky: Error: %s: driver registration failed, error=%d\n", __func__, err);
goto err_dev;
}
// Всячески настраиваем сенсор по I2C ...
// запрашиваем привязку IRQ к нашему callback`у с уникальной строковой меткой
// отмечаем, что реакция должна быть на падающий фронт сигнала
err = request_irq(
i2c_client->irq,
(irq_handler_t)tricky_data_irq_handler,
IRQF_TRIGGER_FALLING,
"tricky_gpio_handler",
NULL); // no shared interrupt lines
if (err < 0) {
pr_err("Tricky: Error: %s: cannot register GPIO_NIRQ irq handler: Error=%d\n", __func__, err);
goto err_drv;
}
// настраиваем очередь для чтения данных в фоновом потоке по приходу IRQ
wq = create_singlethread_workqueue("tricky_work");
mutex_init(&read_data_mutex);
printk(KERN_INFO "Tricky: initialization completed\n");
return 0;
err_irq:
destroy_workqueue(wq);
free_irq(i2c_client->irq, NULL);
err_drv:
i2c_del_driver(&tricky_i2c_driver);
err_dev:
device_destroy(trickyClass, MKDEV(majorNumber, 0)); // remove the device
class_unregister(trickyClass); // unregister the device class
err_class:
class_destroy(trickyClass); // remove the device class
err_char_dev:
unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number
return err;
}
// выгрузка при выключении
static void __exit tricky_temperature_exit(void) {
if (delayed_work_pending(&read_data_work) != 0)
cancel_delayed_work_sync(&read_data_work);
destroy_workqueue(wq);
free_irq(i2c_client->irq, NULL);
i2c_del_driver(&tricky_i2c_driver);
if (tricky_i2c_client != NULL) {
i2c_unregister_device(tricky_i2c_client);
tricky_i2c_client = NULL;
}
device_destroy(trickyClass, MKDEV(majorNumber, 0));
class_unregister(trickyClass);
class_destroy(trickyClass);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_INFO "Tricky: Goodbye\n");
}
static int dev_open(
struct inode *node,
struct file *filep) {
printk(KERN_INFO "Tricky: Open the LKM!\n");
return 0;
}
static ssize_t dev_read(
struct file *filep,
char *buffer,
size_t len,
loff_t *offset) {
int ret;
// да, уровень выше (HAL) знает, сколько ему можно читать байт
if (!buffer || len != I2C_DATA_SIZE) {
return -EINVAL;
}
mutex_lock(&read_data_mutex);
ret = copy_to_user(buffer, sensor_data_buffer, I2C_DATA_SIZE);
mutex_unlock(&read_data_mutex);
if (ret != 0) {
return -ENOMEM;
}
return 0;
}
static ssize_t dev_ioctl(
struct file *file,
unsigned int ioctl_num,
unsigned long ioctl_param) {
switch (ioctl_num) {
case IOCTL_POWER:
ret = set_sensor_power(ioctl_param != CMD_POWER_WAKEUP ? 1 : 0);
if (ret < 0) {
return ret;
}
break;
case ... // more commands
default:
pr_err(KERN_INFO "Tricky: invalid command type to apply\n");
return -EINVAL;
}
return 0;
}
static int set_sensor_power(u8 enabled) {
gpio_set_value(GPIO_PWR_DOWN, enabled != 0);
return 0;
}
// при чтении I2C используем два сообщения: записываем адрес (2 байта)
// и считываем данные по этому адресу
static int read_raw_temperatures(void) {
int ret;
struct i2c_msg write_message;
struct i2c_msg read_message;
write_message.addr = I2C_SLAVE_ADDRESS;
write_message.flags = 0; // plain write
write_message.buf = (char*)i2c_read_temperatures_address;
write_message.len = sizeof(i2c_read_temperatures_address);
memset(sensor_data_buffer, 0, sizeof(sensor_data_buffer));
read_message.addr = I2C_SLAVE_ADDRESS;
read_message.flags = I2C_M_RD; // plain read
read_message.buf = (char*)sensor_data_buffer;
read_message.len = sizeof(sensor_data_buffer);
// read out data
ret = i2c_transfer(tricky_i2c_client->adapter, &write_message, 1);
if (ret < 0) {
pr_err(KERN_INFO "Tricky: Cannot write data address. Error=%d\n", ret);
return ret;
}
ret = i2c_transfer(tricky_i2c_client->adapter, &read_message, 1);
if (ret < 0) {
pr_err(KERN_INFO "Tricky: Cannot read data from the sensor. Error=%d\n", ret);
return ret;
}
return 0;
}
// в обработчике прерывания крайне нехорошо делать что-либо длительное
// не говоря уж о том, что запрос I2C там не пройдёт вообще, так как
// I2C сам использует прерывания в своей работе
// Поэтому по получении сигнала, что данные готовы, добавляем задачу в очередь,
// которая выполняется в фоновом потоке
static irq_handler_t tricky_data_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs) {
// заодно проверяем, что предыдущая задача завершилась
if (delayed_work_pending(&read_data_work) == 0)
queue_delayed_work(wq, &read_data_work, msecs_to_jiffies(1));
return (irq_handler_t)IRQ_HANDLED;
}
// читаем данные в массив с использованием блокировки,
// так как при файловых запросах данные будут копироваться
// из этого же массива (в другом потоке, конечно)
static void read_data_work_handler(struct work_struct *w) {
int ret;
mutex_lock(&read_data_mutex);
ret = read_raw_temperatures();
mutex_unlock(&read_data_mutex);
if (ret < 0) {
printk(KERN_INFO "Tricky: read_data_work_handler. Ret = %d\n", ret);
}
}
// используем системные макросы, чтобы указать точки загрузки и выгрузки драйвера
module_init(tricky_temperature_init);
module_exit(tricky_temperature_exit);
menuconfig THERMAL
tristate "Generic Thermal sysfs driver"
help
Generic Thermal Sysfs driver offers a generic mechanism for
thermal management. Usually it's made up of one or more thermal
zone and cooling device.
Each thermal zone contains its own temperature, trip points,
cooling devices.
All platforms with ACPI thermal support can use this driver.
If you want this support, you should say Y or M here.
config THERMAL_HWMON
bool
depends on THERMAL
depends on HWMON=y || HWMON=THERMAL
default y
config TRICKY_SENSOR
default y
bool
prompt "Tricky temperature sensor support"
obj-$(CONFIG_THERMAL) += thermal_sys.o
obj-$(CONFIG_TRICKY_SENSOR) += tricky_temperature.o
kernel/tegra/arch/arm/mach-tegra/board-grouper-pinmux.c
kernel/tegra/arch/arm/mach-tegra/board-grouper-sensors.c
kernel/tegra/drivers/thermal/tricky_sensor.c
kernel/tegra/drivers/thermal/KConfig
kernel/tegra/drivers/thermal/Makefile
Besides sampling frequency and maximum reporting latency, applications cannot configure sensor parameters. For example, imagine a physical sensor that can function both in “high accuracy” and “low power” modes. Only one of those two modes can be used on an Android device, because otherwise, an application could request the high accuracy mode, and another one a low power mode; there would be no way for the framework to satisfy both applications. The framework must always be able to satisfy all its clients, so this is not an option.
There is no mechanism to send data down from the applications to the sensors or their drivers. This ensures one application cannot modify the behavior of the sensors, breaking other applications.
#ifndef ANDROID_TRICKY_INTERFACE_H
#define ANDROID_TRICKY_INTERFACE_H
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <hardware/hardware.h>
__BEGIN_DECLS
#define TRICKY_HARDWARE_MODULE_ID "tricky"
struct tricky_device_t {
struct hw_device_t common;
int (*read_sample)(unsigned short *psynchro, short *pobj_temp, short *pntc1_temp, short *pntc2_temp, short *pntc3_temp);
int (*activate)(unsigned char enabled);
int (*set_mode)(unsigned char is_continuous);
};
__END_DECLS
#endif // ANDROID_TRICKY_INTERFACE_H
#include <errno.h>
#include <cutils/log.h>
#include <cutils/sockets.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <hardware/sensor_tricky_temperature.h>
#define LOG_TAG "TRICKY"
#define DEVICE_NAME "/dev/tricky_temperature"
#define TRICKY_MODE_0 0
#define TRICKY_MODE_1 1
int fd = 0;
int read_sample(unsigned short *psynchro, short *pobj_temp, short *pntc1_temp, short *pntc2_temp, short *pntc3_temp)
{
int ret = 0;
unsigned char buffer[10];
ALOGD("HAL -- read_sample() called");
ret = read(fd, (char*)buffer, sizeof(buffer));
if (ret < 0) {
ALOGE("HAL -- cannot read raw temperature data");
return -1;
}
if (psynchro) *psynchro = (unsigned short)(buffer[3] << 8 | buffer[2]);
if (pobj_temp) *pobj_temp = (short)(buffer[1] << 8 | buffer[0]);
if (pntc1_temp) *pntc1_temp = (short)(buffer[5] << 8 | buffer[4]);
if (pntc2_temp) *pntc2_temp = (short)(buffer[7] << 8 | buffer[6]);
if (pntc3_temp) *pntc3_temp = (short)(buffer[9] << 8 | buffer[8]);
ALOGD("HAL - sample read OK");
return 0;
}
int activate(unsigned char enabled)
{
int ret = 0;
ALOGD("HAL - activate(%d) called", enabled);
ret = ioctl(fd, 0, enabled ? TRICKY_MODE_0 : TRICKY_MODE_1);
if (ret < 0) {
ALOGE("HAL - cannot write activation state");
return -1;
}
ALOGD("HAL - activation state written OK");
return 0;
}
int set_mode(unsigned char is_continuous)
{
int ret;
ALOGD("HAL -- set_mode(%d) called", is_continuous);
ret = ioctl(fd, 1, is_continuous ? TRICKY_MODE_0 : TRICKY_MODE_1);
if (ret < 0) {
ALOGE("HAL - cannot write mode state");
return -1;
}
ALOGD("HAL - mode state written OK");
return 0;
}
static int open_tricky(const struct hw_module_t* module, char const* name, struct hw_device_t** device)
{
int ret = 0;
struct tricky_device_t *dev = malloc(sizeof(struct tricky_device_t));
if (dev == NULL) {
ALOGE("HAL - cannot allocate memory for the device");
return -ENOMEM;
}
else {
memset(dev, 0, sizeof(*dev));
}
ALOGD("HAL - openHAL() called");
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
dev->read_sample = read_sample;
dev->activate = activate;
dev->set_mode = set_mode;
*device = (struct hw_device_t*) dev;
fd = open(DEVICE_NAME, O_RDWR);
if (fd <= 0) {
ALOGE("HAL - cannot open device driver");
return -1;
}
ALOGD("HAL - has been initialized");
return 0;
}
static struct hw_module_methods_t tricky_module_methods = {
.open = open_tricky
};
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = TRICKY_HARDWARE_MODULE_ID,
.name = "Tricky HAL Module",
.author = "Pavel Akimov",
.methods = &tricky_module_methods,
};
# устанавливаем путь для сборки
LOCAL_PATH := $(call my-dir)
# сборки модулей следуют одна за другой в объединенном .mk файле, так что
# надо очищать настройки предыдущей сборки перед началом
# Да, LOCAL_PATH не убьётся
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
# это то, где ему лежать в заруженной системе
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
# что надо для сборки
LOCAL_SHARED_LIBRARIES := liblog libcutils libhardware
# все исходники, разделенные пробелами
LOCAL_SRC_FILES := sensor_tricky_temperature.c
# имя на выходе
LOCAL_MODULE := techartmsjdts.default
LOCAL_MODULE_TAGS := debug
include $(BUILD_SHARED_LIBRARY)
hardware_modules := gralloc hwcomposer audio nfc nfc-nci local_time power usbaudio audio_remote_submix camera consumerir tricky
include $(call all-named-subdir-makefiles,$(hardware_modules))
hardware/libhardware/include/hardware/sensor_tricky_temperature.h
hardware/libhardware/modules/Android.mk
hardware/libhardware/modules/tricky/sensor_tricky_temperature.c
hardware/libhardware/modules/tricky/Android.mk
frameworks\base\core\java\android\app\ContextImpl.java
frameworks\base\core\java\android\content\Context.java
frameworks\base\core\java\android\hardware\temperature\ITrickyService.aidl
frameworks\base\core\java\android\hardware\temperature\TrickyTemperatureData.aidl
frameworks\base\core\java\android\hardware\temperature\TrickyTemperatureData.java
frameworks\base\core\java\android\hardware\temperature\TrickyManager.java
frameworks\base\services\java\com\android\server\temperature\TrickyService.java
frameworks\base\services\java\com\android\server\SystemServer.java
frameworks\base\services\jni\Android.mk
frameworks\base\services\jni\com_android_server_temperature_TrickyService.cpp
frameworks\base\services\jni\onload.cpp
frameworks\base\Android.mk
// после переопределения LOG_TAG сообщения в логе будут тегированы как нам надо
#define LOG_TAG "TRICKY"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/sensor_tricky_temperature.h>
#include <stdio.h>
// да, всё действие в пределах этого пространства имен, так как
// мы не абы кто, а часть Android
namespace android
{
static jlong init_native(JNIEnv *env, jobject clazz)
{
int err;
hw_module_t* module;
tricky_device_t* dev = NULL;
// найдем наш HAL
// там внутри этой функции проверяется несколько путей, где hw модули могут
// располагаться и должны имен структурированные имена, поэтому имя нашего
// HAL заканчивается на ".default" - хотя не самый честный суффикс (честнее было бы
// написать что это железо-зависимый HAL, но да ладно)
err = hw_get_module(TRICKY_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
err = module->methods->open(module, "", ((hw_device_t**) &dev));
if (err != 0) {
ALOGE("init_native: cannot open device module: %d", err);
return -1;
}
} else {
ALOGE("init_native: cannot get device module: %d", err);
return 0;
}
ALOGD("init_native: start ok");
// этот указатель мы сохраним в Java части сервиса и будем передавать в другие методы
return (jlong)dev;
}
// при выходе не забываем выключить свет
static void finalize_native(JNIEnv *env, jobject clazz, jlong ptr)
{
tricky_device_t* dev = (tricky_device_t*)ptr;
if (dev == NULL) {
ALOGE("finalize_native: invalid device pointer");
return;
}
free(dev);
ALOGD("finalize_native: finalized ok");
}
// тут читаем данные из HAL
// и возвращаем их из C++ в нашем типе TrickyTemperatureData
static jobject read_sample_native(JNIEnv *env, jobject clazz, jlong ptr)
{
tricky_device_t* dev = (tricky_device_t*)ptr;
int ret = 0;
unsigned short synchro = 0;
short obj_temp = 0;
short ntc1_temp = 0;
short ntc2_temp = 0;
short ntc3_temp = 0;
if (dev == NULL) {
ALOGE("read_sample_native: invalid device pointer");
return (jobject)NULL;
}
ret = dev->read_sample(&synchro, &obj_temp, &ntc1_temp, &ntc2_temp, &ntc3_temp);
if (ret < 0) {
ALOGE("read_sample_native: Cannot read TrickyTemperatureData");
return (jobject)NULL;
}
// ищем тип, который мы определили как
// android.hardware.temperature.TrickyTemperatureData
jclass c = env->FindClass("android/hardware/temperature/TrickyTemperatureData");
if (c == 0) {
ALOGE("read_sample_native: Find Class TrickyTemperatureData Failed");
return (jobject)NULL;
}
// находим конструктор (без аргументов)
jmethodID cnstrctr = env->GetMethodID(c, "<init>", "()V");
if (cnstrctr == 0) {
ALOGE("read_sample_native: Find constructor TrickyTemperatureData Failed");
return (jobject)NULL;
}
// получаем ID полей. Да, полей, долго уже пишу, нет сил на getter`ы и setter`ы
jfieldID synchroField = env->GetFieldID(c, "synchro", "I");
jfieldID objTempField = env->GetFieldID(c, "objectTemperature", "I");
jfieldID ntc1TempField = env->GetFieldID(c, "ntc1Temperature", "I");
jfieldID ntc2TempField = env->GetFieldID(c, "ntc2Temperature", "I");
jfieldID ntc3TempField = env->GetFieldID(c, "ntc3Temperature", "I");
if (synchroField == 0 || objTempField == 0 ||
ntc1TempField == 0 || ntc2TempField == 0 || ntc3TempField == 0) {
ALOGE("read_sample_native: cannot get fields of resulting object");
return (jobject)NULL;
}
// создаем объект и наполняем прочитанными данными
jobject jdtsData = env->NewObject(c, cnstrctr);
env->SetIntField(jdtsData, synchroField, (jint)synchro);
env->SetIntField(jdtsData, objTempField, (jint)obj_temp);
env->SetIntField(jdtsData, ntc1TempField, (jint)ntc1_temp);
env->SetIntField(jdtsData, ntc2TempField, (jint)ntc2_temp);
env->SetIntField(jdtsData, ntc3TempField, (jint)ntc3_temp);
ALOGD("read_sample_native: read ok");
return jdtsData;
}
// еще немного аналогичной писанины
// объявляем таблицу методов для упрощения их поиска в терминах JNI
static JNINativeMethod method_table[] = {
{ "init_native", "()J", (void*)init_native },
{ "finalize_native", "(J)V", (void*)finalize_native },
{ "read_sample_native", "(J)Landroid/hardware/temperature/TrickyTemperatureData;", (void*)read_sample_native },
{ "activate_native", "(JZ)Z", (void*)activate_native },
{ "set_mode_native", "(JZ)Z", (void*)set_mode_native},
};
// И вот эта функция будет вызвана при загрузке системы из onload.cpp,
// который вызывается при запуске system server службы
int register_android_server_JdtsService(JNIEnv *env)
{
ALOGD("register_android_server_JdtsService");
return jniRegisterNativeMethods(
env,
"com/android/server/temperature/JdtsService",
method_table,
NELEM(method_table));
};
};
// ...
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
namespace android {
// ...
int register_android_server_JdtsService(JNIEnv* env);
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
// ...
register_android_server_JdtsService(env);
return JNI_VERSION_1_4;
}
package android.hardware.temperature;
parcelable TrickyTemperatureData;
package android.hardware.temperature;
import android.os.Parcel;
import android.os.Parcelable;
/** {@hide} */
public final class TrickyTemperatureData implements Parcelable {
public int synchro;
public int objectTemperature;
public int ntc1Temperature;
public int ntc2Temperature;
public int ntc3Temperature;
public static final Parcelable.Creator<TrickyTemperatureData> CREATOR = new Parcelable.Creator<TrickyTemperatureData>() {
public TrickyTemperatureData createFromParcel(Parcel in) {
return new TrickyTemperatureData(in);
}
public TrickyTemperatureData[] newArray(int size) {
return new TrickyTemperatureData[size];
}
};
public TrickyTemperatureData() {
}
private TrickyTemperatureData(Parcel in) {
readFromParcel(in);
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(synchro);
out.writeInt(objectTemperature);
out.writeInt(ntc1Temperature);
out.writeInt(ntc2Temperature);
out.writeInt(ntc3Temperature);
}
public void readFromParcel(Parcel in) {
synchro = in.readInt();
objectTemperature = in.readInt();
ntc1Temperature = in.readInt();
ntc2Temperature = in.readInt();
ntc3Temperature = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
}
// frameworks\base\core\java\android\content\Context.java
/**
* @hide
*/
public static final String TRICKY_SERVICE = "android.service.tricky.ITrickyService";
// frameworks\base\services\java\com\android\server\SystemServer.java
// initAndLoop ...
try {
Slog.e(TAG, "Tricky Service");
trickyService = new TrickyService(context);
ServiceManager.addService(Context.TRICKY_SERVICE, trickyService);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting TrickyService", e);
}
//frameworks\base\core\java\android\app\ContextImpl.java
registerService(TRICKY_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.TRICKY_SERVICE);
return new TrickyManager(ITrickyService.Stub.asInterface(iBinder));
}});
private static int sNextPerContextServiceCacheIndex = 0;
// т.е. просто добавляем fetcher с севисом в общий map
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
// Далее, всем известная getSystemService просто ищет сервис по имени
// ...
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
#(device/asus/grouper/init.grouper.rc)
# ...
on post-fs-data
# ...
# tricky temperature sensor
# разрешим чтение/запись и поставим владельцем пользователя system
# так как с root`ом иметь дело не представляется возможным
chmod 0660 /sys/class/tricky/tricky_temperature/dev
chown system system /sys/class/tricky/tricky_temperature/dev
# device/asus/grouper/ueventd.grouper.rc
/dev/tricky_temperature 0660 system system
#####################################
# Сначала надо сделать свой домен, в пределах которого будет работать наш сервис
# и будут написаны все правила о доступе и связе кого либо с кем либо.
# Сначала опишем, что такое этот наш домен.
# (te_macros)
# tricky_service_domain(domain)
# Allow a base set of permissions common across Android daemons.
define(`tricky_service_domain', `
init_daemon_domain($1)
# Allow using binder and performing IPC to system services.
binder_use($1)
binder_service($1)
# Allow access to files in /proc.
# Fixes denials like:
# avc: denied { read } for pid=1267 comm="peripheralman" name="misc" dev="proc"
# ino=4026531967 scontext=u:r:peripheralman:s0
# tcontext=u:object_r:proc:s0 tclass=file permissive=0
allow $1 proc:file r_file_perms;
allow $1 tricky_service:service_manager find;
# Cut down on spam.
dontaudit $1 kernel:system module_request;
')
#####################################
# Далее создадим этот домен и исполняемый тип для него, так как у нас не пассивный файлик,
# а исполняемый код
# (tricky_service.te)
type tricky_service, domain;
type tricky_service_exec, exec_type, file_type;
tricky_service_domain(tricky_service)
#####################################
# Укажем, что наш сервис относится к system manager
# (service.te)
type tricky_service, service_manager_type;
# Далее уже конкретно указываем, на какое имя в системе указывает наш абстрактный тип
# "tricky_service" и его правила.
# Подробнее о синтаксисе SELinux можно почитать тут https://source.android.com/security/selinux/
#####################################
# (service_contexts)
android.service.jdtstemperature.IJdstsService u:object_r:tricky_service:s0
#####################################
# С сервисом всё. Далее надо написать правила для драйвера и что до него могут доступаться
# системные сервисы и приложения. Сначала создадим тип для драйвера
# (device.te)
type tricky_device, dev_type, mlstrustedobject;
#####################################
# Свяжем этот тип с файлом нашего драйвера
# (file_contexts)
/dev/tricky_temperature u:object_r:tricky_device:s0
#####################################
# Разрешим системе при загрузке обращаться к нашему устройству (это нужно, как я понял,
# потому что при загрузке сервисы стартуют в другом домене "bootanim" и требуют прав на
# использование файлов)
#(bootanim.te)
allow bootanim tricky_device:chr_file rw_file_perms;
#####################################
# Наконец, укажем, что доступ к нашему устройству разрешен для SystemServer и system apps
# (system_server.te)
allow system_server tricky_device:chr_file rw_file_perms;
#####################################
# (system_app.te)
allow system_app tricky_device:chr_file rw_file_perms;
package com.android.trickydemo;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import android.hardware.temperature.*;
public class MainActivity extends Activity {
private final String TAG = "TrickyDemo";
private final int POLLING_PERIOD_MS = 200;
private TrickyManager mServiceManager = null;
private TrickyTemperatureData mSensorData = null;
private GaugeView mGaugeObj;
private GaugeView mGaugeNtc1;
private GaugeView mGaugeNtc2;
private GaugeView mGaugeNtc3;
private TextView mTextSynchro;
private ImageView mIrqImage;
private TextView mTextObj;
private TextView mTextNtc1;
private TextView mTextNtc2;
private TextView mTextNtc3;
// all temperatures are .2 points precision values in degrees Celsius
final private Object mDataSync = new Object();
private boolean mMeasModeUpdateRequired;
// set up when user switches between measurement modes and queues I2C expander command
// to switch the mode
private boolean mIsContinuousMode;
// continuous mode (power is always on, no control)
// burst mode (every cycle power on, read and power off required)
private boolean mPowerState;
private boolean mPowerUpdateRequired;
private Thread mCommThread = null;
private boolean mIsRunning = true; // the communication thread goes on unless onDestroy method is called
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// enforce the sensor to switch into continuous mode on startup
mPowerUpdateRequired = true;
mPowerState = true;
mMeasModeUpdateRequired = true;
mIsContinuousMode = true;
mIrqImage = (ImageView) findViewById(R.id.image_led_irq);
mGaugeObj = (GaugeView) findViewById(R.id.gauge_view_obj);
mGaugeNtc1 = (GaugeView) findViewById(R.id.gauge_view_ntc1);
mGaugeNtc2 = (GaugeView) findViewById(R.id.gauge_view_ntc2);
mGaugeNtc3 = (GaugeView) findViewById(R.id.gauge_view_ntc3);
mTextSynchro = (TextView) findViewById(R.id.text_synchro);
mTextObj = (TextView) findViewById(R.id.text_obj);
mTextNtc1 = (TextView) findViewById(R.id.text_ntc1);
mTextNtc2 = (TextView) findViewById(R.id.text_ntc2);
mTextNtc3 = (TextView) findViewById(R.id.text_ntc3);
Switch switch_mode = (Switch) findViewById(R.id.switch1);
switch_mode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
synchronized (mDataSync) {
mIsContinuousMode = isChecked;
mMeasModeUpdateRequired = true;
}
}
});
Switch switch_power = (Switch) findViewById(R.id.switch_power);
switch_power.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
synchronized (mDataSync) {
mPowerState = isChecked;
mPowerUpdateRequired = true;
}
}
});
switch_power.setChecked(true); // power is on by default
mServiceManager = (TrickyManager) getSystemService(TRICKY_SERVICE);
mCommThread = new Thread() {
@Override
public void run() {
while(mIsRunning) {
synchronized (mDataSync) {
if (mPowerUpdateRequired) {
if (mServiceManager.activate(mPowerState)) {
mPowerUpdateRequired = false;
} else {
Log.w(TAG, "Cannot update power state");
}
}
if (mMeasModeUpdateRequired) {
if (mServiceManager.setMode(mIsContinuousMode)) {
mMeasModeUpdateRequired = false;
} else {
Log.w(TAG, "Cannot update measurement mode");
}
}
}
mSensorData = mServiceManager.readSample();
if (mSensorData != null) {
updateUI();
} else {
updateNonIRQUI();
}
try {
Thread.sleep(POLLING_PERIOD_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
mCommThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
e.printStackTrace();
}
});
mCommThread.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
mCommThread.join(POLLING_PERIOD_MS * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void updateUI() {
runOnUiThread(new Runnable() {
public void run() {
float obj_temp = mSensorData.objectTemperature / 100.F;
float ntc1_temp = mSensorData.ntc1Temperature / 100.F;
float ntc2_temp = mSensorData.ntc2Temperature / 100.F;
float ntc3_temp = mSensorData.ntc3Temperature / 100.F;
String s_obj = String.format("%.2f °C", obj_temp);
String s_ntc1 = String.format("%.2f °C", ntc1_temp);
String s_ntc2 = String.format("%.2f °C", ntc2_temp);
String s_ntc3 = String.format("%.2f °C", ntc3_temp);
String s_synchro = String.format("Synchro = %d", mSensorData.synchro);
mGaugeObj.setTargetValue(obj_temp);
mTextObj.setText(s_obj);
mGaugeNtc1.setTargetValue(ntc1_temp);
mTextNtc1.setText(s_ntc1);
mGaugeNtc2.setTargetValue(ntc2_temp);
mTextNtc2.setText(s_ntc2);
mGaugeNtc3.setTargetValue(ntc3_temp);
mTextNtc3.setText(s_ntc3);
mTextSynchro.setText(s_synchro);
mIrqImage.setImageDrawable(getResources().getDrawable(R.drawable.led_green_hi));
Log.d(TAG,
s_synchro
+ "Obj = " + s_obj
+ " NTC1 = " + s_ntc1
+ " NTC2 = " + s_ntc2
+ " NTC3 = " + s_ntc3);
}
});
}
private void updateNonIRQUI() {
runOnUiThread(new Runnable() {
public void run() {
mIrqImage.setImageDrawable(getResources().getDrawable(R.drawable.led_green_md));
}
});
}
}
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_FILES := $(addprefix $(LOCAL_PATH)/, res)
LOCAL_PACKAGE_NAME := TrickyDemo
LOCAL_CERTIFICATE := platform
LOCAL_STATIC_JAVA_LIBRARIES := android-support-core-utils-api24
include $(BUILD_PACKAGE)
git clone https://android.googlesource.com/kernel/tegra.git -b android-tegra3-grouper-3.1-kitkat-mr2
mkdir arm-eabi-4.6
cd arm-eabi-4.6
git init
git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/
ARCH=arm SUBARCH=arm CROSS_COMPILE=<path_to_arm_eabi-4.6>/arm-eabi-4.6/bin/arm-eabi- make tegra3_android_defconfig
ARCH=arm SUBARCH=arm CROSS_COMPILE=<path_to_arm_eabi-4.6>/arm-eabi-4.6/bin/arm-eabi- make menuconfig
ARCH=arm SUBARCH=arm CROSS_COMPILE=<path_to_arm_eabi-4.6>/arm-eabi-4.6/bin/arm-eabi- make -j4 zImage
repo init -u https://android.googlesource.com/platform/manifest -b android-4.4.4_r2
cd .repo
repo sync
https://dl.google.com/dl/android/aosp/asus-grouper-ktu84p-b12ce5f7.tgz
https://dl.google.com/dl/android/aosp/broadcom-grouper-ktu84p-646d5a68.tgz
https://dl.google.com/dl/android/aosp/elan-grouper-ktu84p-742223b3.tgz
https://dl.google.com/dl/android/aosp/invensense-grouper-ktu84p-724c855a.tgz
https://dl.google.com/dl/android/aosp/nvidia-grouper-ktu84p-e6d581dc.tgz
https://dl.google.com/dl/android/aosp/nxp-grouper-ktu84p-27abae08.tgz
https://dl.google.com/dl/android/aosp/widevine-grouper-ktu84p-57b01f77.tgz
tar -xvf asus-grouper-ktu84p-b12ce5f7.tgz
tar -xvf broadcom-grouper-ktu84p-646d5a68.tgz
tar -xvf elan-grouper-ktu84p-742223b3.tgz
tar -xvf invensense-grouper-ktu84p-724c855a.tgz
tar -xvf nvidia-grouper-ktu84p-e6d581dc.tgz
tar -xvf nxp-grouper-ktu84p-27abae08.tgz
tar -xvf widevine-grouper-ktu84p-57b01f77.tgz
rm *.tgz
./extract-asus-grouper.sh
./extract-broadcom-grouper.sh
./extract-elan-grouper.sh
./extract-invensense-grouper.sh
./extract-nvidia-grouper.sh
./extract-nxp-grouper.sh
./extract-widevine-grouper.sh
mkdir nexus
cd nexus
make clobber (очистит всё что было до этого)
. build/envsetup.sh
lunch aosp_grouper-userdebug
make -j4
make clobber
require board=grouper
require version-bootloader=4.23
sudo apt-get install abootimg
abootimg --create boot.img -k zImage -r ramdisk.img
adb reboot bootloader (если планшет включен, подключен по usb и авторизован adb)
если выключен, то включить, удерживая кнопки питания и громкость.
fastboot devices
fastboot oem unlock
fastboot erase boot
fastboot erase cache
fastboot erase recovery
fastboot erase system
fastboot erase userdata
fastboot -2 update image.zip
К сожалению, не доступен сервер mySQL