1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "vehicle_hw_default"
18 #define LOG_NDEBUG 1
19 #define RADIO_PRESET_NUM 6
20 
21 #define UNUSED __attribute__((__unused__))
22 
23 #include <errno.h>
24 #include <inttypes.h>
25 #include <malloc.h>
26 #include <pthread.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/prctl.h>
31 #include <sys/time.h>
32 #include <time.h>
33 
34 #include <cutils/log.h>
35 #include <system/radio.h>
36 #include <hardware/hardware.h>
37 #include <hardware/vehicle.h>
38 
39 extern int64_t elapsedRealtimeNano();
40 
41 static char VEHICLE_MAKE[] = "android_car";
42 
43 typedef struct vehicle_device_impl {
44     vehicle_hw_device_t vehicle_device;
45     uint32_t initialized_;
46     vehicle_event_callback_fn event_fn_;
47     vehicle_error_callback_fn error_fn_;
48 } vehicle_device_impl_t ;
49 
50 static pthread_mutex_t lock_;
51 
52 typedef struct subscription {
53     // Each subscription has it's own thread.
54     pthread_t thread_id;
55     int32_t prop;
56     float sample_rate;
57     pthread_mutex_t lock;
58     // This field should be protected by the above mutex.
59     // TODO change this to something better as flag alone takes long time to finish.
60     uint32_t stop_thread;
61     vehicle_device_impl_t* impl;
62     pthread_t thread;
63     pthread_cond_t cond;
64     char name[100];
65 } subscription_t;
66 
67 static vehicle_prop_config_t CONFIGS[] = {
68     {
69         .prop = VEHICLE_PROPERTY_INFO_MAKE,
70         .access = VEHICLE_PROP_ACCESS_READ,
71         .change_mode = VEHICLE_PROP_CHANGE_MODE_STATIC,
72         .value_type = VEHICLE_VALUE_TYPE_STRING,
73         .min_sample_rate = 0,
74         .max_sample_rate = 0,
75         .hal_data = NULL,
76     },
77     {
78         .prop = VEHICLE_PROPERTY_GEAR_SELECTION,
79         .access = VEHICLE_PROP_ACCESS_READ,
80         .change_mode = VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
81         .value_type = VEHICLE_VALUE_TYPE_INT32,
82         .min_sample_rate = 0,
83         .max_sample_rate = 0,
84         .hal_data = NULL,
85     },
86     {
87         .prop = VEHICLE_PROPERTY_DRIVING_STATUS,
88         .access = VEHICLE_PROP_ACCESS_READ,
89         .change_mode = VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
90         .value_type = VEHICLE_VALUE_TYPE_INT32,
91         .min_sample_rate = 0,
92         .max_sample_rate = 0,
93         .hal_data = NULL,
94     },
95     {
96         .prop = VEHICLE_PROPERTY_PARKING_BRAKE_ON,
97         .access = VEHICLE_PROP_ACCESS_READ,
98         .change_mode = VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
99         .value_type = VEHICLE_VALUE_TYPE_BOOLEAN,
100         .min_sample_rate = 0,
101         .max_sample_rate = 0,
102         .hal_data = NULL,
103     },
104     {
105         .prop = VEHICLE_PROPERTY_PERF_VEHICLE_SPEED,
106         .access = VEHICLE_PROP_ACCESS_READ,
107         .change_mode = VEHICLE_PROP_CHANGE_MODE_CONTINUOUS,
108         .value_type = VEHICLE_VALUE_TYPE_FLOAT,
109         .min_sample_rate = 0.1,
110         .max_sample_rate = 10.0,
111         .hal_data = NULL,
112     },
113     {
114         .prop = VEHICLE_PROPERTY_RADIO_PRESET,
115         .access = VEHICLE_PROP_ACCESS_READ_WRITE,
116         .change_mode = VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
117         .value_type = VEHICLE_VALUE_TYPE_INT32_VEC4,
118         .vehicle_radio_num_presets = RADIO_PRESET_NUM,
119         .min_sample_rate = 0,
120         .max_sample_rate = 0,
121         .hal_data = NULL,
122     },
123 };
124 
find_config(int prop)125 vehicle_prop_config_t* find_config(int prop) {
126     unsigned int i;
127     for (i = 0; i < sizeof(CONFIGS) / sizeof(vehicle_prop_config_t); i++) {
128         if (CONFIGS[i].prop == prop) {
129             return &CONFIGS[i];
130         }
131     }
132     return NULL;
133 }
134 
alloc_vehicle_str_from_cstr(const char * string,vehicle_str_t * vehicle_str)135 static int alloc_vehicle_str_from_cstr(const char* string, vehicle_str_t* vehicle_str) {
136     int len = strlen(string);
137     vehicle_str->data = (uint8_t*) malloc(len);
138     if (vehicle_str->data == NULL) {
139         return -ENOMEM;
140     }
141     memcpy(vehicle_str->data, string, len);
142     vehicle_str->len = len;
143     return 0;
144 }
145 
vdev_list_properties(vehicle_hw_device_t * device UNUSED,int * num_properties)146 static vehicle_prop_config_t const * vdev_list_properties(vehicle_hw_device_t* device UNUSED,
147         int* num_properties) {
148     ALOGD("vdev_list_properties.");
149 
150     *num_properties = sizeof(CONFIGS) / sizeof(vehicle_prop_config_t);
151     return CONFIGS;
152 }
153 
vdev_init(vehicle_hw_device_t * device,vehicle_event_callback_fn event_callback_fn,vehicle_error_callback_fn error_callback_fn)154 static int vdev_init(vehicle_hw_device_t* device,
155                      vehicle_event_callback_fn event_callback_fn,
156                      vehicle_error_callback_fn error_callback_fn) {
157     ALOGD("vdev_init.");
158     vehicle_device_impl_t* impl = (vehicle_device_impl_t*)device;
159     pthread_mutex_lock(&lock_);
160     if (impl->initialized_) {
161         ALOGE("vdev_init: Callback and Error functions are already existing.");
162         pthread_mutex_unlock(&lock_);
163         return -EEXIST;
164     }
165 
166     impl->initialized_ = 1;
167     impl->event_fn_ = event_callback_fn;
168     impl->error_fn_ = error_callback_fn;
169     pthread_mutex_unlock(&lock_);
170     return 0;
171 }
172 
vdev_release(vehicle_hw_device_t * device)173 static int vdev_release(vehicle_hw_device_t* device) {
174     vehicle_device_impl_t* impl = (vehicle_device_impl_t*)device;
175     pthread_mutex_lock(&lock_);
176     if (!impl->initialized_) {
177         ALOGD("vdev_release: Already released before, returning early.");
178     } else {
179         // unsubscribe_all()
180         impl->initialized_ = 0;
181     }
182     pthread_mutex_unlock(&lock_);
183     return 0;
184 }
185 
vdev_get(vehicle_hw_device_t * device UNUSED,vehicle_prop_value_t * data)186 static int vdev_get(vehicle_hw_device_t* device UNUSED, vehicle_prop_value_t* data) {
187     ALOGD("vdev_get.");
188     //TODO all data supporting read should support get
189     if (!data) {
190         ALOGE("vdev_get: Data cannot be null.");
191         return -EINVAL;
192     }
193     vehicle_prop_config_t* config = find_config(data->prop);
194     if (config == NULL) {
195         ALOGE("vdev_get: cannot find config 0x%x", data->prop);
196         return -EINVAL;
197     }
198     data->value_type = config->value_type;
199     // for STATIC type, time can be just 0 instead
200     data->timestamp = elapsedRealtimeNano();
201     int r;
202     switch (data->prop) {
203         case VEHICLE_PROPERTY_INFO_MAKE:
204             r = alloc_vehicle_str_from_cstr(VEHICLE_MAKE, &(data->value.str_value));
205             if (r != 0) {
206                 ALOGE("vdev_get: alloc failed");
207                 return r;
208             }
209             break;
210 
211         case VEHICLE_PROPERTY_RADIO_PRESET: {
212               int radio_preset = data->value.int32_array[0];
213               if (radio_preset < VEHICLE_RADIO_PRESET_MIN_VALUE ||
214                   radio_preset >= RADIO_PRESET_NUM) {
215                   ALOGE("%s Invalid radio preset: %d\n", __func__, radio_preset);
216                   return -1;
217               }
218               ALOGD("%s Radio Preset number: %d", __func__, radio_preset);
219               int32_t selector = radio_preset % 2 == 0;
220               // Populate the channel and subchannel to be some variation of the
221               // preset number for mocking.
222 
223               // Restore the preset number.
224               data->value.int32_array[0] = radio_preset;
225               // Channel type values taken from
226               // system/core/include/system/radio.h
227               data->value.int32_array[1] = selector ? RADIO_BAND_FM : RADIO_BAND_AM;
228               // For FM set a value in Mhz and for AM set a value in Khz range
229               // (channel).
230               data->value.int32_array[2] = selector ? 99000000 : 100000;
231               // For FM we have a sub-channel and we care about it, for AM pass
232               // a dummy value.
233               data->value.int32_array[3] = selector ? radio_preset : -1;
234               break;
235         }
236 
237         default:
238             // actual implementation will be much complex than this. It should track proper last
239             // state. Here just fill with zero.
240             memset(&(data->value), 0, sizeof(data->value));
241             break;
242     }
243     ALOGI("vdev_get, type 0x%x, time %" PRId64 ", value_type %d", data->prop, data->timestamp,
244             data->value_type);
245     return 0;
246 }
247 
vdev_release_memory_from_get(struct vehicle_hw_device * device UNUSED,vehicle_prop_value_t * data)248 static void vdev_release_memory_from_get(struct vehicle_hw_device* device UNUSED,
249         vehicle_prop_value_t *data) {
250     switch (data->value_type) {
251         case VEHICLE_VALUE_TYPE_STRING:
252         case VEHICLE_VALUE_TYPE_BYTES:
253             free(data->value.str_value.data);
254             data->value.str_value.data = NULL;
255             break;
256         default:
257             ALOGW("release_memory_from_get for property 0x%x which is not string or bytes type 0x%x"
258                     , data->prop, data->value_type);
259             break;
260     }
261 }
262 
vdev_set(vehicle_hw_device_t * device UNUSED,const vehicle_prop_value_t * data)263 static int vdev_set(vehicle_hw_device_t* device UNUSED, const vehicle_prop_value_t* data) {
264     ALOGD("vdev_set.");
265     // Just print what data will be setting here.
266     ALOGD("Setting property %d with value type %d\n", data->prop, data->value_type);
267     vehicle_prop_config_t* config = find_config(data->prop);
268     if (config == NULL) {
269         ALOGE("vdev_set: cannot find config 0x%x", data->prop);
270         return -EINVAL;
271     }
272     if (config->value_type != data->value_type) {
273         ALOGE("vdev_set: type mismatch, passed 0x%x expecting 0x%x", data->value_type,
274                 config->value_type);
275         return -EINVAL;
276     }
277     switch (data->value_type) {
278         case VEHICLE_VALUE_TYPE_FLOAT:
279             ALOGD("Value type: FLOAT\nValue: %f\n", data->value.float_value);
280             break;
281         case VEHICLE_VALUE_TYPE_INT32:
282             ALOGD("Value type: INT32\nValue: %" PRId32 "\n", data->value.int32_value);
283             break;
284         case VEHICLE_VALUE_TYPE_INT64:
285             ALOGD("Value type: INT64\nValue: %" PRId64 "\n", data->value.int64_value);
286             break;
287         case VEHICLE_VALUE_TYPE_BOOLEAN:
288             ALOGD("Value type: BOOLEAN\nValue: %d\n", data->value.boolean_value);
289             break;
290         case VEHICLE_VALUE_TYPE_STRING:
291             ALOGD("Value type: STRING\n Size: %d\n", data->value.str_value.len);
292             // NOTE: We only handle ASCII strings here.
293             // Print the UTF-8 string.
294             char *ascii_out = (char *) malloc ((data->value.str_value.len + 1) * sizeof (char));
295             memcpy(ascii_out, data->value.str_value.data, data->value.str_value.len);
296             ascii_out[data->value.str_value.len] = '\0';
297             ALOGD("Value: %s\n", ascii_out);
298             break;
299         case VEHICLE_VALUE_TYPE_INT32_VEC4:
300             ALOGD("Value type: INT32_VEC4\nValue[0]: %d Value[1] %d Value[2] %d Value[3] %d",
301                   data->value.int32_array[0], data->value.int32_array[1],
302                   data->value.int32_array[2], data->value.int32_array[3]);
303             break;
304         default:
305             ALOGD("Value type not yet handled: %d.\n", data->value_type);
306     }
307     return 0;
308 }
309 
print_subscribe_info(vehicle_device_impl_t * impl UNUSED)310 void print_subscribe_info(vehicle_device_impl_t* impl UNUSED) {
311     unsigned int i;
312     for (i = 0; i < sizeof(CONFIGS) / sizeof(vehicle_prop_config_t); i++) {
313         subscription_t* sub = (subscription_t*)CONFIGS[i].hal_data;
314         if (sub != NULL) {
315             ALOGD("prop: %d rate: %f", sub->prop, sub->sample_rate);
316         }
317     }
318 }
319 
320 // This should be run in a separate thread always.
fake_event_thread(struct subscription * sub)321 void fake_event_thread(struct subscription *sub) {
322     if (!sub) {
323         ALOGE("oops! subscription object cannot be NULL.");
324         exit(-1);
325     }
326     prctl(PR_SET_NAME, (unsigned long)sub->name, 0, 0, 0);
327     // Emit values in a loop, every 2 seconds.
328     while (1) {
329         // Create a random value depending on the property type.
330         vehicle_prop_value_t event;
331         event.prop = sub->prop;
332         event.timestamp = elapsedRealtimeNano();
333         switch (sub->prop) {
334             case VEHICLE_PROPERTY_GEAR_SELECTION:
335                 event.value_type = VEHICLE_VALUE_TYPE_INT32;
336                 switch ((event.timestamp & 0x30000000)>>28) {
337                     case 0:
338                         event.value.gear_selection = VEHICLE_GEAR_PARK;
339                         break;
340                     case 1:
341                         event.value.gear_selection = VEHICLE_GEAR_NEUTRAL;
342                         break;
343                     case 2:
344                         event.value.gear_selection = VEHICLE_GEAR_DRIVE;
345                         break;
346                     case 3:
347                         event.value.gear_selection = VEHICLE_GEAR_REVERSE;
348                         break;
349                 }
350                 break;
351             case VEHICLE_PROPERTY_PARKING_BRAKE_ON:
352                 event.value_type = VEHICLE_VALUE_TYPE_BOOLEAN;
353                 if (event.timestamp & 0x20000000) {
354                     event.value.parking_brake = VEHICLE_FALSE;
355                 } else {
356                     event.value.parking_brake = VEHICLE_TRUE;
357                 }
358                 break;
359             case VEHICLE_PROPERTY_PERF_VEHICLE_SPEED:
360                 event.value_type = VEHICLE_VALUE_TYPE_FLOAT;
361                 event.value.vehicle_speed = (float) ((event.timestamp & 0xff000000)>>24);
362                 break;
363             case VEHICLE_PROPERTY_RADIO_PRESET:
364                 event.value_type = VEHICLE_VALUE_TYPE_INT32_VEC4;
365                 int presetInfo1[4] = {1  /* preset number */, 0  /* AM Band */, 1000, 0};
366                 int presetInfo2[4] = {2  /* preset number */, 1  /* FM Band */, 1000, 0};
367                 if (event.timestamp & 0x20000000) {
368                     memcpy(event.value.int32_array, presetInfo1, sizeof(presetInfo1));
369                 } else {
370                     memcpy(event.value.int32_array, presetInfo2, sizeof(presetInfo2));
371                 }
372                 break;
373             default: // unsupported
374                 if (sub->impl == NULL) {
375                     ALOGE("subscription impl NULL");
376                     return;
377                 }
378                 if (sub->impl->error_fn_ != NULL) {
379                     sub->impl->error_fn_(-EINVAL, VEHICLE_PROPERTY_INVALID,
380                             VEHICLE_OPERATION_GENERIC);
381                 } else {
382                     ALOGE("Error function is null");
383                 }
384                 ALOGE("Unsupported prop 0x%x, quit", sub->prop);
385                 return;
386         }
387         if (sub->impl->event_fn_ != NULL) {
388             sub->impl->event_fn_(&event);
389         } else {
390             ALOGE("Event function is null");
391             return;
392         }
393         pthread_mutex_lock(&sub->lock);
394         if (sub->stop_thread) {
395             ALOGD("exiting subscription request here.");
396             // Do any cleanup here.
397             pthread_mutex_unlock(&sub->lock);
398             return;
399         }
400         struct timespec now;
401         clock_gettime(CLOCK_REALTIME, &now);
402         now.tv_sec += 1; // sleep for one sec
403         pthread_cond_timedwait(&sub->cond, &sub->lock, &now);
404         pthread_mutex_unlock(&sub->lock);
405     }
406 }
407 
vdev_subscribe(vehicle_hw_device_t * device,int32_t prop,float sample_rate,int32_t zones UNUSED)408 static int vdev_subscribe(vehicle_hw_device_t* device, int32_t prop, float sample_rate,
409         int32_t zones UNUSED) {
410     ALOGD("vdev_subscribe 0x%x, %f", prop, sample_rate);
411     vehicle_device_impl_t* impl = (vehicle_device_impl_t*)device;
412     // Check that the device is initialized.
413     pthread_mutex_lock(&lock_);
414     if (!impl->initialized_) {
415         pthread_mutex_unlock(&lock_);
416         ALOGE("vdev_subscribe: have you called init()?");
417         return -EINVAL;
418     }
419     vehicle_prop_config_t* config = find_config(prop);
420     if (config == NULL) {
421         pthread_mutex_unlock(&lock_);
422         ALOGE("vdev_subscribe not supported property 0x%x", prop);
423         return -EINVAL;
424     }
425     if ((config->access != VEHICLE_PROP_ACCESS_READ) &&
426         (config->access != VEHICLE_PROP_ACCESS_READ_WRITE)) {
427         pthread_mutex_unlock(&lock_);
428         ALOGE("vdev_subscribe read not supported on the property 0x%x", prop);
429         return -EINVAL;
430     }
431     if (config->change_mode == VEHICLE_PROP_CHANGE_MODE_STATIC) {
432         pthread_mutex_unlock(&lock_);
433         ALOGE("vdev_subscribe cannot subscribe static property 0x%x", prop);
434         return -EINVAL;
435     }
436     if ((config->change_mode == VEHICLE_PROP_CHANGE_MODE_ON_CHANGE) && (sample_rate != 0)) {
437         pthread_mutex_unlock(&lock_);
438         ALOGE("vdev_subscribe on change type should have 0 sample rate, property 0x%x, sample rate %f",
439                 prop, sample_rate);
440         return -EINVAL;
441     }
442     if ((config->max_sample_rate < sample_rate) || (config->min_sample_rate > sample_rate)) {
443 
444         ALOGE("vdev_subscribe property 0x%x, invalid sample rate %f, min:%f, max:%f",
445                 prop, sample_rate, config->min_sample_rate, config->max_sample_rate);
446         pthread_mutex_unlock(&lock_);
447         return -EINVAL;
448     }
449     subscription_t* sub = (subscription_t*)config->hal_data;
450     if (sub == NULL) {
451         sub = calloc(1, sizeof(subscription_t));
452         sub->prop = prop;
453         sub->sample_rate = sample_rate;
454         sub->stop_thread = 0;
455         sub->impl = impl;
456         pthread_mutex_init(&sub->lock, NULL);
457         pthread_cond_init(&sub->cond, NULL);
458         config->hal_data = sub;
459         sprintf(sub->name, "vhal0x%x", prop);
460     } else if (sub->sample_rate != sample_rate){ // sample rate changed
461         //TODO notify this to fake sensor thread
462         sub->sample_rate = sample_rate;
463         pthread_mutex_unlock(&lock_);
464         return 0;
465     }
466     int ret_code = pthread_create(
467                                   &sub->thread, NULL, (void *(*)(void*))fake_event_thread, sub);
468     print_subscribe_info(impl);
469     pthread_mutex_unlock(&lock_);
470     return 0;
471 }
472 
vdev_unsubscribe(vehicle_hw_device_t * device,int32_t prop)473 static int vdev_unsubscribe(vehicle_hw_device_t* device, int32_t prop) {
474     ALOGD("vdev_unsubscribe 0x%x", prop);
475     vehicle_device_impl_t* impl = (vehicle_device_impl_t*)device;
476     pthread_mutex_lock(&lock_);
477     vehicle_prop_config_t* config = find_config(prop);
478     if (config == NULL) {
479         pthread_mutex_unlock(&lock_);
480         return -EINVAL;
481     }
482     subscription_t* sub = (subscription_t*)config->hal_data;
483     if (sub == NULL) {
484         pthread_mutex_unlock(&lock_);
485         return -EINVAL;
486     }
487     config->hal_data = NULL;
488     pthread_mutex_unlock(&lock_);
489     pthread_mutex_lock(&sub->lock);
490     sub->stop_thread = 1;
491     pthread_cond_signal(&sub->cond);
492     pthread_mutex_unlock(&sub->lock);
493     pthread_join(sub->thread, NULL);
494     pthread_cond_destroy(&sub->cond);
495     pthread_mutex_destroy(&sub->lock);
496     free(sub);
497     pthread_mutex_lock(&lock_);
498     print_subscribe_info(impl);
499     pthread_mutex_unlock(&lock_);
500     return 0;
501 }
502 
vdev_close(hw_device_t * device)503 static int vdev_close(hw_device_t* device) {
504     vehicle_device_impl_t* impl = (vehicle_device_impl_t*)device;
505     if (impl) {
506         free(impl);
507         return 0;
508     } else {
509         return -1;
510     }
511 }
512 
vdev_dump(struct vehicle_hw_device * device UNUSED,int fd UNUSED)513 static int vdev_dump(struct vehicle_hw_device* device UNUSED, int fd UNUSED) {
514     //TODO
515     return 0;
516 }
517 
518 /*
519  * The open function is provided as an interface in harwdare.h which fills in
520  * all the information about specific implementations and version specific
521  * informations in hw_device_t structure. After calling open() the client should
522  * use the hw_device_t to execute any Vehicle HAL device specific functions.
523  */
vdev_open(const hw_module_t * module,const char * name UNUSED,hw_device_t ** device)524 static int vdev_open(const hw_module_t* module, const char* name UNUSED,
525                      hw_device_t** device) {
526     ALOGD("vdev_open");
527 
528     // Oops, out of memory!
529     vehicle_device_impl_t* vdev = calloc(1, sizeof(vehicle_device_impl_t));
530     if (vdev == NULL) {
531         return -ENOMEM;
532     }
533 
534     // Common functions provided by harware.h to access module and device(s).
535     vdev->vehicle_device.common.tag = HARDWARE_DEVICE_TAG;
536     vdev->vehicle_device.common.version = VEHICLE_DEVICE_API_VERSION_1_0;
537     vdev->vehicle_device.common.module = (hw_module_t *) module;
538     vdev->vehicle_device.common.close = vdev_close;
539 
540     // Define the Vehicle HAL device specific functions.
541     vdev->vehicle_device.list_properties = vdev_list_properties;
542     vdev->vehicle_device.init = vdev_init;
543     vdev->vehicle_device.release = vdev_release;
544     vdev->vehicle_device.get = vdev_get;
545     vdev->vehicle_device.release_memory_from_get = vdev_release_memory_from_get;
546     vdev->vehicle_device.set = vdev_set;
547     vdev->vehicle_device.subscribe = vdev_subscribe;
548     vdev->vehicle_device.unsubscribe = vdev_unsubscribe;
549     vdev->vehicle_device.dump = vdev_dump;
550 
551     *device = (hw_device_t *) vdev;
552     return 0;
553 }
554 
555 static struct hw_module_methods_t hal_module_methods = {
556     .open = vdev_open,
557 };
558 
559 /*
560  * This structure is mandatory to be implemented by each HAL implementation. It
561  * exposes the open method (see hw_module_methods_t above) which opens a device.
562  * The vehicle HAL is supposed to be used as a single device HAL hence all the
563  * functions should be implemented inside of the vehicle_hw_device_t struct (see
564  * the vehicle.h in include/ folder.
565  */
566 vehicle_module_t HAL_MODULE_INFO_SYM = {
567     .common = {
568         .tag = HARDWARE_MODULE_TAG,
569         .module_api_version = VEHICLE_MODULE_API_VERSION_1_0,
570         .hal_api_version = HARDWARE_HAL_API_VERSION,
571         .id = VEHICLE_HARDWARE_MODULE_ID,
572         .name = "Default vehicle HW HAL",
573         .author = "",
574         .methods = &hal_module_methods,
575     },
576 };
577