📜 ⬆️ ⬇️

How to cram your sensor in Android OS


Once, programmers sat and wrote another temperature sensor and programs with buttons. And suddenly it turned out that this sensor wants one small phone manufacturer in a future model. Thus, the task was formed to support the I2C / GPIO sensor at the Android OS level, since the sensor promises to be an integral part of the phone itself.

Being a deep subcontract, there was no hope for a quick and regular response from the final customer, we decided to practice on cats and shove our piece of iron into some affordable Android device.

The task did not look very difficult and meant finding a tablet, a scheme for it, soldering where necessary, without breaking anything, and writing some code that would have a beneficial effect on the presence of our sensor in the final operating system.

We look in order that there is:
')
  1. Introduction
  2. How to connect to a real device like a tablet
  3. How to get to the debug UART in the audio output and find that it does not work
  4. How to write a simple kernel driver with I2C, GPIO, interrupts and background tasks
  5. How to make an abstraction of your piece of iron in Android middleware or use an existing one
  6. How to add your system service and where to add it so that it turns on and is found
  7. How to break through SEAndroid / SELinux by adding your own rules
  8. How to check - we will write simple
  9. How to collect all this
  10. How to understand that in the previous paragraphs something is wrong

Introduction

The matter, as usual, developed in such a way that it was necessary to prove the consistency and feasibility of the task as early as possible, therefore several parts of the work were formed, not all of which will be described, but I will give them for the integrity of the impression.

In search of a tablet, the choice was on the Nexus 7 for a number of reasons: he was with a friend and was not needed because he was beaten up by a moth enough (the mole broke the touch screen and had to use the mouse), but it’s still Nexus, which means there was more information and source on Google sites. Since it was scary to start working on the tablet right away, the Raspberry Pi3 was the first to get into the mix. Most of the debugging occurred on it. Further, the story will not commemorate the Raspberry Pi 3, but you can keep in mind that most of the software problems were solved on it.

How to connect to a real device like a tablet

Connecting to such a sophisticated device as a tablet without a scheme is dementia and bravery is a thankless task. Therefore, at first there was a scheme. Generally speaking, electrical circuits of modern phones and tablets are not the most open thing, but if you try and not be afraid of the abundance of Chinese in the browser window, you can find something. We found it pretty quickly around here . Further sketches using circuitry are clippings from this schematic document.

In theory, the tablet should have quite a lot of I2C tires and orders of magnitude more than any GPIO, you just need to find the right ones, solder and be drawn to the right level. Fortunately, in the Nexus 7 tablets there is no rear camera, which just uses the I2C for control and two pins (power and reset). And we need I2C and 2 GPIO (one for on / off hibernation, and the second for interrupting at the expense of a new temperature measurement).

The correlation of real viscera and the scheme showed that everything is not as simple as in the name of the tablet. Namely, There are at least three versions of the Nexus 7:

  1. The 2013 version does not fit the scheme we found, because it has other processors and a bunch of different small details
  2. Version 2012 .1 has a decoupled seat for the rear camera and everything in it is good
  3. The 2012 version .2 has no decoupled space and it is much more difficult to solder there.

We had a tablet in 2012, where there was no ready-made connector, and in addition a broken touch and a mouse in the kit, which sometimes delivered a lot. In the end, after some dancing with a tambourine around the bush, it was decided to buy another one with the same soldered connector. New Nexus 7 is long gone, so we searched on the "bazaars", which allowed us to look under the cover and select the desired one with a wired space under the camera.

We found the number of the correct I2C bus by a simple search using a simple program using NDK. To do this, I had to install Android and chmod with witchcraft through adb to release all I2C tires. In the process of busting, I had to play a little with the addresses on the bus, since some of them were already reserved and we received an attempt to communicate. In the end, it turned out that there was no one else on the target bus.

Lyric
An interesting detail was the fact that not all versions of Android can be equally useful after rutting: in our case, the latest official version was Android 5.1.1. After its installation and rutting, everything seemed to be without problems, only our program still had no access to the / dev folder. Forcible change of access rights using adb shell and chmod did not give effect. After some deliberation, we decided to roll back to Android 4.4.4. Repeating the same rutting process immediately gave the program access to / dev. It can also be noted that in adb shell the / dev folder on version 4.4.4 was readable without going to super user, while Android 5.1.1 does not. Most likely, the reason lies in fairly large changes in OS security when moving from Android 4 to Android 5 and beyond (perhaps this is the third point of the link ).

And what about GPIO?

On the very first page of our scheme in the general overview, it is clear that there is a camera called “Rear Camera module OV5650”.



It is also written there that it is directly connected to the tegra T30L (i.e., the main SoC). Nearby there are lines I2C_CAM_ ... Let's look ...



On page 9 is what we need. Almost the entire page is devoted to the front and rear cameras. There is also a mention that the camera has two pins CAM_RST_5M and PWDN_5M, which go to the SoC for GPIO_PBB0 and GPIO_PBB5, respectively. It seems - this is what we need. Just find how to solder there, so we continue to look ...



Well that's all. On this page, the description of the FFC connector, where the camera is turned on, including the required pins. On our original tablet connector is not soldered. But later we will find another tablet with a connector, so as not to suffer.

Further, the track of the found pins will be resumed already in the platform code and it is written about this in the part about the driver ...

How to get to the debug UART in the audio output and find that it does not work

When you write drivers and any low-level software under Linux (highly), it is desirable to see the kernel / system boot log, since our driver is also loaded there. And as soon as something goes wrong, everything stops and why it is not known.

Therefore, having smoked the Internet, we found out that the Nexus devices have a debugging UART output via the audio connector. And it works of the type itself without any program settings in the following way:





To celebrate, a USB-UART -> Audio adapter was made. We stuck it, turned it on in the Ubuntu minicom console, loaded the tablet and ... nothing. At all. That is, quite. Further field searches showed only that one way or another, debug uart did not turn on, since the lines of the left and right channels did not reach the zero voltage level of RX / TX. Also tried a lot of commands from fastboot, but nothing helped. The only thing that calmed us at the end of this venture was only the information that another person tried different Nexus `s, and on everyone except the exact same UART tablet it started, but on ours - not. But it was interesting.

An even more fundamental way was to connect the debug interface of the processor, but they didn’t go in this direction, although the layout of the device showed such a possibility.

As a result, our salvation was the preliminary use of the Raspberry Pi for the Android receptacle. There, the debug port worked, it allowed to catch all the errors, and then on Nexus it was clear what to change if the kernel does not boot. Statistics showed that the most time delay was due to the unsoldered pins of the GPIO, as well as the undocumented tegra3 features in terms of permission to work with the GPIO.

By the way, for debugging it is interesting to see the full log of the download, you can get it using adb bugreport.

How to write a simple kernel driver with I2C, GPIO, interrupts and background tasks

So, it was necessary to write the kernel driver, which will drive the device through I2C and GPIO, and also light up in the / dev folder with some original name, so that later the Android middleware can access this file / driver and read or write something.

Some common features when writing a driver:


But first about the prerequisites for loading the driver. those. about platform initialization code.

Nexus 7 2012 is built on a Tegra3 processor. The kernel used on it is not new (3.1.h.h) and without device tree. And this means that all the hardware is described by the C code and is located in / kernel / tegra / arch / arm / mach-tegra /

The board-grouper-pinmux.c file describes the iron configurations of all SoC pins, and also contains general functions for their initialization in the closed part of the kernel from nVidia (all functions beginning with the word "tegra" are implemented in the closed part of the kernel, which is supplied in binary form ). Let's see what we need to change there.

board-grouper-pinmux.c
// ... //      //   ,         , //    ,       /* 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; } // ... 


The board-grouper-sensors.c file contains the registration of all sorts of different devices and the most general level of function for them (for example, power management). Here we need to add a structure to register our device with a driver that will be loaded as part of the kernel. Something like this:

board-grouper-sensors.c
 // ... //           //      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 (  ) 


Driver part code with comments
 #include <linux/init.h> // Macros used to mark up functions eg __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); 


Separately, it is necessary to mention the files for the assembly: KConfig and Makefile.

In KConfig, we add the following paragraph, which by the name TRICKY_SENSOR (without the CONFIG_ prefix) created in the Makefile, will take it into account when building. Also, our driver will be visible when using make menuconfig.

Kconfig
 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" 


Makefile
 obj-$(CONFIG_THERMAL) += thermal_sys.o obj-$(CONFIG_TRICKY_SENSOR) += tricky_temperature.o 


As a result, we get the following files for the kernel:

 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 

How to make an abstraction of your piece of iron in Android middleware or use an existing one

Now move to a higher level. The driver is written and now we are moving to the user space part of Android, where you need to somehow attach to the driver.

In order to work with many implementations of the same type of peripherals in Android, there is a middleware layer (written in C / C ++) that contains various interfaces of iron abstractions (Hardware Abstraction Level - HAL). And for all temperature magnetic, etc. sensors there is a place. But the limitation of this HAL is that its API implies only reading - which is reasonable in view of the many user programs that can simultaneously access these devices. And if one changes the settings for themselves, then for the other it will be a setup. All this is very well described here .

And specifically regarding the read-only mode of working with sensors, this quote from the link above:

It doesn’t have sampling frequency parameters. For example, you can imagine the physical sensor and the low power modes. It can only be used for an Android device; there would be no This is not an option.

There is no mechanism for data transmission. This can not be achieved.

Well, we really want to control our device (power switch and measurement mode, for example) and since our sensor is not officially documented and only our program will work with it, we will write our HAL. Here the basic things are written in accessible English, so that it will be clear later what the data structures are and why.

Let's create the module of piece of iron. To do this, you need to come up with an ID and make it a structure containing hw_device_t with the description of the module, and our derived functions. Google does not specify how exactly the implementation and interfaces should look at this level, so without looking at the big brother, you can begin to sow good.

sensor_tricky_temperature.h
 #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 


sensor_tricky_temperature.c
 #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, }; 


To build the module, you need an Android.mk file, where it says:

Android.mk
 #     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) 


And another Android.mk file is one level higher to enable the compiled library in libhardware. Add by the name of the module ID.

Android.mk
 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)) 


At the output of HAL, we have the following files

 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 

How to add your system service and where to add it so that it turns on and is found

Further it is necessary that someone called our HAL. In other parts of OC, such things are done using system services and their managers, which are written in Java. In order not to stand out from the series, we will write another one. Our service will participate in the following files:

 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 

As can be seen from the sources, we have not yet figured out the level of native, and you need to connect to the HAL module through JNI. At the same time, let's write down our reference type, which will need to be determined via AIDL , and then thrown from C ++ to Java.

The native code of the service
 //   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)); }; }; 


Next, all the JNI parts of the services that need it are loaded into onload.cpp. Including ours.

onload.cpp
 // ... #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; } 


Traditional Android.mk contains information for assembling all the parts, and our JNI piece is also there.

Our reference type must be created using AIDL, since this language is a means of interprocess data transfer to Android, and not only in it. Also, in order to send it, it must be Parcelable, as shown in the listing below:

TrickyTemperatureData.aidl
 package android.hardware.temperature; parcelable TrickyTemperatureData; 


TrickyTemperatureData.java
 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; } } 


, , , .

, context.getSytemService. , hide , , , API, .

 // frameworks\base\core\java\android\content\Context.java /** * @hide */ public static final String TRICKY_SERVICE = "android.service.tricky.ITrickyService"; 

, ServiceManager SystemServer .

 // 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); } 

, context ( ).

 //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)); }}); 

registerService Android 4.4.4 :

 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); } 

SEAndroid/SELinux

, , root, , , - , , . sepolicy.

- init, .

 #(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 

ueventd.

 # device/asus/grouper/ueventd.grouper.rc /dev/tricky_temperature 0660 system system 

… SELinux , ( ), , , . , , Brillo . , , :

 ##################################### #     ,        #              . #  ,     . # (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; 


Here we created a new domain for our service, defined our device and showed that our service has read and write rights to our driver. Of course, it was not the first time to write this. After all this, the boot system finally got rid of messages about the blocked service, and in adb shell it became clear that the driver was recorded in the user name system and open to the world.

How to check - we will write simple

- , . , , adb shell logcat, - , OC . , . , . packages/apps/TrickyDemo, build/target/product/core.mk .

internal application
 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)); } }); } } 


Android Studio ( sdk — 4.4.4), . Android.mk, .

 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) 

- , out/target/common/obj/SHARED_LIBRARIES .



. , :

HW: Nexus 7 (2012 grouper)
OS: Android Kitkat 4.4.4 KTU84P
Kernel: tegra3_android_defconfig 3.1.10-gle42d16

.
:

 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 

make menuconfig device drivers Tricky temperature sensor ( menuconfig — ). . kernel/tegra/arch/arm/boot/zImage.

Android. Android OS , , ( ). , Ubuntu 14.04 LTS x64 ( Windows , ).

, . , , Java ( Android 7 OpenJDK Java 8, Nexus 7 Android 4.x Oracle Java 6).

Android .

Repo — Git, git- ( ). Repo :

 repo init -u https://android.googlesource.com/platform/manifest -b android-4.4.4_r2 cd .repo repo sync 

, 50.

( Android OS) 4.4.4 KTU84P Nexus`:

 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 

out/target/product/grouper (system.img, recovery.img, ramdisk.img, userdata.img). Apk- out/target/product/grouper/obj/APPS/Jdts160demo_intermediates/package.apk.

fastboot. .zip- , FLASH ( boot.img, system.img, recovery.img, userdata.img, ramdisk.img), android-info.txt :

 require board=grouper require version-bootloader=4.23 

, Factory image flash_all.sh . .

boot.img abootimg:

 sudo apt-get install abootimg abootimg --create boot.img -k zImage -r ramdisk.img 

abootimg , .. , , .

fastboot. Options:

adb reboot bootloader ( , usb adb)
, , .

fastboot (usb usb debugging ).

 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 

, -

, , . . :

Source: https://habr.com/ru/post/326640/


All Articles